Sitemesh 2 with Spring MVC 3 – multiple layout for multiple controller

Recently I had bitter experience setting up different layout in sitemesh for different controller of Spring. In general as a developer my expectation is for different controller I setup different url and for each url I add a decorator with unique name and different layout page.

For instance, I have two controllers – AdminController and UserController as follows.

@Controller
@RequestMapping("/admin")
public class AdminController {

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index() {
        return "admin/index";
    }
}
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index() {
        return "user/index";
    }
}

So, url project/admin/index – points to admin controller’s index page
and url project/user/index points to user controller’s index page.

Now I want to decorate two urls with two different sitemesh layout. decorator.xml looks like –

<?xml version="1.0" encoding="UTF-8"?>
<decorators defaultdir="/WEB-INF/layouts">
    <decorator name="admin" page="admin.jsp">
        <pattern>*admin*</pattern>
    </decorator>

    <decorator name="user" page="user.jsp">
        <pattern>*user*</pattern>
    </decorator>
</decorators>

However, it wasn’t behaving as I expected. I wasted a lot of time trying different combination in pattern like /projects/admin*, */admin/* etc.

It was bit frustrating as the official wiki page on multidecorator is empty. In addition, according to README of sitemesh2 github project I am doing it right.

After a while I decided to dive into the code. Then I figure out that the pattern matching class is PathMapper it has sound implementation of wildcard pattern matching. So the problem is in the class who is calling it. After some more digging I found out that the call to PathMapper is made by ConfigDecoratorMapper, which calculates the path by calling request.getServletPath(). If that value is null then it uses the request.getRequestURI() - request.getPathInfo() otherwise use the value of servlet path.

Take a look into public Decorator getDecorator(HttpServletRequest request, Page page) method in ConfigDecoratorMapper.java

Then I realized what went wrong. My web.xml looks like –

<servlet>
    <servlet-name>project</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>project</servlet-name>
    <url-pattern>/project/*</url-pattern>
</servlet-mapping>

As a result the the request.getServletPath() will only return /project. It doesn’t contain the controller/method specific part. So PathMapper won’t even get the opportunity to match controller/method specific part.

Then I converted the url pattern to *.html

<servlet>
    <servlet-name>project</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>project</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>

Now request.getServletPath() will return the whole url with controller/method specific part for example /admin/index.html. So the pattern in decorator will work as expected. However it might not be feasiable for you to setup dispatcher servlet mapping like this and you still want different layout for different controller. In that case you might want to consider setting sitemesh layout via meta tag in view jsp.

Advertisements

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