Getting a Handle on Service Implementations in AEM: Part 2

AEM | February 6, 2017

OSGi- A Brief Introduction

OSGi is a complex framework consisting of several design paradigms, architectures, and technical specifications. Entire books can be written on just a single aspect of it. In this blog, we’ll only focus on the idea of using OSGi services from its service registry.

OSGi originally set out to define a set of specifications to allow the development of highly modular enterprise systems for the Java language. It was founded in 1999 as the Connected Alliance, and comprised researchers from  Ericsson, IBM, Motorola, and Sun Microsystems. Today, the alliance is made from more than 35 multinational technical companies. OSGi has evolved to support a wide variety of platforms, including, systems for mobile phones, the web, and automobiles.1

Services in OSGi are identical to the services we introduced in the previous blog. Recall that they are basically just Java interfaces. The service registry, in the meantime, is a database of all the services that are available in the current environment, and is a crucial component of the framework. The OSGi service registry, which is itself a service, acts as a real-time broker that matches a client-code to any services it wishes to use.

Getting a Service Using Core OSGi API’s

Before we show the code to obtain a service, we have to populate a service-provider into OSGi’s service registry. Historically, this was done by a class that implements a special kind of interface called a ‘BundleActivator’. Classes that implement this interface are provided with an execution environment variable called the ‘BundleContext’ object. The following classes register an email service:

Class EmailRegistrationProvider implements BundleActivator {
  @Override
  Public void start(BundleContext context) throws Exception {
    EmailService gmailService = new GmailProvider();
    context.registerService(EmailService.class.getName(),gmailService,null);
  }
}

Where, GmailProvider is a service-provider and may look as the follows:

Class GmailProvider implements EmailService {

  @Override
  Public String checkEmail(){
    // code to check against the gmail server
  }

  @Override
  Public String sendEmail(){
    // code to send email via the gmail smtp
  }

}

Now any class/client-code that wishes to use this service would have to go through the steps of looking into the same ‘BundleContext’ object to get a handle to the service provider. An example client-code is as follows:

(Note that the client also needs to have implemented the ‘BundleActivator’ interface, so that the appropriate environment variable, i.e the BundleContext object is available for inspection.)

Class EmailServiceClient implements BundleActivator {
  @Override
  Public void start(BundleContext context) throws Exception {
    
    ServiceReference ref = context.getServiceReference(EmailService.class.getName());
    If (ref !=nul ){
      EmailService emailService = (EmailService) context.getService(ref);
      If (emailService != null){
          emailService.sendEmail();
      }
    }
  }
}

Getting Services Using the DS Annotations

In the first section, we saw briefly how to deal with services using the core OSGi APIs. However, the community quickly realized that a lot of boilerplate code was needed to do even the most basic things. Hence, they came up with a set of specifications that allowed the development of OSGi components using pure Java annotations. They were subsequently called DS (Declarative Services) annotations, and had the potential for alleviating almost all OSGi framework code in a client application, while sacrificing only a miniscule amount of code-dexterity. In Neil’s book2, he mentions that ~90% of the use cases are supported by DS annotations, and for cases where they don’t suffice, one can always drop down to the API’s. Here, we give an example of both-

Getting a Service Using Only a DS Annotation:

Using a service using just the DS Annotations is the easiest. In the code below, the ‘@Component’ annotation defines this class as an OSGi component. An OSGi component, just like a sling-model, is also a container-managed entity, but is now being managed by OSGi. In fact, the entire AEM framework is nothing more than a collection of proprietary components and services running inside OSGi. Thus OSGi is the unifying concept to which everything is glued.

@Component // This is a OSGI specific-annotation
class EmailServiceClient {
 
 
  // Use the OSGI annotation to get a service provider
  @Reference
  EmailService emailService;
 
  public String getMail() {
    // do something useful with the service now, e.g.
     results = emailService.checkEmail();
     return results
   }
}

Getting a Service Using a Mix of OSGi Annotations and Lower-level OSGi APIs

Consider a use-case where we have more than one service provider for the same interface. This could perhaps come as individual email clients that each connect to a different email host. Say for example, we have an email provider to check from a Yahoo email account:

Class YahooMailProvider implements EmailService {

  @Override
  Public String checkEmail(){
    // code to check against the yahoo server
  }

  @Override
  Public String sendEmail(){
    // code to send email via yahoo’s email server
  }

}

The client-code that could use both the email providers would then be constructed as follows:

@Component
class ServiceUser{
  ...
  List services

  @Activate
  protected void init(ComponentContext context){
     services = new ArrayList<>()
     BundleContext bundleContext = context.getBundleContext()
     ServiceReference[] references = bundleContext.getAllServiceReferences(EmailService.class.getName(), null)

     for (ServiceReference reference: references) {
        EmailService service = (EmailService) bundleContext.getService(reference)
        parsers.add(service)
     }

     checkEmail(services);
  }

  private void checkEmail(List services){
    for(EmailService service: services){
      service.checkEmail(); // invoke each service to check against a unique mail server
    }
  }
}

The above examples are some of the ways that implementation of a service (i.e. Service Providers) can be used in AEM or any OSGI compliant code-base. The methods shown above are not exhaustive, by any means, but provide coverage for a vast majority of cases. Using services is a highly encouraged pattern for developing AEM code, and should be leveraged as much as possible because of its ease of use and ability to work smoothly in most cases.

References:
1OSGi – Wikipedia
2OSGi In Action, by Neil Bartlett, December 17, 2009

[cta]

Apil is an AEM developer at iCiDigital with experience working on multiple enterprise sites and specializing on the backend side of things. He enjoys exploring techniques to write quality and maintainable code, as well as working on many personal projects ranging from the insanely A.I.-ish to the humble rasperry-pi-ian. When not staring at a computer screen, he enjoys going to the gym, camping, and visiting new places. One of his most important new year's resolution is to start cooking, which he has faithfully delivered; once, much to the surprise of his wife!