Error CSS Stylesheet macro - Import URL 'http://felix.apache.org/ipojo/site/superfish.css' is not on the allowlist. If you want to include this content, contact your Confluence administrator to request adding this URL to the Allowlist.
Error CSS Stylesheet macro - Import URL 'http://felix.apache.org/ipojo/site/style.css' is not on the allowlist. If you want to include this content, contact your Confluence administrator to request adding this URL to the Allowlist.

iPOJO API

The iPOJO API provides a third way to describe iPOJO components and instances. With the API, you can dynamically create new components types and create instances from them. Your component types are described with a Java API. To use the API, deploy and start the iPOJO-API bundle and the iPOJO core bundle.

A simple example

Let's imagine a simple component providing a service Foo. The following code is the implementation class of our component:

package org.example.service.impl;

import org.example.service.Foo;

public class FooImpl implements Foo {

    public void doSomething() {
        // Do something...
    }

}

To create the component type and an instance of the component type just create a class, get the bundle context (either from a Bundle-Activator, or from an iPOJO component), and write the following code:

new PrimitiveComponentType()
    .setBundleContext(context)
    .setClassName(FooImpl.class.getName())
    .addService(new Service()) // Provide the Foo service
    .createInstance(); // Create the instance

So, now let's imagine another component using this service. The following code is the implementation class of this component type:

package org.example.service.impl;

import org.example.service.Foo;

public class MyComponentImpl {
    private Foo myFoo;

    public void start() {
        myFoo.doSomething();
    }

}

It is a regular iPOJO component expecting to get a Foo service in the myFoo field. It also has a method executed when the instance is valid using the Foo service. To describe this component, just write the following lines:

new PrimitiveComponentType()
    .setBundleContext(context)
    .setClassName(MyComponentImpl.class.getName())
    .addDependency(new Dependency().setField("myFoo"))
    .setValidateMethod("start")
    .createInstance();

Deploying the API

Before being able to create component types at runtime, you need to deploy 3 bundles:

  • iPOJO (core)
  • iPOJO Composite
  • iPOJO API

You can deploy all these bundles in one step thanks to OBR:

obr start "Apache Felix iPOJO API"

Primitive Component Type basics

When you create a new Primitive component type (so, using a Java class as implementation), you must set the bundle context and the class name. Everything else is optional.

Note about inner classes: iPOJO is based on a bytecode manipulation. The API embeds the manipulator. So, when you initialize the component type, the specified class name is manipulated (if not already manipulated). So, as this force using a customized classloader, inner classes cannot be manipulated. So, inner class injection is not supported with the API.

Immediate Component, Validate and Invalidate Method

To set the component type as immediate, just call the setImmediate(immediate) method on the primitive component type object.
To set validate and invalidate methods, just call the setValidate(method) and setInvalidate(method). Specify the method name that you want to call in argument.

Declaring services

To declare that a component provides a service, add a new service to your primitive component type.
The Service object can be configured. By default, it exposed every implemented interface (regular iPOJO behavior). So, you can:

  • Add service property
  • Set the creation strategy
  • Set the exposed service specifications
new PrimitiveComponentType()
    .setBundleContext(context)
    .setClassName(org.example.service.impl.MyComponentImpl.class.getName())
    .addService(new Service()
        .addProperty(new ServiceProperty()
            .setField("myServiceProperty")
            .setName("sample.myProperty"))
        .setCreationStrategy(Service.INSTANCE_STRATEGY))
    .createInstance();

Service Dependencies

To declare a service dependency, create and add a Dependency object. The dependency object offers all the iPOJO service dependency features. You can set the injected field and/or bind/unbind methods. Here is an example:

new PrimitiveComponentType()
    .setBundleContext(context)
    .setClassName(org.example.service.impl.MyComponentImpl.class.getName())
    .addDependency(
        new Dependency().setField("myFoo")
       .setOptional(true)
       .setDefaultImplementation("org.sample.FooDefaultImplementation")
       )
    .createInstance();

Properties

Thanks to the addProperty method, you can create component properties. Here is some example of properties:

new PrimitiveComponentType()
    .setBundleContext(context)
    .setClassName(MyComponentImpl.class.getName())
    .addProperty(new Property()
        .setField("myProperty")
        .setValue("default-value")
        )
    .addProperty(new Property()
        .setMethod("setMethod")
        .setName("prop")
    )
    .createInstance();

Temporal Dependencies

Temporal dependencies are also supported:

new PrimitiveComponentType()
        .setBundleContext(context)
        .setClassName(MyComponentImpl.class.getName())
        .addDependency(
             new TemporalDependency().setField("myFoo")
            .setOnTimeoutPolicy(TemporalDependency.NULLABLE)
            .setTimeout(3000)
            .setProxy(true)
            )
        .createInstance();

Instance creation

The API allows you to create instances from the component type you described. Three differents methods.
The createInstance() method just creates an instance. The createInstance(String name) method allows to set the instance name. Finally, the createInstance(Dictionary configuration) allows setting the instance configuration. All those methods returns the ComponentInstance object allowing to manage the instance (stop, start, dispose).

Managed Service and Propagation

You can enable/disable the property propagation thanks to the setPropagation method on the PrimitiveComponentType object.
You can also set the the managed service PID with the setManagedServicePID method. This method should be only use to give a default value of for singleton component. In all other case, the managed service pid has to be provided inside the instance configuration.

Managing iPOJO Factory

Beyond the PrimitiveComponentType, an iPOJO factory is hidden. You can configure this factory to be public or private with the setPublic method. You can also set the name of the factory with the setName method.

Then, you can access to the Factory object by calling the getFactory method.

Access to the introspection API

The API provides bridge to get access to the iPOJO introspection API. The introspection API allows reconfiguring at runtime an instance (properties, service dependencies...). From Service and Dependency, Property and ServiceProperty objects, call the getXXXDescription method. You must give the instance that you want to introspect in argument. If the lookup success, you get an object allowing reconfiguring the service, dependency or property.

Singleton Component Type

If you are sure to create only one instance of your component type, you can use the singleton component type class. This is a kind of primitive component type, but when you start it (with the create method), it will automatically create an instance.

 PrimitiveComponentType type = new SingletonComponentType()
            .setBundleContext(context)
            .setClassName(org.example.service.impl.MyComponentImpl.class.getName())
            .addDependency(new Dependency().setField("myFoo"))
            .setValidateMethod("start");
        
        ((SingletonComponentType) type)
            .setObject(new MyComponentImpl(5)) // Inject a pojo object
            .create();// Create an instance

The type created with the singleton component type are set to private by default. Instead of calling the start method, you have to call one of the create methods to start the type and create the instance.

You can also set the contained POJO object by using the setObject method. The given object MUST be compatible with the component implementation class.

Using external handlers

iPOJO is extensible... So, it makes sense that the API is also extensible. So component type provides a method allowing to add external handler configuration:

return new PrimitiveComponentType()
        .setBundleContext(context)
        .setClassName(HostImpl.class.getName())
        .addHandler(new Whiteboard()
            .onArrival("arrival")
            .onDeparture("departure")
            .setFilter("(foo=foo)")
         );

The addHandler method allows you to add any handler description. A handler description is an object of a class implementing org.apache.felix.ipojo.api.HandlerConfiguration. Handler provider willing to support the API have to provide this class. For example, the example above uses Whiteboard that is the Whiteboard pattern handler description. This class is very simple, and is shown below:

public class Whiteboard implements HandlerConfiguration {
    
    public static final String NAME = "wbp";
    
    public static final String NAMESPACE = "org.apache.felix.ipojo.whiteboard";
    
    private String arrival;
    
    private String departure;
    
    private String modification;
    
    private String filter;
    
    public Whiteboard onArrival(String method) {
        arrival = method;
        return this;
    }
    
    public Whiteboard onDeparture(String method) {
        departure = method;
        return this;
    }
    
    public Whiteboard onModification(String method) {
        modification = method;
        return this;
    }
    
    public Whiteboard setFilter(String fil) {
        filter = fil;
        return this;
    }

    public Element getElement() {
        ensureValidity();
        // Create the root element.
        Element element = new Element(NAME, NAMESPACE);
        // Mandatory attributes
        element.addAttribute(new Attribute("onArrival", arrival));
        element.addAttribute(new Attribute("onDeparture", departure));
        element.addAttribute(new Attribute("filter", filter));
        
        // Optional attribute
        if (modification != null) {
            element.addAttribute(new Attribute("onModification", modification));
        }        
        
        return element;
    }

    private void ensureValidity() {
        if (arrival == null) {
            throw new IllegalStateException("The whiteboard pattern configuration must have a onArrival method");
        }
        if (departure == null) {
            throw new IllegalStateException("The whiteboard pattern configuration must have a onDeparture method");
        }
        if (filter == null) {
            throw new IllegalStateException("The whiteboard pattern configuration must have a filter");
        }
        
    }

The only required method is getElement returning the Element-Attribute structure representing the handler configuration (this uses the internal iPOJO data format). If the metadata cannot be generated, the class throws IllegalStateExceptions.

Creating composition with the API

The API also allows you to create iPOJO compositions in a pretty simple way. So you can create compositions:

  • containing internal instances
  • importing services
  • instantiating sub-services
  • exporting services
  • providing services (by delegation)

Here are some examples:

Contained instances
 PrimitiveComponentType prov = createAProvider(); // Create a primitive type
 PrimitiveComponentType cons = createAConsumer(); // Create another primitive type
       
       CompositeComponentType type = new CompositeComponentType()
           .setBundleContext(context)
           .setComponentTypeName("comp1")
           .addInstance(new Instance(prov.getFactory().getName())) // Create an instance in the composite
           .addInstance(new Instance(cons.getFactory().getName())); 
       
       ComponentInstance ci = type.createInstance();
Importing services
 CompositeComponentType type = new CompositeComponentType()
           .setBundleContext(context)
           .setComponentTypeName("comp3")
           .addSubService(new InstantiatedService() // Importation
              .setSpecification(Foo.class.getName())
              .setOptional(true));
Instantiated sub-services
  CompositeComponentType type = new CompositeComponentType()
           .setBundleContext(context)
           .setComponentTypeName("comp2")
           .addSubService(new InstantiatedService()  // Instantiated service
              .setSpecification(Foo.class.getName()))
           .addInstance(new Instance(cons.getFactory().getName()));
Exported Services
CompositeComponentType type = new CompositeComponentType()
           .setBundleContext(context)
           .setComponentTypeName("compExport")
           .addSubService(new InstantiatedService().setSpecification(Foo.class.getName()))
           .addService(new ExportedService()
                .setSpecification(Foo.class.getName())); // Exports a service


  • No labels