Time for action - writing the BookshelfServiceProxy

I've chosen the package com.packtpub.felix.bookshelf.service.tui for the proxy and com.packtpub.felix.bookshelf.service.tui.activator for the bundle activator.

The BookshelfServiceProxy is the main class for the bookshelf command functionality. For easy reference, we will define the SCOPE and FUNCTIONS constants that define the commands scope ("book") and the functions that are to be exposed. Currently, we will expose one function for the add command:

public class BookshelfServiceProxy
{
public static final String SCOPE = "book";
public static final String[] FUNCTIONS = new String[] {
"search"
};
private BundleContext context;
public BookshelfServiceProxy(BundleContext context)
{
this.context = context;
}

The proxy constructor also takes a BundleContext, which will be needed to look up the BookshelfService when executing the command operations.

The search commands that are exposed have two possible syntax signatures:

  • search <username> <password> <attribute> <filter>: For searches matching books filtering by "author", "title" or "category" attribute
  • search <username> <password> <attribute> <lower> <upper>: For searches matching books with a "rating" attribute between lower and upper

Each one of these signatures matches a method in the proxy class:

  • Set<Book> search(String username, String password, String attribute, String filter)
  • Set<Book> search(String username, String password, String attribute, int lower, int upper)

The @Descriptor annotation provides additional information on the method and its parameters. Here, for example, we provide some help on the search command and include a hint on each parameter it takes:

@Descriptor("Search books by author, title, or category")
public Set<Book> search(
@Descriptor("username") String username,
@Descriptor("password") String password,
@Descriptor(
"search on attribute: author, title, or category")
String attribute,
@Descriptor(
"match like (use % at the beginning or end of <like>"+
" for wild-card)")
String filter)
throws InvalidCredentialsException
{
BookshelfService service = lookupService();
String sessionId = service.login(
username, password.toCharArray());
Set<String> results;
if ("title".equals(attribute))
{
results = service.searchBooksByTitle(sessionId, filter);
}
else if ("author".equals(attribute))
{
results =
service.searchBooksByAuthor(sessionId, filter);
}
else if ("category".equals(attribute))
{
results =
service.searchBooksByCategory(sessionId, filter);
}
else
{
throw new RuntimeException(
"Invalid attribute, expecting one of { 'title', "+
"'author', 'category' } got '"+attribute+"'");
}
return getBooks(sessionId, service, results);
}

The remainder of the method is pretty straightforward, the attribute is checked against the valid values and the appropriate search is triggered.

Since the "rating"-based search is supposed to be directed to the method with another signature, we ensure that this method was not selected by mistake (for example, when upper is not passed or when it cannot be made into an int).

The lookupService() method uses the stored BundleContext to look up the bookshelf service and return it. It throws a RuntimeException if it doesn't find one:

protected BookshelfService lookupService()
{
ServiceReference reference = context.getServiceReference(
BookshelfService.class.getName());
if (reference == null)
{
throw new RuntimeException(
"BookshelfService not registered, cannot invoke "+
"operation");
}
BookshelfService service =
(BookshelfService) this.context.getService(reference);
if (service == null)
{
throw new RuntimeException(
"BookshelfService not registered, cannot invoke "+
"operation");
}
return service;
}

Notice the paired checks of the service reference and the service for null. As we saw earlier, when we stopped the inventory implementation before starting the bookshelf service, the environment can change at any time, such as services are stopped, upgraded, and so on while others are running. This is one of the powers of this service platform, but is also an added responsibility on the developer.

The getBooks() method is defined next. It takes a set of ISBNs and returns the corresponding set of Book entries:

private Set<Book> getBooks(
String sessionId, BookshelfService service,
Set<String> results)
{
Set<Book> books = new HashSet<Book>();
for (String isbn : results)
{
Book book;
try
{
book = service.getBook(sessionId, isbn);
books.add(book);
}
catch (BookNotFoundException e)
{
System.err.println("ISBN " + isbn +
" referenced but not found");
}
}
return books;
}

The second search signature is dedicated to rating-based search. It takes two ints, instead of a String filter, for lower and upper bounds of the rating:

@Descriptor("Search books by rating")
public Set<Book> search(
@Descriptor("username") String username,
@Descriptor("password") String password,
@Descriptor("search on attribute: rating") String attribute,
@Descriptor("lower rating limit (inclusive)") int lower,
@Descriptor("upper rating limit (inclusive)") int upper)
throws InvalidCredentialsException
{
if (!"rating".equals(attribute))
{
throw new RuntimeException(
"Invalid attribute, expecting 'rating' got '"+
attribute+"'");
}
BookshelfService service = lookupService();
String sessionId =
service.login(username, password.toCharArray());
Set<String> results =
service.searchBooksByRating(sessionId, lower, upper);
return getBooks(sessionId, service, results);
}

On Converters

Depending on the number and type of parameters passed to the command on the shell, Gogo will attempt to find (coerce) a best matching method signature for the command request.

The shell can recognize the basic types and convert them for use as parameters when calling the command function. However, for more complex types, it would require the assistance of a helper class.

The Converter (org.apache.felix.service.command.Converter) is a service that knows how to convert a String to an object of a specific type and vice-versa.

Without going into too much detail, the converter is registered as a service, along with a property (osgi.converter.classes) that lists the classes it supports conversion for. The service exposes the following two methods:

  • convert(...) that takes the target class (the desired type) and an input object and is expected to return the converted object
  • format(...) that takes an object to format, a formatting directive, and a Converter for delegation of the formatting of sub-parts

The converters are ordered by service.ranking and attempted until one successfully converts or formats the content.

Let's go back to our case study: What's left is the activator to register the service and its commands with the framework and the Gogo Runtime.

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

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