b/c i can't get enough of this topic, i figured out a workable solution for this...
i created a controller in spring that duplicates all the functionality of the VelocityDecoratorServlet and i just map this pattern:
**/*.vm
to that controller and define a url mapping for *.vm to be handled by the DispatcherServlet.
the only "issue" w/ the solution is that you need resolve against a known template and then override the template location to that of the decorator. the reason for this is that right now, the VelocityViewResolver throws an exception if it can't find the template, instead of returning null so the next resolver can be tried.
the code for the controller is below, feel free to use if you'd like.
Code:
import com.opensymphony.module.sitemesh.Decorator;
import com.opensymphony.module.sitemesh.Factory;
import com.opensymphony.module.sitemesh.HTMLPage;
import com.opensymphony.module.sitemesh.RequestConstants;
import com.opensymphony.module.sitemesh.util.OutputConverter;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.context.SecurityContextHolder;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import java.io.IOException;
import java.io.StringWriter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* controller to handle processing of sitemesh decorator templates
*
* @version $Revision$, $Date$
*/
public class DecoratorController extends AbstractController
{
/**
* @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override protected ModelAndView handleRequestInternal(
HttpServletRequest request, HttpServletResponse response) throws Exception
{
ModelAndView mav = new ModelAndView();
addRequired(request, response, mav);
HTMLPage htmlPage =
(HTMLPage) request.getAttribute(RequestConstants.PAGE);
String template = null;
if (htmlPage == null)
{
addNullPage(mav);
template = request.getServletPath();
}
else
{
addPage(mav, htmlPage);
Factory factory = Factory.getInstance(new Config(getServletContext()));
Decorator decorator =
factory.getDecoratorMapper().getDecorator(request, htmlPage);
template = decorator.getPage();
}
mav.setView(resolveView(template));
return mav;
}
/**
* add "null" page attributes
*
* <p>these attributes are added if a {@link
* com.opensymphony.module.sitemesh.Page} object was not found in the
* request</p>
*/
private void addNullPage(ModelAndView mav)
{
mav.addObject("title", "Title?");
mav.addObject("body", "<p>Body?</p>");
mav.addObject("head", "<!-- head -->");
}
/**
* add page attributes
*
* <p>these attributes are added if a {@link
* com.opensymphony.module.sitemesh.Page} object was found in the request</p>
*/
private void addPage(ModelAndView mav, HTMLPage htmlPage) throws IOException
{
mav.addObject("title", OutputConverter.convert(htmlPage.getTitle()));
{
StringWriter buffer = new StringWriter();
htmlPage.writeBody(OutputConverter.getWriter(buffer));
mav.addObject("body", buffer.toString());
}
{
StringWriter buffer = new StringWriter();
htmlPage.writeHead(OutputConverter.getWriter(buffer));
mav.addObject("head", buffer.toString());
}
mav.addObject("page", htmlPage);
}
/**
* add required attributes
*
* <p>this attributes should exist reguardless of whether or not a {@link
* com.opensymphony.module.sitemesh.Page} object was found in the request</p>
*/
private void addRequired(HttpServletRequest request,
HttpServletResponse response, ModelAndView mav)
{
// add base path, request and response
mav.addObject("base", request.getContextPath());
mav.addObject("req", request);
mav.addObject("res", response);
}
/**
* resolve the view
*/
private View resolveView(String template) throws Exception
{
ViewResolver viewResolver =
(ViewResolver) getApplicationContext().getBean("viewResolver");
/*
* XXX: if the view resolver can't find the template, it throws an
* exception, so we lookup a dummy template that we know exists, and then
* override the url w/ that of the decorator's location.
*/
View view = viewResolver.resolveViewName("dummy", null);
((AbstractUrlBasedView) view).setUrl(template);
return view;
}
/**
* allows direct use of the {@link javax.servlet.ServletContext} when looking
* up a {@link com.opensymphony.module.sitemesh.Factory} instance
*
* @version $Revision$, $Date$
*/
static private class Config extends com.opensymphony.module.sitemesh.Config
{
/** servlet context */
private ServletContext ctx;
/**
* create a new Config object.
*
* @param ctx ServletContext object
*/
public Config(ServletContext ctx)
{
super(new MockServletConfig(ctx));
this.ctx = ctx;
}
/**
* @see com.opensymphony.module.sitemesh.Config#getServletContext()
*/
@Override public ServletContext getServletContext()
{
return this.ctx;
}
}
}