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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With