Spring MVC + FreeMarker + Tiles 2

This is extended and translated into English version of my post Spring MVC + FreeMarker + Tiles 2. I've found that many are interested about the joint work of those frameworks.
Well, this article shows how to configure the Spring MVC application with Tiles 2 and FreeMarker. You may don't use the SpringMVC framework, and in this case you need to make by your hands all the configuration work that Spring MVC does for you.
As you may know, Freemarker is popular template engine write on Java and used by mostly by Java applications. Tiles 2 is also Java frameworks used to prepare document using one or more "tiles" - the parts that are defined separately but used to be generated at one document. For example, using tiles you can define site header/footer, news or post blocks at your site etc.
So, how did I get it working?
Simple!

In file web.xml add the freemarker configuration.

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

<servlet-mapping>
<servlet-name>pages</servlet-name>
<url-pattern>*.page</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>

<init-param>
<param-name>TemplatePath</param-name>
<param-value>/</param-value>
</init-param>
<init-param>
<param-name>NoCache</param-name>
<param-value>true</param-value>
</init-param>
<init-paramv
<param-name>ContentType</param-name>
<param-value>text/html</param-value>
</init-param>

....

<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>


And here is the important part *-servlet.xml configuration:

<!-- Tiles 2 configuration-->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<!-- the tiles configuration files are declared here -->
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/secure.xml</value>
</list>
</property>
</bean>

<bean id="tilesViewResolver" class="com.javaua.spring.integration.ext.ExtUrlBasedViewResolver">
<property name="viewClass" value="com.javaua.spring.integration.ext.ExtTilesView"/>
<property name="exposeSpringMacroModel" value="true"/>
</bean>

<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".ftl"/>
<!-- if you want to use the Spring FreeMarker macros, set this property to true -->
<property name="exposeSpringMacroHelpers" value="true"/>
</bean>

<!-- Controller that returns view = /layout/header -->
<bean name="/layout/header.page" class="com.javaua.HeaderController"/>


Also show here the part of general.xml file where Tiles definitions are set up.:

<definition name="home" extends="default">
<put-attribute name="title" value="Home Page"/>
<put-attribute name="body" value="/templates/home.ftl"/>
</definition>
<definition name="default" template="/templates/main.ftl">
<put-attribute name="header" value="/layout/header.page"/>
<put-attribute name="footer" value="/templates/layout/footer.ftl"/>
</definition>
<definition name="/layout/header" template="/templates/layout/header.ftl"/>


Source code of com.javaua.spring.integration.ext.ExtUrlBasedViewResolver class:

import org.springframework.core.Ordered;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

public class ExtUrlBasedViewResolver extends UrlBasedViewResolver implements Ordered {

private boolean exposeSpringMacroModel = false;

public void setExposeSpringMacroModel(boolean exposeSpringMacroModel) {
this.exposeSpringMacroModel = exposeSpringMacroModel;
}

protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View viewObj = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);

if (viewObj instanceof ExtTilesView) {
ExtTilesView tilesView = (ExtTilesView) viewObj;
tilesView.setExposeSpringMacroModel(exposeSpringMacroModel);
}

return viewObj;
}



Source code of com.javaua.spring.integration.ext.ExtTilesView class:

import org.springframework.web.servlet.view.tiles2.TilesView;
import org.springframework.web.servlet.view.AbstractTemplateView;
import org.springframework.web.servlet.support.RequestContext;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletException;
import java.util.Map;


public class ExtTilesView extends TilesView {

private boolean exposeSpringMacroModel = false;

public void setExposeSpringMacroModel(boolean exposeSpringMacroModel) {
this.exposeSpringMacroModel = exposeSpringMacroModel;
}

@SuppressWarnings("unchecked")
protected final void renderMergedOutputModel(Map model, HttpServletRequest request,
HttpServletResponse response) throws Exception {

if (exposeSpringMacroModel) {
if (model.containsKey(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
throw new ServletException(
"Cannot expose bind macro helper '" +
AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
"' because of an existing model object of the same name");
}
model.put(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
new RequestContext(request, getServletContext(), model));
}

super.renderMergedOutputModel(model, request, response);
}
}


Why we need those two classes? Simple enough. Class ExtTilesView exposes Spring macro model which is required when using standard macros used to support work with Freemarker and Velocity typelate frameworks. And the class ExtUrlBasedViewResolver helps us to prevent view-not-found-error. Because, standard UrlBasedViewResolver is used for getting tiles view, it will throw an error when view with specified name is not found. In my case, I have two view resolvers and each will throw an error if view can't be found.
And FreeMarkerViewResolver will request for the file, while tiles requests just for XML definition. That's why I've ran the tiles view resolver as first one.



9 comments:

aminmc said...

Hi there. Fantastic stuff here! I was looking for this for ages. Quick question I can't seem to get the tiles tags to work in my ftls. I am doing
<#assign tiles=JspTaglibs["http://tiles.apache.org/tags-tiles"]>

However I get a blank page with the following:

<#assign tiles=JspTaglibs["http://tiles.apache.org/tags-tiles"]>

Any help would be appreciated!

Thanks

Ruslan Khmelyuk said...

Hi out there!
Thanks.

I really would like to help you with this problem, but don't have enough of information. Can you provide some stacktrace or maybe part of configuration you're doubt in?

We are using the similar assignation
<#assign tiles=JspTaglibs["http://tiles.apache.org/tags-tiles"]/>
and it works fine for us.

Maybe you have configured to use [] exception <> and you need to write
[#assign tiles=JspTaglibs["http://tiles.apache.org/tags-tiles"]]
or maybe some errors in Freemarker configuration. Just assumptions.

Cheers,
Ruslan

aminmc said...

Hey, thanks for getting back to me. I have the following defined at the top of the freemarker page:

<#assign tiles=JspTaglibs["http://tiles.apache.org/tags-tiles"]>

and then I do the following:

< tiles:insertAttribute name="appHeader" / >

< tiles:insertAttribute name="body" / >

and so on. I don't get an exception, just a blank page with the following:

<#assign tiles=JspTaglibs["http://tiles.apache.org/tags-tiles"]>

I will have a go at what you suggested!

Thanks again!

Ruslan Khmelyuk said...

Hey, I know where is the problem :).

FreeMarker uses another syntax for tags (syntax is like macros syntax in FreeMarker), so in your case would be correctly to write not <tiles:insertAttribute name="appHeader"/ > but
<@tiles.insertAttribute name="appHeader" />, and not <tiles:getAsString .../> but <@tiles.getAsString .../> etc.

I think, that if you'll open rendered page html source you'll see <tiles:insertAttribute/> tags, which are not rendered, because are incorrect HTML tags.

Hope, it would help you.

aminmc said...

Sigh! I re-did the tags to be <@tiles. but no luck. I'm wondering whether my assign tag is incorrect. I have copied the web.xml servlet definition from your blog. I don't know whether this is possible but would be ok to get a snippet of the assign that you use?

Thanks again for your help!

Ruslan Khmelyuk said...

Would be great, if you'll give me yours email. Ping me by ruslan.khmelyuk at gmail.com

And than I'll end you simple sample.

yogami said...

@rkhmelyuk Hey man, I saw your blog on integrating freemarker, tiles 2 and spring 2.5. It seems yours is the only blog/site wich seems to have documented this. I am running into the following issue: If i use tilesviewresolver then I cannot use the spring macros specific to freemarker (spring.ftl file from spring-webvc.jar) despite having the variable enableSpringMacroModel to true as you mentioned in the blog. If I use freemarkerviewresolver then i cannot use tiles. SO I am kind of stuck at this point. Can you point me to the right direction? Thanks- Yami

Bruce Martin said...

Hi,thanks for the applicability shown..

cheer up!

tiles

khurram said...

Hi
i am doing the way you have mentioned in your post but
i am facing this exception
java.lang.IllegalArgumentException: argument type mismatch
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
freemarker.ext.jsp.JspTagModelBase.setupTag(JspTagModelBase.java:87)
freemarker.ext.jsp.SimpleTagDirectiveModel.execute(SimpleTagDirectiveModel.java:51)
freemarker.core.Environment.visit(Environment.java:274)
freemarker.core.UnifiedCall.accept(UnifiedCall.java:126)
freemarker.core.Environment.visit(Environment.java:221)
freemarker.core.MixedContent.accept(MixedContent.java:92)
freemarker.core.Environment.visit(Environment.java:221)
freemarker.core.Environment.process(Environment.java:199)
freemarker.template.Template.process(Template.java:259)
freemarker.ext.servlet.FreemarkerServlet.process(FreemarkerServlet.java:452)
freemarker.ext.servlet.FreemarkerServlet.doGet(FreemarkerServlet.java:391)
javax.servlet.http.HttpServlet.service(HttpServlet.java:743)
javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
org.apache.tiles.servlet.context.ServletTilesRequestContext.forward(ServletTilesRequestContext.java:241)
org.apache.tiles.servlet.context.ServletTilesRequestContext.dispatch