How to write your iPOJO Handler

This document explains how developers can use iPOJO extensibility mechanism to extend the (primitive) component instance container. Such extensibility mechanism does not require to modify the iPOJO core.

First, iPOJO concepts are briefly explained. The second section explains the steps to create a handler. The two last sections 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.

The code is available in this archive file.

iPOJO Concepts

iPOJO is a service oriented component model aiming to simplify 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 separated the POJO from the external "wild" world. Moreover, this container can be extended, using handlers.

Basically, iPOJO contains two main concepts: component 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 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 handlers. 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.

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 looks for each handler needed by a component instance and plugs an instance of each (required) 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 cannot be created.

An external handler is identified by a namespace. This namespace 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.

iPOJO core contains 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 use the iPOJO default namespace (i.e. org.apache.felix.ipojo).

Handler development 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 org.apache.felix.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 a 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, using the @Handler annotations. You can, of course, declare several handlers, and even declare components using these handlers, in the same bundle.

@Handler(name = "Log", namespace = LogHandler.NAMESPACE)
public class LogHandler extends PrimitiveHandler {
    public static final String NAMESPACE = "org.apache.felix.ipojo.log.handler";
    // ...
}

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 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 annotations section.

Handler lifecycle

A handler lifecycle is composed of four different states.

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 (from 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 tag).

Several methods allows to browse the entire configuration from the root Element :

Note : As described in the description 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) :

Element[] log_elements = metadata.getElements("log", NAMESPACE);

For example, the log handler provided in the archive file has the following configure method:

/**
 * Parses the component's metadata to retrieve the log level in which we log messages.
 *
 * @param metadata      component's metadata
 * @param configuration instance configuration (unused in this example)
 * @throws ConfigurationException the configuration is inconsistent
 */
@Override
public void configure(Element metadata, Dictionary configuration) throws ConfigurationException {
    // First parse the metadata to check if the log handler logLevel

    // Get all Namespace:log element from the metadata
    Element[] log_elements = metadata.getElements("log", NAMESPACE);

    // If an element match, parse the logLevel attribute of the first found element
    if (log_elements[0].containsAttribute("level")) {
        String l = log_elements[0].getAttribute("level");
        if (l.equalsIgnoreCase("info")) {
            logLevel = LogService.LOG_INFO;
        } else if (l.equalsIgnoreCase("error")) {
            logLevel = LogService.LOG_ERROR;
        } else if (l.equalsIgnoreCase("warning")) {
            logLevel = LogService.LOG_WARNING;
        }
    }

    instanceManager = getInstanceManager();
}

You can also access instance configuration (properties defined in the instance 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. The property handler given in the archive file extract the location of the loaded properties file from the instance configuration:

// Look if the instance overrides file location :
String instanceFile = (String) configuration.get("properties.file");
if (instanceFile != null) {
    m_file = instanceFile;
}

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 :

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.

The property handler is registering field interceptors on injected properties:

//First get Pojo Metadata metadata :
PojoMetadata pojoMeta = getPojoMetadata();
Enumeration e = m_properties.keys();
while (e.hasMoreElements()) {
    String field = (String) e.nextElement();
    FieldMetadata fm = pojoMeta.getField(field);

    if (fm == null) { // The field does not exist
        throw new ConfigurationException("The field " + field + " is declared in the properties file but does not exist in the pojo");
    }

    // Then check that the field is a String field
    if (!fm.getFieldType().equals(String.class.getName())) {
        throw new ConfigurationException("The field " + field + " exists in the pojo, but is not a String");
    }

    // All checks are ok, register the interceptor.
    getInstanceManager().register(fm, this);
}
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 property handler contains the following onGetz andonSet` methods:

/**
 * This method is called at each time the pojo 'get' a listened field. The method return the stored value.
 * @param pojo : pojo object getting the field
 * @param field : field name.
 * @param o : previous value.
 * @return the stored value.
 */
public Object onGet(Object pojo, String field, Object o) {
    // When the pojo requires a value for a managed field, this method is invoked.
    // So, we have just to return the stored value.
    return m_properties.get(field);
}

/**
 * This method is called at each time the pojo 'set' a listened field. This method updates the local properties.
 * @param pojo : pojo object setting the field
 * @param field : field name
 * @param newvalue : new value
 */
public void onSet(Object pojo, String field, Object newvalue) {
    // When the pojo set a value to a managed field, this method is invoked.
    // So, we update the stored value.
    m_properties.put(field, newvalue);
}

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 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 :
     getInstanceManager().register(anInterestingFieldMetadata, this);
     ...
     getInstanceManager().register(anInterestingMethodMetadata, this);
     ...
    
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 :

<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:HandlerName>
        ...
    </component>
    ...
</ipojo>

Obviously, you probably want to use annotations. You just have to provide the annotation classes: handler_namespace.handler_element. For instance, the log handler provides the org.apache.felix.ipojo.log.handler.Log annotation:

package org.apache.felix.ipojo.log.handler;

/**
 * The annotation used to configure the LogHandler.
 */
public @interface Log {

    public enum Level {
        INFO, ERROR, WARNING
    }

    /**
     * @return the log level
     */
    Level level();
}

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.

Handler metadata

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.

// Declare a handler.
@Handler(name = "Log", namespace = LogHandler.NAMESPACE)
public class LogHandler extends PrimitiveHandler {

    public static final String NAMESPACE = "org.apache.felix.ipojo.log.handler";

    // Handlers are iPOJO components, so can use service dependencies
    @Requires(optional = true, nullable = false)
    LogService log;
    private InstanceManager instanceManager;
    private int logLevel;

//...

Handler implementation

The handler needs to override following methods: configure : to parse the metadata and load the properties file stateChanged : 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.

Configure Method

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.

/**
 * Parses the component's metadata to retrieve the log level in which we log messages.
 *
 * @param metadata      component's metadata
 * @param configuration instance configuration (unused in this example)
 * @throws ConfigurationException the configuration is inconsistent
 */
@Override
public void configure(Element metadata, Dictionary configuration) throws ConfigurationException {
    // First parse the metadata to check if the log handler logLevel

    // Get all Namespace:log element from the metadata
    Element[] log_elements = metadata.getElements("log", NAMESPACE);

    // If an element match, parse the logLevel attribute of the first found element
    if (log_elements[0].containsAttribute("level")) {
        String l = log_elements[0].getAttribute("level");
        if (l.equalsIgnoreCase("info")) {
            logLevel = LogService.LOG_INFO;
        } else if (l.equalsIgnoreCase("error")) {
            logLevel = LogService.LOG_ERROR;
        } else if (l.equalsIgnoreCase("warning")) {
            logLevel = LogService.LOG_WARNING;
        }
    }

    instanceManager = getInstanceManager();
}

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.

/**
 * Logging messages when the instance state is changing
 *
 * @param state the new state
 */
public void stateChanged(int state) {
    if (log != null) {
        if (state == InstanceManager.VALID) {
            System.out.println("The component instance " + instanceManager.getInstanceName() + " becomes valid");
            log.log(logLevel, "The component instance " + instanceManager.getInstanceName() + " becomes valid");
        }
        if (state == InstanceManager.INVALID) {
            System.out.println("The component instance " + instanceManager.getInstanceName() + " becomes invalid");
            log.log(logLevel, "The component instance " + instanceManager.getInstanceName() + " becomes invalid");
        }
    }
}

Start and Stop

The handler also contains two methods called by the instance manager when the underlying instance starts and stops.

/**
 * The instance is starting.
 */
public void start() {
    if (log != null) {
        log.log(logLevel, "The component instance " + instanceManager.getInstanceName() + " is starting");
    }
}

/**
 * The instance is stopping.
 */
public void stop() {
    if (log != null) {
        log.log(logLevel, "The component instance " + instanceManager.getInstanceName() + " is stopping");
    }
}

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 use the Log annotation, with a level attribute. This level attribute's value can be "error", "warning" or "info". Here is an usage example:

package org.apache.felix.ipojo.log.handler.example;

import org.apache.felix.ipojo.annotations.*;
import org.apache.felix.ipojo.foo.FooService;
import org.apache.felix.ipojo.log.handler.Log;

@Component(immediate = true)
@Log(level = Log.Level.INFO) // We configure the handler.
@Instantiate(name = "my.simple.consumer")
public class SimpleComponent {

    @Requires
    FooService fs;

    @Validate
    public void starting() {
        System.out.println("Starting...");
        fs.foo();
    }

    @Invalidate
    public void stopping() {
        System.out.println("Stopping...");
    }
}

Playing with the handler

The archive contains a project named Log-Handler-In-Felix, which once built, provides a Felix framework with all the bundles deployed.

Unzip the archive, and build the whole project using Maven: mvn clean install. It builds the log handler and the property handler. Then navigate to the felix-framework-VERSION directory:

mvn clean install
#...
cd Log-Handler-In-Felix/target/felix-framework-4.2.1/
java -jar bin/felix.jar

Once you have launched Felix, you get the Gogo Shell prompt:

Starting...
Foo
The component instance my.simple.consumer becomes valid
____________________________
Welcome to Apache Felix Gogo

g! lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (4.2.1)
    1|Active     |    1|Apache Felix Bundle Repository (1.6.6)
    2|Active     |    1|Apache Felix Gogo Command (0.12.0)
    3|Active     |    1|Apache Felix Gogo Runtime (0.10.0)
    4|Active     |    1|Apache Felix Gogo Shell (0.10.0)
    5|Active     |    1|Apache Felix iPOJO (1.8.6)
    6|Active     |    1|Apache Felix iPOJO Gogo Command (1.0.1)
    7|Active     |    1|iPOJO Log Handler Consumer (1.9.0.SNAPSHOT)
    8|Active     |    1|iPOJO Foo Service (1.9.0.SNAPSHOT)
    9|Active     |    1|iPOJO Log Handler (1.9.0.SNAPSHOT)
   10|Active     |    1|Apache Felix Log Service (1.0.1)
g!

You can already see some of the messages printed by the handler (The component instance my.simple.consumer becomes valid). To see more message, stop and start the Foo Service bundle:

g! stop 8
The component instance my.simple.consumer becomes invalid
Stopping...
g! start 8
g! Starting...
Foo
The component instance my.simple.consumer becomes valid

By stopping the Foo service bundle, you withdrew the foo service from the service registry making our component invalid (and unhappy). The handler is notified of the new state and logs a message. When the bundle restarts, the service is republished. So the instance becomes valid again. The handler is notified and logs another message.

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.

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:

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:

Note: the file name is the absolute path on the local machine of the file.

/**
 * This handler load a properties file containing property value.
 * The handler injects this values inside fields. When stopped the handler stores updated value inside the file. The
 * properties file contains <pre>field-name : field-value</pre> (field-value are strings)
 *
 * Instances can override file locations by setting the {@literal properties.file} property.
 *
 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
@Handler(name="properties", namespace = PropertiesHandler.NAMESPACE)
public class PropertiesHandler extends PrimitiveHandler {

    /**
     * The Handler namespace.
     */
    public static final String NAMESPACE = "org.apache.felix.ipojo.handler.properties";

    /**
     * The loaded properties.
     */
    private Properties m_properties = new Properties();

    /**
     * The properties file location, configured in the component's metadata.
     */
    private String m_file;

Configure Method

This method begins by parsing the component type metadata. The handler needs a properties element from its namespace. According to the result, the configure method 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 is going to be notified of field access.

/**
 * This method is the first to be invoked.
 * This method aims to configure the handler. It receives the component type metadata and the instance
 * configuration. The method parses given metadata and registers fields to inject.
 *
 * Step 3 : when the instance configuration contains the properties.file property, it overrides the properties file location.
 *
 * @param metadata : component type metadata
 * @param configuration : instance description
 * @throws ConfigurationException : the configuration of the handler has failed.
 */
@SuppressWarnings("unchecked")
public void configure(Element metadata, Dictionary configuration) throws ConfigurationException {
    // Get all elements to configure the handler
    Element[] elem = metadata.getElements("properties", NAMESPACE);

    switch (elem.length) {
        case 0:
            // No matching element in metadata, throw a configuration error.
            // It actually happen only if you force the handler to be plugged.
            throw new ConfigurationException("No properties found");
        case 1:
            // One 'properties' found, get attributes.
            m_file = elem[0].getAttribute("file");
            if (m_file == null) {
                // if file is null, throw a configuration error.
                throw new ConfigurationException("Malformed properties element : file attribute must be set");
            }
            break;
        default:
            // To simplify we handle only one properties element.
            throw new ConfigurationException("Only one properties element is supported");
    }

    // Look if the instance overrides file location :
    String instanceFile = (String) configuration.get("properties.file");
    if (instanceFile != null) {
        m_file = instanceFile;
    }

    // Load properties
    try {
        loadProperties();
    } catch (IOException e) {
        throw new ConfigurationException("Error when reading the " + m_file + " file : " + e.getMessage());
    }

    // Register fields
    // By convention, properties file entry are field name, so look for each property to get field list.

    //First get Pojo Metadata metadata :
    PojoMetadata pojoMeta = getPojoMetadata();
    Enumeration e = m_properties.keys();
    while (e.hasMoreElements()) {
        String field = (String) e.nextElement();
        FieldMetadata fm = pojoMeta.getField(field);

        if (fm == null) { // The field does not exist
            throw new ConfigurationException("The field " + field + " is declared in the properties file but does not exist in the pojo");
        }

        // Then check that the field is a String field
        if (!fm.getFieldType().equals(String.class.getName())) {
            throw new ConfigurationException("The field " + field + " exists in the pojo, but is not a String");
        }

        // All checks are ok, register the interceptor.
        getInstanceManager().register(fm, this);
    }
}

Notice that the handler is using the instance configuration. So instances can set their own file location using the properties.file property.

The start and stop methods

The start method does nothing, but needs to be implemented.

public void start() {}

The stop method stores properties inside the properties file.

public void stop() { 
    try { 
        saveProperties();
    } catch (IOException e) { 
        // Log an error message by using the iPOJO logger 
        error("Cannot read the file : " + m_file, e); 
    } 
    m_properties = null;
}

onGet and onSet methods

The onGet method is called when the POJO need a field value. When called, the method needs to return the stored value.The onSet method is called when the POJO modifies a field value. If the new value if null, the handler will remove this properties from the property list.

public Object onGet(Object pojo, String field, Object o) { 
    // When the pojo requires a value for a managed field,
    // this method is invoked. 
    // So, we have just to return the stored value. 
    return m_properties.get(field);
}

public void onSet(Object pojo, String field, Object newvalue) { 
    // When the pojo set a value to a managed field, 
    // this method is invoked. 
    // So, we update the stored value. 
    m_properties.put(field, newvalue);
}

Creating the annotation

The handler provides an annotation to ease its use:

package org.apache.felix.ipojo.handler.properties;

/**
 * The Properties annotation.
 * This annotation may be used in POJO class to used the Property handler.
 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
public @interface Properties {

    /**
     * Returns the property file used by the handler.
     */
    String file();

}

Handler packaging

This handler needs to be inside a bundle importing the org.apache.felix.ipojo packages and exporting the org.apache.felix.ipojo.properties.handler package.

Playing with the handler

As for the log handler , the archive contains a felix distribution with all bundles deployed.

cd Property-Handler-In-Felix/target/felix-framework-4.2.1/
java -jar bin/felix.jar

In Gogo you immediately see the loaded properties:

-- listing properties --
property2="bbb"
property1="aaa"
PropertiesTester is starting ...
Property 1 : "aaa"
Property 2 : "bbb"
Update properties
-- listing properties --
property2="bbb"
property1="aaa"
PropertiesTester is starting ...
Property 1 : "aaa"
Property 2 : "bbb"
Update properties
____________________________
Welcome to Apache Felix Gogo
g!

In this example, we have two instances of the same component type loading different properties files. The first instance loads the default properties file. The second one is configured to read another one. This configuraiton is given in the instance configuration:

<ipojo>
    <!-- Declare an instance illustrating instance configuration -->
    <instance component="PropertiesTester"
        name="instance-using-properties-i1">
        <property name="props.file"
            value="props\properties-i1.properties" />
    </instance>
</ipojo>

Advanced topics

Handler reconfiguration

iPOJO has the ability to reconfigure component instances while they are running. When instances are reconfigured, their used handler need to update their configuration (if they support such an operation). To do so, reconfigurable handlers must override the reconfigure() method, which notify the concerned handlers of the new instance configuration (represented as a Dictionary).

Describing your handler

Handlers have the possibility to describe their state, overriding the getDescription() method and the HandlerDescription class. By default, only the handler's name and validity are displayed in component instance's description (informations displayed by the (arch -instance an.instance.name command). The standard way to add description to your handler is shown hereafter :

public class YourHandler extends PrimitiveHandler {  
    ... 
    // Method returning the handler description. 
    public HandlerDescription getDescription() { 
        return new YourHandlerDescription(this);
    }

    ...

    private class YourHandlerDescription extends HandlerDescription {  
        public Description(PrimitiveHandler h) { super(h); }

        // Method returning the custom description of this handler. 
        public Element getHandlerInfo() { 
             // Needed to get the root description element. 
             Element elem = super.getHandlerInfo();  
             // Add here attributes and sub-elements 
             // into the root description element. 
             // Example : elem.addAttribute(new Attribute("param", "value")); 
             Element subElement = new Element("subElement", ""); 
             subElement.addAttribute(new Attribute("subParam", "subValue")); 
             elem.addElement(subElement);
             ...
             return elem; 
       }
   }
}

Handler's annotations

Your handle can also provide annotations. Annotations will allows users to configure the Handler from the source code (avoiding XML edition). iPOJO supports annotation of external handlers. Indeed, it detects annotations and re-creates the Element-Attribute structure. So, first, external Handler annotations MUST follow some principles:

So, when iPOJO detects the annotation, an Element is created with the annotation package as the Element namespace and the annotation name as the Element name. Then, 'scalar' annotation attributes are mapped to Attribute. Sub-annotations (annotation attribute) are mapped to sub-elements. For example, the annotation for the property handler is:

package org.apache.felix.ipojo.properties.handler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
public @interface Properties {

    String file();

}

This annotations is put on the element, and allows setting the property file:

@Component
@Properties(file="/Users/clement/felix/properties/i1.properties")
public class Example {
    ...
}

However, your handler can also provide several annotations to represent Element and sub-elements. Your annotations can also be placed on different code elements (Type, Field, Method). In this case, to recreate the Element/Sub-Element hierarchy, iPOJO processes as following:

For example, the following code:

import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.handlers.jmx.Config;
import org.apache.felix.ipojo.handlers.jmx.Method;
import org.apache.felix.ipojo.handlers.jmx.Property;

@Component
@Config(domain="my-domain", usesMOSGi=false) // External handler annotation
public class JMXSimple {

    @Property(name="prop", notification=true, rights="w") //External handler annotation
    String m_foo;

    @Method(description="set the foo prop") //External handler annotation
    public void setFoo(String mes) {
        System.out.println("Set foo to " + mes);
        m_foo = mes;
    }

    @Method(description="get the foo prop") //External handler annotation
    public String getFoo() {
        return m_foo;
    }
}

will be translated to:

component { 
    $classname="org.apache.felix.ipojo.test.scenarios.component.jmx.JMXSimple"
    $public="true" $name="org.apache.felix.ipojo.test.scenarios.component.jmx.JMXSimple"
    org.apache.felix.ipojo.handlers.jmx:config { 
        $usesmosgi="false" $domain="my-domain" 
        org.apache.felix.ipojo.handlers.jmx:property { 
            $rights="w" $notification="true" $field="m_foo" $name="prop" }
        org.apache.felix.ipojo.handlers.jmx:method { 
            $description="set the foo prop" $method="setFoo" }
        org.apache.felix.ipojo.handlers.jmx:method { 
            $description="get the foo prop" $method="getFoo" }
    }
}

Note: To customize this hierarchy, you can also use the id/parent annotation attributse. The id attribute is used to refer to an Element. An annotation with a parent (targeting an id) attribute will be processed as a sub-element of the Element identified by the given id.

Handler's XSD

Coming soon...

Conclusion

In this document, we present how-to develop handler for your components. We describe two small examples : a log handler and a properties handler. These handlers are plugged on (primitive) instance. However, it is possible to extends CompositeHandler too to customize the composition model.

If you develop handler and you want to share it, feel free to contact us by sending a mail on the Felix mailing list.