Adobe Experience Manager Blog

AEM concepts, snippets and implementation

Tag Archives: Multiple Service Implementation

DS Annotation – Multiple implementation of Service

0

Multiple Service Implementation can be used to define multiple implementations of a Service, and then execute all/specific implementation depending on the use-case.

A close example would be Replication Agents. Here a specific replication implementation (Static Agent, Default Agent, Custom Transport Handler/Builders etc) is called, depending on the configuration.

Lets construct a multiple service implementation via an Example.

Scenario:

Consider an online course website. A lecturer can publish his content by submitting a course bundle comprising of html pages, videos and assessment questions.

While the bundle is processed, each content type is to be dealt with specific import-service implementation:

  1. Page import
  2. Video import
  3. Assessment import

Lets get started with the implementation !!!

Create multiple service implementation

The implementation can be divided into 3 simple steps:

  1. Create an interface which each of Service implementation would implement.
  2. Implement concrete service implementations
  3. A handler which would hold list of service implementation & execute relevent implementation.

Step 1: Interface which each service implementation would implement

The interface would comprise of abstract methods which:

  • would execute business logic for each service.
  • allow identification of best service to process current use-case.

Sample interface for the course import scenario:

package blog.techrevel.service;
public interface CourseImport {
    //Import pages, videos and assessment
    public abstract void importContent();

    //Identify the service which would be able to process current file
    public abstract boolean canProcess(String fileName);
}

Step 2: Implement concrete service implementations

Here we would be create multiple content import implementation to deal specifically with each content type. Example:

PageImport.java

package blog.techrevel.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import blog.techrevel.service.CourseImport;
@Component(name="pages", service = CourseImport.class, property={"type=page"})
public class PageImport implements CourseImport {
    private static final Logger LOGGER = LoggerFactory.getLogger(PageImport.class);
    @Override
    public void importContent() {
        LOGGER.info("Business Logic to import HTML Pages");
    }

    @Override
    public boolean canProcess(String fileName) {
        return StringUtils.endsWith(fileName, ".html");
    }

    @Activate
    @Modified
    protected void activate(ComponentContext componentContext){
         LOGGER.info("Registering: " + componentContext.getProperties().get("type").toString());
    }
}

 AssessmentImport.java

package blog.techrevel.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import blog.techrevel.service.CourseImport;
@Component(name = "assessment", service = CourseImport.class, property = { "type=assessment" })
public class AssessmentImport implements CourseImport {
    private static final Logger LOGGER = LoggerFactory.getLogger(AssessmentImport.class);
    @Activate
    @Modified
    protected void activate(ComponentContext componentContext)
    {
        LOGGER.info("Registering: " + componentContext.getProperties().get("type").toString());
    }
@Override
    public void importContent() {
         LOGGER.info("Business Logic to import assessment questions");
    }
@Override
    public boolean canProcess(String fileName) {
       return StringUtils.equals(fileName, "assessment.xls");
    }
}

VideoImport.java

package blog.techrevel.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import blog.techrevel.service.CourseImport;
@Component(name = "videos", service = CourseImport.class, property = { "type=video" })
public class VideoImport implements CourseImport {
    private static final Logger LOGGER = LoggerFactory.getLogger(VideoImport.class);
    @Override
    public void importContent() {
         LOGGER.info("Business Logic to import course videos");
    }
@Override
    public boolean canProcess(String fileName) {
         return StringUtils.endsWith(fileName, ".mp4");
    }
@Activate
    @Modified
    protected void activate(ComponentContext componentContext) {
         LOGGER.info("Registering: " + componentContext.getProperties().get("type").toString());
    }
}

Step 3: Handler to reference service and trigger execution of appropriate implementation.

Step 3.1: To aggregate references for all implementations in a collection, use @Reference annotation with following attributes:

  • name=“The name of this reference.”
  • cardinality=”The cardinality of the service reference. This must be one of value from the enumeration ReferenceCardinality
    • AT_LEAST_ONE: The reference is mandatory and multiple.
    • MANDATORY: The reference is mandatory and unary.
    • MULTIPLE: The reference is optional and multiple.
    • OPTIONAL: The reference is optional and unary.
  • policy=”ReferencePolicy.DYNAMIC Or ReferencePolicy.STATIC”
    • If dynamic the service will be made available to the component as it comes and goes.
    • If static the component will be deactivated and re-activated if the service comes and/or goes away
  • unbind=”name of the method to be called when the service is to be unbound from the component”
    • To declare no unbind method, the value "-" must be used.
    • If not specified, the name of the unbind method is derived from the name of the annotated bind method. If the annotated method name begins with bindset or add, that is replaced withunbindunset or remove, respectively, to derive the unbind method name. Otherwise, un is prefixed to the annotated method name to derive the unbind method name. The unbind method is only set if the component type contains a method with the derived name.
private List courseImportImplList;
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void bind(CourseImport courseImport) {
    if (courseImportImplList == null) {
        courseImportImplList = new ArrayList();
    }
    courseImportImplList.add(courseImport);
}
protected void unbind(CourseImport courseImport) {
    courseImportImplList.remove(courseImport);
}

Step 3.2 Trigger execution of appropriate implementation

Use the ‘collection created in Step-3.1’ to shortlist the best implementation for current use-case. The shortlisted implementation reference can then be used to trigger corresponding business logic.

In the below sample code, canProcess() identifies the right implementation to deal with current content type. Corresponding importContent() is then executed to import html as per business requirement.

package blog.techrevel.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import blog.techrevel.service.CourseImport;
import blog.techrevel.service.CourseImportHandler;
@Component(name = "courseImportHandler", immediate = true, service=CourseImportHandler.class)
public class CourseImportHandlerImpl implements CourseImportHandler {
     private static final Logger LOGGER = LoggerFactory.getLogger(CourseImportHandlerImpl.class);
     private List courseImportImplList;
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
     protected void bind(CourseImport courseImport) {
          if (courseImportImplList == null) {
              courseImportImplList = new ArrayList();
          }
          courseImportImplList.add(courseImport);
     }
protected void unbind(CourseImport courseImport) {
         courseImportImplList.remove(courseImport);
     }
@Override
     public void importContent() {
        for (CourseImport courseImportImpl : courseImportImplList) {
            if (courseImportImpl.canProcess("introduction.mp4")) {
                courseImportImpl.importContent();
                break;
            }
        }
    }
}
Advertisements

Apache Felix – Multiple implementation of Service

1

Multiple Service Implementation can be used to define multiple implementations of a Service, and then execute all/specific implementation depending on the use-case.

A close example would be Replication Agents. Here a specific replication implementation (Static Agent, Default Agent, Custom Transport Handler/Builders etc) is called, depending on the configuration.

Lets construct a multiple service implementation via an Example.

Scenario:

Consider an online course website. A lecturer can publish his content by submitting a course bundle comprising of html pages, videos and assessment questions.

While the bundle is processed, each content type is to be dealt with specific import-service implementation:

  1. Page import
  2. Video import
  3. Assessment import

Lets get started with the implementation !!!

Create multiple service implementation

The implementation can be divided into 3 simple steps:

  1. Create an interface which each of Service implementation would implement.
  2. Implement concrete service implementations
  3. A handler which would hold list of service implementation & execute relevent implementation.

Step 1: Interface which each service implementation would implement

The interface would comprise of abstract methods which:

  • would execute business logic for each service.
  • allow identification of best service to process current use-case.

Sample interface for the course import scenario:

package blog.techrevel.service;
public interface CourseImport {
    //Import pages, videos and assessment
    public abstract void importContent();

    //Identify the service which would be able to process current file
    public abstract boolean canProcess(String fileName);
}

Step 2: Implement concrete service implementations

Here we would be create multiple content import implementation to deal specifically with each content type. Example:

package blog.techrevel.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;

@Component(name="Import HTML Pages")
@Service
public class PageImport implements CourseImport{

    private static final Logger LOGGER = LoggerFactory.getLogger(PageImport.class);

    @Override
    public void importContent() {
        LOGGER.info("Business Logic to import HTML Pages");
    }

    @Override
    public boolean canProcess(String fileName) {
        return StringUtils.endsWith(fileName, ".html");
    }
}

Step 3: Handler to reference service and trigger execution of appropriate implementation.

Step 3.1: To aggregate references for all implementations in a collection, use @Reference annotation with following attributes:

  • referenceInterface=”name of the service interface/class”
  • cardinality=”The cardinality of the service reference. This must be one of value from the enumeration ReferenceCardinality“. For Multiple service implementation, use one of the following enum values:
    • OPTIONAL_MULTIPLE: (“0..n”) Optional, multiple reference: No service required to be available for the reference to be satisfied. All matching services are available through this reference.
    • MANDATORY_MULTIPLE: (“1..n”) Mandatory, multiple reference: At least one service must be available for the reference to be satisfied. All matching services are available through this reference.
  • policy=”ReferencePolicy.DYNAMIC Or ReferencePolicy.STATIC”
    • If dynamic the service will be made available to the component as it comes and goes. If static the component will be deactivated and re-activated if the service comes and/or goes away
  • bind=”name of the method to be called when the service is to be bound to the component”
    • The default value is the name created by appending the reference name to the string bind.
    • bind method will be called for each service implementation that would be registered
  • unbind=”name of the method to be called when the service is to be unbound from the component”
    • The default value is the name created by appending the reference name to the string unbind.
    • unbind method will be called for each service implementation that would be unregistered
@Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, referenceInterface = CourseImport.class, policy = ReferencePolicy.DYNAMIC)
private List courseImportImplList;

protected void bind(CourseImport courseImportImpl) {
    if (courseImportImplList == null) {
        courseImportImplList = new ArrayList();
    }
    courseImportImplList.add(courseImportImpl);
}

protected void unbind(CourseImport courseImportImpl) {
    courseImportImplList.remove(courseImportImpl);
}

Step 3.2 Trigger execution of appropriate implementation

Use the ‘collection created in Step-3.1’ to shortlist the best implementation for current use-case. The shortlisted implementation reference can then be used to trigger corresponding business logic.

In the below sample code, canProcess() identifies the right implementation to deal with current content type. Corresponding importContent() is then executed to import html as per business requirement.

package blog.techrevel.service.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import blog.techrevel.service.CourseImport;
import blog.techrevel.service.CourseImportHandler;

@Component(name = "Course Import Handler", immediate = true)
@Service
public class CourseImportHandlerImpl implements CourseImportHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CourseImportHandler.class);

    @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, referenceInterface = CourseImport.class, policy = ReferencePolicy.DYNAMIC)
    private List courseImportImplList;

    protected void bind(CourseImport courseImportImpl) {
        if (courseImportImplList == null) {
            courseImportImplList = new ArrayList();
        }
        courseImportImplList.add(courseImportImpl);
    }

    protected void unbind(CourseImport courseImportImpl) {
        courseImportImplList.remove(courseImportImpl);
    }

    @Override
    public void importContent() {
        for (CourseImport courseImportImpl : courseImportImplList) {
            if (courseImportImpl.canProcess("introduction.html")) {
                courseImportImpl.importContent();
                break;
            }
        }
    }
}

 

If you always need reference to a specific implementation of Service, then the same can be achieved as per the details on link