OSGi Services

Learn the basics of OSGi services development, including:

  • How to convert a Java POJO into an OSGi Service
  • How to bind an OSGi Service to a Java interface
Transcript
Let’s take a look at OSGi components and services, which are fundamental building blocks of AEM. To do this we’ll take a look at how a regular Java class can be turned into an OSGi service, what this means, and what it gets us. Just a heads up, I’ll mostly refer to OSGi components and services simply as OSGi services or even just services for simplicity. But don’t worry, we’ll take a look at what each of these terms actually means and how they’re the same and how they’re different. So OSGi services are core Java building blocks in AEM. Services typically perform some logical set of back-end functionality that various parts of your application might use.
This is basically where we want to put reusable business logic that often, but not always, interacts with the JCR or even calls to external services. Before we dive into code, it’s worth noting, OSGi components and services must be deployed as part of an OSGi bundle. Which is what the AEM projects core subproject creates. The reason for this is the OSGi bundle’s manifest generated by the core projects bnd-maven-plugin contains information about the OSGi service that allows it to be wired up and run inside of AEM’s OSGi container, which is Apache Felix.
Okay, enough talk. Let’s check out the code. We’ll start with a regular Java class, that’s in the core project, called activities, and we’ll keep it really simple for now. All it does is return a random activity from a list of hard coded activities.
So what do we have to do to turn this into an OSGi service? So the first thing we need to do is annotate this class with @Component, which is provided by the OSGi annotations package. This registers this class with AEM as a Java Singleton. This integrates the class into the OSGi lifecycle, which allows us to create hooks for when the OSGi component is activated, modified, or deactivated.
And it also allows for dependency injection of other OSGi services using the reference annotation.
So, notice that I just said OSGi component a lot, but I didn’t actually mention OSGi service. This is because so far, we’ve only turned this class into an OSGi component. The key thing to know about OSGi components and OSGi services, is that OSGi services are simply a specific type of OSGi component. So what’s the difference between an OSGi component and an OSGi service? We just talked about the characteristics of an OSGi component: Java Singleton, OSGi lifecycle, supporting dependency injection. So why do we need it to become an OSGi service? Well OSGi components by themselves, are not referenceable in the OSGi container. So basically there’s no way other code can use our OSGi component. So for instance if we had a Sling Model that we wanted to use to get a random activity, we’d have no way of actually invoking our code here, because it hasn’t been registered in an addressable way within AEM. So what we need to do is extend this OSGi component to become an OSGi service, so that it can be addressable. So the best practice for registering an OSGi component as an OSGi service, is to make it implemented interface. So let’s do that. So by convention, the implementation of an OSGi service, which is of course not the interface, but the code behind it that implements the interface, should be placed in a i-m-p-l or impl Java package, which keeps it from being accidentally leaked into AEM and it forces any use of it through the interface contract. By convention, the implementation Java class itself is postfixed with i-m-p-l as well, to make it clear to developers which is the interface versus the implementation of the service and interface. The Java interface should only define the public methods that we want to expose for our service. Let’s move up a Java package so we’re out of the impl package and create a new Java interface. And this Java interface, as we just mentioned, will define all public methods for this OSGi service. As noted before, typically the OSGi services interface and the OSGi service or component implementation share the same name, however the implementation has that impl postfix.
Since this OSGi service only needs to expose random activity let’s add that single get random activity method. We’ll also want to annotate our interface with either the provider type or consumer type annotations. Provider type essentially means that only implementations in this bundle will implement this interface, whereas consumer type means you’re expecting other OSGi bundles to have their own implementations that implement this OSGi service interface. Unless you have a very complex project you will almost always want to have this set to provider type for your OSGi service interfaces.
Back in our implementation file, let’s go ahead and make sure that our class implements our new activities interface.
The @Component annotation will automatically register this OSGi component as an OSGi service for any and all implemented interfaces. We can have a little bit more control over this if we want. So for example, if we implemented many interfaces but only wanted to register our OSGi component to one as an OSGi service, you can use the service property and the component annotation and specify what Java interface name to register with. So now other AEM code such as OSGi services, Sling Models, or even Sling scripts, can get a reference to this OSGi service singleton using the referenced service interface. One last thing we need to do is make sure we add a package.info Java file to the Java package that contains our public interface.
And this will allow the interface in this package to be exported and used elsewhere in AEM.
So let’s do a quick deploy of this OSGi bundle and invoke our get random activity method which should return a random activity every time we invoke it.
So this is a small Harness webpage I created for AEM that simply executes our OSGi services method and prints out the return value. Okay. So refreshing this page, which executes the OSGi’s get random activity method, displays a result from our hard coded list. But if I keep refreshing this page you’ll notice that the result doesn’t change, which is incorrect behavior since the intent here is every time we call get random activity we get a random activity. If you were paying close attention as to the characteristics of an OSGi component, you might know why. Looking back at our code and remembering that OSGi components and thus OSGi services are singletons, it might be more obvious why this is happening. Our random index variable has been set as a class field. So remember that this is a singleton, so there’s a single instance of the service in AEM, and that same service is being invoked by all the http requests associated with the browser refreshes. So when this OSGi service is first instantiated, which again ties back to the OSGi component lifecycle, this random index value is generated, but then it doesn’t change. So you need to be very careful about managing and maintaining state in OSGi services, since all consumers of the service will share that same state. That said this can actually come in handy if you can share state and cache a computed value for all consumers of the service. To fix this we can move this into the method so a new random index is generated every time the method is invoked.
Let’s test this change out with a quick deploy.
Instead of deploying the entire package to my local STK, I’ll speed this up by only deploying my core OSGi bundle, since that’s the only place we made a code change.
And there you go. As we can see on each refresh our OSGi services get random activity method is invoked, since we now compute the random index value within the method itself and not within the singleton class state we can see the value change. -

Resources

Code

Activities.java

/core/src/main/java/com/adobe/aem/wknd/examples/core/adventures/Activities.java

package com.adobe.aem.wknd.examples.core.adventures;

import org.osgi.annotation.versioning.ProviderType;

@ProviderType
public interface Activities {
    String getRandomActivity();
}

ActivitiesImpl.java

/core/src/main/java/com/adobe/aem/wknd/examples/core/adventures/impl/ActivitiesImpl.java

package com.adobe.aem.wknd.examples.core.adventures.impl;

import java.util.Random;

import com.adobe.aem.wknd.examples.core.adventures.Activities;

import org.osgi.service.component.annotations.Component;

@Component(
    service = { Activities.class }
)
public class ActivitiesImpl implements Activities {

    private static final String[] ACTIVITIES = new String[] {
        "Camping", "Skiing",  "Skateboarding"
    };

    //private final int randomIndex = new Random().nextInt(ACTIVITIES.length);
    private final Random random = new Random();

    /**
     * @return the name of a random WKND adventure activity
     */
    public String getRandomActivity() {
        int randomIndex = random.nextInt(ACTIVITIES.length);
        return ACTIVITIES[randomIndex];
    }
}

package-info.java

/core/src/main/java/com/adobe/aem/wknd/examples/core/adventures/package-info.java

@Version("1.0")
package com.adobe.aem.wknd.examples.core.adventures;

import org.osgi.annotation.versioning.Version;

Adding a package-info.java is required to ensure other OSGi bundles in AEM can resolve the OSGi service interface (or any Java class). If the package-info.java is missing, the Java package and its Java interfances or classes are not exported. Other OSGi bundles trying to import these Java interfaces or classes from this Java package, will error with the message Cannot be resolved in AEM’s OSGi Bundle console.

recommendation-more-help
4859a77c-7971-4ac9-8f5c-4260823c6f69