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.TemplateExceptionHandler;
46  import freemarker.template.TemplateModel;
47  import freemarker.template.TemplateModelException;
48  import info.magnolia.cms.beans.config.ServerConfiguration;
49  import info.magnolia.context.MgnlContext;
50  import info.magnolia.context.WebContext;
51  import info.magnolia.objectfactory.Components;
52  
53  import javax.servlet.GenericServlet;
54  import javax.servlet.ServletContext;
55  import javax.servlet.ServletException;
56  import java.io.IOException;
57  import java.io.Reader;
58  import java.io.Writer;
59  import java.util.Locale;
60  import java.util.Map;
61  import java.util.Set;
62  
63  /**
64   * A generic helper to render Content instances with Freemarker templates.
65   * Is used to render both paragraphs and templates.
66   *
67   * TODO : expose Configuration#clearTemplateCache()
68   *
69   * @author gjoseph
70   * @version $Revision: $ ($Author: $)
71   */
72  public class FreemarkerHelper {
73      private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FreemarkerHelper.class);
74  
75      public static FreemarkerHelper getInstance() {
76          return Components.getSingleton(FreemarkerHelper.class);
77      }
78  
79      private final Configuration cfg;
80  
81      // taglib support stuff
82      private TaglibFactory taglibFactory;
83      private ServletContextHashModel servletContextHashModel;
84  
85      public FreemarkerHelper() {
86          this(Components.getSingleton(FreemarkerConfig.class));
87      }
88  
89      public FreemarkerHelper(final FreemarkerConfig freemarkerConfig) {
90          // we subclass freemarker.Configuration to override some methods which must delegate to our observed FreemarkerConfig
91          this.cfg = new Configuration() {
92              @Override
93              public Set getSharedVariableNames() {
94                  final Set names = super.getSharedVariableNames();
95                  names.addAll(freemarkerConfig.getSharedVariables().keySet());
96                  return names;
97              }
98  
99              @Override
100             public TemplateModel getSharedVariable(String name) {
101                 final TemplateModel value = super.getSharedVariable(name);
102                 if (value==null) {
103                     return freemarkerConfig.getSharedVariables().get(name);
104                 }
105                 return value;
106             }
107         };
108         cfg.setTemplateExceptionHandler(freemarkerConfig.getTemplateExceptionHandler());
109 
110         // ... and here we essentially do the same by instantiate delegator implementations of FreeMarker components, which delegate to our observed FreemarkerConfig
111         // these setters do more than their equivalent getters, so we can't just override the getter instead.
112         // ultimately, we could probably have our own clean subclass of freemarker.Configuration to hide all these details off FreemarkerHelper
113         cfg.setTemplateLoader(new ConfigDelegatingTemplateLoader(freemarkerConfig));
114         cfg.setObjectWrapper(new ConfigDelegatingObjectWrapper(freemarkerConfig));
115 
116         cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
117         cfg.setDefaultEncoding("UTF8");
118         //cfg.setTemplateUpdateDelay(10);
119     }
120 
121     /**
122      * @deprecated not needed anymore since 4.3
123      */
124     public void resetObjectWrapper() {
125         // getConfiguration().setObjectWrapper(newObjectWrapper());
126     }
127 
128     /**
129      * @see #render(String, Locale, String, Object, java.io.Writer)
130      */
131     public void render(String templatePath, Object root, Writer out) throws TemplateException, IOException {
132         render(templatePath, null, null, root, out);
133     }
134 
135     /**
136      * Renders the given template, using the given root object (can be a map, or any other type of object
137      * handled by MagnoliaContentWrapper) to the given Writer.
138      * If the root is an instance of a Map, the following elements are added to it:
139      * - ctx, the current Context instance retrieved from MgnlContext
140      * - contextPath, if we have an available WebContext (@deprecated)
141      * - defaultBaseUrl, as per Server.getDefaultBaseUrl()
142      *
143      * @see ServerConfiguration#getDefaultBaseUrl()
144      */
145     public void render(String templatePath, Locale locale, String i18nBasename, Object root, Writer out) throws TemplateException, IOException {
146         final Locale localeToUse = checkLocale(locale);
147         prepareRendering(localeToUse, i18nBasename, root);
148 
149         final Template template = cfg.getTemplate(templatePath, localeToUse);
150         template.process(root, out);
151     }
152 
153     /**
154      * Renders the template read by the given Reader instance. It should be noted that this method completely bypasses
155      * Freemarker's caching mechanism. The template will be parsed everytime, which might have a performance impact.
156      *
157      * @see #render(Reader, Locale, String, Object, Writer)
158      */
159     public void render(Reader template, Object root, Writer out) throws TemplateException, IOException {
160         render(template, null, null, root, out);
161     }
162 
163     protected void render(Reader template, Locale locale, String i18nBasename, Object root, Writer out) throws TemplateException, IOException {
164         final Locale localeToUse = checkLocale(locale);
165         prepareRendering(localeToUse, i18nBasename, root);
166 
167         final Template t = new Template("inlinetemplate", template, cfg);
168         t.setLocale(localeToUse);
169         t.process(root, out);
170     }
171 
172     /**
173      * Returns the passed Locale if non-null, otherwise attempts to get the Locale from the current context.
174      */
175     protected Locale checkLocale(Locale locale) {
176         if (locale != null) {
177             return locale;
178         } else if (MgnlContext.hasInstance()) {
179             return MgnlContext.getLocale();
180         } else {
181             return Locale.getDefault();
182         }
183     }
184 
185     /**
186      * Call checkLocale() before calling this method, to ensure it is not null.
187      */
188     protected void prepareRendering(Locale checkedLocale, String i18nBasename, Object root) {
189         if (root instanceof Map) {
190             final Map<String, Object> data = (Map<String, Object>) root;
191             addDefaultData(data, checkedLocale, i18nBasename);
192         }
193     }
194 
195     protected void addDefaultData(Map<String, Object> data, Locale locale, String i18nBasename) {
196         if (MgnlContext.hasInstance()) {
197             data.put("ctx", MgnlContext.getInstance());
198         }
199         if (MgnlContext.isWebContext()) {
200             final WebContext webCtx = MgnlContext.getWebContext();
201             // @deprecated (-> update all templates) - TODO see MAGNOLIA-1789
202             data.put("contextPath", webCtx.getContextPath());
203             data.put("aggregationState", webCtx.getAggregationState());
204 
205             addTaglibSupportData(data, webCtx);
206         }
207 
208         data.put("defaultBaseUrl", ServerConfiguration.getInstance().getDefaultBaseUrl());
209 
210         if (i18nBasename != null) {
211             data.put("i18n", new MessagesWrapper(i18nBasename, locale));
212         }
213 
214         // TODO : this is currently still in FreemarkerUtil. If we add it here,
215         // the attribute "message" we put in the Freemarker context should have a less generic name
216         // (-> update all templates)
217 //            if (AlertUtil.isMessageSet(mgnlCtx)) {
218 //                data.put("message", AlertUtil.getMessage(mgnlCtx));
219 //            }
220     }
221 
222     protected void addTaglibSupportData(Map<String, Object> data, WebContext webCtx) {
223         final ServletContext servletContext = webCtx.getServletContext();
224         try {
225             data.put(FreemarkerServlet.KEY_JSP_TAGLIBS, checkTaglibFactory(servletContext));
226             data.put(FreemarkerServlet.KEY_APPLICATION_PRIVATE, checkServletContextModel(servletContext));
227             data.put(FreemarkerServlet.KEY_REQUEST_PRIVATE, new HttpRequestHashModel(webCtx.getRequest(), cfg.getObjectWrapper()));
228         } catch (ServletException e) {
229             // 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
230             throw new RuntimeException("Can't initialize taglib support for FreeMarker: ", e);
231         }
232     }
233 
234     protected TaglibFactory checkTaglibFactory(ServletContext servletContext) {
235         if (taglibFactory == null) {
236             taglibFactory = new TaglibFactory(servletContext);
237         }
238         return taglibFactory;
239     }
240 
241     protected ServletContextHashModel checkServletContextModel(ServletContext servletContext) throws ServletException {
242         if (servletContextHashModel == null) {
243             // FreeMarker needs an instance of a GenericServlet, but it doesn't have to do anything other than provide references to the ServletContext
244             final GenericServlet fs = new DoNothingServlet(servletContext);
245             servletContextHashModel = new ServletContextHashModel(fs, cfg.getObjectWrapper());
246         }
247         return servletContextHashModel;
248     }
249 
250     protected Configuration getConfiguration() {
251         return cfg;
252     }
253 
254     private class ConfigDelegatingTemplateLoader implements TemplateLoader {
255         private final FreemarkerConfig freemarkerConfig;
256 
257         public ConfigDelegatingTemplateLoader(FreemarkerConfig freemarkerConfig) {
258             this.freemarkerConfig = freemarkerConfig;
259         }
260 
261         public Object findTemplateSource(String name) throws IOException {
262             return freemarkerConfig.getTemplateLoader().findTemplateSource(name);
263         }
264 
265         public long getLastModified(Object templateSource) {
266             return freemarkerConfig.getTemplateLoader().getLastModified(templateSource);
267         }
268 
269         public Reader getReader(Object templateSource, String encoding) throws IOException {
270             return freemarkerConfig.getTemplateLoader().getReader(templateSource, encoding);
271         }
272 
273         public void closeTemplateSource(Object templateSource) throws IOException {
274             freemarkerConfig.getTemplateLoader().closeTemplateSource(templateSource);
275         }
276     }
277 
278     private class ConfigDelegatingObjectWrapper implements ObjectWrapper {
279         private final FreemarkerConfig freemarkerConfig;
280 
281         public ConfigDelegatingObjectWrapper(FreemarkerConfig freemarkerConfig) {
282             this.freemarkerConfig = freemarkerConfig;
283         }
284 
285         public TemplateModel wrap(Object obj) throws TemplateModelException {
286             return freemarkerConfig.getObjectWrapper().wrap(obj);
287         }
288     }
289 }