Featured

Tuesday 5 January 2016

Servlets in CQ5 | How to write/register servlets in CQ5

      Initially the Servlet API specification states the following with respect to the life cycle of Servlets:

       The servlet container calls the init method exactly once after instantiating the servlet.
This works perfectly in a regular servlet container which both instantiates and initializes the servlets. With Sling the tasks of instantiation and initialization are split:
  • The provider of the Servlet service takes care of creating the servlet instance
  • The Sling Servlet Resolver picks up the Servlet services and initializes and destroys them as needed
     So Sling has not way of making sure a Servlet is only initialized and destroyed once in the life time of the Servlet object instance.
The provider of the Servlet service on the other can cope with this situation by making sure to drop the servlet instance once it is destroyed. The mechanism helping the provider here is the OSGi Service Factory.

Registering a Servlet : 


     Servlets can be registered as OSGi services. The following service reference properties are defined for Servlets defined as OSGi services of type javax.servlet.Servlet:
  • sling.servlet.paths
  • sling.servlet.resourceTypes
  • sling.servlet.selectors
  • sling.servlet.extensions
  • sling.servlet.methods
  • sling.servlet.prefix

        A SlingServletResolver listens for Servlet{}services and - given the correct service registration properties - provides the servlets as resources in the (virtual) resource tree. Such servlets are provided as ServletResource instances which adapt to the javax.servlet.Servlet class.

        For a Servlet registered as an OSGi service to be used by the Sling Servlet Resolver, either or both of the sling.servlet.paths or the sling.servlet.resourceTypes service reference properties must be set. If neither is set, the Servlet service is ignored.

        Each path to be used for registration - either from the sling.servlet.paths property or constructed from the other sling.servlet.\* properties - must be absolute. Any relative path is made absolute by prefixing it with a root path. This prefix may be set with the sling.servlet.prefix service registration property. If this property is not set, the first entry in the ResourceResolver search path for the ResourceResolver.getResource(String) method is used as the prefix. If this entry cannot be derived, a simpe slash - / - is used as the prefix.

       If sling.servlet.methods is not specified, the servlet is only registered for handling GET requests. Make sure to list all methods you want to be handled by this servlet.


Registering a Servlet using Java Annotations


      If you are working with the default Apache Sling development stack you can use Java Annotations from Apache Felix Maven SCR Plugin to register your Sling servlets and describe their binding details.

sling.servlet.paths = \[ "/libs/sling/sample/html", "/libs/sling/sample/txt" \]
sling.servlet.resourceTypes = \[ "sling/unused" \]
sling.servlet.selectors = \[ "img" \]
sling.servlet.extensions = \[ "html", "txt", "json" \]


      These are the ways of doing , either with a Sling-specific @SlingServlet annotation or with the more generic maven-scr-plugin annotations etc..

1) The @SlingServlet annotation


@SlingServlet(
    resourceTypes = "sling/servlet/default",
    selectors = "hello",
    extensions = "html",
    methods = "GET")
public class MyServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
        ...
    }
}


2) The @Properties and @Property annotations


@Component(metatype = true)
@Service(Servlet.class)
@Properties({
    @Property(name = "sling.servlet.resourceTypes", value = "sling/servlet/default"),
    @Property(name = "sling.servlet.selectors", value = "hello"),
    @Property(name = "sling.servlet.extensions", value = "html"),
    @Property(name = "sling.servlet.methods", value = "GET")
})
public class MyServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
        ...
    }
}

Upon these we can define in following ways too

3) Registering the servlet by path


@SlingServlet(
    paths={"/services/unicom/v1/"}
)
@Properties({
    @Property(name="service.pid", value="com.adobe.unicom.v1.servlets.OmnnitureLoggingServlet",propertyPrivate=false),
    @Property(name="service.description",value="Omniture service call logging servlet", propertyPrivate=false),
    @Property(name="service.vendor",value="Adobe Systems Incorporated - Adobe@Adobe Team", propertyPrivate=false)
})
public class OmnnitureLoggingServlet extends SlingAllMethodsServlet
{
    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException
    {
        //Do something fun here
    }
    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException
    {
        //Do something fun here
    }
}

4) Registering the servlet by resource type and extension


@SlingServlet(
    resourceTypes = {"rep:User"},
    methods = {"GET", "POST"}
)
@Properties({
    @Property(name="service.pid", value="com.adobe.unicom.v1.servlets.OmnnitureLoggingServlet",propertyPrivate=false),
    @Property(name="service.description",value="Omniture service call logging servlet", propertyPrivate=false),
    @Property(name="service.vendor",value="Adobe Systems Incorporated - Adobe@Adobe Team", propertyPrivate=false)
})
public class OmnnitureLoggingServlet extends SlingAllMethodsServlet
{
    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException
    {
        //Do something fun here
    }
    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException
    {
        //Do something fun here
    }
}

5) handle all requests and filter on selector you can change the example above to read something like this.


@SlingServlet(
    resourceTypes = {"sling/servlet/default"},
    methods = {"GET"},
    selectors = {"report"},
    extensions = {"json"}
)
@Properties({
    @Property(name="service.pid", value="com.adobe.unicom.v1.servlets.OmnnitureLoggingServlet",propertyPrivate=false),
    @Property(name="service.description",value="Omniture service call logging servlet", propertyPrivate=false),
    @Property(name="service.vendor",value="Adobe Systems Incorporated - Adobe@Adobe Team", propertyPrivate=false)
})
public class OmnnitureLoggingServlet extends SlingAllMethodsServlet
{
........
}

6) To handle all requests for a Page with a special selector.


ie http://www.demosample.com/content/page.mycustomselector.html  

@SlingServlet(
    resourceTypes = {"cq:Page"},
    methods = {"GET"},
    selectors = {"mycustomselector"}
)
@Properties({
    @Property(name="service.pid", value="com.adobe.unicom.v1.servlets.OmnnitureLoggingServlet",propertyPrivate=false),
    @Property(name="service.description",value="Omniture service call logging servlet", propertyPrivate=false),
    @Property(name="service.vendor",value="Adobe Systems Incorporated - Adobe@Adobe Team", propertyPrivate=false)
})
public class OmnnitureLoggingServlet extends SlingAllMethodsServlet
{
........
}

7) To handle all requests for a Page with a special extension.


ie http://www.demosample.com/content/page.mydemoext 

@SlingServlet(
    resourceTypes = {"cq:Page"},
    methods = {"GET"},
    extensions = {"mydemoext"}
)
@Properties({
    @Property(name="service.pid", value="com.adobe.unicom.v1.servlets.OmnnitureLoggingServlet",propertyPrivate=false),
    @Property(name="service.description",value="Omniture service call logging servlet", propertyPrivate=false),
    @Property(name="service.vendor",value="Adobe Systems Incorporated - Adobe@Adobe Team", propertyPrivate=false)
})
public class OmnnitureLoggingServlet extends SlingAllMethodsServlet
{
........
}

By following these can define/achieve creation of servlets in cq5 as per our tech specs.  

     Here we can prefer resourceType for writing SlingServlets most efficiently.
Some of the reasons comes into picture while development phase itself.

There are :
  • While defining a path , you must be specific what all paths are allowed to be used in the ServletResource OSGi service. If you define something randomly, your servlet might not be fucntional. Only a limited paths are allowed and the rest are blocked unless you open them up. This is resolved using resourceType.
  • You may have also configure the dispatcher , if you use some random path for your servlet. This might be a potential security threat and a needless configuration.
  • You might also have to specify the paths to your consumers for your servlet and any change in that path could have a serious affect. This might not be the case when you use resourceType
Thanks,
SonyCharan