The previous section we have seen how to implement a multilingual “Hello World” using properties. In this section we will re-implement it using services. The purpose of this section is to introduce :
This project is different from the previous “Hello World”. We advise you to delete the content of the applications directory so to avoid interference. In this tutorial, we will use a single bundle to contains our service and components (in the next tutorial, you will learn how to dispatch the services in multiple bundles)
First, create an iPOJO project call “hello.using.service”.
We will give our bundle a different symbolic name from the previous one to avoid conflicts. Let’s name it : “Hello World Using Service”
At the core of service interaction is the service specification. In OSGi, the service specification is made of an interface and several properties contained in a Dictionnary.
Before creating any new component, we will start by creating the service interface. This interface is one of the only class shared by providers and consumers of a service.
Create a new Hello.java interface into a new “org.example.hello.service” package :
The Hello service is very simple and
package org.example.hello.service; public interface Hello { void sayHello(String name); }
As we explained, in OSGi, the meta-information are provided using a Dictionary. The code below shows how a service is registered in pure OSGi (you won’t have to do that, it is managed by the iPOJO) :
Dictionary properties = new Hashtable();//... properties.add("lang", "en"); // ... registry.registerService(ServiceInterface.class.getName(), serviceObject, properties);
As you see, the registration is very flexible, you can add whatever property you want when registering the service. This properties can then be retrieved by the consumer :
//When searching in the registry using a filter : ServiceReference myServiceReference = registry.getServiceReferences(ServiceInterface.class, "(lang=fr)"); //On a service to get its properties : String lang = myServiceReference.getProperty("lang");
OSGi specifies no easy ways to enforce the content of the dictionary. This raises some maintainability problems : if you change the name of the property, you will have to change the name everywhere (consumers and providers). OSGi won’t throw an error if you try to read the get the property language instead of lang (you will get null).
Actually properties name are a common source of bug in OSGi when refactoring your code. So if you have always pay attention to that when debugging.
One way to partially avoid that problems is to put the name of the properties in the interface :
package org.example.hello.service; public interface Hello { /** * The property lang defines the language used by the service. **/ public static final String PROP_LANG = "lang"; void sayHello(String name); }
We suggest you to do that, this way, you can use the constant instead of the property name. Note that this is far from being magical and you will still have to pay attention to properties in the iPOJO metadata (and then in the metadata configuration in the IDE).
We will start by creating our service provider. The service provider is a component that implement the service interface.
In the “Provided Service” section click on add. This will let you choose the service interface. Select org.example.hello.service.Hello
We will add a new property “lang”. Right click on the newly added interface and select “add property”.
The “Property dialog” ask for :
Call your property “lang” with a default value “en” and String as a type. Leave the property field to blank, we won’t use it this time.
If successful the property will be listed as a service property under the interface name.
Also add a start and stop method (as seen on the first tutorial)
No you can generate the class. We will use org.example.hello.service.impl as package name. As we will discuss the next section, it is a good practice to always separate the implementation package from the service specification package. This way you can hide the implementation package from the providers and prevents non-standard use of your service.
The generated class should look like that :
package org.example.hello.service.impl; import org.example.hello.service.Hello; public class HelloProviderImpl implements Hello { @Override public void sayHello(String name) { // TODO Auto-generated method stub } /** Component Lifecycle Method */ public void stop() { // TODO: Add your implementation code here } /** Component Lifecycle Method */ public void start() { // TODO: Add your implementation code here } }
The implementation is quite simple. We will implement an english Hello World :
package org.example.hello.service.impl; import org.example.hello.service.Hello; public class HelloProviderImpl implements Hello { @Override public void sayHello(String name) { System.out.println("Hello "+name); } /** Component Lifecycle Method */ public void stop() { System.out.println("The english hello service is stopping"); } /** Component Lifecycle Method */ public void start() { System.out.println("The english hello service is starting"); } }
Create a new instance of your component and configure the property “lang” to “en”.
The client will use this property to discover the client.
Now, we will implement a client of our english provider that will call it on a regular basis.
Create a new component called “HelloClient”.
This component will depend on the “Hello” service. To define the dependency, go to the “Required Service” panel and select add.
The dialog is a little more complex than for the provider. You have to determine :
Configure the dependency type to “org.example.hello.service.Hello” by selecting the interface in the selection wizard.
Let’s generate the client. Once again, you should use a different package name (org.example.hello.client)
The skeleton looks like this :
package org.example.hello.client; import org.example.hello.service.Hello; import java.util.Map; public class HelloClientImpl { /** Field for helloServices dependency */ private Hello[] helloServices; /** Bind Method for null dependency */ public void bindHello(Hello hello, Map properties) { // TODO: Add your implementation code here } /** Unbind Method for null dependency */ public void unbindHello(Hello hello, Map properties) { // TODO: Add your implementation code here } /** Component Lifecycle Method */ public void stop() { // TODO: Add your implementation code here } /** Component Lifecycle Method */ public void start() { // TODO: Add your implementation code here } }
The implementation of the client is a little trickier than the provider but if you are used to multi-threading it won’t be too complex for you. Note that we could also have used a ScheduledExcecutor.
What the code basically do is calling askProvidersToSayHello every second (1000 ms). Note that in a better implementation we could have use a configuration property to configure the delay.
The methods bindHello and unbindHello are just used to prompt a message everytime a matching service is found. In this basic implementation, they are not required by the client to work. You can try to remove them from the code and the component definition if you want (the client will still work).
The field helloServices is the most important part of the code. It is automatically updated by iPOJO each time a new service is found.
A little warning about concurrency
Using iPOJO will make the concurrency management easier. In most cases, you won’t have to deal with concurrency which is a good thing considering how error-prone it is. However the way iPOJO manages concurrency can be surprising at first and can be error-prone.
Basically, iPOJO ensures that the service you are using won’t change during a method call. That implies that :
The field helloServices is used by the method askProvidersToSayHello to call the services.
package org.example.hello.client; import org.example.hello.service.Hello; import java.util.Map; public class HelloClientImpl implements Runnable { /** Field for helloServices dependency */ private Hello[] helloServices; /** Bind Method for null dependency */ public void bindHello(Hello hello, Map properties) { System.out.println("New Provider language = " + properties.get(Hello.PROP_LANG)); } /** Unbind Method for null dependency */ public void unbindHello(Hello hello, Map properties) { System.out.println("Provider leaving language = " + properties.get(Hello.PROP_LANG)); } private void askProvidersToSayHello() { for (int i = 0; i < helloServices.length; i++) { helloServices[i].sayHello("client"); } } /** * When m_end is false and the component is started, the component ask * providers to say hello on a regular basis. When m_end is true, the thread * is stopped */ private boolean m_end = false; /** Component Lifecycle Method */ public void start() { Thread t = new Thread(this); m_end = false; t.start(); } /** Component Lifecycle Method */ public void stop() { m_end = true; } @Override public void run() { try { while (!m_end) { askProvidersToSayHello(); Thread.sleep(1000); } } catch (InterruptedException e) { stop(); } } }
Now you can deploy and test you application. You should get something like this :
The english hello service is starting New Provider language = en Hello client Hello client Hello client Hello client ...
The first message is printed by the english hello service. The next message is from the client : it has discover a new provider and can start running. Then, the client start calling the english Hello client every second.
You can try to create multiple client and services instances to see how the different components behave.
Now we will create a new french provider. The process is the same than the one you followed for the creation of the English provider.
Create your component (and add the lang property)
Then generate the class (HelloFrenchProviderImpl) and complete the code :
package org.example.hello.service.impl; import org.example.hello.service.Hello; public class HelloFrenchProviderImpl implements Hello { @Override public void sayHello(String name) { System.out.println("Bonjour "+name); } /** Component Lifecycle Method */ public void stop() { System.out.println("The french hello service is stopping"); } /** Component Lifecycle Method */ public void start() { System.out.println("The french hello service is starting"); } }
Now, if you redeploy the code, your client will use the two services :
The english hello service is starting New Provider language = en The french hello service is starting New Provider language = fr Hello client Bonjour client Hello client Bonjour client Hello client
If for some reasons you want to use a service with specific properties, you can use an LDAP filter when expressing the dependency. See the ldap filter syntax to have a quick overview on how to write filters.
In our case, we will configure our client to use the French provider only.
Go to the Hello Client component definition and edit the “Hello” service dependency. Add a (lang=fr) filter as shown below :
If you redeploy your code, you will see that the english component is started but not used by the Hello Client.
The english hello service is starting The french hello service is starting New Provider language = fr Bonjour client Bonjour client Bonjour client Bonjour client Bonjour client
Try to create several different french provider and english provider to see it the filter is working. You can also add a new “location” property and use a filter to get a subset of providers only.
In this tutorial you have learn how to create and use services. In the next section, you will learn how to split your application into several bundles so as to benefits from the dynamism offered by services.