Creating Services
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
- Creating a Service Interface
- Implementing a Service
- Calling a Service
- 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>.