Overriding out of the box servlet in AEM (Sling)

Technical Context
In AEM (CQ), sevlets are defined as OSGi services implementing javax.servlet.Servlet interface. A service listener org.apache.sling.servlets.resolver.internal.SlingServletResolver  in sling framework listens for OSGi services implementing javax.servlet.Servlet interface and registers its reference. When a request hits the server, this SlingServletResolver kicks in to executes its sling script resolution procedure to choose the right servlet to respond to the request.

Scenario
In this post, we are taking an example to demonstrate how we can override an out of the box (OOTB) servlet in AEM. In DAM touch UI  experience interface, one or more assets can be chosen and downloaded in zip format. Lets presume that we would want to download the asset without  zipping (compressing) for the case when only one asset has been chosen. This needs overriding the OOTB asset download servlet.

Step 1 – Find out the servlet which you want to override.
In our case, it’s the asset download servlet we want to override. If we track down the url of download asset feature in Dam touch UI, we could easily decipher the path, selectors, extension and suffix. Use sling url structure as reference to decompose your url.

http://localhost:4502/content/dam/geometrixx/offices/basel%20roof.jpg.assetdownload.zip/Basel%20View%20from%20Roof.zip?_charset_=utf-8&downloadAssets=true&downloadRenditions=false&downloadSubassets=undefined&licenseCheck=true

Path - /content/dam/geometrixx/offices/basel%20roof.jpg
Selectors - assetdownload
Extension - Zip

You could also use the sling servlet resolver test available at AEM system console to identify the servlet that respond to the url invocation. In our example scenario of asset download, it’s the com.day.cq.dam.core.impl.servlet.AssetDownloadServlet which responds to download requests. We would have to override this servlet to implement our customization.

Step 2 – Get hold of servlet specific OSGi properties of the OOTB servlet
If you’ve access to the source code, well and good. If not, use the depfinder available at AEM system console to identify the maven dependency of the bundle that houses this servlet. With this maven coordinates, you get hold of the bundle and check the OSGi properties specific for this servlet. In case of our AssetDownloadServlet, below are the OSGi properties.

@Component(metatype = false)
@Service
@Properties({
      @Property(name = "sling.servlet.resourceTypes", value = "sling/servlet/default"),
      @Property(name = "sling.servlet.methods", value = {"GET","POST"}),
      @Property(name = "sling.servlet.selectors", value = "assetdownload")
})

Decoding the above, we understand that this AssetDownloadServlet responds to any requests of HTTP method GET/POST with the selector “assetdownload”. Note that this servlet is marked as default servlet using the special resourceType “sling/servlet/default”.

Step 3 – Create your custom servlet with right servlet specific OSGi properties.

@Component(metatype = false)
@Service
@Properties(value = {
        @Property(name = "sling.servlet.resourceTypes", value = DamConstants.NT_DAM_ASSET),
        @Property(name = "sling.servlet.methods", value = {"GET", "POST"}),
        @Property(name = "sling.servlet.selectors", value = "assetdownload")
})
public class CustomAssetDownloadServlet extends SlingAllMethodsServlet implements OptingServlet {
    private static final Logger log = LoggerFactory.getLogger(CustomAssetDownloadServlet.class);

    protected void doGet(SlingHttpServletRequest request,
                         SlingHttpServletResponse response)
            throws ServletException, IOException {
        log.info("Custom download servlet being invoked. Path(s) requested for asset download - {}", request.getResource().getPath());
    }

    /**
     * Examines the request, and return <code>true</code> if this servlet is
     * willing to handle the request. If <code>false</code> is returned, the
     * request will be ignored by this servlet, and may be handled by other
     * servlets.
     *
     * @param request The request to examine
     * @return <code>true</code> if this servlet will handle the request,
     * <code>false</code> otherwise
     */
    @Override
    public boolean accepts(SlingHttpServletRequest request) {
        if (null != request.getRequestParameter("path")) { //multiple asset download case
            log.info("Custom download servlet being invoked. Multiple assets chosen and hence not accepting.");
            return false;
        }
        return true;
    }
}

Here we’ve the custom servlet which overrides the OOTB AssetDownloadServlet only for cases when single asset has been chosen for download. All the servlet specific OSGi properties of OOTB servlet have been copied down to this custom servlet with one exception that sling.servlet.resourceType now pointing to dam:Asset (meaning, no more it’s the default servlet). By explicitly mentioning that this custom servlet tergets these HTTP methods, the selectors and the specific resource type (dam:Asset) sling servlet resolution in SlingServletResolver prioritizes this custom servlet over the OOTB AssetDownloadServlet. In case of download request for multiple asset selection, this custom servlet do not accept the request but pass it on to the OOTB asset download servlet by implementing OptingServlet interface and having this check in accept method.

Implementation of this example in executable form is available in github. Please click here.

References

Learn more on OSGi design patterns including basics on OSGi trail.

Overriding out of the box servlet in AEM (Sling)