Service Dependency ManagementThe dependency handler manages OSGi service dependencies/requirements. It allows a component to consume service without managing service discovery, tracking and binding. The handler manages all this interaction and injects required service in the component. Service RequirementWhat's a service requirement?A requirement represents a required service. Therefore, it manages the service lookup and the service binding. When an instance requires a service, the handler injects directly a service object inside a field, or invokes a method when a consistent service appears (or disappears). Service requirements can be:
Dynamism & Instance LifecycleIn OSGi™, services can appear and disappear dynamically. This implies dependencies can target a provider which can appear or disappear dynamically. So, dependencies need to manage this dynamism by tracking every time available services. At any moment, a dependency can be unresolved (i.e. no more provider can fulfill the requirement). In the case of a mandatory requirement, the instance becomes invalid (an invalid instance is no more accessible externally, for example provided service are unpublished). If a service, resolving the unfilled dependency appears, the instance becomes valid. In consequence, dependencies affect directly the instance state, and must manage correctly OSGi dynamism to allow a complete unloading when a service goes away. As soon a mandatory dependency cannot be fulfilled, the instance is invalidated. By default, dependencies are managed dynamically (as previously explained). However, iPOJO supports two other types of binding policies:
Service Requirement Injection MechanismsThe handler manages two types of injections:
Moreover, the two injections type can be merged. A component can declare a requirement containing both a field and 'binding'. Field injectionImagine a Hello service with one method 'getMessage' returning a "Hello Message". The following component implementation can use this service by attaching this service to a field and by using the field: public class HelloConsumer { private Hello m_hello; public doSomething() { System.out.println(m_hello.getMesage()); } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires field="m_hello"/> ... </component> The metadata contains a 'requires' element (representing the service dependency). This element has a field attribute. This attribute is the name of the field representing the service dependency in the implementation class. The implementation uses the field as a normal field without managing service interactions. Method invocationThe second injection mechanism uses methods in the implementation class. By this way, the dynamics can be managed directly by the developer. Each dependency can declare two methods:
Moreover, callbacks can be in the component super class (in this case methods must be public). These methods can have one of these four signatures:
The following component implementation shows an example of implementation using this mechanism: public class HelloConsumer { private Hello m_hello; public void bindHello(Hello h) { m_hello = h; } public void unbindHello() { m_hello = null; } public doSomething() { System.out.println(m_hello.getMesage()); } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> ... </component> Note, that the bind the unbind method can be have different
signatures. By using this mechanism, you need to be sure to manage the
dynamism correctly. Field injections and Method invocationsThe two mechanisms can be used together. In this case, the field receives the value before the bind method invocation. So, if the field is use in the method, the returned value will be up to date. The following component implementation uses this mechanism: public class HelloConsumer { private Hello m_hello; // Injected Field public void bindHello() { System.out.println("Hello appears"); } public void unbindHello() { System.out.println("Hello disapears"); } public doSomething() { System.out.println(m_hello.getMesage()); } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires field="m_hello"> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> ... </component> Injection mechanisms & lazy object creationIPOJO creates objects only when required. When needed, iPOJO invokes the constructor of the implementation class. The implementation class can use field requirement because values are already injected. However, method dependencies are called just after the constructor. If the service already presents, the invocation of the methods are delayed just after the constructor invocation. Some ExamplesSimple RequirementBy default, a requirement is mandatory, non-filtered and simple (non-aggregate). The two previous examples illustrate this kind of dependency. When services goes away and appears, the service substitution is hidden. Fields attached to simple requirement point always a consistent service object. For a simple dependency, the bind method is called once time when the service appears or just after the POJO constructor invocation is the service is available. When the service disappears the unbind method is called. The bind method is re-invoked as soon as another service provider is available. This invocation occurs immediately if another service provider if available. In this case, the instance is not invalidated. Aggregate RequirementWhen a component requires several providers of the same service, it declares an aggregate dependency. Aggregate Dependency with field injectionpublic class HelloConsumer { private Hello m_hellos[]; public doSomething() { for(int I = 0; I < m_hellos.length; i++) { System.out.println(m_hellos[i].getMessage()); } } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires field="m_hellos"/> ... </component> To declare an aggregate field for field requirement, you only need to declare an array (instead of a scalar type). IPOJO will create and inject the service object array. iPOJO discover that the dependency is aggregate during the bytecode introspection. Note: The synchronization is managed by iPOJO. As soon as you are 'touching' a dependency in a method, iPOJO ensure that you will keep these objects until the end of the method. Nested methods will share the same service object set. Aggregate Dependency with field injection: list, vector, collection and setIt is also possible to inject service objects inside fields of the type:
public class HelloConsumer { private List m_hellos; public doSomething() { for(int I = 0; I < m_hellos.size(); i++) { System.out.println(((Hello) m_hellos.get(i)). getMessage()); } } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires field="m_hellos" specification="o.a.f.i.Hello"/> ... </component> In this case, just use the supported type that you want. iPOJO will
automatically understand that it is an aggregate dependency, and will
create the collection object containing service objects. Aggregate Dependency with method invocationpublic class HelloConsumer { private List m_hellos= new ArrayList(); private void bindHello(Hello h) { m_hellos.add(h); } private void unbindHello(Hello h) { m_hellos.remove(h); } public synchronized doSomething() { for(int I = 0; I < m_hellos.size(); i++) { System.out.println(m_hellos.get(i). getMessage()); } } } } This requirement is configured as following: <requires aggregate="true"> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> In this case, iPOJO cannot detect if the dependency is aggregate or not. So, you need to add the 'aggregate' attribute. The bindHello and unbindHello will be called each time a Hello service appears or disappears. Optional Requirement (non-aggregate)An optional requirement does not invalidate the instance despite no providers are available. Moreover, it is possible to inject a default service implementation when no real providers are available. Optional Requirement with field injectionpublic class HelloConsumer { private Hello m_hello; public doSomething() { System.out.println(m_hello.getMesage()); } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires field="m_hello" optional="true"/> ... </component> To declare an optional requirement, you need to add the 'optional' attribute. To avoid null pointer exception, iPOJO injects a Nullable object in the field when no service provider is available. The nullable object implements the service interface, but does nothing. Moreover, it is possible to set a default-implementation for the service. A default-implementation is a class implementing the service but used only when no others service providers are available. The default-implementation object will be injected instead of the Nullable objet. For further information refer to the note about nullable object. Optional Dependency with method invocationpublic class HelloConsumer { private Hello m_hello; public void bindHello(Hello h) { m_hello = h; } public void unbindHello() { m_hello = null; } public doSomething() { if(m_hello != null) { System.out.println(m_hello.getMesage()); } } } For this component, metadata should be: <component classname="...HelloConsumer"> <requires optional="true"> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> ... </component> As for field requirement, the dependency metadata needs to contain the optional attribute. IPOJO invokes the method only when a 'real' service is available, so you need to test if m_hello is null before to use it. Aggregate & Optional RequirementA dependency can be both aggregate and optional. Aggregate & Optional Dependency with field injectionpublic class HelloConsumer { private Hello m_hellos[]; public doSomething() { for(int I = 0; I < m_hellos.length; i++) { System.out.println(m_hellos[i].getMessage()); } } } For this component, metadata could be: <component classname="...HelloConsumer"> <requires field="m_hellos" optional="true"/> ... </component> To declare an optional & aggregate field requirement you need to write the optional attribute in the dependency metadata and to point on a field array. If no service available, iPOJO injects an empty array. Aggregate & Optional Requirement with method invocationpublic class HelloConsumer { private List m_hellos<Hello> = new ArrayList<Hello>(); private void bindHello(Hello h) { m_hellos.add(h); } private void unbindHello(Hello h) { m_hellos.remove(h); } public synchronized doSomething() { for(int I = 0; I < m_hellos.size(); i++) { System.out.println(m_hellos.get(i).getMessage()); } } } For this component, metadata could be: <requires aggregate="true" optional="true"> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> In this case, you need to add the _'aggregate'_attribute and the _'optional'_attribute. The bindHello and unbindHello will be called each time a Hello service appears or disappears. These bind / unbind methods are not called when binding / unbinding a Nullable object (when both field and method are used). Filtered RequirementA filtered dependency applies an LDAP filter on service provider. IPOJO reuses OSGi LDAP filter ability. The following metadata illustrates how to use filters: <component classname="...HelloConsumer"> <requires filter="(language=fr)"> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> ... </component> To add a filter, just add a 'filter' attribute in your dependency containing the LDAP filter. iPOJO will select only provider matching with this filter. Moreover, filters can be customized instance by instance. It is possible to specialize / change / add the filter of a component in the instance description. It is useful when you want to create different instances of the same component, with different filter. To do it, you have to identify your dependency with an 'id' attribute. Then, you can adapt the filter of the dependency in the instance description by using the property "requires.filters". In this property you can specify each dependency identified by its id and the new value of the filter. <component className="org.apache.felix.ipojo.example.FilteredDependency" name="FOO"> <requires field="m_foo" fiter="(foo.property=FOO)" id="id1"> <callback type="bind" method="bind"/> <callback type="unbind" method="unbind"/> </requires> </component> <instance name="FOO1" component="FOO"/> <instance name="FOO2" component="FOO"> <property name="requires.filters"> <property name="id1" value="(foo.property=BAR)"/> </property> </instance> <instance name="FOO3" component="FOO"> <property name="requires.filters"> <property name="id1" value="(foo.property=BAZ)"/> </property> </instance> The FOO component type declares a service dependency with the 'id1' id. This dependency has no filter by default. The first instance is just an instance of the FOO component type and does not modify the dependency. The second one adds a filter to the declared dependency to target providers with foo.property = BAR. The last one adds another filter to the declared dependency. By using instance filter customization, it is possible to create complex applications where you avoid binding problems by filtering dependencies instance by instance. Targeting a specific providerA service dependency can choose a specific provider. To achieve this, add a 'from' attribute in your requirement description such as in: <component classname="...HelloConsumer"> <requires from="MyHelloProvider"> <callback type="bind" method="bindHello"> <callback type="unbind" method="unbindHello"> </requires> ... </component> iPOJO maps the from attribute to a specific filter : '|(instance.name=MyHelloProvider)(service.pid=MyHelloProvider)'. Then the dependency can only be fulfilled by a service matching this filter. Moreover, from attributes can be customized instance by instance. It is possible to specialize / change / add a 'from' attribute of a component in the instance configuration. It is useful when you want to create different instances of the same component, with different 'from' clauses. To do it, you have to identify your dependency with an 'id' attribute. Then, you can adapt the 'from' of the dependency in the instance configuration by using the property "requires.from". In this property you can specify each dependency identified by its id and the 'from' value. <component className="org.apache.felix.ipojo.example.FilteredDependency" name="FOO"> <requires field="m_foo" id="id1"> <callback type="bind" method="bind"/> <callback type="unbind" method="unbind"/> </requires> </component> <instance name="FOO1" component="FOO"/> <instance name="FOO2" component="FOO"> <property name="requires.from"> <property name="id1" value="myprovider"/> </property> </instance> <instance name="FOO3" component="FOO"> <property name="requires.from"> <property name="id1" value="myotherprovider"/> </property> </instance> The FOO component type declares a service dependency with the 'id1' id. This dependency has no 'from' attribute by default. The first instance is just an instance of the FOO component type and does not modify the dependency. The second one adds a 'from' attribute to the declared dependency to target the 'myprovider' provider. The last one adds another 'from' clause to the declared dependency. Binding PoliciesThree binding policies are supported inside iPOJO.
A static binding is declared as following: <component classname="...HelloConsumer"> <requires field="m_hellos" policy="static"/> ... </component> A dynamic-priority binding is declared as following: <component classname="...HelloConsumer"> <requires field="m_hellos" policy="dynamic-priority"/> ... </component> By default, the dynamic-priority policy uses the OSGi service ranking policy. However, it is possible to customize the policy by adding the 'comparator' attribute. This attribute indicates the class name of a class implementing the java.util.Comparator interface. IPOJO will create an instance of your comparator and use it to sort service references (so your customized comparator needs to be able to sort OSGi Service Reference). <component classname="...HelloConsumer"> <requires field="m_hellos" policy="dynamic-priority" comparator="my.great.Comparator"/> ... </component> Note about nullable object & default-implementationThe instance implementation can use an optional dependency without any checking. Indeed, when an instance declares an optional dependency using field injection, iPOJO create on the fly a Nullable class implementing the service specification but doing nothing (mock object). Therefore, iPOJO cannot return a service to the instance, for an optional dependency, it returns a nullable object. A nullable object returns:
You can check if the returned object is a nullable object with the test: "myservice instanceof Nullable". You can disable the Nullable pattern too (activated by default). In this case, iPOJO will inject null instead of a Nullable object. So, you can just test if your field is equals to null to check if the service is available. To disable the Nullable pattern, you need to add the 'nullable="false"' attribute in your service dependency description as follows: <component classname="...LogExample"> <requires field="m_log" optional="true" nullable="false"/> ... </component> However, you can also indicate a default-implementation for your optional service. In this case, if no providers are found, iPOJO creates an instance of the default-implementation and injects it. The default-implementation attribute describes the class name of your implementation. The given class MUST implement the required service interface. For example, the following component uses a default implementation for a Log Service dependency: <component classname="...LogExample"> <requires field="m_log" optional="true" default-implementation= "org.apache.felix.ipojo.example.default.MyLogService"/> ... </component> If the log service is not available, iPOJO creates an object of the 'org.apache.felix.ipojo.example.default.MyLogService' class. This object is injected instead of a Nullable object. For instance, the default implementation can print messages on the System.err stream. The nullable object does no display anything. Note about CallbacksDependency manages two type of callback: bind and unbind. A callback with a type "bind" is called each type that a service provider arrives and the binding is necessary. According to the cardinality of the dependency it means:
An unbind callback is called each time that a used service provider goes away. For a simple dependency this method is called each time that the used service provider goes away. For a multiple dependency this method is called each time that a service provider goes away. The method can receive in argument the service object or the service reference (in order to obtain service properties). The bind methods are delayed since a POJO object is created. Note on service interface discoveryThe specification attribute is generally optional except when iPOJO cannot discover the type of the service. iPOJO cannot infer the type when the dependency has no field and callbacks do not receive the service object in parameter. In this case, you need to declare the service interface. |
OverviewGetting Started
User GuideToolsDeveloper Guide
Misc & Contact
|