Time for action - implementing the mock (memory-stored) Book Inventory

In fast development cycles, applying continuous integration, we develop mock implementations of interfaces that we need to get our system up and running as quickly as possible.

The mock implementation provides the functionality required by its interface in a minimalistic manner. The goal is to make it available to components that depend on that interface early on in the development process.

This way, the mock implementation can be replaced with a more final one at the same time as the rest of the application is developed. This development strategy speeds up time-to-market quite a bit.

Here, we will store the books in a Map, indexed by ISBN, for look-up on modify operations.

public class BookInventoryMockImpl implements BookInventory
{
public static final String DEFAULT_CATEGORY = "default";
private Map<String, MutableBook> booksByISBN =
new HashMap<String, MutableBook>();

The factory method

A factory method provides the caller with a new instance of a MutableBook. This re-enforces the decoupling between the components the component that uses the Book and MutableBook does not need to know the class or classes that implement the interfaces.

public MutableBook createBook(String isbn)
{
return new MutableBookImpl(isbn);
}

Implementing a mock getGoups()

In order to have a listing of the categories (without having to go through the whole map every time), we'll keep a count of the books that are in a category and update that count as we store and remove books.

private Map<String, Integer> categories =
new HashMap<String, Integer>();
public Set<String> getCategories()
{
return this.categories.keySet();
}

Storing a book

Before storing a book, we first check if its attributes are valid. In our case, the only requirement is that it has an ISBN set.

We also need to keep track of the category to which it belongs to, to update the categories cache. This implementation will place books that don't have a set category into the default category.

public String storeBook(MutableBook book)
throws InvalidBookException
{
String isbn = book.getIsbn();
if (isbn == null) {
throw new InvalidBookException("ISBN is not set");
}
this.booksByISBN.put(isbn, book);
String category = book.getCategory();
if (category == null) {
category = DEFAULT_CATEGORY;
}
if (this.categories.containsKey(category)) {
int count = this.categories.get(category);
this.categories.put(category, count + 1);
}
else {
BookInventory interface, implementingBookInventory interface, implementingbook, storingthis.categories.put(category, 1);
}
return isbn;
}

Removing a stored book

Removing a stored book is the opposite operation. We remove it from the mapping and update the categories cache accordingly.

public void removeBook(String isbn)
throws BookNotFoundException
{
Book book = this.booksByISBN.remove(isbn);
if (book == null) {
throw new BookNotFoundException(isbn);
}
String category = book.getCategory();
int count = this.categories.get(category);
if (count == 1) {
this.categories.remove(category);
}
else {
this.categories.put(category, count - 1);
}
}

Loading a stored book

Since we've carefully designed our BookInventory interface, separating operations that use a Book from those that use a MutableBook, we are safe to use the same implementation (MutableBookImpl) for either of them.

In our case, we don't pay any attention to concurrency concerns as they are beyond the scope of this book and therefore no data locking is implemented when an item is loaded for edit. When a file- or database-based implementation of this interface is written, consider adding a lock mechanism to prevent multiple parties from editing a book simultaneously.

public Book loadBook(String isbn)
throws BookNotFoundException
{
return loadBookForEdit(isbn);
}
public MutableBook loadBookForEdit(String isbn)
throws BookNotFoundException
{
MutableBook book = this.booksByISBN.get(isbn);
if (book == null) {
throw new BookNotFoundException(isbn);
}
return book;
}

Implementing the book search

We're expecting the search functionality to be slow. Remember, this is a mock implementation. In a database-based implementation, the criteria can be used in turn to specify a filter and provide a significantly better performance.

public Set<String> searchBooks(
Map<SearchCriteria, String> criteria)
{
LinkedList<Book> books = new LinkedList<Book>();
books.addAll(this.booksByISBN.values());
for (Map.Entry<SearchCriteria, String> criterion
: criteria.entrySet()) {
Iterator<Book> it = books.iterator();
while (it.hasNext()) {
Book book = it.next();
switch (criterion.getKey()) {
case AUTHOR_LIKE:
if (
!checkStringMatch(book.getAuthor(), criterion.getValue()))
{
it.remove();
continue;
}
break;

The checkStringMatch method will attempt to match the given string attribute to the given criterion value.

The same matching mechanism is applied for the ISBN, group, and title. The rating matching uses another set of methods listed further down.

case ISBN_LIKE:
if (!checkStringMatch(
book.getISBN(), criterion.getValue()))
{
it.remove();
continue;
}
break;
case CATEGORY_LIKE:
if (!checkStringMatch(
book.getCategory(), criterion.getValue()))
{
it.remove();
continue;
}
break;
case TITLE_LIKE:
if (!checkStringMatch(
book.getTitle(), criterion.getValue()))
{
it.remove();
continue;
}
break;
case RATING_GT:
if (!checkIntegerGreater(
book.getRating(), criterion.getValue()))
{
it.remove();
continue;
}
break;
case RATING_LT:
if (!checkIntegerSmaller(
book.getRating(), criterion.getValue()))
{
it.remove();
continue;
}
break;
}
}
}

Next, gather the books that match, extract their ISBNs, and return the result to the caller.

// copy ISBNs
HashSet<String> isbns = new HashSet<String>();
for (Book book : books) {
isbns.add(book.getISBN());
}
return isbns;
}

In a typical implementation, returning the references to items as a result of a search improves performance, especially when the results returned are many. It saves the time and resources required to load and transmit the results.

The method checkIntegerGreater for checking the rating criterion match is as follows:

private boolean checkIntegerGreater(int attr, String critVal)
{
int critValInt;
try {
critValInt = Integer.parseInt(critVal);
}
catch (NumberFormatException e) {
return false;
}
if (attr >= critValInt) {
return true;
}
return false;
}

The method checkIntegerSmaller is similar to the previous one, the difference being the presence of the compare operator (not listed).

The last missing method is the checkStringMatch method for comparing strings having a wildcard:

private boolean checkStringMatch(String attr, String critVal)
{
if (attr == null) {
return false;
}
attr = attr.toLowerCase();
critVal = critVal.toLowerCase();
boolean startsWith = critVal.startsWith("%");
boolean endsWith = critVal.endsWith("%");
if (startsWith && endsWith) {
if (critVal.length()==1) {
return true;
}
else {
return attr.contains(
critVal.substring(1, critVal.length() - 1));
}
BookInventory interface, implementingBookInventory interface, implementingbook search, implementing}
else if (startsWith) {
return attr.endsWith(critVal.substring(1));
}
else if (endsWith) {
return attr.startsWith(
critVal.substring(0, critVal.length() - 1));
}
else {
return attr.equals(critVal);
}
}
}

Even though we're done with the implementation of the service, we still need to hook it into the Felix framework and make it available as a service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset