Monday, September 10, 2012

Declarative Services with Karaf Part 2: The Basics

Now that you have had the chance to install the SCR Commands and the Basic Service Example lets start to look at the code that was executed. The code for the basic GreeterService example is on Github. Follow the steps below to retrieve and build it.

Source Checkout and Build
git clone https://github.com/sully6768/karaf-scr-examples.git
cd karaf-scr-examples
git fetch
mvn clean install
cd service

SCR POM

First lets examine what is required to make our build work. After changing to the service project open the pom.xml in your favorite editor. It is a standard Maven OSGi build configuration using the maven-bundle-plugin with the pom.xml packaging set to bundle. To use the the SCR annotations though we need to add the BND Library. We will also add the Felix SCR Library though in reality we don't need it to build in the case of the GreeterService as it sits. It is only required when your code needs to use the lower level SCR APIs which we will use as we get further along.

Maven Dependency Configuration
<dependencies>
    <!-- 
        Contains the SCR Annotations
     -->
    <dependency>
        <groupId>biz.aQute</groupId>
        <artifactId>bndlib</artifactId>
        <version>1.50.0</version>
    </dependency>
    <!-- 
        Contains the SCR binaries.  Only required 
        if you need the low level SCR APIs 
    <dependency>
        <groupId>org.apache.felix</groupId>
        <artifactId>org.apache.felix.scr</artifactId>
        <version>1.6.0</version>
    </dependency>
    -->
    <!-- 
        Snipped.  See example for full config
    -->
</dependencies>

Looking over the Maven Bundle Plugin there is a new addition to our configurations that will help create our SCR XML configuration for our bundle. It is the <Service-Component> element. This element tells the BND library to search through our source code and when the @Component annotation is found, create a new configuration for it.

Maven Bundle Plugin Configuration
<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <configuration>
        <instructions>
            <Service-Component>*</Service-Component>
        </instructions>
    </configuration>
</plugin>

SCR Sources

Our sources for SCR are annotated POJOs. In the case of the basic GreeterService example detailed below there are two types of Components defined: an Immediate Component (a service consumer) and a Delayed Component (a service provider).

First lets look at the GreeterService. GreeterService.java is a simple interface that provides a single method of invocation.

GreeterService.java
public interface GreeterService {
    void printGreetings();
}

The implementation for the GreeterService is found in GreeterServiceImpl.java.

GreeterServiceImpl.java
@Component
public class GreeterServiceImpl implements GreeterService {
    
    private static final Logger LOG = LoggerFactory.getLogger(GreeterServiceImpl.class);

    private String name = System.getProperty("user.name", "Scott ES");

    private String salutation = "Hello";

    public void printGreetings() {
        LOG.info(salutation + " " + name);
    }
}

This service provider is a very simple implementation that will print a greeting to the log upon activation. The key to this POJO is the inclusion of the @Component annotation at the top of implementation class. As the source code is parsed by BND, the annotations are then used to create the SCR configuration XML that is then included in the makes decisions by what is available as it parses the source code.

In this case the GreeterService is considered a Delayed Component. As BND parses the source code will first consider a Java class with @Component to be a Delayed Component if the class implements an interface. The containers SCR instance will then register the Component and not activate it until the service is required by a service consumer.

If your service needs to be realized immediately you can do so by adding the immediate attribute set to true on the @Component annotation. For instance if you want to activate a component that does implement an interface but isn't considered a service you will need to add this attribute.

Back in Part 1 you may remember that the GreeterService was in an Active state after it was installed.  This was due to there being an active consumer of the service in the container, the GreeterComponent. Since the GreeterComponent was in an Active state, the containers SCR instance created an instance of the GreeterService and injected into our GreeterComponent. To understand what happend lets take a look at GreeterComponent.java.

GreeterComponent.java
@Component(name = GreeterComponent.COMPONENT_NAME)
public class GreeterComponent {

    public static final String COMPONENT_NAME = "GreeterComponent";

    public static final String COMPONENT_LABEL = "Greeter Component";
    
    private static final Logger LOG = LoggerFactory.getLogger(GreeterComponent.class);

    private GreeterService greeterService;

    /**
     * Called when all of the SCR Components required dependencies have been
     * satisfied.
     */
    @Activate
    public void activate() {
        LOG.info("Activating the " + COMPONENT_LABEL);
        greeterService.printGreetings();
    }

    /**
     * Called when any of the SCR Components required dependencies become
     * unsatisfied.
     */
    @Deactivate
    public void deactivate() {
        LOG.info("Deactivating the " + COMPONENT_LABEL);
    }

    @Reference
    public void setGreeterService(final GreeterService greeterService) {
        this.greeterService = greeterService;
    }

    public void unsetGreeterService(final GreeterService greeterService) {
        this.greeterService = null;
    }
}

The GreeterComponent.java class is our service consumer class. It is annotated with the @Component annotation just like GreeterService but considered an Immediate Component. BND will configure GreeterComponent.java as an Immediate Component because it doesn't implement an interface. Therefore once all of its required dependencies are available this component will be activated.

Again, this can be overridden if you need to by adding the immediate attribute set to false on the @Component annotation.

The GreeterComponent also introduces 3 more annotations: @Activate, @Deactivate & @Reference.

The @Reference annotation is used to configure a dependency requirement for a component, in this case the GreeterService. As such the defaults will cause the GreeterComponent to remain deactivated if the GreeterService is not available.

When the GreeterService dependency becomes satisfied, the SCR service will call the configured activation method denoted by the @Activate annotation. Conversely if the dependency becomes unsatisfied post activation the SCR service will call the components configured deactivate method denoted by the @Deactivate annotation.

Running the Example

Open up your instance of Karaf where we installed the Karaf SCR Components in Part 1 so you can manipulate the example components. Once at the Karaf CLI, execute the scr:list command:

karaf@root> scr:list 
   ID   State             Component Name
[7   ] [ACTIVE          ] GreeterComponent
[6   ] [ACTIVE          ] org.apache.karaf.scr.examples.service.impl.GreeterServiceImpl
karaf@root>;

Both components are current active as all dependencies are satisfied.

What happens though when the GreeterService is deactivated?

karaf@root> scr:deactivate GreeterComponent 
karaf@root> scr:list 
   ID   State             Component Name
[-1  ] [DISABLED        ] GreeterComponent
[9   ] [REGISTERED      ] org.apache.karaf.scr.examples.service.impl.GreeterServiceImpl
karaf@root>

As you can see from above the GreeterComponent becomes unsatisfied.  If you review the log file it will show that the GreeterComponents deactivate method has also been called:

INFO  | Deactivating the GreeterService Component

Now that it is deactivated lets activate the GreeterService again using scr:activate GreeterComponent. Executing scr:list displays all components are now active.

karaf@root> scr:activate GreeterComponent
karaf@root> scr:list 
   ID   State             Component Name
[7   ] [ACTIVE          ] GreeterComponent
[9   ] [ACTIVE          ] org.apache.karaf.scr.examples.service.impl.GreeterServiceImpl
karaf@root>

The log file also shows that the GreeterComponent's activate method was called once its dependencies became satisfied.

INFO  | Activating the GreeterService Component
INFO  | Hello sully6768

Lets see the difference though when we deactivate the GreeterService:

karaf@root> scr:deactivate org.apache.karaf.scr.examples.service.impl.GreeterServiceImpl
karaf@root> scr:list 
   ID   State             Component Name
[11  ] [UNSATISFIED     ] GreeterComponent
[-1  ] [DISABLED        ] org.apache.karaf.scr.examples.service.impl.GreeterServiceImpl
karaf@root>

With the GreeterService now disabled the GreeterComponent becomes unsatisfied resulting in the GreeterComponent deactivate method being called.

Modifying and Running Your Own Example

Now that we have covered the basics feel free to review other options that are a part of the BND/OSGi Annotations to the example to see how SCR behaves in Karaf. By now, if you haven't used SCR before, I am sure you are starting to see the benefits and the possiblities. In Part 3 we will start looking at the easiest ManagedServices you have ever developed for OSGi.

Next Article:
Part 3: Managed Services

Previous Articles:
Part 1: Getting Started

References:
Maven Bundle Plugin: http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html
BND: http://www.aqute.biz/Bnd/Components

1 comment:

  1. I tried to configure the maven-bundle-plugin like described in your article. It did not seem to process the DS annotations though. Through the felix mailing list I found that for the maven bundle plugin >= 2.5.0 you need another configuration:
    <_dsannotations>*< /_dsannotations>.
    I hope this helps some people who try your example.

    ReplyDelete