Integrating SpringMVC with OpenCms

Integrating SpringMVC with OpenCms

OpenCms websites tend to place a lot of responsibility for the controller and view functionality of a page on the JSP, a JEE anti-pattern known as a Monolithic/Compound JSP. We recently integrated SpringMVC into an OpenCMS website at one of our clients to gain the advantages of a Model-View-Controller framework without an extensive rewrite of the codebase.

Problem Solving

Many pages in the current website had long suffered from being written as Monolithic/Compound JSPs. Problems include untested functionality, simple changes to the view requiring a back end developer to implement, not to mention barely readable page source, making JSP-level debugging a tedious task.

SpringMVC provides a solution to this by separating the view logic from the controller logic, the division simplifying the responsibility of each component. By integrating SpringMVC into the OpenCms website, our goal is to make our page controller code more robust using a technology familiar to web developers. We had several requirements/constraints when planning this integration:

  • Existing pages should still work without the need for modification.
  • We wanted to retain the concept of page requests mapping to OpenCms resources. Moving, renaming, or previewing OpenCms resources by content editors shouldn’t break the Controller-View relationship.
  • The solution should be otherwise non-disruptive to the current website.

Our proposed solution involves SpringMVC running alongside OpenCms:

SpringMVC working with OpenCms

SpringMVC working with OpenCms

In order to achieve this, there needed to be a way of linking to a Spring controller when an OpenCms resource is called, as well as a linking an OpenCms resource to the view name returned by the controller. We decided to leverage the flexibility of OpenCms resource properties to specify whether a particular resource should call a Spring controller, or whether a particular resource should be returned as a view (or both). We introduced two new properties:

  • SpringInitialisationURL – a string that specifies the path to a request handler in a Spring controller.
  • SpringViewName – a string that specifies a view name for the OpenCms resource.

We would then need a way for OpenCms to communicate to the Spring controller, and vice versa; the glue between the two technologies. This is implemented using a custom Rewrite Filter and View Resolver.

The Rewrite Filter

Rewrite Filter workflow

The workflow for handling a request to a Spring managed OpenCms resource

The Rewrite Filter will be used to forward from a Spring-enabled OpenCms resource to the corresponding Spring controller using the specified initialisation URL. Using the OpenCms API (in particular, the CmsObject class), we are able to inspect the properties of a resource when it is requested, allowing us to test for a SpringInitialisationURL property. To provide this functionality, we have introduced a helper class called MvcCmsUtil.

public class MvcCmsUtil {
	...
	private static final String SITE_ROOT="/sites/default";
	public static final String SPRING_INITIALISATION_URL = "springInitialisationUrl";
	...
	public CmsObject getOnlineObjectSiteRootAsGuest() throws CmsException {
		CmsObject cmsObj = OpenCms.initCmsObject(OpenCms.getDefaultUsers().getUserGuest());
		cmsObj.getRequestContext().setSiteRoot(SITE_ROOT);
		return cmsObj;
	}

	public String getOnlineProperty(String sResourcePath, String sProp) throws CmsException {
		CmsObject cmsObj = getOnlineObjectSiteRootAsGuest();
		cmsObj.getRequestContext().setSiteRoot(SITE_ROOT);
		CmsProperty prop = cmsObj.readPropertyObject(sResourcePath, sProp, true);
		String sValue = prop.getValue();
		return sValue;
	}
	...
}

The getOnlineProperty method will attempt to retrieve a resource at a specified path, and return the value of a given property – in this case, the Spring initialisation URL to forward the request on to:

public class RewriteFilter implements Filter {
	@Autowired
	private MvcCmsUtil mvcCmsUtil;

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
			ServletException {
		...
		String springInitialisationUrl =
			mvcCmsUtil.getProperty(cmsResourcePath, MvcCmsUtil.SPRING_INITIALISATION_URL);
		if (null != springInitialisationUrl) {
			RequestDispatcher dispatcher = request.getRequestDispatcher(springInitialisationUrl);
			dispatcher.forward(request, response);
			return;
		}
		...
	}
}

The Tomcat configuration needs to be able to differentiate between requests to the OpenCms servlet and the Spring dispatcher servlet. In the web.xml configuration, this is done by mapping all requests beginning with “/spring” to the dispatcher servlet. Request handlers in the Spring controller should reflect this naming convention.

<servlet>
<servlet-name>OpenCmsServlet</servlet-name>
<description>
The main servlet that handles all requests to the OpenCms VFS.
</description>
<servlet-class>org.opencms.main.OpenCmsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>OpenCmsServlet</servlet-name>
    <url-pattern>/opencms/*</url-pattern>
</servlet-mapping>

Lastly, the corresponding request handler in the Spring controller itself needs to be setup to accept these requests.

@RequestMapping(value="/spring/controller/myController", method = RequestMethod.GET)
	public String myController(HttpServletRequest request, HttpServletResponse response)
	{
		// Controller logic goes here
		return "myView";
	}

This sample controller will be called when “/spring/controller/myController” is set as the value of the SpringInitialisationURL on the OpenCms resource. The next step is resolving the view name “myView” in this example back to an OpenCms resource, using a View Resolver.

The View Resolver

View resolver workflow

The workflow for returning an OpenCms resource from the Spring controller

The CmsObject class has a method to retrieve a resource by a given property value. We can use this to find a resource with a matching Spring view name. To do this, we have expanded the MvcCmsUtil helper class to include a new method:

public class MvcCmsUtil {
	...
	public static final String SPRING_VIEW_NAME = "springViewName";
	public static final String CMS_CONTENT_PATH = "/content/";
	...
	public String findCmsRessourceByViewName(String viewNameWithParameters) throws CmsException {
		String[] viewNameWithParametersArray = viewNameWithParameters.split("\\?");
		String viewName = viewNameWithParametersArray[0];

		CmsObject cmsObject = getOnlineObjectSiteRootAsGuest();
		List cmsResources =
			(List) cmsObject.readResourcesWithProperty(CMS_CONTENT_PATH, SPRING_VIEW_NAME, viewName);

		for (CmsResource cmsResource : cmsResources) {
			if (viewName.equals(cmsObject.readPropertyObject(cmsResource, SPRING_VIEW_NAME, false).getValue())) {
		        if (viewNameWithParametersArray.length > 1) {
		            return cmsObject.getSitePath(cmsResource) + "?" + viewNameWithParameters.split("\\?")[1];
		        } else {
		            return cmsObject.getSitePath(cmsResource);
		        }
			}
		}
		return null;
	}
	...
}

The above method will retrieve a list of resources that have the “springViewName” property filled, and will return a resource that matched the view name given. Some extra work is done to handle returning view names with URI parameters. A View Resolver – registered in the dispatcher servlet – needs to then call our method in order to return the correct OpenCms resource.

public class OpenCmsViewResolver extends InternalResourceViewResolver {
	private static final String OPEN_CMS_SERVLET_PATH = "/opencms";

	@Autowired
	private MvcCmsUtil mvcCmsUtil;
	@Override
	public View resolveViewName(String name, Locale locale) throws Exception {
		String cmsResourcePath = OPEN_CMS_SERVLET_PATH + mvcCmsUtil.findCmsRessourceByViewName(name);
		return super.resolveViewName(cmsResourcePath, locale);
	}
}

The Benefits (after all that)

We are now able to create an entry point to a Spring controller anywhere within the logical structure of the OpenCms website by configuring a single property – the SpringInitialisationURL of a resource. Since this functionality depends on whether this property is set, it integrates seamlessly into the existing OpenCms website with no impact to existing pages. Being able to configure the entry point and Spring view independently of the controller itself offers greater flexibility and ultimately requires less changes to the code base in the future.

Furthermore, by offloading more responsibility to a Java class for the controller logic of a page, we are ensuring that future pages are more easily testable, and reducing the amount of clutter and code irrelevant to the view in the JSP itself.

Issues & Considerations

This implementation only retrieves the online properties of the OpenCms resource. Therefore, if a developer is working in offline mode, they must first publish the resource before the Spring controller can be called. This is because MvcCmsUtil currently accesses a given OpenCms resource as a guest user only. This could be improved to retrieve an OpenCms object instance from the current session. One reason this might be useful is that several administrative pages require the user to be logged in to OpenCms to access certain functions. Using the CmsUser object, retrieving the logged in user from the session would allow us to discern whether someone accessing a Spring controller has the appropriate privileges.

A second issue lies with the robustness of this solution. For example, in its current state there is no restriction that that view name of a resource must be unique, and multiple resources with the same view name will result in only the first resource in the list returned from the readResourcesWithProperty() method call being returned by the controller. Addressing these issues would require further consideration and development.

Conclusions

What we have achieved is allowing an existing website with a JSP-Controller oriented approach to leverage the benefits of a Model-View-Controller framework without the need to rewrite the codebase or drastically alter its current implementation. In doing so, we have made the process relatively straightforward for existing developers to take advantage of, and have increased our effective test code coverage by taking the responsibility of the controller logic away from the JSP and into a Java controller that is easier to unit test.

This entry was posted in Java and tagged , , , , , . Bookmark the permalink.

2 Responses to Integrating SpringMVC with OpenCms

  1. chandrajeet says:

    Hi, Can I have this sample code to test out. I am trying a similar integration with opencms.

    thanks,

  2. Pingback: OpenCMS integration with Spring MVC | My Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s