Skip to content
Carsten Stocklöw edited this page Apr 26, 2018 · 3 revisions

Table of Contents

Introduction

This page presents an approach for the usage of middleware Service Bus in a simplified manner in the universAAL project. In order to do so, we had realized an Annotation-based API which could be used instead of an ordinary low-level service bus API. All examples are presented in case of Lighting Example and are tested with the use of OSGi Integration Testing Framework implemented also in the universAAL project.

The main idea of creating such alternative approach was to simplify the implementation of Services. In the regular approach, in order to create Application the developer has to perform following steps:

  1. Model and implement ontology
  2. Implement server side:
    • Create service profile
    • Implement handleCall in ServiceCallee (unwrapping ServiceCall and creating ServiceResponse)
  3. Implement client side:
    • Implement construction of ServiceRequest and proper unwrapping of ServiceResponse
  4. Create launch file
    • Launch everything and manually test the Service
Only when all of those steps are finished, it is possible to detect any errors and all testing has to be done at runtime. Also the amount of time necessary for getting familiar with writing Application is quite big (ontologies, middleware buses etc.). In order to change such situation, the AAPI (Annotation-base API) was created. It allows one to use simple Java interface and annotate it for the purpose of automatic implementation of both server and client side of Application. Additionally, to ease the curve of learning, AAPI introduces three levels of its complexity:
Level 3 - CallMe AAPI : ontology is not modelled; service is matched by its URI
Level 2 - Partial ontological API : ontology is modelled; only change effects are used for match-making
Level 1 - Full ontological API : ontology is modelled; inputs, outputs and change effects are used for match-making (complete interoperability with the current service bus functionallities)
Taking those levels into account, the developer can start from level 3, create a running prototype and then iteratively extend its fully working application going through level 2 and finally reaching level 1. Depending on choosen level, the AAPI allows to omit the following steps of Application development process (thanks to abstract classes and code generation):
  • Omitted on all levels:
    • Create service profile (omitted on all levels)
    • Implement handleCall on the server-side (omitted on all levels)
    • Implement ServiceRequest and ServiceResponse processing on the client-side (omitted on all levels)
  • Ommited on level 3
    • Model and implement ontology

Level 3 - CallMe API

First step we need to perform when creating the Application is to define an ontology namespace. In order to do so, we create an LigtingOntologySimplified [Fig] and LightingSimplified (which extends Service) [Fig] class and store them in ont-lighting bundles.

1.01: public class LightingOntologySimplified {
1.02:     public static final String NAMESPACE = "http://ontology.universaal.org/Lighting.owl#";
1.03: }
2.01: public class LightingSimplified extends Service {
2.02:     public static final String MY_URI = LightingOntologySimplified.NAMESPACE + "Lighting";
2.03: 
2.04:     public LightingSimplified() {
2.05:   super();
2.06:     }
2.07: 
2.08:     public LightingSimplified(String uri) {
2.09:   super(uri);
2.11:     }
2.12:     
2.13:     public String getClassURI() {
2.14:   return MY_URI;
2.15:     }
2.16: }

The next step is to create a service interface class which will be annotated with AAPI annotations. The example implementation of such interface is presented in Fig 3.

3.01: @UniversAALService(namespace = LightingInterfaceLevel3.namespace, name = "LightingService")
3.02: @OntologyClasses(value = { Lighting.class })
3.03: public interface LightingInterfaceLevel3 {
3.04: 
3.05:     public final static String namespace = "http://ontology.igd.fhg.de/LightingServer.owl#";
3.06: 
3.07:     @ServiceOperation
3.08:     public Integer[] getControlledLamps();
3.09: 
3.10:     @ServiceOperation
3.11:     @Outputs(value = { @Output(name = "brightness"), @Output(name = "location") })
3.12:     public Object[] getLampInfo(@Input(name = "lampURI") int lampID);
3.13: 
3.14:     @ServiceOperation
3.15:     public void turnOff(@Input(name = "lampURI") int lampID);
3.16: 
3.17:     @ServiceOperation
3.18:     public void turnOn(@Input(name = "lampURI") int lampID);
3.19: }

There are several important information provided in presented figure. First of all the whole interface is given a namespace (analog to assigning namespace in old lighting-server) and reference to the ontology service class. Second, each method is annotated as service operation and its output and input parameters are annotated with names. If method name follows the Java Beans convention of getX then the output is automatically named X (of course can be overwritten). This interface is also put in ont-lighting bundle. Next we create lighting-server bundle where we put an implementation of our lighting interface service. Example of such implementation is presented in Fig 4. There is an assumption that implementation of lighting service method is already provided in MyLighting (once again stick to best practices and separate business logic from middleware code). There is no need to create any additional code! MyLighting will be simply exposed to the bus.

4.01: public class LightingSimplifiedServiceLevel3 extends MyLighting implements LightingInterfaceLevel3 {
4.02: }

That is all what is needed. The only thing is to register such a service with the use of classes provided in AAPI and it will be exposed as universAAL service on the bus. Fig 5 presents the registration of such AAPI annotated service.

5.01: public void start(final BundleContext context) throws Exception {
5.02:     mc = uAALBundleContainer.THE_CONTAINER.registerModule(new Object[] { context });
5.03:     SimpleServiceRegistrator ssr = new SimpleServiceRegistrator(mc);
5.04:     LightingInterfaceLevel3 providerLevel3 = new LightingSimplifiedServiceLevel3();
5.05:     ssr.registerService(providerLevel3);
5.06: }

SimpleServiceRegistrator of AAPI is used for registering the service in the bus. ServiceProfile and implementation of ServiceCallee are created on the fly on basis of annotations.

Level 2 - Partial ontological API

This level assumes that the developer wants to use ontologies, however in a simplified manner. At the first step, it is necessary to model and implement an onthology (scope of this task is out of boundaries of this chapter, however details can be found in capter Ontologies in universAAL). After the creation of ontology, we annotate our service interface in a similar way as in level 3. The examplary Lighting interface with such annotations is presented in Fig.6.

6.01: @UniversAALService(namespace = LightingInterfaceLevel2.namespace, name = "LightingService")
6.02: @OntologyClasses(value = { Lighting.class })
6.03: public interface LightingInterfaceLevel2 {
6.04:     public final static String namespace = "http://ontology.igd.fhg.de/LightingServer.owl#";
6.05: 
6.06:     @ServiceOperation
6.07:     public LightSource[] getControlledLamps();
6.08: 
6.09:     @ServiceOperation
6.10:     @Outputs(value = { @Output(name = "brightness"), @Output(name = "location") })
6.11:     public Object[] getLampInfo(@Input(name = "lampURI") LightSource lamp);
6.12: 
6.13:     @ServiceOperation
6.14:     @ChangeEffect(propertyPaths = { Lighting.PROP_CONTROLS, LightSource.PROP_SOURCE_BRIGHTNESS },
6.15:       value = "0", valueType = Integer.class)
6.16:     public void turnOff(@Input(name = "lampURI") LightSource lamp);
6.17: 
6.18:     @ServiceOperation
6.19:     @ChangeEffect(propertyPaths = { Lighting.PROP_CONTROLS, LightSource.PROP_SOURCE_BRIGHTNESS },
6.20:       value = "100", valueType = Integer.class)
6.21:     public void turnOn(@Input(name = "lampURI") LightSource lamp);
6.22: }

As it can be seen now, classes of ontology are used as input/output parameters of particular methods. Additionally, ChangeEffects of methods are provided. This annotations need property path and value to which property is supposed to be changed. All of this information provided inside of annotations is directly mapped into proper definitions of generated ServiceProfiles for annotated service. The difference between Level 2 and Level 3 is mostly the ontology. Thanks to that, Level 2 can be successfully used to verify if ontology was modeled correctly. Additionally, it was necessary to modify MyLighting in order to use additional ontology classes as input and output parameters. In case of this level, some semantic information is provided for turnOn and turnOff methods. AAPI detects it and instructs middleware to perform regular match-making of these invocations while retaining match-making by URI in case of other two methods.

Level 1 - Full ontological API

Now, when developer has working prototype based on the ontology, he can proceed to the AAPI Level 1 where all semantic information is provided. The interface itself is identical to the interface of Level 2. However, annotations of all methods are extended to define semantics which were presented in the non-simplified Lighting example [Fig.].

7.01: @UniversAALService(namespace = LightingInterfaceLevel1.namespace, name = "LightingService")
7.02: @OntologyClasses(value = { Lighting.class })
7.03: public interface LightingInterfaceLevel1 {
7.04:     public final static String namespace = "http://ontology.igd.fhg.de/LightingServer.owl#";
7.05:     
7.06:     @ServiceOperation(value = "getControlledLamps")
7.07:     @Output(name = "controlledLamps", propertyPaths = { Lighting.PROP_CONTROLS })
7.08:     public LightSource[] getControlledLamps();
7.09: 
7.10:     @ServiceOperation
7.11:     @Outputs(value = {
7.12:       @Output(name = "brightness", filteringClass = Integer.class, propertyPaths = {Lighting.PROP_CONTROLS, LightSource.PROP_SOURCE_BRIGHTNESS }),
7.13:       @Output(name = "location", filteringClass = Location.class, propertyPaths = {Lighting.PROP_CONTROLS, Resource.uAAL_VOCABULARY_NAMESPACE + "hasLocation" }) })
7.14:     public Object[] getLampInfo(@Input(name = "lampURI", propertyPaths = { Lighting.PROP_CONTROLS }) LightSource lamp);
7.15: 
7.16:     @ServiceOperation
7.17:     @ChangeEffect(propertyPaths = { Lighting.PROP_CONTROLS, LightSource.PROP_SOURCE_BRIGHTNESS }, value = "0", valueType = Integer.class)
7.18:     public void turnOff(@Input(name = "lampURI", propertyPaths = { Lighting.PROP_CONTROLS }) LightSource lamp);
7.19: 
7.20:     @ServiceOperation
7.21:     @ChangeEffect(propertyPaths = { Lighting.PROP_CONTROLS, LightSource.PROP_SOURCE_BRIGHTNESS }, value = "100", valueType = Integer.class)
7.22:     public void turnOn(@Input(name = "lampURI", propertyPaths = { Lighting.PROP_CONTROLS }) LightSource lamp);
7.23: }

The difference between Level 1 and Level 2 is only additional annotation of service interface. @Output and @Input annotations were enriched with parameters: propertyPaths and filteringClass. Thanks to that Level 1 can be successfully used to verify correctness of these semantic elements. Since the interface (not annotations) of Level 1 is exactly the same as Level 2, implementation of our service could be directly used for implementation of Level 1 provider. Providers of Level 1 and Level 2 are identical. In case of this level, all semantic information is provided in all methods. AAPI detects it and instructs middleware to perform regular match-making of all invocations. In other words, everything works under the hood as in the non-simplified lighting example. The developer has created fully working Lighting example in much less lines of code and having it continuously tested during the development process.

FAQ

How to use the AAPI?

After annotation of service interface and implementation of its business logic, such a service can be easily exposed on the Service Bus. The only thing that is obligatory is to create a new instance of SimpleServiceRegistrator and invoke its register method (as presented in Fig. 8)

8.01: public void start(final BundleContext context) throws Exception {
8.02:     mc = uAALBundleContainer.THE_CONTAINER.registerModule(new Object[] { context });
8.03:     SimpleServiceRegistrator ssr = new SimpleServiceRegistrator(mc);
8.04:     LightingInterfaceLevel3 providerLevel3 = new LightingSimplifiedServiceLevel3();
8.05:     ssr.registerService(providerLevel3);
8.06: }
Such service can be seamlessly invoked by regular Service Bus invocations or with the use of annotated interface and provided SimpleServiceLocator class (Fig. 9).
9.01: LightingInterfaceLevel1 service = (LightingInterfaceLevel1) new SimpleServiceLocator(
9.02:     Activator.mc).lookupService(LightingInterfaceLevel1.class);
9.03:     LightSource[] controlledLamps = service.getControlledLamps();

In order to use this "tapas" developer has to add the following dependencies to its project:

<groupId>org.universAAL.middleware</groupId>
<artifactId>mw.bus.service.aapi.core</artifactId>
<version>1.2.1-SNAPSHOT</version>

<groupId>org.universAAL.middleware</groupId>
<artifactId>mw.bus.service.aapi.osgi</artifactId>
<version>1.2.1-SNAPSHOT</version>

What is a SimpleServiceRegistrator and a SimpleServiceLocator?

SimpleServiceRegistrator of AAPI is used for registering the service in the bus. ServiceProfile and implementation of ServiceCallee are created on the fly on basis of annotations.

To invoke service through AAPI, developer looks it up in SimpleServiceLocator passing reference to annotated interface. Returned object is a proxy implementing the interface. When method on this proxy is invoked, AAPI creates ServiceRequest, sends it to the bus and waits for ServiceResponse. When it is received, the output is unwrapped and passed as return value.

How can it be mixed with my old code?

AAPI maps directly to code snippets of Classic API that is already well-known for developers. Therefore, mixing of AAPI and Classic API is possible and the interoperability between them is retained. The only difference is in the area of developing Service Profiles manually (in case of non-simplified approach) or generating it automatically by AAPI with the use of annotated service interface.

What middleware classes are generated in the AAPI approach ?

The AAPI annotations are used for generation of the following elements of Classic API:

  • ServiceProfile
  • ServiceCallee
  • ServiceRequest
  • ServiceResponse

Support:

Found a problem?
  • Report suggestions, missing, outdated or wrong documentation creating an Issue with "documentation" tag
Clone this wiki locally