Running Spring MVC in Sling

It is sometimes desirable to run Spring MVC within a Sling container. However, since Sling is a web application, you may not be able to utilize the features within Spring Dynamic Modules and drop a WAR file within the container. In this case, you need to create an application which will bootstrap the Spring Web container within the Sling context.

Dependencies

The first thing you're going to need are the dependencies to get Spring MVC up and running within the Sling context. Some dependencies are already released with the OSGi headers. However, for those that are not, the Spring bundle repository is a great resource for downloading versions with the headers already modified so you do not have to re-wrap them. The dependencies are:
Spring AOP v3.0.6.RELEASE
Spring ASM v3.0.6.RELEASE
Spring Aspects v3.0.6.RELEASE
Spring Beans v3.0.6.RELEASE
Spring Context v3.0.6.RELEASE
Spring Context Support v3.0.6.RELEASE
Spring Core v3.0.6.RELEASE
Spring Expression v3.0.6.RELEASE
Spring Web v3.0.6.RELEASE
Spring Web Servlet v3.0.6.RELEASE
Spring OSGi IO v1.2.1
Spring OSGi Core v1.2.1
Spring OSGi Extender v1.2.1
Spring OSGi Annotation v1.2.1
Spring OSGi Web v1.2.1
AOP Alliance v1.0.0 (need OSGi version)
CGLib 2.2.0 (need OSGi version)

Commons Lang v2.6
Commons Codec v1.5
Commons Logging v1.1.1
ASM v3.2.0 (need OSGi version)
JSR 330 (javax.inject) v1.0.0 (need OSGi version)
JSR 250 (javax.annotation) v1.0.0 (need OSGi version)
Apache Sling API v2.2.0
Apache Sling Auth Core v1.0.6
Apache Sling Engine v2.2.0

Additionally, you will need an OSGi version of AspectJ Tools v1.6.6. Unfortunately, the exports in the version available from the Spring Bundle repository may conflict with some OSGi containers (like Felix), therefore we'll need to re-wrap AspectJ Tools.

 

Sling containers

Naturally, you're going to need a Sling container to run within. Sling offers a few options for download or building yourself. Alternative containers exist, such as Adobe CRX and Adobe CQ5.

 

Create Maven project in Eclipse

Next, let's create our Maven project in Eclipse. I chose the maven-archetype-quickstart archetype as a base, but you can choose whatever type you'd prefer. Once your Maven project in Eclipse is created, open up the pom.xml file to start making our changes. 

New Maven Project

Add the Maven Compiler Plugin so we can set up the source and target Java version:

  ...
  
    
      
        org.apache.maven.plugins
        maven-compiler-plugin
        2.3.2
        
          
          1.6
          1.6
        
      
    
  
  ...

Once you've added the Maven Compiler Plugin, we can update the packaging to "bundle".  Thiswill tell Maven to package this jar file with the OSGi headers required to be present in the META-INF/MANIFEST.MF file.

Next, let's start the configuration of the Maven Bundle Plugin in order to properly bundle the jar file with the OSGi headers.  Right now, we just need to export our primary package.  There are no dependencies yet, so we don't need to import anything either.

...
  
    
      ...
      
        org.apache.felix
        maven-bundle-plugin
        2.3.5
        true
        
          
            
              
              net.jasonday.examples.sling.spring.mvc,
              
              
              !*
            
            
              
              !*
            
          
        
      
    
  
  ...

Now let's add the Spring Bundle Repositories so Maven can find the Spring dependencies.  Add the Spring release and external repositories in the <repositories> pom.xml element.

...
  
    
      com.springsource.repository.bundles.release
      SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases
      http://repository.springsource.com/maven/bundles/release
    
    
      com.springsource.repository.bundles.external
      SpringSource Enterprise Bundle Repository - External Bundle Releases
      http://repository.springsource.com/maven/bundles/external
    
  
...

Let's now add Spring Dynamic Modules and it's dependencies.  Add each dependency to your <dependencies> pom.xml element.

...
  
    ...
    3.0.6.RELEASE
    1.2.1
  
...
  
    ...
    
    
      org.springframework
      org.springframework.aop
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.asm
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.aspects
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.beans
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.context
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.context.support
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.core
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.expression
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.web
      ${spring.maven.artifact.version}
    
    
      org.springframework
      org.springframework.web.servlet
      ${spring.maven.artifact.version}
    

    
    
      org.springframework.osgi
      spring-osgi-io
      ${spring.osgi.maven.artifact.version}
    
    
      org.springframework.osgi
      spring-osgi-core
      ${spring.osgi.maven.artifact.version}
    
    
      org.springframework.osgi
      spring-osgi-extender
      ${spring.osgi.maven.artifact.version}
    
    
      org.springframework.osgi
      spring-osgi-annotation
      ${spring.osgi.maven.artifact.version}
    
    
      org.springframework.osgi
      spring-osgi-web
      ${spring.osgi.maven.artifact.version}
    

    
    
      org.aopalliance
      com.springsource.org.aopalliance
      1.0.0
    
    
      net.sourceforge.cglib
      com.springsource.net.sf.cglib
      2.2.0
    
    
      commons-lang
      commons-lang
      2.6
    
    
      commons-codec
      commons-codec
      1.5
    
    
      commons-logging
      commons-logging
      1.1.1
    
    
      org.objectweb.asm
      com.springsource.org.objectweb.asm
      3.2.0
    
    
      javax.inject
      com.springsource.javax.inject
      1.0.0
    
    
      javax.annotation
      com.springsource.javax.annotation
      1.0.0
    

    
    
      org.apache.sling
      org.apache.sling.api
      2.2.0
      bundle
    
    
      org.apache.sling
      org.apache.sling.auth.core
      1.0.6
      bundle
    

    
    
      org.aspectj
      net.jasonday.org.aspectj.tools
      1.6.6
    
  
...

 

Configure Spring OSGi

First, let's add the OSGi Bundle imports that we'll need to start the Spring OSGi ApplicationContext.  Add the Spring imports to the <Import-Package> element within the Maven Bundle Plugin configuration in the pom.xml like so:

...
            
              
              net.sf.cglib.beans,
              net.sf.cglib.core,
              net.sf.cglib.proxy,
              net.sf.cglib.reflect,
              net.sf.cglib.transform,
              net.sf.cglib.transform.impl,
              net.sf.cglib.util,
              org.aopalliance.aop,
              org.aopalliance.intercept,
              org.apache.commons.lang,
              org.apache.commons.lang.builder,
              org.apache.commons.lang.time,
              org.apache.commons.lang.math,
              org.apache.commons.codec,
              org.apache.commons.codec.binary,
              org.apache.commons.logging,
              org.aspectj.lang.annotation,
              org.aspectj.lang,
              org.objectweb.asm,
              org.objectweb.asm.signature,
              org.springframework.aop,
              org.springframework.aop.aspectj,
              org.springframework.aop.aspectj.annotation,
              org.springframework.aop.aspectj.autoproxy,
              org.springframework.aop.config,
              org.springframework.aop.framework,
              org.springframework.aop.framework.adapter,
              org.springframework.aop.framework.autoproxy,
              org.springframework.aop.framework.autoproxy.target,
              org.springframework.aop.interceptor,
              org.springframework.aop.scope,
              org.springframework.aop.support,
              org.springframework.aop.support.annotation,
              org.springframework.aop.target,
              org.springframework.aop.target.dynamic,
              org.springframework.beans,
              org.springframework.beans.factory,
              org.springframework.beans.factory.annotation,
              org.springframework.beans.factory.config,
              org.springframework.context,
              org.springframework.context.annotation,
              org.springframework.context.support,
              org.springframework.core,
              org.springframework.core.io,
              org.springframework.stereotype,
              org.springframework.util,
              
              
              org.springframework.osgi.extensions.annotation,
              org.springframework.osgi.web.context.support,
              
              
              org.springframework.web.bind.annotation,
              org.springframework.web.bind.support,
              org.springframework.web.context,
              org.springframework.web.context.request,
              org.springframework.web.context.support,
              org.springframework.web.servlet,
              org.springframework.web.servlet.mvc.annotation,
              org.springframework.web.servlet.handler,
              org.springframework.web.util,

              !*
            
...

 Now let's create the Spring configuration files needed to create an ApplicationContext for our bundle.  Create two files, META-INF/spring/spring.xml and META-INF/spring/spring-osgi.xml.  The spring.xml file will be used to configure general Spring settings.  The spring-osgi.xml file will contain Spring Dynamic Modules configurations, so we can keep track of the OSGi configurations in a separate file.




  




  
  

  
  

  
  

  
  

Now that Spring itself is configured, we need an instance of OsgiBundleXmlWebApplicationContext within the Sling context.  Since our OSGi environment is running within Sling and we want to utilize Slings resource resolution, we're going to avoid using the typical ContextLoaderListener and DispatcherServlet that would need to be configured within the web.xml.  Instead, we're going to create our own ContextLoader extension that will load the OsgiBundleXmlWebApplicationContext.  Create a class named SlingContextLoader that extends ContextLoader as so:

package net.jasonday.examples.sling.spring.mvc.sling;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.servlet.ServletContext;

import org.springframework.context.ApplicationContext;
import org.springframework.osgi.extensions.annotation.ServiceReference;
import org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ContextLoader;

/**
 * Create this as a bean so it's created when the bundle ApplicationContext is
 * initialized.
 * 
 */
@Component
public class SlingContextLoader extends ContextLoader {
  private ServletContext servletContext;

  private ApplicationContext applicationContext;

  public ApplicationContext getApplicationContext() {
    return applicationContext;
  }

  @Inject
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  public ServletContext getServletContext() {
    return servletContext;
  }

  /**
   * Retrieve a reference to the {@link javax.servlet.ServletContext
   * ServletContext} from the container for the
   * {@link org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
   * OsgiBundleXmlWebApplicationContext}
   * @param servletContext Instance of ServletContext
   */
  @ServiceReference
  public void setServletContext(ServletContext servletContext) {
    this.servletContext = servletContext;
  }

  /**
   * Return the type of {@link org.springframework.context.ApplicationContext
   * ApplicationContext} that we want to create. In our case, we want an
   * {@link org.springframework.context.ApplicationContext ApplicationContext}
   * that is OSGi aware and configured for Spring MVC.
   */
  @Override
  protected Class determineContextClass(ServletContext currentServletContext) {
    return OsgiBundleXmlWebApplicationContext.class;
  }

  /**
   * Return the bundle {@link org.springframework.context.ApplicationContext
   * ApplicationContext} in order to properly access the bundles' classpath.
   */
  @Override
  protected ApplicationContext loadParentContext(ServletContext currentServletContext) {
    return applicationContext;
  }

  /**
   * Initialize the actual
   * {@link org.springframework.web.context.WebApplicationContext
   * WebApplicationContext} instance.
   */
  @PostConstruct
  public void init() {
    initWebApplicationContext(servletContext);
  }

  /**
   * Since we'll be running within an OSGi environment, the bundle may be
   * unregistered. This means we need to make sure we clean up the
   * {@link javax.servlet.ServletContext ServletContext} if this bundle is
   * unregistered.
   */
  @PreDestroy
  public void destroy() {
    closeWebApplicationContext(servletContext);
  }
}

Make sure you add your newly required packages to the pom.xml Maven Bundle Plugin <Import-Package> element.

...
            
              ...
              
              javax.annotation,
              javax.inject,
              
              
              javax.servlet,
              javax.servlet.http,
              ...
              
              !*
            
...

We'll also need to now create an WEB-INF/applicationContext.xml file for the OsgiBundleXmlWebApplicationContext because it will be used to build the local WebApplicationContext. 




  
  

Now that we have the OsgiBundleXmlWebApplicationContext configured, we need to initialize the DispatcherServlet.  Since we're in an OSGi environment using a shared ServletContext, we will need to create a subclass of DispatcherServlet that cleans up the ServletContext of the servlet name. Create a new subclass named SlingDispatcherServlet.

package net.jasonday.examples.sling.spring.mvc.sling;

import javax.annotation.PreDestroy;
import javax.servlet.Servlet;

import org.springframework.web.servlet.DispatcherServlet;

/**
 * Subclass of a {@link org.springframework.web.servlet.DispatcherServlet
 * DispatcherServlet} that will remove the instances' name from the
 * {@link javax.servlet.ServletContext ServletContext} attributes on
 * destruction.
 * 
 */
public class SlingDispatcherServlet extends DispatcherServlet implements Servlet {
  /**
   * Destory the servlet and remove the name from the
   * {@link javax.servlet.ServletContext ServletContext}'s attributes
   */
  @PreDestroy
  @Override
  public void destroy() {
    // don't forget to call the super destroy method
    super.destroy();

    // remove the ServletContext attribute for this servlets' ApplicationContext
    String attrName = getServletContextAttributeName();
    getServletContext().removeAttribute(attrName);
  }
}

Additionally, since we're going to have to export our DispatcherServlet as an OSGi service so Sling can find it, we'll need to create an instance.

package net.jasonday.examples.sling.spring.mvc.sling;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

@Configuration
public class SlingConfiguration {
  /**
   * Create and return an instance of {@link SlingDispatcherServlet}.
   * 

* This is required to be instantiated as a Spring bean in order for it to be * exported as an OSGi service by Spring. *

* This bean must depend on the {@link SlingContextLoader} finishing first, * else this instance will attempt to create it's own * {@link org.springframework.web.context.WebApplicationContext * WebApplicationContext} rather than using the one that will be set up by * {@link SlingContextLoader}. *

* We will need the bean name in order to configure the Spring DM service * exportation. Additionally, since we may create multiple instances of a * {@link SlingDispatcherServlet} for different purposes, we should give each * one a unique bean name. * @return SlingDispatcherServlet A new instance of SlingDispatcherServlet */ @Bean @DependsOn("slingContextLoader") public SlingDispatcherServlet globalSlingDispatcherServlet() { return new SlingDispatcherServlet(); } }

We will need a Spring configuration file for every instance of SlingDispatcherServlet created.  Alternatively, you could override this behavior when creating an instance of each SlingDispatcherServlet.  By default the SlingDispatcherServlet will look for a WEB-INF/[servlet-name]-servlet.xml file.  In our case we'll use the servlet-name globaldispatcher.




  
  

  
  
    
  

  
  

  
  

Now that we've created the necessary DispatcherServlet instance, we need to expose our SlingDispatcherServlet as an OSGi service.  To do this, we'll update the META-INF/spring/spring-osgi.xml file to export our SlingDispatcherServlet under the ServletContext interface.

...
  
  
    
    
      javax.servlet.Servlet
    
    
    
      
      
      
      
      
        
          json
          html
        
      
      
      
      
        
          GET
          POST
        
      
      
      
      
      
      
      
    
  

...

 

Build a Spring MVC Controller

Now that we've configured Spring to work in a Sling context, let's create a Spring MVC Controller to test it out. We'll make our controller simple for now to test it out.  At this point, we'll use the @Controller annotation to mark our controller.  The @Controller annotation requires the use of a @RequestMapping annotation to map URLs to requests.  Since we haven't built anything to change this behavior, we'll map our helloWorld method to /content/examples/sling/spring/mvc/mySlingController

package net.jasonday.examples.sling.spring.mvc.controller;

import java.io.IOException;

import javax.servlet.ServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Example Spring MVC Controller class.
 * 
 */
@Controller
public class MySlingController {
  /**
   * Hello world method that will listen for requests at the URL
   * /content/examples/sling/spring/mvc/mySlingController.
   * 

* Right now, we'll map to a specific URL to ensure everything is working * properly. * @param servletResponse ServletResponse object for writing out to the * response * @throws IOException Thrown if there is an exception writing out to the * response */ @RequestMapping("/content/examples/sling/spring/mvc/mySlingController") public void helloWorld(ServletResponse servletResponse) throws IOException { // write out "Hello world!" to test the response servletResponse.getWriter().println("Hello world!"); } }

Now that we have a Spring MVC Controller created, we need to create the node in the JCR repository at the /content/examples/sling/spring/mvc/mySlingController URL location that will use our SlingDispatcherServlet to handle requests.  Let's create the following structure:

/content [sling:Folder]
/content/examples [sling:Folder]
/content/examples/sling [sling:Folder]
/content/examples/sling/spring [sling:Folder]
/content/examples/sling/spring/mvc [sling:Folder]
/content/examples/sling/spring/mvc/mySlingController [nt:unstructured]

Now, to use our SlingDispatcherServlet for requests to this node, we must add a sling:resourceType property to the mySlingController node with the resource type we configured in our spring-osgi.xml file, in our case examples/sling/spring/mvc/dispatcher/global.  We do this because Sling needs to know what script to use for handling requests to the /content/examples/sling/spring/mvc/mySlingController. 

Even though we've configured our MySlingController class to map requests to this URL, only Spring knows about this mapping.  In order to hook the Spring mapping into Sling, we need a sling:resourceType property on our /content/examples/sling/spring/mvc/mySlingController node that will tell Sling to map requests for our node to the SlingDispatcherServlet.  Add a property to the /content/examples/sling/spring/mvc/mySlingController node named sling:resourceType with a value of examples/sling/spring/mvc/dispatcher/global.  When requests for /content/examples/sling/spring/mvc/mySlingController come into Sling, it will check the sling:resourceType property and see it's configured to use the examples/sling/spring/mvc/dispatcher/global script.  This sling:resourceType resolves to the SlingDispatcherServlet we configured in our spring-osgi.xml file (the sling.servlet.resourceTypes service property).  Sling will dispatch the request to the SlingDispatcherServlet, which will then check for a mapping to the /content/examples/sling/spring/mvc/mySlingController URL.  We configured our MySlingController Controller class to map to /content/examples/sling/spring/mvc/mySlingController through the use of the @RequestMapping annotation, so the SlingDispatcherServlet will dispatch the request to our controller.  Test this out by going to /content/examples/sling/spring/mvc/mySlingController.html in your browser, and you should see the following:

Hello world! 

 

Configure SlingDispatcherServlet to be Sling aware

Now that we've verified Sling is resolving to our SlingDispatcherServlet to handle requests when the URLs are properly mapped, we can update SlingDispatcherServlet to be Sling aware.  What does this mean?  Well, right now our Controller class has to know the exact URL to map to.  This creates a tight coupling between the Controller and the possible nodes we configure in the JCR to be handled by Spring MVC, which we would like to avoid.  What we would really like to do is to allow our Controller classes to map to specific controller types we configure on our JCR nodes.  For example, instead of mapping the MySlingController helloWorld method only to the /content/examples/sling/spring/mvc/mySlingController URL, we would rather add a property to our JCR node that identifies the Spring MVC Controller class to which requests should be handled.  To do this, we're going to need to change the how the SlingDispatcherServlet maps Controller classes.  Fortunately, Spring gives us a hook in order to do this through the configuration of the HandlerMapping and HandlerAdapter interfaces.  We will need to create a subclass of the UrlPathHelper class that changes the lookup path Spring uses to map Controllers. 

First, we need to create a utility class with a method that will unrwap the HttpServletRequest to find the SlingHttpServletRequest.

package net.jasonday.examples.sling.spring.mvc.spring;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;

import org.apache.sling.api.SlingHttpServletRequest;

/**
 * Utility class for working with
 * {@link org.apache.sling.api.SlingHttpServletRequest SlingHttpServletRequest}.
 * 
 */
public class SlingHttpServletRequestUtils {
  /**
   * Unwrap the {@link org.apache.sling.api.SlingHttpServletRequest
   * SlingHttpServletRequest} from the given
   * {@link javax.servlet.ServletRequest ServletRequest}. If an instance of
   * {@link org.apache.sling.api.SlingHttpServletRequest
   * SlingHttpServletRequest} is not found, returns null.
   * 
   * @param servletRequest Wrapped instance of
   * {@link org.apache.sling.api.SlingHttpServletRequest
   * SlingHttpServletRequest}
   * @return SlingHttpServletRequest The unwrapped
   * {@link SlingHttpServletRequest} instance if found, else null
   */
  public static SlingHttpServletRequest unwrap(ServletRequest servletRequest) {
    while (servletRequest instanceof ServletRequestWrapper) {
      servletRequest = ((ServletRequestWrapper) servletRequest).getRequest();

      // immediate termination if we found one
      if (servletRequest instanceof SlingHttpServletRequest) {
        return (SlingHttpServletRequest) servletRequest;
      }
    }
    return null;
  }
}

Now we can create a subclass of UrlPathHelper that will check for a property on the node corresponding to the URL requested for a property named controllerType that will give us the mapping for the Spring MVC Controller class/method.

package net.jasonday.examples.sling.spring.mvc.spring;

import javax.servlet.http.HttpServletRequest;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.springframework.web.util.UrlPathHelper;

/**
 * 
 * Sub-class of {@link org.springframework.web.util.UrlPathHelper UrlPathHelper}
 * that will check for a property named {@link #CONTROLLER_TYPE_PROPERTY} on a
 * Sling {@link org.apache.sling.api.resource.Resource Resource} and return it
 * as the lookup path if found. Defaults to returning the super methods' lookup
 * path value if a Sling {@link org.apache.sling.api.resource.Resource Resource}
 * with a {@link #CONTROLLER_TYPE_PROPERTY} property is not found.
 * 
 */
public class SlingUrlPathHelper extends UrlPathHelper {
  public static final String CONTROLLER_TYPE_PROPERTY = "controllerType";

  /**
   * Override the method to check for a Sling
   * {@link org.apache.sling.api.resource.Resource Resource} with a property
   * named {@link #CONTROLLER_TYPE_PROPERTY} and return it's value as the lookup
   * path. If not found, defaults to super methods behavior.
   */
  @Override
  public String getLookupPathForRequest(HttpServletRequest request) {
    // get the SlingHttpServletRequest instance from the given
    // HttpServletRequest
    SlingHttpServletRequest slingRequest = SlingHttpServletRequestUtils.unwrap(request);

    if (null != slingRequest) {
      // get the Resource for this request, then pull a property from it
      Resource resource = slingRequest.getResource();
      if (null != resource) {
        ValueMap properties = resource.adaptTo(ValueMap.class);
        if (properties.containsKey(CONTROLLER_TYPE_PROPERTY)) {
          return properties.get(CONTROLLER_TYPE_PROPERTY, null);
        }
      }
    }

    // default to super behavior
    return super.getLookupPathForRequest(request);
  }
}

Once our SlingUrlPathHelper class is created, we need to configure Spring to use our SlingUrlPathHelper for mapping requests rather than the UrlPathHelper class.  We do this by configuring our own HandlerMapping and HandlerAdapter instances.  We can easily do this with a Spring @Configuration file.

package net.jasonday.examples.sling.spring.mvc.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;

/**
 * Configure the Sling aware handler classes for Spring MVC.
 * 
 */
@Configuration
public class SlingHandlerConfiguration {
  /**
   * Configures a {@link org.springframework.web.servlet.HandlerMapping
   * HandlerMapping} instance for use in a Sling environment by overriding the
   * {@link org.springframework.web.util.UrlPathHelper UrlPathHelper} with a
   * {@link SlingUrlPathHelper} instance.
   * 
   * @return HandlerMapping An instance of HandlerMapping for use in a Sling
   * environment
   */
  @Bean
  public HandlerMapping handlerMapping() {
    // override the DefaultAnnotationHandlerMappings' UrlPathHelper
    DefaultAnnotationHandlerMapping handlerMapping = new DefaultAnnotationHandlerMapping();
    handlerMapping.setUrlPathHelper(new SlingUrlPathHelper());
    return handlerMapping;
  }

  /**
   * Configures a {@link org.springframework.web.servlet.HandlerAdapter
   * HandlerAdapter} instance for use in a Sling environment by overriding the
   * {@link org.springframework.web.util.UrlPathHelper UrlPathHelper} with a
   * {@link SlingUrlPathHelper} instance.
   * 
   * @return HandlerAdapter An instance of HandlerAdapter for use in a Sling
   * environment
   */
  @Bean
  public HandlerAdapter handlerAdapter() {
    // override the AnnotationMethodHandlerAdapters' UrlPathHelper
    AnnotationMethodHandlerAdapter handlerAdapter = new AnnotationMethodHandlerAdapter();
    handlerAdapter.setUrlPathHelper(new SlingUrlPathHelper());
    return handlerAdapter;
  }
}

With our customized HandlerMapping and HandlerAdapter instances, we can now modify our MySlingController classes to map to anything we want.  In our case, we're going to map them to /examples/sling/spring/mvc/mySlingController controller types.  Note: The DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter classes both expect mappings to begin with "/", therefore we'll need our controllerType properties to be configured with this or else Spring won't properly find the mappings.

...
public class MySlingController {
  /**
   * Hello world method that will map to
   * examples/sling/spring/mvc/mySlingController controller
   * types.
   * 

* @param servletResponse ServletResponse object for writing out to the * response * @throws IOException Thrown if there is an exception writing out to the * response * @see net.jasonday.examples.sling.spring.mvc.spring.SlingUrlPathHelper */ @RequestMapping("examples/sling/spring/mvc/mySlingController") public void helloWorld(ServletResponse servletResponse) throws IOException { // write out "Hello world!" to test the response servletResponse.getWriter().println("Hello world!"); } }

Finally, we can create any node in the repository with a property named controllerType with a value of /examples/sling/spring/mvc/mySlingController in order to have our MySlingController instance handle requests.  Create a new node anywhere you would like in your repository.  I'll use /content/mySlingController.

/content [sling:Folder]
/content/mySlingController [nt:unstructured]

We still need to add a property named sling:resourceType with a value of examples/sling/spring/mvc/dispatcher/global in order to tell Sling to use the SlingDispatcherServlet to handle requests.  However, now we'll need to add an additional property named controllerType with a value of /examples/sling/spring/mvc/mySlingController in order to have our MySlingController instance handle requests.

Lastly, we have to add the Sling imports to our <Import-Package> element within the Maven Bundle Plugin configuration in the pom.xml so our bundle can use the Sling classes we need.

...
            
              ...
              
              org.apache.sling.api,
              org.apache.sling.api.resource,
              org.apache.sling.auth.core,
              ...
              
              !*
            
...

Now, you should be able to pull up /content/mySlingController.html and see MySlingController handling the response.

Hello world!

 

Add Sling WebArgumentResolvers

Now that our Controller is Sling aware, we may want to inject some Sling classes into our Controller methods.  For example, we may want access to the SlingHttpServletRequest or ResourceResolver.  Fortunately, Spring makes it easy to add this functionality through the use of the WebArgumentResolver interface.  First up, let's allow the SlingHttpServletRequest class to be injected into our Controller methods.  Create an implementation of WebArgumentResolver to unwrap the SlingHttpServletRequest from the given HttpServletRequest.

package net.jasonday.examples.sling.spring.mvc.spring.resolver;

import javax.servlet.http.HttpServletRequest;

import net.jasonday.examples.sling.spring.mvc.spring.SlingHttpServletRequestUtils;

import org.apache.sling.api.SlingHttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;

/**
 * {@link org.springframework.web.bind.support.WebArgumentResolver Resolver} for
 * resolving the {@link org.apache.sling.api.SlingHttpServletRequest
 * SlingHttpServletRequest} for injection into
 * {@link org.springframework.stereotype.Controller Controller} methods.
 * 
 */
public class SlingRequestArgumentResolver implements WebArgumentResolver {
  /**
   * Implementation of the resolveArgument method to search for an instance of
   * {@link org.apache.sling.api.SlingHttpServletRequest
   * SlingHttpServletRequest}
   */
  @Override
  public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
    // verify we're looking for an instance of SlingHttpServletRequest
    if (!methodParameter.getParameterType().equals(SlingHttpServletRequest.class)) {
      return UNRESOLVED;
    }

    // check if the request is an instance of SlingHttpServletRequest
    HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest();
    SlingHttpServletRequest slingRequest = SlingHttpServletRequestUtils.unwrap(httpServletRequest);
    if (null != slingRequest) {
      // we found it, return it
      return slingRequest;
    }

    return UNRESOLVED;
  }
}

Now, let's create an implementation of WebArgumentResolver to pull the ResourceResolver instance out of the SlingHttpServletRequest.

package net.jasonday.examples.sling.spring.mvc.spring.resolver;

import javax.servlet.http.HttpServletRequest;

import net.jasonday.examples.sling.spring.mvc.spring.SlingHttpServletRequestUtils;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.auth.core.AuthenticationSupport;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;

/**
 * {@link org.springframework.web.bind.support.WebArgumentResolver Resolver} for
 * resolving the {@link org.apache.sling.api.resource.ResourceResolver
 * ResourceResolver} for injection into
 * {@link org.springframework.stereotype.Controller Controller} methods.
 * 
 */
public class ResourceResolverArgumentResolver implements WebArgumentResolver {
  /**
   * Implementation of the resolveArgument method to search for an instance of
   * {@link org.apache.sling.api.resource.ResourceResolver ResourceResolver}
   */
  @Override
  public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
    // verify we're looking for an instance of ResourceResolver
    if (!methodParameter.getParameterType().equals(ResourceResolver.class)) {
      return UNRESOLVED;
    }

    // first check if the request is an instance of SlingHttpServletRequest
    HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest();
    SlingHttpServletRequest slingRequest = SlingHttpServletRequestUtils.unwrap(httpServletRequest);
    if (null != slingRequest) {
      return slingRequest.getResourceResolver();
    }

    // check for it in the request attributes
    if (null != httpServletRequest.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) {
      return httpServletRequest.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
    }

    return UNRESOLVED;
  }
}

All we need to do now is register our WebArgumentResolver with the AnnotationMethodHandlerAdapter we configured.  We can do this by using the setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) method.

...
public class SlingHandlerConfiguration {
  ...

  /**
   * Configures a {@link org.springframework.web.servlet.HandlerAdapter
   * HandlerAdapter} instance for use in a Sling environment by overriding the
   * {@link org.springframework.web.util.UrlPathHelper UrlPathHelper} with a
   * {@link SlingUrlPathHelper} instance.
   * 
   * @return HandlerAdapter An instance of HandlerAdapter for use in a Sling
   * environment
   */
  @Bean
  public HandlerAdapter handlerAdapter() {
    // override the AnnotationMethodHandlerAdapters' UrlPathHelper
    AnnotationMethodHandlerAdapter handlerAdapter = new AnnotationMethodHandlerAdapter();
    handlerAdapter.setUrlPathHelper(new SlingUrlPathHelper());
    
    // configure the custom WebArgumentResolvers
    handlerAdapter.setCustomArgumentResolvers(new WebArgumentResolver[] { new ResourceResolverArgumentResolver(),
        new SlingRequestArgumentResolver() });
    return handlerAdapter;
  }
}

Now you can pass SlingHttpServletRequest and ResourceResolver as arguments to your Controller methods just like any of the other pre-configured class types.

...
public class MySlingController {
  /**
   * Hello world method that will map to
   * examples/sling/spring/mvc/mySlingController controller
   * types.
   * 

* @param servletResponse ServletResponse object for writing out to the * response * @param slingRequest SlingHttpServletRequest object * @param resourceResolver ResourceResolver object * @throws IOException Thrown if there is an exception writing out to the * response * @see net.jasonday.examples.sling.spring.mvc.spring.SlingUrlPathHelper */ @RequestMapping("examples/sling/spring/mvc/mySlingController") public void helloWorld(ServletResponse servletResponse, SlingHttpServletRequest slingRequest, ResourceResolver resourceResolver) throws IOException { // write out "Hello world!" to test the response servletResponse.getWriter().println("Hello world!"); servletResponse.getWriter().println("My slingRequest is '" + (null == slingRequest ? "null" : slingRequest) + "'"); servletResponse.getWriter().println("My resourceResolver is '" + (null == resourceResolver ? "null" : resourceResolver) + "'"); } }

Comments

What repo do you specify in order to get those OSGified dependencies?I remember Spring used to have their repo of all of the OSGified jars, but it's not there anymore.

Very nice!Have you done any performance comparisons on requests that go through to Spring Controllers compared to regular Sling? I.e. is there any overhead involved in routing requests through both frameworks like this?

There is always going to be some performance overhead going through a framework due to the extra work it provides.  Depending on the situation, that may be a good enough trade off, eg. slight performance overhead vs. development savings.  In my case, no, I haven't gone through any benchmarking yet.  From what I can tell, there's little overhead of Sling/OSGi resolving a servlet and I suspect Spring MVC itself is relatively well benchmarked.  The glue code overhead should be minimal given that it's mostly a resolution process that occurs at application startup.