<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="336" height="280" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"> <param name="src" value="http://img.alimama.cn/bm/wrappler_swf/2009-11-26/2042702_0af5658d088e285c4ac474cd20865090_336x280.swf?v=1259240549"> <embed type="application/x-shockwave-flash" width="336" height="280" src="http://img.alimama.cn/bm/wrappler_swf/2009-11-26/2042702_0af5658d088e285c4ac474cd20865090_336x280.swf?v=1259240549"></embed></object>
http://forum.springframework.org/showthread.php?t=53476
I hope this thread will help others who are trying to combine these technologies. After much pain, I finally got it working exactly the way I want.
My goal was to build a website using Freemarker for templating, SiteMesh for layouts, and Spring MVC. Normally you would just put the Freemarker files in WEB-INF/freemarker.
However, this site is one of many we build that re-use the same Content Management Admin screens - so we wanted the Freemarker templates for those forms in the classpath. But we sometimes need to override those admin screens with customizations in a specific project. To enable customizations, we want to be able to first try to load the Freemarker template from the WAR file in /modules/ , and if it's not found there, load it from the classpath package modules .
Additionally, each website will have its site-specific HTML content written in Freemarker - these files be stored in /WEB-INF/freemarker .
Summary:
* The content of a site-specific web page is written in Freemarker and stored in /WEB-INF/freemarker .
* Site-specific SiteMesh decorators are written in Freemarker and stored in the website's /decorators/ directory. (This could probably be in /WEB-INF/freemarker/decorators)
* Re-usable, default admin screens are written in Freemarker and stored in classpath:modules .
* If needed, a developer can override an admin screen for the site by copying the freemarker file from classpath:modules into the site's /modules. folder. Then they can make any customizations needed.
We also use Freemarker in the service tier to generate emails, so we can't just follow the Reference Guide instructions for using Freemarker in the web tier . Instead, we have to use Spring's FreeMarkerConfigurationFactoryBean class to create a custom Freemarker Configuration bean.
applicationContext-freemarker.xml
<bean id="freemarkerConfiguration" class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean"> <description>Using the Config directly so we can use it outside the web tier</description> <property name="templateLoaderPaths"> <list> <value>/WEB-INF/freemarker/</value> <value>/modules/</value> <value>/</value> <value>classpath:modules</value> <value>classpath:org/springframework/web/servlet/view/freemarker</value> </list> </property> <property name="freemarkerSettings"> <props> <prop key="datetime_format">MM/dd/yyyy</prop> <prop key="number_format">#</prop> <prop key="whitespace_stripping">true</prop> </props> </property> <property name="freemarkerVariables"> <map> <entry key="xml_escape" value-ref="fmXmlEscape" /> <entry key="html_escape" value-ref="fmHtmlEscape" /> </map> </property> </bean> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <description>Required for Freemarker to work in web tier</description> <property name="configuration" ref="freemarkerConfiguration" /> </bean> <bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape" /> <bean id="fmHtmlEscape" class="freemarker.template.utility.HtmlEscape" />
In the freemarkerConfiguration bean above, the important setting is the templateLoaderPaths . When Freemarker tries to find a template file, it will look for the template in the following order:
* /WEB-INF/freemarker
* /modules/
* / (the site's root - important for decorators)
* The modules package in the classpath - this could be a JAR file in WEB-INF/lib or in WEB-INF/classes
* The org/springframework/web/servlet/view/freemarker package on the classpath - this is so we can reuse Spring's Freemarker Macros
By Default, The Freemarker Servlet creates a new Configuration object, which will only load templates from the Servlet Context, a physical directory, OR a classpath location (in other words, it does not use freemarker.cache.MultiTemplateLoader). SiteMesh extends the FreemarkerServlet to do its decorating. Since we need to re-use the Configuration created in applicationContext-freemarker.xml (which loads templates from multiple locations), I needed to extend com.opensymphony.module.sitemesh.freemarker.Freema rkerDecoratorServlet . I wrote the following Subclass:
package com.kazaam.sitemesh.servlet; import java.io.IOException; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import com.opensymphony.module.sitemesh.freemarker.FreemarkerDecoratorServlet; import freemarker.cache.TemplateLoader; import freemarker.template.Configuration; /** * @author mstralka * */ public class KzSpringFreemarkerDecoratorServlet extends FreemarkerDecoratorServlet { @Override public void init() throws ServletException { super.init(); WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); Configuration springConfiguration = (Configuration) ctx.getBean("freemarkerConfiguration"); TemplateLoader templateLoader = springConfiguration.getTemplateLoader(); getConfiguration().setTemplateLoader(templateLoader); } }
Normally, The SiteMesh FreemarkerDecoratorServlet is configured in web.xml. Since we want to re-use our Spring Interceptors, we are going to use Spring's ServletWrappingController to configure the servlet as a Spring bean instead.
In spring-servlet.xml (or whatever you call your Spring MVC XML file), add the following bean (which wraps the Servlet in a controller so we can re-use our Spring MVC interceptors for Hibernate, etc):
<bean id="freemarkerWrapperServletController" class="org.springframework.web.servlet.mvc.ServletWrappingController"> <property name="servletClass" value="com.kazaam.sitemesh.servlet.KzSpringFreemarkerDecoratorServlet" /> <property name="servletName" value="sitemesh-freemarker" /> <property name="initParameters"> <props> <prop key="TemplatePath">/</prop><!--this is ignored by our custom implementation but keep it here--> <prop key="default_encoding">ISO-8859-1</prop> </props> </property> </bean>
Add the following to your UrlMapping Bean's mappings property:
<prop key="/**/*.ftl">freemarkerWrapperServletController</prop> <prop key="/**/*.ftd">freemarkerWrapperServletController</prop>
In web.xml, make sure *.ftl and *.ftd are handled by your Spring dispatcher servlet (my dispatcher servlet is named spring ):
<servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
In addition to whatever other URL patterns you let Spring handle, add the following so the Freemarker templates are handled by Spring too:
<servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.ftd</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.ftl</url-pattern> </servlet-mapping>
To complete the SiteMesh configuration, here is my sitemesh.xml file in the /WEB-INF/ directory (Note that I name my decorators.xml file sitemesh-decorators.xml file instead because I like them to appear next to each other alphabetically in Eclipse)
<sitemesh> <property name="decorators-file" value="/WEB-INF/sitemesh-decorators.xml" /> <excludes file="/WEB-INF/sitemesh-decorators.xml" /> <page-parsers> <parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" /> <parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" /> </page-parsers> <decorator-mappers> <mapper class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper"> <param name="property.1" value="meta.decorator" /> <param name="property.2" value="decorator" /> </mapper> <mapper class="com.opensymphony.module.sitemesh.mapper.FrameSetDecoratorMapper"/> <mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper"> <param name="decorator" value="printable" /> <param name="parameter.name" value="printable" /> <param name="parameter.value" value="true" /> </mapper> <mapper class="com.opensymphony.module.sitemesh.mapper.FileDecoratorMapper"/> <mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper"> <param name="config" value="/WEB-INF/sitemesh-decorators.xml" /> </mapper> </decorator-mappers> </sitemesh>
And here is my sitemesh-decorators.xml file (yours will be different):
<decorators defaultdir="/decorators"> <!-- SiteMesh Decorator Mappings: See http://opensymphony.com/sitemesh/dm.html --> <!-- No decorator --> <excludes> <pattern>/misc/*</pattern> <pattern>/dwr/*</pattern> <pattern>/articles/rss.*</pattern> <pattern>/dwr/index.html</pattern> </excludes> <decorator name="main" page="main.ftd"> <url-pattern>*</url-pattern> </decorator> <decorator name="authoring" page="authoring.ftd"> <url-pattern>/page/create*</url-pattern> <url-pattern>/page/edit*</url-pattern> <url-pattern>/article/create*</url-pattern> <url-pattern>/article/edit*</url-pattern> </decorator> <decorator name="popup" page="popup.ftd"> <url-pattern>/page/wikihelp*</url-pattern> <url-pattern>/page/help*</url-pattern> </decorator> </decorators>