Something you can’t do with ComponentFactories

About 2 weeks ago i started to implement the main architecture of EOS which is a subproject based on redVoodo. The main goal is to provide a very flexible and extendable base implementation for business applications. Based on OSGi DS (declarative services) i started to design my application. I decided to use ComponentFactories to create different instances of the same interface / class. For instance this is necessary to provide web session based classes like an ApplicationContext with application scope. I was suppressed how powerful OSGi services are. But then i recognized, that i have used ComponentFactory a way they are not intended to be used. See http://www.eclipse.org/forums/index.php/t/209483/

This post should explain the problems i faced during the implementation of EOS. Therefore i have prepared a simple example based on “shapes”. A IShapeFactory provides instances of IShapes for different types (circle, square,…). Implementations of IShapes are provided by ComponentFactories. These componenteFactories are required by the shapeFactory.

Class dependencies

This image shows the dependencies between the classes involved and their function.

ShapeFactory

public interface IShapeFactory {

/**
* Creates a new shape for the given type or <code>null</code> if no shape
* is registered for the type.
*
* @param type
* @return
*/
IShape createNewShape(String type);

}

Based on the shape factory several new instances of a shape can be created. Since the architecture of this factory is extendable, the type should be specified by a property (study1.shapeType) in the OSGi component definition of the IShape component factory. The component factories are registered at the shapeFactory by the OSGi DS (declarative service) framework.

If createNewShape is called, the factory tries to find a component factory which is responsible for the given type and uses this factory to create a new instance of IShape.

IShape

public interface IShape {

/**
* Calling this method will paint the
* content of that shape instance.
*/
void paint();

}

Instances of IShape are used to paint the shape defined by the implementation. For instance it paints a circle or a square or something else.
Since each visualized shape is a new instance of the IShape interface, i decided to use a ComponentFactory which provides new instances of that interface. How this works will be described later.

A really nice way, which allows the new instantiated components to use OSGi “declarative services” as well. If we would instantiate the shapes with “new SomeShape()”, that classes can not use DS since they are not defined by a service component definition. But since we are using ComponentFactories the IColorProvider from image above could also be defined as a required service for the class Circle. That’s the BIG advantage using ComponentFactories.

Service dependencies

This image shows the service dependencies.

org.redvoodo.osgi.study1.services

This bundle contains all service interfaces. The ComponentFactory rectangle in the image above is only displayed there to make the visualization much clearer. So it is not really contained in that bundle.

org.redvoodo.osgi.componentfactory.shape

This bundle contains 2 IShape implementations. Square and Circle. Instead of providing the IShape interface, it provides a ComponentFactory service which can be used to create new instances of the IShape implementation.

The really important entries in that service component definition are:

factory="study1.shapesfactory"

Defines the service component as a component factory.

property name="study1.shapeType" type="String" value="circle"

Adds a shape type property to the component definition. The shapeFactory should use this property to identify the type of the shape which is provided by the component factory.

org.redvoodo.osgi.componentfactory.factory

It provides the IShapeFactory service and contains an implementation of ShapeFactory. The ShapeFactory requires 0:n “ComponentFactories providing IShape”.

If a ComponentFactory was added (addShapeComponentFactory), the passed properties will be used to determine the type of the shape that is provided by the component factory. And the component factory is registered in an internal map.

If a ComponentFactory was removed, it will be removed from the internal map by its type.

If createNewShape(String) is called, the factory registered by the given type will be used to create a new instance of the shape. If no registered factory could be found, null will be returned.

public class ShapeFactoryImpl implements IShapeFactory {

private Map<String, ComponentFactory> factories
= new HashMap<String, ComponentFactory>();

/**
* Called by OSGi
*
* @param shapeFactory
* @param properties
*/
protected void addShapeComponentFactory(
     ComponentFactory shapeFactory,
     Map<String, Object> properties) {

     String type = (String)
       properties.get("study1.shapeType");
     factories.put(type, shapeFactory);
}

/**
* Called by OSGi
*
* @param shapeFactory
* @param properties
*/
protected void removeShapeComponentFactory(
     ComponentFactory shapeFactory,
     Map<String, Object> properties) {

     String type = (String)
       properties.get("study1.shapeType");
     factories.remove(type);
}

@Override
public IShape createNewShape(String type) {
     IShape result = null;
     if (factories.containsKey(type)) {
        ComponentFactory factory =
          factories.get(type);
        result = (IShape) factory
          .newInstance(null).getInstance();
        // for normal you should also
       // handle the lifecycle of
       // ComponentInstance
     }
  return result;
}

Conclusion

After finishing this implementation i was really suppressed. Really suppressed that nothing happend! My architecture did not work at all.

But what is the problem? Well, the problem was, that i did not know some issues about ComponentFactories. So i asked at the newsgroup (equinox newsgroup) an got following answer:

BJ Hargrave

DS spec. From 112.2.4 Factory Component:

SCR must register a Component Factory service on behalf of the component as soon as the component factory is satisfied. The service properties must be:

* component.name The name of the component.
* component.factory The factory identifier.

The service properties of the Component Factory service must not include the component properties.

The error

Now i know why this kind of architecture does not work.

I assumed, that the properties specified in the service component definition of the ComponentFactory are passed as properties to the instance of the ComponentFactory. But, as DS spec. about Factory Components describes, only the properties component.name and component.factory are passed to the service. So the ShapeFactory could not determine the type the ComponentFactory is responsible for!

This property is not accessible for the component factory service!

And so the type will always be null.

The solution

Currently i am working on a nested component factory solution. It uses a service which provides some kind of IFactory. Implementations of the IFactory are using nested ComponentFactories to avoid the instantiation by new().

The solution should be probably finished next week. Then i am writing a new post about it.

If you have any suggestions feel free to post a comment. Any input is welcome!

2 Responses to “Something you can’t do with ComponentFactories”

  1. Andrei Pozolotin Says:

    fyI: couple of examples of building declarative services factories:

    1) via config admin

    https://github.com/carrot-garden/carrot-osgi/tree/master/carrot-osgi-scr-factory-cm

    2) via component factory

    https://github.com/carrot-garden/carrot-osgi/tree/master/carrot-osgi-scr-factory-ds

Leave a comment