How to write your iPOJO HandlerThis document explains how developers can use iPOJO extensibility mechanism to extends extend the (primitive) component instance container. This tutorial shows two small examples: a Log Handler logging messages inside the OSGi log service and a Property Handler injecting properties values inside fields. This document is organized as follow. First, iPOJO concepts are briefly explained. The next second section explains the first step steps to create a handler. The two last sections describe describes the implementation and the usage of two small example handlers : a Log Handler, logging messages inside the OSGi log service, and a Property Handler, injecting properties values inside fields. iPOJO Concepts iPOJO is a service oriented component model aiming to simplify OSGiâ„¢ OSGi applications development. iPOJO is based on the POJO concepts. A POJO is a simple Java class without any dependency on its runtime environment. In iPOJO, POJO are encapsulated in a container managing the relation between the POJO and the external world. This container keeps separate separated the POJO from the external "wild" world. Moreover, this container is flexible and extensiblecan be extended, using handlers. Basically, iPOJO contains two main concepts: Component Type and Component Instancecomponent type and component instance. A component type is a type of component. A component type defines its implementation class, its creation policy, and its container. A component instance is a configured instance of a component type. This instance is created with the component type factory. A component instance inherits of all component type characteristics but has a unique name , and owns a its own configuration (set of <key, value>). Above these concepts, iPOJO runtime will manage component type factories and component instances. Each component instance is managed separately . (but the factory can delete them). A component type declares its container configuration. Each component instance owns its container conform to the component type container configuration. An iPOJO container is composed by an "InstanceManager ", encapsulating the POJO, on which are plugged Handlershandlers. A handler manages one non functional concern. Handlers participate to the component instance lifecycle; can interact with the POJO; can manage relations with external entity like database, or other POJOs... For example, a persistence handler may interact with a database to store and inject POJO state, while an administration handler may use JMX to allow remote configuration of instance properties. Image. iPOJO is an extensible model allowing developer to manage other non functional concerns. Indeed, handlers can be developed singly, without modifying the iPOJO core. At runtime, iPOJO will look looks for each handler needed by a component instance and plug plugs an instance of each (neededrequired) handler on the container. So iPOJO containers are flexible, light and adaptable to each component. When a needed handler cannot be found, the component instance management failedcannot be created. An external handler is identified by a Namespacenamespace. This namespace matches with the class name (qualified name) implementing the handler behavior. This namespace will be used by will be used by developers to refer to the external handler (when he configures its component type) and by iPOJO to instantiate the handler object. Note : iPOJO core contains 5 6 "core" handlers managing service providing, service dependencies, lifecycle callbacks, lifecycle controller, instance dynamic configuration, and component instance architecture. Theses handlers follow the same rules than external handlers, except that they don't have a namespace.use the iPOJO default namespace (i.e. org.apache.felix.ipojo). Handler development first stepsGo Go Go!To create a handler, you need to create a Bundle. To know how to create bundles see: http://cwiki.apache.org/confluence/display/FELIX/Bundle+Plugin+for+Maven+%28BND%29 basis Fundamentals As explain above, the handler interacts with the POJO, with the component's container and with the external world (e.g. : other components, services, bundles, the OSGi framework, ...). The skeleton of such an agent is defined in iPOJO is defined by the PrimitiveHandler (abstract) class that can be found in the Your bundle will contain a class extending the "org.apache.felix.ipojo .Handler" class. All handler need to extends this class. This class defines three abstracts methods: configure, start and stop. This method refers to the component instance lifecycle: - The Configure method is called when the component instance is created. The configure method have three parameters: the instance manager representing the component instance, the component type metadata and the component instance configuration. Inside the Configure method, the handler needs to look for its metadata, and register itself on the component instance.
- The Start method is called when the component instance starts. Then the component instance is started. A started component instance is either VALID or INVALID. A component instance is valid if and only if all plugged handler are valid. In the Start, method a handler needs to start its management (service publication, listener registration...)
- The Stop method is called when the component instance stops. In the Stop, method a handler needs to release all references on services ...
Before, implementing the handler, you need to answer to some questions: - How configuring your handler? : Which metadata need to write a developer in its component type configuration to configure your handler?
- Does your handler participate to the component instance lifecycle? : Does your handler invalid the component instance?
- Does your handler interact with POJO fields?
According to theses answers, your handler class needs to parse the component type metadata, and select which method it needs to override from the Handler class. Your resulting bundle needs to export the package containing your handler class. Handlers class methodsThis section will explain the handler class. According to this description, you can select which method you want to override. - void configure(InstanceManager im, Element metadata, Dictionary configuration) : this method is mandatory. Your handler will receive the instance manager on which it need to registers, the component type metadata (to parse to find your handler metadata) and the component instance configuration.
- void start() : this method is called when the component instance management starts. Your handler needs to begin its management.
- void stop() : this method is called when the component instance management stops. Your handler needs to end its management, and release all used services.
- boolean isValid() : this method is called by the instance manager, when it needs to check the component instance state (VALID or INVALID). If your handler does not participate to the component instance lifecycle, you don't need to override this method. Else, you need to return if your handler is valid.
- void stateChanged(int state) : this method is called by the instance manager each time that the instance state changes. The parameter describe the new instance state (-1 : Disposed, 0 : Stopped, 1 : Invalid, 2 : Valid).
- void setterCallback (String fieldName, Object value) : this method is called by the instance manager, when a POJO field value changes. To be called, your handler needs to register to the field. The first argument of the method is the field name. The second argument is the new value of the field. If the field type is a primitive type, the method sends the boxing object.
- Object getterCallback (String fieldName, Object value) : this method is called by the instance manager, when a POJO field asks for a value. To be called, your handler needs to register to the field. The first argument of the method is the field name. The second argument is the actual value of the field. This method returns the value that your handler wants to inject in the field.
- void createInstance (Object inst) : this method is called by the instance manager, when a POJO object is created but before that anyone can use it. The argument is the created object.
- void reconfigure(Dictionary configuration) : this method is called when the instance manager recevie a new instance configuration. This configugration can be applied whithout restarting the instance. The parameter contains the new configuration. Each handler managing the dynamic reconfiguration can, by overriden this method, apply the new configuration.
- HandlerDescription getDescription(): this method allow an handler to participate to the instance introspection. When called, the handler needs to describe its state and returns a HandlerDescription object.
Log Handler exampleThis section describes how to create a simple handler. This handler logs a message in the OSGiâ„¢ log service when the component instance state changes. It participates to the component instance lifecycle (the handler in not valid when there is no log service available). The handler namespace is "org.apache.felix.ipojo.log.handler.LogHandler". It is the handler implementation class too. A developer who wants to use the handler will configure its component type with the following metadata: Code Block |
---|
| xml | xml |
<iPOJO xmlns:log= org.apache.felix.ipojo.log.handler.LogHandler>
<component className="...">
<log:Log level="error"/><!- - error, warning or info accepted - ->
</component>
<instance component="..." name="LogHandlerTest-1"/>
</iPOJO>
package.
You need to implement the three basic lifecycle methods of this class, but you can extends this model by redefining some other methods (e.g. : to intercept POJO method calls, field accesses, ...).
Declaring your handler
You first need to declare your handler, so iPOJO will be able to initialize, configure and use it when needed. First, you must give a name and an XML namespace to your handler. By doing that, iPOJO can recognize that a certain component uses your handler, so it can initialize it. You need to declare, in the metadata.xml
of the bundle containing your handler, the class name of your handler and its name and XML namespace. You can, of course, declare several handlers, and even declare components using these handlers, in the same bundle.
Then, you must know that a handler is a component (almost) like standard iPOJO components : it can use other handlers (like core handlers : service requirements, provided services, ...). You can consequently describe your handler's required services, provided services, etc. in its metadata.xml, as for classic iPOJO components.
Code Block |
---|
|
<ipojo>
...
<handler className="your.handler.class" name="HandlerName" namespace="the.namespace.of.your.handler">
...
<provides interface="a.provided.Service">
<property field="a_field" name="a.property" value="a.value"/>
</provides>
...
</handler>
</ipojo>
|
Note : In order to use iPOJO annotations processing, the namespace must be a valid package name and the name must be a valid annotation name (without the '@'). Refer to the Handler's annotations section in the Advanced Topics chapter.
Hint : It is a good idea to provide a documented XML schema description (XSD) with your handler to help users to configure your handler and to validate their configurations. Refer to the Handler's XSD section in the Advanced Topics chapter.
Handler lifecycle
A handler lifecycle is composed of four different states.
Image.
- First, when iPOJO parses the
metadata.xml
of a bundle, it detects that a certain component type use your handler (using XML qualified names, see the following ìUsing your handlerî section). When it finds such a reference, it initializes the handler by calling the initializeComponentFactory()
method. This method should be static but actually can't be so for some technical reasons. Consequently, a ìmockî instance of the handler is created, the initializeComponentFactory()
method is called, and this instance is destroyed. This method aims to check the validity of the component type description, avoiding starting invalid factories.If you override this method, you should here set up the component description (e.g. : common properties, exported services, ...) and check the handler configuration. The parameters passed to this method are the ComponentTypeDescription
and the component's Metadata
(i.e. : the structure of the component type declaration). - Once your handler has been initialized, iPOJO configures it for each created instance of the using components. The
ComponentTypeDescription
and the instance specific properties are passed to the configure()
method of your handler.This method is mandatory so you have to implement it. Here you should check the handler configuration (if not already done in the initializeComponentFactory()
method) and configure the handler with given instance specific properties. - Then, iPOJO starts the handler, following the component instance lifecycle, by calling the
start()
method. You have to put in this method the activation code of your handler. A freshly started handler is by default in an active state (if all its used handlers, like required services, are in an active state). - Once started, the handler state can be either in a valid or in an invalid state, depending on its used handlers (a handler is an iPOJO component, so it can depend on other handlers, like service dependencies, provided services, ... See the ìHandler extends Componentî section). The validity of your handler depends on used handlers status, but it also can be changed in your handler code by using the
setValidity()
method. - Finally, when the component instance is stopped (generally just before being destroyed), the stop method of the handler is called. Place here the inactivation code of your handler.
Note : Keep in mind that the stop()
method of your handler is called when the component instance is stopped (not necessarily destroyed). This instance can be restarted later, so the same instance of your handler must have to ability to restart too.
Reading handler and instance configurations
Your handler need to read how it is configured in the using component type description. The configuration is written in the metadata.xml
of the using bundle, but is passed to the initializeComponentFactory()
and configure()
methods as an Element
object.
The Element
type (placed in the org.apache.felix.ipojo.metadata package
), coupled with the Attribute
type, is used to retrieve the structure and the content of the component configuration. The Element
parameter, passed to the initialization and configuration methods, represents the root of the component type description (i.e. the root of the tree is the component
XML tag).
Several methods allows to browse the entire configuration from the root Element
:
- The
getElement()
methods let you access the content of an Element
(i.e. the children elements) - The
getAttribute()
methods allows you to access the attributes of an Element
. - The
containsElement()
and containsAttribute()
methods test the presence of a child-element or an attribute in an Element
.
Note : As described in the Declaring your handler section, a name and a namespace are associated to each handler. To safely retrieve the configuration of this handler from the component metadata, you can take inspiration from the following snippet (the componentMetadata
variable is the component root Element
passed to the initializeComponentFactory()
and configure()
methods) :
Code Block |
---|
Element[] handlerConfig = componentMetadata.getElements("HandlerName",
"the.namespace.of.your.handler");
|
You will also need to read specific instance configuration (properties defined in the instance
XML tag). The instance properties are directly passed, as a Dictionary,
to the configure()
method. With these properties, you can easily allow instances to override some component fixed configuration.
Interacting with the POJO
One of the most interesting features of an handler is the ability to interact with the component's POJO. Indeed, you can intercept method calls and returns, inject values in the POJO's fields...
The getPojoMetadata()
method of the PrimitiveHandler class lets you access the structure of the POJO (represented by the PojoMetadata
type) without having to use (slow) reflection. It allows you to list all fields and methods of the POJO, and get informations about implemented interfaces and the super-class. The PojoMetadata
class implements the following operations :
- The
getInterfaces()
method returns the list of implemented interfaces, while the isInterfaceImplemented()
methods test if a given interface is implemented by the POJO. - The
getSuperClass()
method returns the name of the class extended by the POJO (or null
instead of java.lang.Object
). - The
getField()
methods lets you access the fields of the POJO. The returned object is a FieldMetadata
that provides informations about a particular field inside the POJO. - The
getMethod()
methods lets you access the methods of the POJO. The returned object is a MethodMetadata
that provides informations about a particular method in the POJO.
Once you've retrieved informations about the POJO structure, you can interact with it, via the InstanceManager
, accessible in your handler by the getInstanceManager()
method. It allows you to register interceptors, that are called before and after POJO method calls or field accesses.
Note : The InstanceManager manages the component instance attached to your handler instance. Thus, it can't be available in the initializeComponentFactory()
because this method is run before the creation of any component instance.
You need to implement some of the following methods to intercept fields accesses :
- The
void onSet(Object pojo, String fieldName, Object value)
method: This method is called each time a field of the POJO is assigned. The first parameter is the instance of the concerned POJO, the second is the name of the accessed field and the third is the value assigned to the POJO's field. If the field type is a primitive type, this method receives the boxed object. - The
Object onGet(Object pojo, String fieldName, Object value)
method : This method is called each time a field of the POJO is read. The first parameter is the instance of the concerned POJO, the second is the name of the accessed field and the third is the actual value of the POJO's field. If the field type is a primitive type, this method receives the boxed object. The returned object is the value the intercepted read process will return. It's the standard way to inject a value in the field : returning a specific object whatever the field really contains.
You need to implements some of the following methods to intercept methods accesses. When these methods are called, the first parameter is the POJO's instance on which the intercepted method is called and the second parameter contains the descriptor of the called method.
- The
void onEntry(Object pojo, Method method, Object[] args)
method: This method is called before the execution of an intercepted method. The third parameter is the list of parameters with which the method have been called. The method is executed just after the execution of the onEntry()
callback. - The
void onExit(Object pojo, Method method, Object returnedObj)
method: This method is called right after the successful execution of an intercepted method. The third parameter is the value returned by the method (or null
if the method return type is void
). This value must not be modified. - The
void onError(Object pojo, Method method, Throwable throwable)
method: This method is called right after the unexpected return of an intercepted method (i.e. when an uncaught exception occurred). The third parameter is the thrown object that caused the method termination. - The
void onFinally(Object pojo, Method method)
method: This method is called after the termination of an intercepted method (expected or not), after the call of the onExit()
or onError()
callback.
Warning : The InstanceManager
has to know your handler wants to intercept fields or methods access, otherwise the implemented callbacks won't be called. Thus you need to register each field and method you want to intercept, so the InstanceManager
will call the appropriated callbacks when the specified field or method is accessed :
Code Block |
---|
getInstanceManager().register(anInterestingFieldMetadata, this);
...
getInstanceManager().register(anInterestingMethodMetadata, this);
...
|
Note : The PrimitiveHandler
abstract class implements the FieldInterceptor
and MethodInterceptor
interfaces, which declares the methods described just above. You can create your own interceptor class (implementing one or both of these interfaces) and give it to the InstanceManager
register method instead of the handler object itself.
Using your handler
Once your handler has been declared, you can use it in iPOJO components. To do so, you first have to be bound to your handler's namespace (using standard XML namespace declaration). Then you can configure the handler in your components type description. An example of bundle's metadata.xml
declaring components using the handler is shown hereafter :
Code Block |
---|
<ipojo xmlns:your-shortcut="the.namespace.of.your.handler">
...
<component className="your.component.class">
...
<your-shortcut:HandlerName param1="value1" ...>
<!-- Configuration of your handler for this component type -->
</your-shortcut{noformat}:HandlerName>
...
</component>
...
</ipojo>
|
The remainder of this document describes two examples of handlers:
- A log handler logging messages in the OSGi Log Service
- A properties handler reading a property files to configure POJO field
Log Handler example
This section describes how to create a simple handler. This handler logs a message in the OSGi Log Service (if present) when the component instance state changes. The code source of this handler is downloadable here.
The handler namespace is "org.apache.felix.ipojo.log.handler.LogHandler"
. It is also the name of the handler implementation class. You can note that the handler has an optional dependency on a OSGi log service. If no log services are found, a default implementation is used instead.
Code Block |
---|
|
<ipojo>
<!-- Declare the handler -->
<handler classname="org.apache.felix.ipojo.handler.log.LogHandler" name="log" namespace="org.apache.felix.ipojo.log.handler.LogHandler" >
<!-- The log service dependency -->
<requires field="m_log" optional="true" default-implementation="org.apache.felix.ipojo.handler.log.PrimitiveLogService"/>
</handler>
</ipojo>
|
Handler implementation
The handler needs to override following methods:
configure
: to parse the metadata and load the properties filestateChanged
: to log messages when the instance state changes.
LogHandler class
The handler is implemented inside the LogHandler
class in the org.apache.felix.ipojo.handler.log
package. This class extends the org.apache.felix.ipojo.PrimitiveHandler
class.
The handler needs to be notified when component instances becomes valid or invalid, thus it implements the InstanceStateListener
interface.
InitializeComponentFactory Method
This method parses and checks the component type metadata. The handler needs a log element from its namespace. According to the result, the configure method can throw an exception or parse the level attribute (to get the logging level).
Code Block |
---|
public void initializeComponentFactory(ComponentTypeDescription typeDesc, Element metadata) throws ConfigurationException {
// Get all Namespace:log element from the metadata
Element[] log_elements = metadata.getElements("log", NAMESPACE); // There must be exactly one configuration element.
if (log elements.length == 1) {
Element log_element = log_elements[0]; // There must be a level attribute in the configuration element
if(log_element.containsAttribute("level")) {
String level = log_element.getAttribute("level"); // Check the value of the level attribute
if (level == null) {
throw new ConfigurationException("No level attribute found in the configuration");
}
if (!level.equalsIgnoreCase("info") && !level.equalsIgnoreCase("warning") && !level.equalsIgnoreCase("error")) {
throw* new ConfigurationException("Bad log level specified, accepted values are : info, warning, error");
}
}
}
}
|
This method reads the component description and configures the handler. Then, the handler registers itself to the instance manager to be informed of the component's validity changes.
Code Block |
---|
public void configure(Element metadata, Dictionary config)
throws ConfigurationException {
// Get the configuration element
Element log_element = metadata.getElements("log", _NAMESPACE_{noformat})[0];{noformat}
// Get the level attribute's value
String level = log_element.getAttribute("level");
// Extract the log level
if(level.equalsIgnoreCase("info")) {
m_level = LogService.LOG_INFO;
} else if (level.equalsIgnoreCase("warning")) {
m_level = LogService.LOG_WARNING;
} else if (level.equalsIgnoreCase("error")) {
m_level = LogService.LOG_ERROR;
}
m_manager = getInstanceManager();
m_context = m_manager.getContext();
}
|
StateChanged Method
This method is called by the instance manager to notify that the component instance state changes. The handler needs to log a message containing the new state.
Code Block |
---|
public void stateChanged(ComponentInstance instance, int state) {
// Log the state changed events
if (state == InstanceManager.VALID) {
m_log.log(m_level, "The component instance " + m_manager.getInstanceName() + " becomes VALID");
} else if (state == InstanceManager.INVALID) {
m_log.log(m_level, "The component instance " + }}m_manager{{.getInstanceName() + " becomes INVALID");
}
}
|
Handler packaging
This handler needs to be packaged inside an iPOJO bundle. The bundle will import the org.apache.felix.ipojo
, org.osgi.framework
and org.osgi.service.log
packages.
Handler usage
To use this handler, a component needs to declare an org.apache.felix.ipojo.log.handler.LogHandler:log
XML element, with a level attribute. This level attribute's value can be "error"
, "warning"
or "info"
. Here is an usage example:
Code Block |
---|
|
<ipojo xmlns:log="org.apache.felix.ipojo.log.handler.LogHandler">
<!-- Declare a component using the LogHandler -->
<component_classname=_"..."_>
...
<!-- Configuration of the LogHandler ?
<log:log level=_"WARNING"_ />
</component>
...
</ipojo>
|
Download
The LogHandler is available here : INSERT URL HERE. The archive file contains the handler implementation and a simple component using this handler.
Properties Handler example
This section presents a second handler. This handler loads a property file containing field name and initial value. Then it injects and maintains these values inside POJO fields. In this example, only String values are managed.
You can find the sources of this example handler in the example/handler/property
directory of the iPOJO sources.
This handler is always valid, so do not participate to the component instance lifecycle. Moreover, the handler does not need to be notified when the component instance state changed. But, it need to be notified when POJO fields need a value or change their value.
Handler implementation
The handler needs to override following methods:
- Configure
configure
: to parse the metadata and get the logging level - Start and Stop : to track Log Service
- stateChanged : to log the message
- isValid : to be invalid when the Log Service is not available
Log Handler class
The handler is implemented inside the "org.apache.felix.ipojo.log.handler.LogHandler" class. This class extends the "org.apache.felix.ipojo.Handler" class. This class has the final static string field "NAMESPACE", with the "org.apache.felix.ipojo.log.handler.LogHandler" value.
The handler needs the Log Service. So it will implement the "org.osgi.framework.ServiceListener" interface too.
The class has several other fields:
- The bundle context (context)
- The instance manager (manage)
- The service reference of the actual used Log Service (ref)
- The used log service (log)
- The level of the log (level)
- load the properties file
stop
: to store the propertiesonGet
: to inject a values inside a fieldonSet
: to obtain the new field value
PropertiesHandler class
The handler is implemented by the PropertiesHandler
class present in the org.apache.felix.ipojo.properties.handler
package. The class has several fields:
- The properties to maintain (
m_properties
) - The properties file name (
m_file
)
Note: the file name is the absolute path on the local machine of the fileNote: Handler code is OSGiâ„¢ standard code. A handler cannot be a POJO.
This method begins by parsing the component type metadata. The handler needs a log properties element from its namespace. According to the result, the configure method can return immediately or parse the level attribute (to get the logging level). Then, the handler stores some value inside field as the instance manager and the bundle context (note how your handler can access to the bundle context). To finish, the handler must register itself to the instance manager to become a part of the instance container.can return immediately or parse the file attribute (to get the properties file path). Then, it builds a field list (String array) to register to field notification. By registering with a field array, the handler will be a part of the component instance container and will be notified of field access.
Code Block |
---|
public void configure(Element metadata, Dictionary configuration) throws ConfigurationException {
// Parse metadata to get <properties file="$file"/>
Element[] elem = metadata.getElements("properties", NAMESPACE). // Get all example.handler.properties:properties element
switch (elem.length) {
case 0: // No matching element in metadata, throw a configuration error.
throw new ConfigurationException("No properties found");
case 1: // One 'properties' found, get attributes
m_file = elem |
Code Block |
---|
java | java |
title | LogHandler.java:Configure Method |
---|
public void configure(InstanceManager im, Element metadata, Dictionary config) {
// First parse the metadata to check if the log handler level
// Get all Namespace:log element from the metadata
Element[] log_elements = metadata.getElements("log", NAMESPACE);
// if no element found, return
if(log_elements.length == 0) { return; }
else {
// If an element match, parse the level attribute of the first element
if(log_elements[0].containsAttribute("level")) {
String l = log_elements[0].getAttribute("levelfile");
if(l.equalsIgnoreCase("info")){level=LogService.LOG_INFO; }
else if(l.equalsIgnoreCase("error")){level=LogService.LOG_ERROR; }
if (m_file == null) { // if file is null, throw a configuration error.
else {level=LogService.LOG_WARNING;}
}
// Register on the instance manager, to be athrow part of the component new ConfigurationException("Malformed properties element : file attribute must be set");
// instance} container
manager = im;
context = manager.getContext(); // Store the bundle context
manager.register(this) break;
}
}
|
The start and stop methods
The start method needs to look for a Log Service, and register a service listener to track it. If a log service is available, it logs a message.
Code Block |
---|
java | java |
title | LogHandler.java:Start Method |
---|
public void start() {
default: // When starting, look for an LogService
ref = context.getServiceReference(LogService.class.getName());
if(ref != null) {
log = (LogService) context.getService(ref);
// Log a starting message
log.log(level,"The component instance " + manager.getInstanceName() + " is starting");
}
// Registered a service listenner
try {
context.addServiceListener(this,"(OBJECTCLASS="+LogService.class.getName()+")");
} catch (InvalidSyntaxException e) { e.printStackTrace(); }
}
|
The stop method logs a message if the Log Service is available. Then it releases the used Log Service.
Code Block |
---|
java | java |