Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

This tutorial presented presents some iPOJO features . It and shows:

  • How a component can provide two services
  • How to attach a service property to provided service
  • How a service property can be dynamically updated by component code
  • How to configure instances
  • How a service dependency can filter providers
  • How creating to create instance(s) for a component not contained in the same bundle
  • How configuring instances
  • How to use the lifecycle controller handler

The sources of this tutorial is are available here. This tutorial used uses the iPOJO Eclipse plugin.

Context

This tutorial uses is based on a very simple application. Customers use a Vendor ; customers are using a vendor service to buy hot dog or pop corn according to the availability of these two appropriate providers. Both the pop corn vendor and the hot dog vendor of the vendors implement (and provide) the vendor service. The hot dog vendor depends on two other services for getting ingredientto get the ingredients. It depends on a bun service and a wiener service.

...

The hot dog vendor requires at the same time the bun service and the wiener service. In our application these two services are provided by one the same component. This component can be implemented as followingfollows:

Code Block
 
public class BunWienerProvider implements BunService, WienerService {
    
    public void getBun() {
        System.out.println("Get a bun");
    }

    public void getWiener() {
        System.out.println("Get a wiener");
    }
}

This class just implements the two service interfaces. Then, the descriptor of this component Its descriptor is:

Code Block
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.provider.BunWienerProvider" name="buns_and_wieners" factory="false">
	<provides/>
</component>

<instance component="buns_and_wieners"/>
</ipojo>

First, In the descriptor we declare a component type for our component . The component type which contains the implementation class. The attribute "classname" attribute contains simply the qualified name of the component implementation. The "name" attribute is the component type name. It is only used to refer to this type.

The attribute "factory=false" allows avoiding attribute disables factory exposition. A component type publishing a factory provides a way to create instance of this type from outside this descriptor. In our case, when we want to guaranty guarantee that only one instance will be created.

IPOJO manages service publication and providing automatically at runtime. The "<provides/>" element means that the component provide services. IPOJO will manage automatically service publication and providing at runtimeprovides services. If this element is not describespresent, iPOJO will publish all implemented interfaces by the implementation class. In our case, it will publish the only BunService and WienerService interfaces.

Finally, we create on one instance of our component. The instance contains the component attribute describing the component type to use. We use the component type name to target the wanted component type.
At runtime, the bundle containing this component will create an instance . This instance which provides the BunService and the WienerService.

Publishing a service property

The hot dog vendor just only provides the Vendor service. To provide this service, it used uses a bun service and a wiener service. The following code snippet shows a very simple implementation of this component:

Code Block
public class HotDogVendor implements VendorService {
    
    private BunService bunProvider;
    private WienerService wienerProvider;
    
    public String getName() {
        return "The Best Hot Dogs";
    }

    public String sell() {
        bunProvider.getBun();
        wienerProvider.getWiener();
        return "sell an hotdog";
    }
}

The two fields field attributes in the "requires" elements are used to inject the two required services. At runtime, iPOJO injects automatically a BunService provider in the "bunProvider" field and a WienerService provider in the "wienerProvider" field. The implementation uses these field as other field fields the same way it would have used any other fields (as illustrated in the sell method).
The metadata of this type This type of metadatas are very simple:

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.hotdog.HotDogVendor" name="HD" factory="false">
	<provides/>
	<requires field="bunProvider"/>
	<requires field="wienerProvider"/>
</component>

<instance component="HD"/>
</ipojo>

The component type declares a provided service (the Vendor Service). Then, the component declares the two service dependencies (using the "requires" element). However, we would like to add a service property on the Vendor service describing the sale product (here, "hotdog"). To achieve this, we just need to add a property element in the "provides" tags as following:

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.hotdog.HotDogVendor" name="HD" factory="false">
	<provides>
		<property name="product" type="string" value="hotdog"/>
	</provides>
	<requires field="bunProvider"/>
	<requires field="wienerProvider"/>
</component>

<instance component="HD"/>
</ipojo>

IPOJO then publishes the "product" property in the "vendor" service registration. This property has the "hotdog" value.

...

The bun service and the wiener service can also expose service properties too. These In our case, these service properties will describe the stock of the ingredientingredients. At each Each time the service is used, the property is decreased.
To achieve this, we modify the current implementation to add a field representing the property:

Code Block
public class BunProvider implements BunService, WienerService {
    
    private int bunStock;
    
    private int wienerStock;

    public void getBun() {
        bunStock = bunStock - 1;
    }

    public void getWiener() {
        wienerStock = wienerStock - 1;
    }
}

Then the The component type metadata must also be modified in order to describe this property:

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.provider.BunProvider" name="buns_and_wieners" factory="false">
	<provides>
		<property name="buns" field="bunStock" value="10"/>
		<property name="wieners" field="wienerStock" va	lue="10"/>
	</provides>
</component>

<instance component="buns_and_wieners"/>
</ipojo>

In the "provides" element, two properties are added. This property contains a "field" attribute aiming to attach the service property with a field of the implementation class. Then a default value is given. In the code, the property fields will obtain the initial value (10). Then at each time the fields are modified, the service property is updated (and the OSGi? as well as the OSGi™ service registration is updated).

Configuring instances

In the previous example, the properties were configured in the component type description. It is also possible to customized customize any value in the instance declaration. ThenThis way, each instance can obtain different values.

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.provider.BunProvider" name="buns_and_wieners" factory="false">
	<provides>
		<property name="buns" field="bunStock" value="10"/>
		<property name="wieners" field="wienerStock" va	lue="10"/>
	</provides>
</component>

<instance component="buns_and_wieners">
	<property name="buns" value="9"/>
	<property name="wieners" value="8"/>
</instance>
</ipojo>

...

Using filter in service requirement

Now that bun and wiener provider publishes its providers are publishing their remaining stock, the hot dog provider can look for a bun service and a wiener service with a non empty stock. To achieve this, we simply must describe an LDAP filter in the service dependency description. The following xml XML snipped shows this metadata:

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.hotdog.HotDogVendor" name="HD" factory="false">
	<provides>
		<property name="product" type="string" value="hotdog"/>
	</provides>
	<requires field="bunProvider" filter="(buns>=1)"/>
	<requires field="wienerProvider" filter="(wieners>=1)"/>
</component>

<instance component="HD"/>
</ipojo>

When a used provider does no more matches with the LDAP filter, the provider is no more used, and another (matching with the filter) is looked for. If no provider fulfilling the constraint is found, the instance becomes invalid and waits a matching provider.

...

Now that we get the hot dog provider, we are going to implement customers. A customer simply looks for a vendor service and buys a product.:

Code Block
public class Customer {
    
    private VendorService vendor;
    
    private String name;
    
    public Customer() {
        System.out.println("Customer " + name + " bought " +  vendor.sell() + " from " + vendor.getName());
    }

The previous code shows a possible implementation of a customer. However, the "sell" method is called in a constructor. The constructor is , and the constructor can only be called only if an object of the class is created. With iPOJO there is are two different way to "activate" an instance as soon as it becomes valid.
The first one use uses the lifecycle callback (describe described in the previous tutorial).
The second one is by declaring the component as an immediate component. An immediate component instance creates an object of its implementation as soon as it becomes valid.

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.customer.Customer" factory="customer" immediate="true">
	<requires field="vendor"/>
	<properties>
		<property field="name"/>
	</properties>
</component>
<instance component="customer">
	<property name="name" value="my_customer"/>
</instance>
</ipojo>

To declare a component immediate, just write add "immediate=true" in the component descriptor. Then as soon as the vendor service is available, it creates the object is created.

Note: There is a difference beetween immedaite componenet between immediate component and component with a 'validate' lifecycle callback. Indeed, the callback is call at each time the instance becomes valid . IT call and calls the constructor only if no object already existexists. On the other side, the immediate component called the constructor only once 's constructor is called each time.

Creating instances from an external component type

In the previous section we have declared a customer component type. This component type , which does not have the "factory=false" attribute. This allows another descriptor to use this type. This feature allows deploying separately implementation separate deployment from instance creation.
So another Another metadata file can be used to declare instances from the customer types. This , this descriptor is being contained in another bundle. The following descriptor creates 10 customer instances:

Code Block
xml
xml
<ipojo>
<instance component="customer">
	<property name="name" value="customer-1"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-2"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-3"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-4"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-5"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-6"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-7"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-8"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-9"/>
</instance>
<instance component="customer">
	<property name="name" value="customer-10"/>
</instance>
</ipojo>

When Once deployed, this bundle looks for the required factory. If it's not available it the bundle waits for it apparitionthe factory to be created. When this bundle is stopped, all instances are destroyed.

...

Sometimes you want to invalidate your instance from your in the code (for example: to unregister a service...). That's possible with the lifecycle controller handler.
Imagine the popcorn vendor with a corn stock. At each Each time he it sells popcornsome popcorns, its stock decreases. When the stock reach reaches 0, it can no more sell popcorn cannot sell popcorns any more (so , the vendor service need needs to be withdrawedwithdrawn).

The following implementation uses a field to control the lifecycle.

Code Block
public class PopCornVendor implements VendorService {
    
    private int m_corn_stock = 5;
    private boolean m_can_sell = true;
    
    public String getName() {
        return "Eat my pop corn !"; 
    }

    public String sell() {
        m_corn_stock = m_corn_stock - 1;
        if (m_corn_stock == 0 && m_can_sell) {
            m_can_sell = false;
        }
        return "popcorn"; 
    }
}

When Once the field is set to "false", the instance is invalidated (the vendor service is no more available). To configure the controller, just you can use the following metadata :

Code Block
xml
xml
<ipojo>
<component classname="org.apache.felix.ipojo.example.vendor.popcorn.PopCornVendor" name="popcorn" factory="false" architecture="true">
	<provides/>
	<controller field="m_can_sell"/>
</component>

<instance component="popcorn"/>
</ipojo>

The instance can be re-validated if by setting the field is set to true.

Conclusion

This small tutorial has presented some of the main iPOJO features. If you have comments or questions, do not hesitate to send me an email to: clement.escoffier@gmail.com.