Creating Services

by Andrew Taylor <andy@benow.ca>
The BeNOW Service API is a light API for defining, invoking and getting the results of processing . A simple way to call stuff that does stuff and returns stuff. In this tutorial, a Service will be created and called in order to produce items. After this tutorial, you'll be ready to create services within your own applications, as well as to further use Services with other APIs, such as the Repository and Web APIs.
Contents
  1. Creating a Service Interface
  2. Implementing a Service
  3. Calling a Service
  4. Registering and Querying Services
Creating a Service Interface
Services are defined groupings of processing. To make a Service, one first defines an interface extending org.benow.service.Service, and then defines the overview of the processing to be available (the interface for the service). For the purposes of this tutorial, a service has been defined in src/java/test/org/benow/service/TutorialService.java:
@Publish
public interface TutorialService extends Service {
  /**
   * @return all known people
   */
  public List<Person> getPeople();
  
  /**
   * @param city city to fetch people for
   * @return known people within the given city
   */
  public List<Person> getPeopleInCity(
      @ParamName("city")
      String city);
  
  /**
   * @param heightInCM minimum height of people to return
   * @return known people of the given minimum height
   */
  public List<Person> getPeopleOfHeight(
      @ParamName("heightInCM")
      int heightInCM);
}
The TutorialService has 3 methods, which return people. The TutorialService is a Service as it extends the org.benow.service.Service class.
@Publish Annotation
The @Publish annotation indicates that this service has been verified and is ready for use. Services without the @Publish annotation are not accessible and may not be invoked (by anyone). This is a simple check to make sure that the author is aware of what the service does and that they are ok publishing it as it stands. Services which are in development or are stale or dangerous should not have the @Publish annotation. @Deprecated services are also not published.
Service Methods
Service methods are declared as normal interface methods, with the exception of some custom annotations.
 public List<Person> getPeopleOfHeight(
     @ParamName("heightInCM")
     int heightInCM);
the @ParamName annotation redeclares the name of the attribute. The reasoning for this is the attribute name is not available to reflection, which is used for remote invocation. If the @ParamName is not given, the params can be specified as Param0, Param1, etc. At this time we're not using remote invocation, but it's a good thing to do anyway. The @ParamName annotation might not be required in future versions of the Service api, but source code parsing has not yet been completed.

So, it's pretty much exactly like standard java interface, but with a few annotations.

Implementing a Service
The task now is to implement the service interface... to do the processing. src/java/test/org/benow/service/TutorialServiceImpl.java demonstrates this:
@Publish
public class TutorialServiceImpl implements TutorialService {

  private static final List<Person> people=new ArrayList<Person>();
  static {
    Person andy=new Person("Andrew", "Taylor", "36 End of Internet Cl.", "Noosphere", "T4C 4C4");
    andy.setHeightInCMs(185);
    people.add(andy);
    Person ben=new Person("Ben", "Yeardly", "420 Green Loop", "Sloquet Hotsprings", "T4C 0C0");
    ben.setHeightInCMs(175);
    people.add(ben);
    Person john=new Person("John","Smith","123 Here Cres.","Heresville","910222");
    john.setHeightInCMs(180);
    people.add(john);
    Person mary=new Person("Mary","Smith","123 Here Cres.","Heresville","910222");
    mary.setHeightInCMs(155);
    people.add(mary);
  }
  
  @Override
  public List<Person> getPeople() {
    return people;
  }

  @Override
  public List<Person> getPeopleInCity(String city) {
    List<Person> results=new ArrayList<Person>();
    for (Person curr: people) {
      if (curr.getCity().equals(city)) 
        results.add(curr);
    }
    return results;
  }

  @Override
  public List<Person> getPeopleOfHeight(int heightInCM) {
    List<Person> results=new ArrayList<Person>();
    for (Person curr: people) {
      if (curr.getHeightInCMs()>=heightInCM) 
        results.add(curr);
    }
    return results;
  }

}
The implementation implements the previously defined interface and performs the work. The @Publish annotation is required, for the same reasons as before. The methods do not need any annotations and should just do the work, returning the result.
Calling a Service
Services are called locally using the LocalServices accessor. A request can be made for a service of a given interface, which returns an implementor which is used to perform the processing, as shown in src/java/test/org/benow/service/TutorialServiceRunner.java:
TutorialService svc=(TutorialService) LocalServices.takeAService(TutorialService.class);
try {
  System.out.println("All people:");
  List<Person> people=svc.getPeople();
  for (Person p: people)
    System.out.println("\t"+p);
  
  System.out.println("People in Heresville:");
  people=svc.getPeopleInCity("Heresville");
  for (Person p: people)
    System.out.println("\t"+p);
  
  System.out.println("People in 180cm or over:");
  people=svc.getPeopleOfHeight(180);
  for (Person p: people)
    System.out.println("\t"+p);
} finally {
  LocalServices.returnAService(svc);
}
An instance of the requested service is taken via LocalServices.takeAService(Class). Once taken, it is interacted with at the interface level. After processing is done, the service instance can be returned with LocalServices.returnAService(Service). Returning after a take is not manditory, but it is good practice. The service instances are recycled by LocalServices, and so there is a performance advantage for tight loops.

It should be noted that as there might be multiple instances of a single Service class, you don't want to store information in your service at a instance level. If information is to be shared between instances of services, use static variables (and be aware of multi threaded issues). The multiple instancing of Services allows for easy calling in multi-threaded environments, but can make shared data a bit tricky. In practice, this way works well.

Registering and Querying Services
Services are automatically registered by the Packager mechanism when doing an ant jar. The Packager scans classes for Services and creates a file within the output jar which contains the list of services. This mechanism allows for services to be queried for and inspected, so that someone can discover and use services without having to have prior knowledge of them. We've not seen this behaviour in this tutorial, but the functionality is made use of in other higher level APIs, such as the web API. To register your service do an ant jar from the root of the project.

Registered services can be queried via the LocalServices:

List<ServiceSpecification> svcss = LocalServices.getInstance().listServices();
You can see this as doQuery() in src/java/test/org/benow/service/Tutorial and run it via bin/tutorial_service.sh query (or bin\tutorial_service.bat query in windows):
Known services:
	org.benow.security.service.SecurityService
	org.benow.service.ServiceService
	org.benow.service.Services
	test.org.benow.service.TestService
	test.org.benow.service.TutorialSecureService
	test.org.benow.service.TutorialService
a fully detailed listing, including all the methods and permissions can be seen as as doQueryFull() and can be run via bin/tutorial_service.sh queryFull (or bin\tutorial_service.bat queryFull in windows). The full XML dump of query full shows the detail that is available on query. All available/accessible service classes, their methods and the required permissions can be seen. In future a service api version, the javadoc for interfaces and methods will also be queriable, to further ease discovery and use of services.

Due to the registration, listings of services retrieved by listServices() are fetched from within any jars on the classpath. Due to this, simply writing services and packaging them via ant jar is all that is required to create redistributable, discoverable services. Once your jar containing services is on the classpath, services can be queried for and invoked.

The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <andy@benow.ca>.