KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > servlet > view > freemarker > FreeMarkerView


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.web.servlet.view.freemarker;
18
19 import java.io.IOException JavaDoc;
20 import java.util.Locale JavaDoc;
21 import java.util.Map JavaDoc;
22
23 import javax.servlet.GenericServlet JavaDoc;
24 import javax.servlet.ServletContext JavaDoc;
25 import javax.servlet.ServletRequest JavaDoc;
26 import javax.servlet.ServletResponse JavaDoc;
27 import javax.servlet.http.HttpServletRequest JavaDoc;
28 import javax.servlet.http.HttpServletResponse JavaDoc;
29 import javax.servlet.http.HttpSession JavaDoc;
30
31 import freemarker.core.ParseException;
32 import freemarker.ext.jsp.TaglibFactory;
33 import freemarker.ext.servlet.FreemarkerServlet;
34 import freemarker.ext.servlet.HttpRequestHashModel;
35 import freemarker.ext.servlet.HttpRequestParametersHashModel;
36 import freemarker.ext.servlet.HttpSessionHashModel;
37 import freemarker.ext.servlet.ServletContextHashModel;
38 import freemarker.template.Configuration;
39 import freemarker.template.ObjectWrapper;
40 import freemarker.template.Template;
41 import freemarker.template.TemplateException;
42
43 import org.springframework.beans.BeansException;
44 import org.springframework.beans.factory.BeanFactoryUtils;
45 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
46 import org.springframework.context.ApplicationContextException;
47 import org.springframework.web.servlet.support.RequestContextUtils;
48 import org.springframework.web.servlet.view.AbstractTemplateView;
49
50 /**
51  * View using the FreeMarker template engine.
52  *
53  * <p>Exposes the following JavaBean properties:
54  * <ul>
55  * <li><b>url</b>: the location of the FreeMarker template to be wrapped,
56  * relative to the FreeMarker template context (directory).
57  * <li><b>encoding</b> (optional, default is determined by FreeMarker configuration):
58  * the encoding of the FreeMarker template file
59  * </ul>
60  *
61  * <p>Depends on a single {@link FreeMarkerConfig} object such as {@link FreeMarkerConfigurer}
62  * being accessible in the current web application context, with any bean name.
63  * Alternatively, you can set the FreeMarker {@link Configuration} object as bean property.
64  * See {@link #setConfiguration} for more details on the impacts of this approach.
65  *
66  * <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
67  *
68  * @author Darren Davison
69  * @author Juergen Hoeller
70  * @since 03.03.2004
71  * @see #setUrl
72  * @see #setExposeSpringMacroHelpers
73  * @see #setEncoding
74  * @see #setConfiguration
75  * @see FreeMarkerConfig
76  * @see FreeMarkerConfigurer
77  */

78 public class FreeMarkerView extends AbstractTemplateView {
79
80     private String JavaDoc encoding;
81
82     private Configuration configuration;
83
84     private TaglibFactory taglibFactory;
85
86     private ServletContextHashModel servletContextHashModel;
87
88
89     /**
90      * Set the encoding of the FreeMarker template file. Default is determined
91      * by the FreeMarker Configuration: "ISO-8859-1" if not specified otherwise.
92      * <p>Specify the encoding in the FreeMarker Configuration rather than per
93      * template if all your templates share a common encoding.
94      */

95     public void setEncoding(String JavaDoc encoding) {
96         this.encoding = encoding;
97     }
98
99     /**
100      * Return the encoding for the FreeMarker template.
101      */

102     protected String JavaDoc getEncoding() {
103         return encoding;
104     }
105
106     /**
107      * Set the FreeMarker Configuration to be used by this view.
108      * If this is not set, the default lookup will occur: a single {@link FreeMarkerConfig}
109      * is expected in the current web application context, with any bean name.
110      * <strong>Note:</strong> using this method will cause a new instance of {@link TaglibFactory}
111      * to created for every single {@link FreeMarkerView} instance. This can be quite expensive
112      * in terms of memory and initial CPU usage. In production it is recommended that you use
113      * a {@link FreeMarkerConfig} which exposes a single shared {@link TaglibFactory}.
114      */

115     public void setConfiguration(Configuration configuration) {
116         this.configuration = configuration;
117     }
118
119     /**
120      * Return the FreeMarker configuration used by this view.
121      */

122     protected Configuration getConfiguration() {
123         return this.configuration;
124     }
125
126
127     /**
128      * Invoked on startup. Looks for a single FreeMarkerConfig bean to
129      * find the relevant Configuration for this factory.
130      * <p>Checks that the template for the default Locale can be found:
131      * FreeMarker will check non-Locale-specific templates if a
132      * locale-specific one is not found.
133      * @see freemarker.cache.TemplateCache#getTemplate
134      */

135     protected void initApplicationContext() throws BeansException {
136         super.initApplicationContext();
137
138         if (getConfiguration() != null) {
139             this.taglibFactory = new TaglibFactory(getServletContext());
140         }
141         else {
142             FreeMarkerConfig config = autodetectConfiguration();
143             setConfiguration(config.getConfiguration());
144             this.taglibFactory = config.getTaglibFactory();
145         }
146
147         this.servletContextHashModel = new ServletContextHashModel(
148                 new GenericServletAdapter(getServletContext()), getObjectWrapper());
149
150         checkTemplate();
151     }
152
153     /**
154      * Autodetect a {@link FreeMarkerConfig} object via the ApplicationContext.
155      * @return the Configuration instance to use for FreeMarkerViews
156      * @throws BeansException if no Configuration instance could be found
157      * @see #getApplicationContext
158      * @see #setConfiguration
159      */

160     protected FreeMarkerConfig autodetectConfiguration() throws BeansException {
161         try {
162             return (FreeMarkerConfig) BeanFactoryUtils.beanOfTypeIncludingAncestors(
163                     getApplicationContext(), FreeMarkerConfig.class, true, false);
164         }
165         catch (NoSuchBeanDefinitionException ex) {
166             throw new ApplicationContextException(
167                     "Must define a single FreeMarkerConfig bean in this web application context " +
168                     "(may be inherited): FreeMarkerConfigurer is the usual implementation. " +
169                     "This bean may be given any name.", ex);
170         }
171     }
172
173     /**
174      * Return the configured FreeMarker {@link ObjectWrapper}, or the
175      * {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified.
176      * @see freemarker.template.Configuration#getObjectWrapper()
177      */

178     protected ObjectWrapper getObjectWrapper() {
179         ObjectWrapper ow = getConfiguration().getObjectWrapper();
180         return (ow != null ? ow : ObjectWrapper.DEFAULT_WRAPPER);
181     }
182
183     /**
184      * Check that the FreeMarker template used for this view exists and is valid.
185      * <p>Can be overridden to customize the behavior, for example in case of
186      * multiple templates to be rendered into a single view.
187      * @throws ApplicationContextException if the template cannot be found or is invalid
188      */

189     protected void checkTemplate() throws ApplicationContextException {
190         try {
191             // Check that we can get the template, even if we might subsequently get it again.
192
getTemplate(getConfiguration().getLocale());
193         }
194         catch (ParseException ex) {
195             throw new ApplicationContextException(
196                     "Failed to parse FreeMarker template for URL [" + getUrl() + "]", ex);
197         }
198         catch (IOException JavaDoc ex) {
199             throw new ApplicationContextException(
200                     "Could not load FreeMarker template for URL [" + getUrl() + "]", ex);
201         }
202     }
203
204
205     /**
206      * Process the model map by merging it with the FreeMarker template.
207      * Output is directed to the servlet response.
208      * <p>This method can be overridden if custom behavior is needed.
209      */

210     protected void renderMergedTemplateModel(
211             Map JavaDoc model, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws Exception JavaDoc {
212
213         exposeHelpers(model, request);
214         doRender(model, request, response);
215     }
216
217     /**
218      * Expose helpers unique to each rendering operation. This is necessary so that
219      * different rendering operations can't overwrite each other's formats etc.
220      * <p>Called by <code>renderMergedTemplateModel</code>. The default implementation
221      * is empty. This method can be overridden to add custom helpers to the model.
222      * @param model The model that will be passed to the template at merge time
223      * @param request current HTTP request
224      * @throws Exception if there's a fatal error while we're adding information to the context
225      * @see #renderMergedTemplateModel
226      */

227     protected void exposeHelpers(Map JavaDoc model, HttpServletRequest JavaDoc request) throws Exception JavaDoc {
228     }
229
230     /**
231      * Render the FreeMarker view to the given response, using the given model
232      * map which contains the complete template model to use.
233      * <p>The default implementation renders the template specified by the "url"
234      * bean property, retrieved via <code>getTemplate</code>. It delegates to the
235      * <code>processTemplate</code> method to merge the template instance with
236      * the given template model.
237      * <p>Adds the standard Freemarker hash models to the model: request parameters,
238      * request, session and application (ServletContext), as well as the JSP tag
239      * library hash model.
240      * <p>Can be overridden to customize the behavior, for example to render
241      * multiple templates into a single view.
242      * @param model the template model to use for rendering
243      * @param request current HTTP request
244      * @param response current servlet response
245      * @throws IOException if the template file could not be retrieved
246      * @throws Exception if rendering failed
247      * @see #setUrl
248      * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
249      * @see #getTemplate(java.util.Locale)
250      * @see #processTemplate
251      * @see freemarker.ext.servlet.FreemarkerServlet
252      */

253     protected void doRender(Map JavaDoc model, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws Exception JavaDoc {
254         // Expose model to JSP tags (as request attributes).
255
exposeModelAsRequestAttributes(model, request);
256
257         // Expose all standard FreeMarker hash models.
258
model.put(FreemarkerServlet.KEY_JSP_TAGLIBS, this.taglibFactory);
259         model.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel);
260         model.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(request, response));
261         model.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel(request, response, getObjectWrapper()));
262         model.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(request));
263
264         if (logger.isDebugEnabled()) {
265             logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'");
266         }
267         // Grab the locale-specific version of the template.
268
Locale JavaDoc locale = RequestContextUtils.getLocale(request);
269         processTemplate(getTemplate(locale), model, response);
270     }
271
272     /**
273      * Build a FreeMarker {@link HttpSessionHashModel} for the given request,
274      * detecting whether a session already exists and reacting accordingly.
275      * @param request current HTTP request
276      * @param response current servlet response
277      * @return the FreeMarker HttpSessionHashModel
278      */

279     private HttpSessionHashModel buildSessionModel(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) {
280         HttpSession JavaDoc session = request.getSession(false);
281         if (session != null) {
282             return new HttpSessionHashModel(session, getObjectWrapper());
283         }
284         else {
285             return new HttpSessionHashModel(null, request, response, getObjectWrapper());
286         }
287     }
288
289     /**
290      * Retrieve the FreeMarker template for the given locale,
291      * to be rendering by this view.
292      * <p>By default, the template specified by the "url" bean property
293      * will be retrieved.
294      * @param locale the current locale
295      * @return the FreeMarker template to render
296      * @throws IOException if the template file could not be retrieved
297      * @see #setUrl
298      * @see #getTemplate(String, java.util.Locale)
299      */

300     protected Template getTemplate(Locale JavaDoc locale) throws IOException JavaDoc {
301         return getTemplate(getUrl(), locale);
302     }
303
304     /**
305      * Retrieve the FreeMarker template specified by the given name,
306      * using the encoding specified by the "encoding" bean property.
307      * <p>Can be called by subclasses to retrieve a specific template,
308      * for example to render multiple templates into a single view.
309      * @param name the file name of the desired template
310      * @param locale the current locale
311      * @return the FreeMarker template
312      * @throws IOException if the template file could not be retrieved
313      */

314     protected Template getTemplate(String JavaDoc name, Locale JavaDoc locale) throws IOException JavaDoc {
315         return (getEncoding() != null ?
316                 getConfiguration().getTemplate(name, locale, getEncoding()) :
317                 getConfiguration().getTemplate(name, locale));
318     }
319
320     /**
321      * Process the FreeMarker template to the servlet response.
322      * <p>Can be overridden to customize the behavior.
323      * @param template the template to process
324      * @param model the model for the template
325      * @param response servlet response (use this to get the OutputStream or Writer)
326      * @throws IOException if the template file could not be retrieved
327      * @throws TemplateException if thrown by FreeMarker
328      * @see freemarker.template.Template#process(Object, java.io.Writer)
329      */

330     protected void processTemplate(Template template, Map JavaDoc model, HttpServletResponse JavaDoc response)
331             throws IOException JavaDoc, TemplateException {
332
333         template.process(model, response.getWriter());
334     }
335
336
337     /**
338      * Simple adapter class that extends {@link GenericServlet} and exposes the currently
339      * active {@link ServletContext}. Needed for JSP access in FreeMarker.
340      */

341     private static final class GenericServletAdapter extends GenericServlet JavaDoc {
342
343         private final ServletContext JavaDoc servletContext;
344
345         public GenericServletAdapter(ServletContext JavaDoc servletContext) {
346             this.servletContext = servletContext;
347         }
348
349         public void service(ServletRequest JavaDoc servletRequest, ServletResponse JavaDoc servletResponse) {
350             // no-op
351
}
352
353         public ServletContext JavaDoc getServletContext() {
354             return this.servletContext;
355         }
356     }
357
358 }
359
Popular Tags