View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.freemarker;
35  
36  import freemarker.cache.TemplateLoader;
37  import freemarker.ext.jsp.TaglibFactory;
38  import freemarker.ext.servlet.FreemarkerServlet;
39  import freemarker.ext.servlet.HttpRequestHashModel;
40  import freemarker.ext.servlet.ServletContextHashModel;
41  import freemarker.template.Configuration;
42  import freemarker.template.ObjectWrapper;
43  import freemarker.template.Template;
44  import freemarker.template.TemplateException;
45  import freemarker.template.TemplateModel;
46  import freemarker.template.TemplateModelException;
47  import info.magnolia.cms.beans.config.ServerConfiguration;
48  import info.magnolia.context.MgnlContext;
49  import info.magnolia.context.WebContext;
50  import info.magnolia.objectfactory.Components;
51  
52  import javax.servlet.GenericServlet;
53  import javax.servlet.ServletContext;
54  import javax.servlet.ServletException;
55  import java.io.IOException;
56  import java.io.Reader;
57  import java.io.Writer;
58  import java.util.Locale;
59  import java.util.Map;
60  import java.util.Set;
61  
62  /**
63   * A generic helper to render Content instances with Freemarker templates.
64   * Is used to render both paragraphs and templates.
65   *
66   * TODO : expose Configuration#clearTemplateCache()
67   *
68   * @author gjoseph
69   * @version $Revision: $ ($Author: $)
70   */
71  public class FreemarkerHelper {
72  
73      public static FreemarkerHelper getInstance() {
74          return Components.getSingleton(FreemarkerHelper.class);
75      }
76  
77      private final Configuration cfg;
78  
79      // taglib support stuff
80      private TaglibFactory taglibFactory;
81      private ServletContextHashModel servletContextHashModel;
82  
83      public FreemarkerHelper() {
84          this(Components.getSingleton(FreemarkerConfig.class));
85      }
86  
87      public FreemarkerHelper(final FreemarkerConfig freemarkerConfig) {
88          // we subclass freemarker.Configuration to override some methods which must delegate to our observed FreemarkerConfig
89          this.cfg = new Configuration() {
90              @Override
91              public Set getSharedVariableNames() {
92                  final Set names = super.getSharedVariableNames();
93                  names.addAll(freemarkerConfig.getSharedVariables().keySet());
94                  return names;
95              }
96  
97              @Override
98              public TemplateModel getSharedVariable(String name) {
99                  final TemplateModel value = super.getSharedVariable(name);
100                 if (value==null) {
101                     return freemarkerConfig.getSharedVariables().get(name);
102                 }
103                 return value;
104             }
105         };
106         cfg.setTemplateExceptionHandler(freemarkerConfig.getTemplateExceptionHandler());
107 
108         // ... and here we essentially do the same by instantiate delegator implementations of FreeMarker components, which delegate to our observed FreemarkerConfig
109         // these setters do more than their equivalent getters, so we can't just override the getter instead.
110         // ultimately, we could probably have our own clean subclass of freemarker.Configuration to hide all these details off FreemarkerHelper
111         cfg.setTemplateLoader(new ConfigDelegatingTemplateLoader(freemarkerConfig));
112         cfg.setObjectWrapper(new ConfigDelegatingObjectWrapper(freemarkerConfig));
113 
114         cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
115         cfg.setDefaultEncoding("UTF8");
116         //cfg.setTemplateUpdateDelay(10);
117     }
118 
119     /**
120      * @deprecated not needed anymore since 4.3
121      */
122     public void resetObjectWrapper() {
123         // getConfiguration().setObjectWrapper(newObjectWrapper());
124     }
125 
126     /**
127      * @see #render(String, Locale, String, Object, java.io.Writer)
128      */
129     public void render(String templatePath, Object root, Writer out) throws TemplateException, IOException {
130         render(templatePath, null, null, root, out);
131     }
132 
133     /**
134      * Renders the given template, using the given root object (can be a map, or any other type of object
135      * handled by MagnoliaContentWrapper) to the given Writer.
136      * If the root is an instance of a Map, the following elements are added to it:
137      * - ctx, the current Context instance retrieved from MgnlContext
138      * - contextPath, if we have an available WebContext (@deprecated)
139      * - defaultBaseUrl, as per Server.getDefaultBaseUrl()
140      *
141      * @see ServerConfiguration#getDefaultBaseUrl()
142      */
143     public void render(String templatePath, Locale locale, String i18nBasename, Object root, Writer out) throws TemplateException, IOException {
144         final Locale localeToUse = checkLocale(locale);
145         prepareRendering(localeToUse, i18nBasename, root);
146 
147         final Template template = cfg.getTemplate(templatePath, localeToUse);
148         template.process(root, out);
149     }
150 
151     /**
152      * Renders the template read by the given Reader instance. It should be noted that this method completely bypasses
153      * Freemarker's caching mechanism. The template will be parsed everytime, which might have a performance impact.
154      *
155      * @see #render(Reader, Locale, String, Object, Writer)
156      */
157     public void render(Reader template, Object root, Writer out) throws TemplateException, IOException {
158         render(template, null, null, root, out);
159     }
160 
161     protected void render(Reader template, Locale locale, String i18nBasename, Object root, Writer out) throws TemplateException, IOException {
162         final Locale localeToUse = checkLocale(locale);
163         prepareRendering(localeToUse, i18nBasename, root);
164 
165         final Template t = new Template("inlinetemplate", template, cfg);
166         t.setLocale(localeToUse);
167         t.process(root, out);
168     }
169 
170     /**
171      * Returns the passed Locale if non-null, otherwise attempts to get the Locale from the current context.
172      */
173     protected Locale checkLocale(Locale locale) {
174         if (locale != null) {
175             return locale;
176         } else if (MgnlContext.hasInstance()) {
177             return MgnlContext.getLocale();
178         } else {
179             return Locale.getDefault();
180         }
181     }
182 
183     /**
184      * Call checkLocale() before calling this method, to ensure it is not null.
185      */
186     protected void prepareRendering(Locale checkedLocale, String i18nBasename, Object root) {
187         if (root instanceof Map) {
188             final Map<String, Object> data = (Map<String, Object>) root;
189             addDefaultData(data, checkedLocale, i18nBasename);
190         }
191     }
192 
193     protected void addDefaultData(Map<String, Object> data, Locale locale, String i18nBasename) {
194         if (MgnlContext.hasInstance()) {
195             data.put("ctx", MgnlContext.getInstance());
196         }
197         if (MgnlContext.isWebContext()) {
198             final WebContext webCtx = MgnlContext.getWebContext();
199             // @deprecated (-> update all templates) - TODO see MAGNOLIA-1789
200             data.put("contextPath", webCtx.getContextPath());
201             data.put("aggregationState", webCtx.getAggregationState());
202 
203             addTaglibSupportData(data, webCtx);
204         }
205 
206         data.put("defaultBaseUrl", ServerConfiguration.getInstance().getDefaultBaseUrl());
207 
208         if (i18nBasename != null) {
209             data.put("i18n", new MessagesWrapper(i18nBasename, locale));
210         }
211 
212         // TODO : this is currently still in FreemarkerUtil. If we add it here,
213         // the attribute "message" we put in the Freemarker context should have a less generic name
214         // (-> update all templates)
215 //            if (AlertUtil.isMessageSet(mgnlCtx)) {
216 //                data.put("message", AlertUtil.getMessage(mgnlCtx));
217 //            }
218     }
219 
220     protected void addTaglibSupportData(Map<String, Object> data, WebContext webCtx) {
221         final ServletContext servletContext = webCtx.getServletContext();
222         try {
223             data.put(FreemarkerServlet.KEY_JSP_TAGLIBS, checkTaglibFactory(servletContext));
224             data.put(FreemarkerServlet.KEY_APPLICATION_PRIVATE, checkServletContextModel(servletContext));
225             data.put(FreemarkerServlet.KEY_REQUEST_PRIVATE, new HttpRequestHashModel(webCtx.getRequest(), cfg.getObjectWrapper()));
226         } catch (ServletException e) {
227             // this should be an IllegalStateException (i.e there's no reason we should end up here) but this constructor isn't available in 1.4
228             throw new RuntimeException("Can't initialize taglib support for FreeMarker: ", e);
229         }
230     }
231 
232     protected TaglibFactory checkTaglibFactory(ServletContext servletContext) {
233         if (taglibFactory == null) {
234             taglibFactory = new TaglibFactory(servletContext);
235         }
236         return taglibFactory;
237     }
238 
239     protected ServletContextHashModel checkServletContextModel(ServletContext servletContext) throws ServletException {
240         if (servletContextHashModel == null) {
241             // FreeMarker needs an instance of a GenericServlet, but it doesn't have to do anything other than provide references to the ServletContext
242             final GenericServlet fs = new DoNothingServlet(servletContext);
243             servletContextHashModel = new ServletContextHashModel(fs, cfg.getObjectWrapper());
244         }
245         return servletContextHashModel;
246     }
247 
248     protected Configuration getConfiguration() {
249         return cfg;
250     }
251 
252     private class ConfigDelegatingTemplateLoader implements TemplateLoader {
253         private final FreemarkerConfig freemarkerConfig;
254 
255         public ConfigDelegatingTemplateLoader(FreemarkerConfig freemarkerConfig) {
256             this.freemarkerConfig = freemarkerConfig;
257         }
258 
259         public Object findTemplateSource(String name) throws IOException {
260             return freemarkerConfig.getTemplateLoader().findTemplateSource(name);
261         }
262 
263         public long getLastModified(Object templateSource) {
264             return freemarkerConfig.getTemplateLoader().getLastModified(templateSource);
265         }
266 
267         public Reader getReader(Object templateSource, String encoding) throws IOException {
268             return freemarkerConfig.getTemplateLoader().getReader(templateSource, encoding);
269         }
270 
271         public void closeTemplateSource(Object templateSource) throws IOException {
272             freemarkerConfig.getTemplateLoader().closeTemplateSource(templateSource);
273         }
274     }
275 
276     private class ConfigDelegatingObjectWrapper implements ObjectWrapper {
277         private final FreemarkerConfig freemarkerConfig;
278 
279         public ConfigDelegatingObjectWrapper(FreemarkerConfig freemarkerConfig) {
280             this.freemarkerConfig = freemarkerConfig;
281         }
282 
283         public TemplateModel wrap(Object obj) throws TemplateModelException {
284             return freemarkerConfig.getObjectWrapper().wrap(obj);
285         }
286     }
287 }