Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring + Tiles2 + Freemarker - integrate via Freemarker Servlet or via Spring's FreeMarkerViewResolver?

I was strugling today trying to migrate from Freemarker to Tiles2 + Freemarker.

My freemarker templates use macros that come from spring.ftl.

If I provide a fremarker servlet in web.xml, my model is visible to freemarker, but specific spring variables (naturally) are not populated into the model as springs FreemarkerView is responsible for that.

If I configure a separate DispatcherServlet for specific url (say "/tpl/*") and configure freemarker resolver as default view resolver for that servlet and provide UrlFilenameViewController as default controller, special spring variables do get populated to model, but my own model is not visible: it is bound as a request attribute. I can access my model via ${Request.mymodel.myvar} but this way I have to change all my freemarker templates and I see something smelly in the idea.

Now my solution was to extend UrlFilenameViewController and add my model from request to ModelAndView:

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)  {
        ModelAndView mav = super.handleRequestInternal(request, response);

        HashMap<String, Object> map = new HashMap<String, Object>();

        Enumeration<String> attributes = request.getAttributeNames();

        while(attributes.hasMoreElements()) {
            String attribute = attributes.nextElement();

            if("model".equals(attribute)) {
                logger.debug("FreemarkerViewController.handleRequestInternal: putting attribute to model: " + attribute + "=" + request.getAttribute(attribute));
                map.put(attribute, request.getAttribute(attribute));
            }
        }
        logger.debug("FreemarkerViewController.handleRequestInternal: VIEW: " + mav.getViewName());
        return new ModelAndView(mav.getViewName(), map);
    }

But this solution is somewhat smelly too - if I add something to the model in my business controllers, I have to add it here.

Is there an elegant solution for my problem?

like image 652
miceuz Avatar asked Oct 24 '08 14:10

miceuz


1 Answers

I remember I solved the same problem in two projects. Your second approach is almost right (FreeMarkerViewResolver), but if I remember correctly I also had to extend from FreeMarkerView and TilesView to explicitly bridge both models together.

Custom Tiles view:

public class CustomTilesView extends TilesView {

    @Override
    protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) {
        request.setAttribute(CustomFreeMarkerView.MODEL_KEY, model);
    }
}

Custom FreeMarker view:

public class CustomFreeMarkerView extends FreeMarkerView {

    public static final String MODEL_KEY = FreeMarkerView.class.getName() + ".MODEL";

    @Override
    protected void exposeHelpers(Map model, HttpServletRequest request) throws Exception {
        super.exposeHelpers(model, request);
        final Map savedModel = (Map) request.getAttribute(MODEL_KEY);
        if (savedModel != null) {
            mergeModels(model, savedModel);
        }
    }

    private void mergeModels(Map<String, Object> targetModel, Map<String, Object> recipientModel) throws ServletException {
        for (Map.Entry<String, Object> entry : recipientModel.entrySet()) {
            String key = entry.getKey();
            if (targetModel.containsKey(key)) {
                throw new ServletException("Cannot merge models because of an existing model object of the same name: " + key);
            }
            targetModel.put(key, entry.getValue());
        }
    }
}

Register both in Spring:

<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"
      p:viewClass="com.my.CustomTilesView"
      p:contentType="text/html;charset=UTF-8"/>

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"
      p:suffix=".ftl"
      p:exposeSpringMacroHelpers="true"
      p:viewClass="com.my.CustomFreeMarkerView"
      p:contentType="text/html;charset=UTF-8"/>

Should work.

like image 158
Alex Vayda Avatar answered Oct 06 '22 22:10

Alex Vayda