Requiring services¶
One of the main iPOJO feature is the service injection. So, a component can consume a service without managing the service discovery, tracking and binding. iPOJO manages all these interactions and injects required service into the component. This page explains how to use services.
- Requiring services
- Service Dependency
- Service Requirement Injection Mechanisms
- Examples
- Managing resilience to dynamism - Binding Policies
- Optional Scalar Dependencies - No Service actions
- Wait for service : the timeout option
- Note about Callbacks
- Proxies
- Note on service interface discovery
Service Dependency¶
What's a service dependency?¶
A required service is described by a service dependency. The dependency defines what kind of service is required, how to select the it, the resilience to its dynamism... iPOJO handles all these aspects for you, just tell, iPOJO tracks, selects, binds and injects the matching services directly in your code. Service dependencies can be:
- Scalar or Aggregate : the component can require one or several service providers
- Mandatory or Optional : a component can declare an optional dependency, and defined what should be done when no services are available
- Filtered : a component can filter available providers, and even choose a specific provider
- Resilient to dynamism : iPOJO supports three binding policy depending on your reaction to dynamism
Dynamism, Resilience & Instance Lifecycle¶
In 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 services 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:
- Static : if a bound service disappears, the instance is invalidated and cannot be revalidated (binding broken forever)
- Dynamic-Priority: at each injection, the best provider is injected, or the providers array is sorted according to the OSGi Ranking policy or to a specified sorting algorithm.
Service Requirement Injection Mechanisms¶
iPOJO support several types of injections:
Field injection: a field contains the service object. As soon as the field is used, a consistent service object is injected. This injection type fully hides the dynamism
@Requires private LogService log;
Method invocation: when a service appears, or disappears a method in the component is invoked. For each dependency, bind / unbind / modified methods are invoked to notify the component of the event.
@Bind public void bindLogService(LogService log) { /*...*/ } @Unbind public void unbindLogService(LogService log) { /*...*/ } @Modified public void modifiedLogService(LogService log) { /*...*/ }
Constructor injection: services can also be injected as constructor parameter (only if proxies are enabled). 1.7.0-SNAPSHOT
public MyComponent(@Requires LogService log) { /*...*/ }
Moreover, the injections types can be mixed. A component can declare a requirement containing both a field and 'binding' methods.
Field injection¶
Let's imagine 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:
@Component @Instantiate public class HelloConsumer { @Requires private Hello m_hello; public doSomething() { System.out.println(m_hello.getMesage()); } }
You can also use XML to describe this component type:
<component classname="...HelloConsumer"> <requires field="m_hello"/> ... </component>
The metadata contains a 'requires' element (representing the service dependency) and specify a field used to inject the service. The implementation uses the field as a normal field without managing service interactions.
Method invocation¶
The 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 three methods:
- A bind method called when a service appears
- An unbind method called when a service disappears
- A modified method called when a service is modified (the service properties have changed, but the service still matches the requirement)
Moreover, callbacks can be in the component super class (in this case methods must be public). These methods can have one of these signatures:
-
Without any argument: the method is just a notification
public void bindService() { // ... }
-
With the service object: the object is the implicated service object. Service dependency type is inferred from the parameter's type.
public void bindService(HelloService hello) { m_hello = hello; }
-
With an OSGi service reference: the service reference appearing or disappearing.
public void bindService(ServiceReference<HelloService> reference) { // ... } public void bindService(ServiceReference reference) { // ... } public void bindService(ServiceReference reference) { // ... }
-
With the service object and the OSGi service reference.
public void bindService(HelloService hello, ServiceReference<HelloService> reference) { // ... }
-
With the service object and the service properties inside a Map (no adherence to OSGi APIs).
public void bindService(HelloService hello, Map<String, Object> properties) { // ... }
-
With the service object and the service properties inside a Dictionary (no adherence to OSGi APIs).
public void bindService(HelloService hello, Dictionary<String, Object> properties) { // ... }
Important
Notice that, when missing (typically no interface can be inferred from the code) dependency information must be supplied to iPOJO in some way
-
@Bind
withspecification
and/orfilter
attribute - Using XML metadata declaration
The following component implementation shows an example of implementation using this mechanism:
@Component public class HelloConsumer { private Hello m_hello; @Bind public void bindHello(Hello h) { m_hello = h; } @Unbind public void unbindHello() { m_hello = null; } public doSomething() { System.out.println(m_hello.getMesage()); } }
The modified
callback is not mandatory. The following XML metadata are describing the same component type:
<component classname="...HelloConsumer"> <requires> <callback type="bind" method="bindHello"/> <callback type="unbind" method="unbindHello"/> </requires> ... </component>
Note, that the different callbacks can be have different signatures. By using this mechanism, you need to be sure to manage the dynamism correctly. (See note on type discovery
Using the @Modified
callback is also quite simple:
@Component public class HelloConsumer { private Hello m_hello; @Bind public void bindHello(Hello h) { m_hello = h; } @Unbind public void unbindHello() { m_hello = null; } @Modified public void modifiedHello() { /* ... */ } public doSomething() { System.out.println(m_hello.getMesage()); } }
Using constructor injection (1.7.0-SNAPSHOT)¶
Services can also be injected using constructor parameters:
@Component public class MyComponent { private LogService log; public MyComponent(@Requires LogService log) { this.log = log; } }
Mixing injections types¶
The different mechanisms can be used together. In this case, the field receives the value before the bind method invocation. Constructor parameters get their values during the constructor invocation. So, if the field is used in the method, the returned value will be up to date. The following component implementation uses this mechanism:
public class HelloConsumer { @Requires(id="hello") private Hello m_hello; // Injected Field @Bind(id="hello") public void bindHello() { System.out.println("Hello appears"); } @Unbind(id="hello") public void unbindHello() { System.out.println("Hello disapears"); } public doSomething() { System.out.println(m_hello.getMesage()); } }
In XML, it results in:
<component classname="...HelloConsumer"> <requires field="m_hello"> <callback type="bind" method="bindHello"/> <callback type="unbind" method="unbindHello"/> </requires> ... </component>
The id
attribute is used to determine which callbacks / fields go together. If ommitted, it is computed automaticcally:
- for field it uses the field type.
- for method starting with
bind
/unbind
/modified
, it extract the end of the method name (bindFoo => Foo
) - for constructor parameter, it uses the parameter index
So, it is strongly recommended to specify the id manually.
Injection mechanisms & lazy object creation¶
iPOJO 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 and obviously constructor parameters. However, method dependencies are called after the constructor. If the service is available before the constructor call, the invocation of the bind methods is delayed until the a component class object is created.
Examples¶
For all examples both annotations and XML forms are given. Just choose what you'd like to use.
Simple Requirement¶
By default, a requirement is mandatory, non-filtered and simple (non-aggregate). The 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 Requirement¶
When a component requires several providers of the same service, it declares an aggregate dependency.
Aggregate Dependency with field injection¶
@Component public class HelloConsumer { @Requires private Hello m_hellos[]; // Array => Aggregate public doSomething() { for(int I = 0; I < m_hellos.length; i++) { System.out.println(m_hellos[i].getMessage()); } } }
For this component, XML 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.
Array types cannot be 'proxied'. Moreover array dependencies cannot be injected as constructor parameter.
Synchronization
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 set¶
It is also possible to inject service objects inside fields of the type:
- list
- vector
- collection
-
set
:::java @Component public class HelloConsumer { @Requires(specification="org.apache.felix.ipojo.example.Hello") private List
m_hellos; public doSomething() { for(Hello h : m_hellos) { System.out.println(h).getMessage()); } } }
For this component, XML metadata could be:
<component classname="...HelloConsumer"> <requires field="m_hellos" specification="org.apache.felix.ipojo.example.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. However, you must specify the service specification. Indeed, generics types cannot be discovered at runtime reliably.
Service specification discovery
The service specification (i.e. interface) cannot be discovered when using these types as the bytecode does not provide enough information. So, you have to indicate the required service interface (with the 'specification' attribute) in the requirement description.
How iPOJO manage the synchronization for you
As in the previous case, 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 callbacks¶
public class HelloConsumer { private List m_hellos = new ArrayList(); @Bind(aggregate=true) private void bindHello(Hello h) { m_hellos.add(h); } @Unbind private void unbindHello(Hello h) { m_hellos.remove(h); } public synchronized doSomething() { for(Hello h : m_hellos) { System.out.println(h.getMessage()); } } } }
This dependency can also be described in XML as follow:
<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.
Synchronization
To avoid the list modification during the loop, you need synchronized the block. Indeed, as the field is not an iPOJO requirement, iPOJO will not manage the synchronization.
Optional Requirement (Scalar)¶
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 injection¶
@Component public class HelloConsumer { @Requires(optional=true) private Hello m_hello; public doSomething() { System.out.println(m_hello.getMesage()); } }
For this component, equivalent XML 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 callbacks invocation¶
@Component public class HelloConsumer { private Hello m_hello; @Bind(optional=true) public void bindHello(Hello h) { m_hello = h; } @Unbind public void unbindHello() { m_hello = null; } public doSomething() { if(m_hello != null) { // Must be checked System.out.println(m_hello.getMesage()); } } }
For this component, XML metadata could 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 Requirement¶
A dependency can be both aggregate and optional.
Aggregate & Optional Dependency with field injection¶
@Component public class HelloConsumer { @Requires(optional=true) private Hello m_hellos[]; public doSomething() { for(Hello h : m_hellos) { System.out.println(h.getMessage()); } } }
For this component, XML 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 callbacks¶
@Component public class HelloConsumer { private List m_hellos<Hello> = new ArrayList<Hello>(); @Bind(aggregate=true, optional=true) private void bindHello(Hello h) { m_hellos.add(h); } @Unbind private void unbindHello(Hello h) { m_hellos.remove(h); } public synchronized doSomething() { for(Hello h : m_hellos) { System.out.println(h.getMessage()); } } }
For this component, XML 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 Requirement¶
A filtered dependency applies an LDAP filter on service provider. iPOJO reuses OSGi LDAP filter ability. The following metadata illustrates how to use filters:
@Requires(filter="(language=fr)") private String DictionaryService dict;
<requires filter="(language=fr)" field="dict"/>
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.
When using a filter, you can also use the modified
callback invoked when a matching service is modified but still matches the filter:
@Component public class MyComponent { @Bind(filter="(langage=en)") public void bindHDictionary(DictionaryService svc) { ... } @Unbind public void unbindDictionary() { ...} @Modified public void modifiedDictionary() { ... } }
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 achieve this customization, you have to identify your dependency with the '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"> <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 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 provider¶
A service dependency can choose a specific provider. To achieve this, add a 'from' attribute in your requirement description such as in:
@Requires(from="MyHelloProvider") private Hello m_hello;
or in XML:
<requires from="MyHelloProvider" field="m_hello"/>
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.
Managing resilience to dynamism - Binding Policies¶
Three binding policies are supported inside iPOJO.
- Dynamic policy (default): the binding are managed dynamically. At each injection, the same provider is injected if the provider is always available. Else a new one is chosen. For aggregate dependency, the array order does not change; new providers are placed at the end of the array.
- Static policy: the binding is static. So, once bound a provider cannot disappear. If it disappears, the instance is invalidated and cannot be revalidated without stopping and restarting the instance.
- Dynamic-priority policy: the binding is managed dynamically but the injected provider is selected by using a ranking policy. Two injections can return two different providers, is a new provider is 'better' than the previous one, despite the first one is always available. For aggregate dependency, the array is sorted.
A static binding is declared as following:
@Requires(policy=BindingPolicy.STATIC) private Hello[] m_hellos;
or
<requires field="m_hellos" policy="static"/>
A dynamic-priority binding is declared as following:
@Requires(policy=BindingPolicy.DYNAMIC_PRIORITY) private Hello[] m_hellos;
or
<requires field="m_hellos" policy="dynamic-priority"/>
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 creates an instance of your comparator and uses it to sort service references (so your customized comparator needs to be able to sort OSGi Service Reference).
@Requires(policy=BindingPolicy.DYNAMIC_PRIORITY, comparator=MyComparator.class) private Hello[] m_hellos;
or
<requires field="m_hellos" policy="dynamic-priority" comparator="great.MyComparator"/>
Optional Scalar Dependencies - No Service actions¶
When using optional dependencies a special case needs to be handled for field and contructor injection: what happen when there are no service providers available. By default, iPOJO uses nullable
objects. It has the advantage to not require any additional code. However, iPOJO supports other options:
null
: injectsnull
instead of a nullable object, it requiresnull
check before using the inject servicedefault-implementation
: injects a specific implementation of the service that you provide. It must implement the same service interface.exception
: throws a runtime exception (that you specify), it requires atry-catch
block for specific management.
By default, scalar optional dependencies injects a nullable
object, i.e. a mock implementing the service interface but does not implement any behavior. A nullable object returns:
null
when the method returns an object0
when the method returns an int, log, byte, short, char, float or a doublefalse
when the method return a boolean
To inject null
instead of a nullable
object, just set the nullable
attribute to false
.
@Requires(optional=true, nullable=false) private LogService m_log;
or
<requires field="m_log" optional="true" nullable="false"/>
However be aware that in this case, you must check for null
before using the service:
if (m_log != null) { m_log.log(LogService.INFO, "an important message"); }
Don't worry about the synchronization, iPOJO keep the injected object consistent on the entire method flow.
Sometimes you need to customize the behavior when a service is not available. You can do this directly in your code, but this can be very cumbersome. default-implementation
let you inject a fake service when no providers are present. It's like a nullable
object, but you can implement your own behavior. The given class MUST implement the required service interface.
For example, the following component uses a default-implementation
for a Log Service dependency:
@Requires(optional=true, default-implementation=MyLogService.class) private LogService m_log;
or
<requires field="m_log" optional="true" default-implementation= "org.apache.felix.ipojo.example.default.MyLogService"/>
If the log service is not available, iPOJO creates an instance of the org.apache.felix.ipojo.example.default.MyLogService
. This object is injected instead of the Nullable
object. In the example, the default implementation can print messages on the System.err
. In comparison, the nullable
object would have done nothing.
Finally, you can also instructs iPOJO to throw a runtime exception when there are no service providers available. This option is often use in combination with the timeout
option, that delay the decision. To throw an exception, use the exception
attribute specifying the exception class to use. It must be a subclass of RuntimeException
. Obvisouly, you can use java.lang.RuntimeException
directly.
@Requires(optional=true, exception=NoServiceException.class) private LogService m_log;
or
<requires field="m_log" optional="true" exception= "org.apache.felix.ipojo.example.default.NoServiceException"/>
Wait for service : the timeout option¶
For scalar optional dependencies injected inside fields or constructors, you may want to wait for a service to arrive before injecting a stub (nullable
, null
, default-implementation
or exception
). The timeout
attribute let you specify the amount of time (in milliseconds) to wait. If there are still no services available when the timeout is reached, then the no service action is applied.
In the following example, the AuthenticationService
is essential, but also may be subjected to updates. When the service is not there, you don't want to fail immediately, but give it a chance to re-appear soon:
@Requires(optional=true, exception=UpdateInProgessException.class, timeout=1000) private AuthenticationService m_auth;
Note about Callbacks¶
Dependency 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:
- Simple dependency : at the firs binding and at each rebinding to another service provider
- Aggregate dependencies: each time that a service provider arrives
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.
Proxies¶
Since iPOJO 1.6, iPOJO injects proxy objects. Those proxies are by default smart proxies and are design to be lightweight:
- for scalar requirement : the service object is a proxy
- for aggregate dependencies : iPOJO injects a smart collections
The goal of the proxies is to hide the dynamism and more particularly the dynamism. So, you can gives a service dependency to another object, using the service object still supports the dynamism. For collections, you can iterate over the collection without managing the potential departures and arrivals of services. The proxy also manage that the component class and the delegate objects shared the same services is they are accessed in the same Thread.
By default iPOJO injects proxy except for arrays. Moreover, it is possible to disable the proxy injection by adding proxy=false
to the requires
element (or to the @Requires
and @Bind
annotations). It is also possible to inject dynamic proxies (if the platform does not support dynamically generated classes). To enable dynamic proxies, set the system or bundle property ipojo.proxy.type
to dynamic-proxy
. You can also disable completely the proxy injection by setting the system property ipojo.proxy
to disabled
.
Note on service interface discovery¶
The specification
attribute is generally optional except when iPOJO cannot discover the type of the service. iPOJO cannot deduce the servce specification when the dependency has no field and callbacks do not receive the service object in parameters. In this case, you must the service specification (i.e. interface).