Wednesday, November 25, 2015

Exposing a Servlet from an OSGI Bundle in WSO2 Servers

In this post I'm providing a sample on how to expose a Servlet from an OSGI bundle which can be put in any WSO2 server. One usecase of such a component is when you write a web application to be put in a WSO2 Application Server, you may need to perform some tasks like creating registry resources from the web application. But since it is a web application, it will not be able to access the registry for creating or reading resources. In such case, we can write an OSGI bundle which does the registry operations and expose a servlet to be consumed by the client web application. So from the client web application, it can call this servlet and provide instructions for consuming the registry resources. Then since the servlet is inside an OSGI bundle, it will be able to access registry resources. This is just one usecase and it will be useful in many other scenarios. 

This is a maven project and the file structure is as below.



The pom.xml file is below.

<?xml version="1.0" encoding="UTF-8"?>
<project
   xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.wso2.carbon</groupId>
   <artifactId>org.wso2.carbon.sample.servlet</artifactId>
   <version>1.0</version>
   <packaging>bundle</packaging>
   <repositories>
       <repository>
           <id>wso2-nexus</id>
           <name>WSO2 internal Repository</name>
           <url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
           <releases>
               <enabled>true</enabled>
               <updatePolicy>daily</updatePolicy>
               <checksumPolicy>ignore</checksumPolicy>
           </releases>
       </repository>
   </repositories>
   <dependencies>
       <dependency>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
           <version>1.1.1</version>
       </dependency>
       <dependency>
           <groupId>commons-lang</groupId>
           <artifactId>commons-lang</artifactId>
           <version>2.6</version>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>2.0</version>
               <configuration>
                   <source>1.5</source>
                   <target>1.5</target>
               </configuration>
           </plugin>
           <plugin>
               <groupId>org.apache.felix</groupId>
               <artifactId>maven-scr-plugin</artifactId>
               <version>1.0.10</version>
               <executions>
                   <execution>
                       <id>generate-scr-scrdescriptor</id>
                       <goals>
                           <goal>scr</goal>
                       </goals>
                   </execution>
               </executions>
           </plugin>
           <plugin>
               <groupId>org.apache.felix</groupId>
               <artifactId>maven-bundle-plugin</artifactId>
               <version>1.4.0</version>
               <extensions>true</extensions>
               <configuration>
                   <instructions>
                       <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
                       <Bundle-Name>${pom.artifactId}</Bundle-Name>
                       <Private-Package>
                           org.wso2.carbon.sample.servlet.internal
                       </Private-Package>
                       <Export-Package>
                           !org.wso2.carbon.sample.servlet.internal,
                           org.wso2.carbon.sample.servlet.*,
                       </Export-Package>
                       <Import-Package>
                           javax.servlet; version=2.4.0,
                           javax.servlet.http; version=2.4.0,
                           org.wso2.carbon.base.*,
                           *;resolution:=optional
                       </Import-Package>
                       <DynamicImport-Package>*</DynamicImport-Package>
                   </instructions>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

First let’s create a Servlet with the name SampleServlet extending the javax.servlet.http.HttpServlet class. This will simply print some message.


package org.wso2.carbon.sample.servlet.servlet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SampleServlet extends HttpServlet {

    private static final long serialVersionUID = -7182121722709941646L;
    private static final Log log = LogFactory.getLog(SampleServlet.class);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        log.info("Sample Servlet doGet got Hit !");

        resp.setContentType("text/plain");
        resp.getWriter().write("You are inside the servlet !");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        log.info("Sample Servlet doPost got Hit !");

        resp.setContentType("text/plain");
        resp.getWriter().write("You are inside the servlet !");
    }
}





For registering the Servlet to as an HTTP service, we need to have an instance of org.osgi.service.http.HttpService and for that I am creating a class called DataHolder which is a singleton that holds the HttpService. 


package org.wso2.carbon.sample.servlet;

import org.osgi.service.http.HttpService;

/**
 * Singleton class to hold HTTP Service
 */

public class DataHolder {

    private static volatile DataHolder dataHolder = null;

    private HttpService httpService;

    private DataHolder() {

    }

    public static DataHolder getInstance() {

        if (dataHolder == null) {
            synchronized (DataHolder.class) {
                if (dataHolder == null) {
                    dataHolder = new DataHolder();
                }
            }
        }

        return dataHolder;
    }

    public HttpService getHttpService() {
        return httpService;
    }

    public void setHttpService(HttpService httpService) {
        this.httpService = httpService;
    }

}

Next step is to create a class for activating the OSGI bundle. For that I am creating a class named OSGIServletDSComponent. The bundle will wait for org.osgi.service.http.HttpService and it will call setHttpService method to instantiate the HttpService. In the activate method, I am registering the servlet to the HttpService with the URL /sampleservlet


package org.wso2.carbon.sample.servlet.internal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.equinox.http.helper.ContextPathServletAdaptor;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.http.HttpService;
import org.wso2.carbon.sample.servlet.DataHolder;
import org.wso2.carbon.sample.servlet.servlet.SampleServlet;

import javax.servlet.Servlet;

/**
 * @scr.component name="osgi.servlet.dscomponent" immediate=true
 * @scr.reference name="osgi.httpservice" interface="org.osgi.service.http.HttpService"
 * cardinality="1..1" policy="dynamic" bind="setHttpService"
 * unbind="unsetHttpService"
 */



public class OSGIServletDSComponent {

    private static Log log = LogFactory.getLog(OSGIServletDSComponent.class);

    public static final String SAMPLE_SERVLET_URL = "/sampleservlet";

    protected void activate(ComponentContext ctxt) {

        // Register Sample Servlet
        Servlet sampleServlet = new ContextPathServletAdaptor(new SampleServlet(), SAMPLE_SERVLET_URL);

        try {
            DataHolder.getInstance().getHttpService().registerServlet(SAMPLE_SERVLET_URL, sampleServlet, nullnull);
        } catch (Exception e) {
            String errMsg = "Error when registering Sample Servlet via the HttpService.";
            log.error(errMsg, e);
            throw new RuntimeException(errMsg, e);
        }

        log.info("OSGI SERVLET  bundle activated successfully..");
    }

    protected void deactivate(ComponentContext ctxt) {

        if (log.isDebugEnabled()) {
            log.debug("OSGI SERVLET  bundle is deactivated ");
        }
    }

    protected void setHttpService(HttpService httpService) {

        DataHolder.getInstance().setHttpService(httpService);

        if (log.isDebugEnabled()) {
            log.info("HTTP Service is set in the OSGI SERVLET bundle");
        }
    }

    protected void unsetHttpService(HttpService httpService) {

        DataHolder.getInstance().setHttpService(null);
        if (log.isDebugEnabled()) {
            log.debug("HTTP Service is unset in the OSGI SERVLET bundle");
        }
    }

}


The source code of the project is available in [1]. Once we have built the project, we need to copy the jar to the <WSO2_SERVER>/repository/components/dropins directory of the server and restart it. Here I am copying this bundle to WSO2 Application Server, but you can try this out with any WSO2 server.

Then in the browser, you can visit the URL https://SERVER_HOST:PORT/sampleservlet . Then the servlet will get hit and you will see the message we wrote inside the servlet. 

 

Referenes :


Tharindu Edirisinghe
Identity Server Team
WSO2