Ad Code

Sunday, October 25, 2020

Register Sling Servlet or Any Service Dynamically in AEM

 Hello Everyone,

While working on a project last week, I went through a very interesting use case and so many learnings while solving that. This blog is shared with you about that learning process I have been through.


Problem Statement: I was working on an integration tool with AEM, in which we are having many servlets registered with paths . Few of the servlets are being called by a third party and few are for internal calls.


All the servlets start with a specific prefix. Let’s suppose: /bin/aem-integration/events, /bin/aem-integration/tasks etc. So the prefix for all the servlets is "/bin/aem-integration" and it was as a constant in the code.


But while using that tool, a client requested to provide them the flexibility to define the prefix as per their requirement. So that, while they made calls from any external system to AEM, they could make their own choice of prefix or may be a very environment specific prefix like for dev it will be /bin/aem-integration/dev/tasks but for stage it will be /bin/aem-integration/stage/tasks. The requirement looks easier but while implementing, it was quite challenging.


How I approach this problem:

So if I mention the above problem precisely, it is registering the servlets based on dynamic paths.


How can we do that? 

Let’s suppose I have a servlet "RunModeServlet" using SlingSettings as a Reference and I want to register this servlet dynamically. So how to do that.



1.First I create a service "ServletRegistration" having an OSGI Configuration asking for Endpoint Prefix Configuration, because every service is a component, so on the activate method of the component we need to register all the servlets dynamically  and unregister and re-register with new values, if the author makes any change in configuration.



Now if you are creating reference of a component via new RunModeServlet() and if in the RunModeServlet is using any @Reference annotation, all the services injections will be null. To solve this, pass the service reference via the constructor to the servlet like shown in the code.


Note: In this case in the ServletRegistration class need to have all the service injections using @Reference annotation and need to pass to the servlets via the constructor new RunModeServlet(slingSettings).


2. In the RunModeServlet, we remove all the annotations and get the services via the constructor in place of @Reference annotation.


Note: This approach works well but if you are registering many servlets dynamically, you always need to manage all the constructors for every servlet and pass the service reference from ServletRegistration OSGi service. To see so many service injections in this service (ServletRegistration ) can be overwhelming.

Is there any alternative way to handle it?
The answer is yes.
Use a componentFactory to create instances of a component.
[Preferred Approach]

1. Use this on the component/servlet you want to create programmatically:

@Component(factory= "aem.servlet.runModeServlet")

public class RunModeServlet extends SlingAllMethodsServlet {


 @Reference

   private SlingSettingsService slingSettingsService;

@Override

protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response){

}

}


2. Then in another component/service (ServletRegistration) you can get the ComponentFactory:

@Reference(target = "(component.factory=aem.servlet.runModeServlet)")

private ComponentFactory runModeServletFactory;


3. Create an instance from it and register the servlet like this.

Dictionary<String, Object> propertiesMap = getPropertiesMap(runModeServletPaths, "json", "GET");

RunModeServlet runModeServlet = (RunModeServlet) runModeServletFactory.newInstance(null).getInstance();

bundleContext.registerService(Servlet.class, runModeServlet, propertiesMap);



Note: The way we have registered a servlet, you can register any service, filter or any authenticationHandler dynamically.

Dictionary<String, Object> propertiesMap = new Hashtable<>();

propertiesMap.put(AuthenticationHandler.PATH_PROPERTY, new String[]{endpoint_prefix, DamConstants.MOUNTPOINT_ASSETS});

propertiesMap.put(Constants.SERVICE_RANKING, 100000);

propertiesMap.put(Constants.SERVICE_DESCRIPTION, "Custom – Authentication Handler");

bundleContext.registerService(AuthenticationHandler.class, customAuthenticationHandler, propertiesMap);


I hope you find it interesting and useful.


Thanks and Happy sharing.