View Javadoc

1   /*
2    * $Id: FreemarkerManager.java 454565 2006-10-10 00:02:56Z jmitchell $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts2.views.freemarker;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.Map;
24  import java.util.Properties;
25  
26  import javax.servlet.GenericServlet;
27  import javax.servlet.ServletContext;
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.http.HttpServletResponse;
30  import javax.servlet.http.HttpSession;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.struts2.StrutsConstants;
35  import org.apache.struts2.config.Settings;
36  import org.apache.struts2.views.JspSupportServlet;
37  import org.apache.struts2.views.freemarker.tags.StrutsModels;
38  import org.apache.struts2.views.util.ContextUtil;
39  
40  import com.opensymphony.xwork2.util.FileManager;
41  import com.opensymphony.xwork2.util.ValueStack;
42  import com.opensymphony.xwork2.ObjectFactory;
43  
44  import freemarker.cache.FileTemplateLoader;
45  import freemarker.cache.MultiTemplateLoader;
46  import freemarker.cache.TemplateLoader;
47  import freemarker.cache.WebappTemplateLoader;
48  import freemarker.ext.beans.BeansWrapper;
49  import freemarker.ext.jsp.TaglibFactory;
50  import freemarker.ext.servlet.HttpRequestHashModel;
51  import freemarker.ext.servlet.HttpRequestParametersHashModel;
52  import freemarker.ext.servlet.HttpSessionHashModel;
53  import freemarker.ext.servlet.ServletContextHashModel;
54  import freemarker.template.ObjectWrapper;
55  import freemarker.template.SimpleHash;
56  import freemarker.template.TemplateException;
57  import freemarker.template.TemplateExceptionHandler;
58  import freemarker.template.TemplateModel;
59  
60  
61  /***
62   * Static Configuration Manager for the FreemarkerResult's configuration
63   *
64   * <p/>
65   *
66   * Possible extension points are :-
67   * <ul>
68   *   <li>createConfiguration method</li>
69   *   <li>loadSettings method</li>
70   *   <li>getTemplateLoader method</li>
71   *   <li>populateContext method</li>
72   * </ul>
73   *
74   * <p/>
75   * <b> createConfiguration method </b><br/>
76   * Create a freemarker Configuration.
77   * <p/>
78   *
79   * <b> loadSettings method </b><br/>
80   * Load freemarker settings, default to freemarker.properties (if found in classpath)
81   * <p/>
82   *
83   * <b> getTemplateLoader method</b><br/>
84   * create a freemarker TemplateLoader that loads freemarker template in the following order :-
85   * <ol>
86   *   <li>path defined in ServletContext init parameter named 'templatePath' or 'TemplatePath' (must be an absolute path)</li>
87   *   <li>webapp classpath</li>
88   *   <li>struts's static folder (under [STRUT2_SOURCE]/org/apache/struts2/static/</li>
89   * </ol>
90   * <p/>
91   *
92   * <b> populateContext method</b><br/>
93   * populate the created model.
94   *
95   */
96  public class FreemarkerManager {
97  
98      private static final Log log = LogFactory.getLog(FreemarkerManager.class);
99      public static final String CONFIG_SERVLET_CONTEXT_KEY = "freemarker.Configuration";
100     public static final String KEY_EXCEPTION = "exception";
101 
102     // coppied from freemarker servlet - since they are private
103     private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application";
104     private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs";
105     private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
106     private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters";
107 
108     // coppied from freemarker servlet - so that there is no dependency on it
109     public static final String KEY_APPLICATION = "Application";
110     public static final String KEY_REQUEST_MODEL = "Request";
111     public static final String KEY_SESSION_MODEL = "Session";
112     public static final String KEY_JSP_TAGLIBS = "JspTaglibs";
113     public static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters";
114     private static FreemarkerManager instance = null;
115 
116 
117     /***
118      * To allow for custom configuration of freemarker, sublcass this class "ConfigManager" and
119      * set the Struts configuration property
120      * <b>struts.freemarker.configmanager.classname</b> to the fully qualified classname.
121      * <p/>
122      * This allows you to override the protected methods in the ConfigMangaer
123      * to programatically create your own Configuration instance
124      */
125     public final static synchronized FreemarkerManager getInstance() {
126         if (instance == null) {
127             String classname = FreemarkerManager.class.getName();
128 
129             if (Settings.isSet(StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME)) {
130                 classname = Settings.get(StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME).trim();
131             }
132 
133             try {
134                 log.info("Instantiating Freemarker ConfigManager!, " + classname);
135                 // singleton instances shouldn't be built accessing request or session-specific context data
136                 instance = (FreemarkerManager) ObjectFactory.getObjectFactory().buildBean(classname, null);
137             } catch (Exception e) {
138                 log.fatal("Fatal exception occurred while trying to instantiate a Freemarker ConfigManager instance, " + classname, e);
139             }
140         }
141 
142         // if the instance creation failed, make sure there is a default instance
143         if (instance == null) {
144             instance = new FreemarkerManager();
145         }
146 
147         return instance;
148     }
149 
150     public final synchronized freemarker.template.Configuration getConfiguration(ServletContext servletContext) throws TemplateException {
151         freemarker.template.Configuration config = (freemarker.template.Configuration) servletContext.getAttribute(CONFIG_SERVLET_CONTEXT_KEY);
152 
153         if (config == null) {
154             config = createConfiguration(servletContext);
155 
156             // store this configuration in the servlet context
157             servletContext.setAttribute(CONFIG_SERVLET_CONTEXT_KEY, config);
158         }
159 
160         config.setWhitespaceStripping(true);
161 
162         return config;
163     }
164 
165     protected ScopesHashModel buildScopesHashModel(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper, ValueStack stack) {
166         ScopesHashModel model = new ScopesHashModel(wrapper, servletContext, request, stack);
167 
168         // Create hash model wrapper for servlet context (the application)
169         // only need one thread to do this once, per servlet context
170         synchronized (servletContext) {
171             ServletContextHashModel servletContextModel = (ServletContextHashModel) servletContext.getAttribute(ATTR_APPLICATION_MODEL);
172 
173             if (servletContextModel == null) {
174 
175                 GenericServlet servlet = JspSupportServlet.jspSupportServlet;
176                 // TODO if the jsp support  servlet isn't load-on-startup then it won't exist
177                 // if it hasn't been accessed, and a JSP page is accessed
178                 if (servlet != null) {
179                     servletContextModel = new ServletContextHashModel(servlet, wrapper);
180                     servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel);
181                     TaglibFactory taglibs = new TaglibFactory(servletContext);
182                     servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
183                 }
184 
185             }
186 
187             model.put(KEY_APPLICATION, servletContextModel);
188             model.put(KEY_JSP_TAGLIBS, (TemplateModel) servletContext.getAttribute(ATTR_JSP_TAGLIBS_MODEL));
189         }
190 
191         // Create hash model wrapper for session
192         HttpSession session = request.getSession(false);
193         if (session != null) {
194             model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper));
195         } else {
196             // no session means no attributes ???
197             //            model.put(KEY_SESSION_MODEL, new SimpleHash());
198         }
199 
200         // Create hash model wrapper for the request attributes
201         HttpRequestHashModel requestModel = (HttpRequestHashModel) request.getAttribute(ATTR_REQUEST_MODEL);
202 
203         if ((requestModel == null) || (requestModel.getRequest() != request)) {
204             requestModel = new HttpRequestHashModel(request, response, wrapper);
205             request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
206         }
207 
208         model.put(KEY_REQUEST_MODEL, requestModel);
209 
210 
211         // Create hash model wrapper for request parameters
212         HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel) request.getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
213         if (reqParametersModel == null || requestModel.getRequest() != request) {
214         	reqParametersModel = new HttpRequestParametersHashModel(request);
215         	request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL, reqParametersModel);
216         }
217         model.put(KEY_REQUEST_PARAMETER_MODEL, reqParametersModel);
218 
219         return model;
220     }
221 
222     protected void populateContext(ScopesHashModel model, ValueStack stack, Object action, HttpServletRequest request, HttpServletResponse response) {
223         // put the same objects into the context that the velocity result uses
224         Map standard = ContextUtil.getStandardContext(stack, request, response);
225         model.putAll(standard);
226 
227         // support for JSP exception pages, exposing the servlet or JSP exception
228         Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception");
229 
230         if (exception == null) {
231             exception = (Throwable) request.getAttribute("javax.servlet.error.JspException");
232         }
233 
234         if (exception != null) {
235             model.put(KEY_EXCEPTION, exception);
236         }
237     }
238 
239     protected BeansWrapper getObjectWrapper() {
240         return new StrutsBeanWrapper();
241     }
242 
243     /***
244      * The default template loader is a MultiTemplateLoader which includes
245      * a ClassTemplateLoader and a WebappTemplateLoader (and a FileTemplateLoader depending on
246      * the init-parameter 'TemplatePath').
247      * <p/>
248      * The ClassTemplateLoader will resolve fully qualified template includes
249      * that begin with a slash. for example /com/company/template/common.ftl
250      * <p/>
251      * The WebappTemplateLoader attempts to resolve templates relative to the web root folder
252      */
253     protected TemplateLoader getTemplateLoader(ServletContext servletContext) {
254         // construct a FileTemplateLoader for the init-param 'TemplatePath'
255         FileTemplateLoader templatePathLoader = null;
256 
257         String templatePath = servletContext.getInitParameter("TemplatePath");
258         if (templatePath == null) {
259             templatePath = servletContext.getInitParameter("templatePath");
260         }
261 
262         if (templatePath != null) {
263             try {
264                 templatePathLoader = new FileTemplateLoader(new File(templatePath));
265             } catch (IOException e) {
266                 log.error("Invalid template path specified: " + e.getMessage(), e);
267             }
268         }
269 
270         // presume that most apps will require the class and webapp template loader
271         // if people wish to
272         return templatePathLoader != null ?
273                 new MultiTemplateLoader(new TemplateLoader[]{
274                         templatePathLoader,
275                         new WebappTemplateLoader(servletContext),
276                         new StrutsClassTemplateLoader()
277                 })
278                 : new MultiTemplateLoader(new TemplateLoader[]{
279                 new WebappTemplateLoader(servletContext),
280                 new StrutsClassTemplateLoader()
281         });
282     }
283 
284     /***
285      * Create the instance of the freemarker Configuration object.
286      * <p/>
287      * this implementation
288      * <ul>
289      * <li>obtains the default configuration from Configuration.getDefaultConfiguration()
290      * <li>sets up template loading from a ClassTemplateLoader and a WebappTemplateLoader
291      * <li>sets up the object wrapper to be the BeansWrapper
292      * <li>loads settings from the classpath file /freemarker.properties
293      * </ul>
294      *
295      * @param servletContext
296      */
297     protected freemarker.template.Configuration createConfiguration(ServletContext servletContext) throws TemplateException {
298     	freemarker.template.Configuration configuration = new freemarker.template.Configuration();
299 
300         configuration.setTemplateLoader(getTemplateLoader(servletContext));
301 
302         configuration.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
303 
304         configuration.setObjectWrapper(getObjectWrapper());
305 
306         if (Settings.isSet(StrutsConstants.STRUTS_I18N_ENCODING)) {
307         	configuration.setDefaultEncoding(Settings.get(StrutsConstants.STRUTS_I18N_ENCODING));
308         }
309 
310         loadSettings(servletContext, configuration);
311 
312         return configuration;
313     }
314 
315     /***
316      * Load the settings from the /freemarker.properties file on the classpath
317      *
318      * @see freemarker.template.Configuration#setSettings for the definition of valid settings
319      */
320     protected void loadSettings(ServletContext servletContext, freemarker.template.Configuration configuration) {
321         try {
322             InputStream in = FileManager.loadFile("freemarker.properties", FreemarkerManager.class);
323 
324             if (in != null) {
325                 Properties p = new Properties();
326                 p.load(in);
327                 configuration.setSettings(p);
328             }
329         } catch (IOException e) {
330             log.error("Error while loading freemarker settings from /freemarker.properties", e);
331         } catch (TemplateException e) {
332             log.error("Error while loading freemarker settings from /freemarker.properties", e);
333         }
334     }
335 
336     public SimpleHash buildTemplateModel(ValueStack stack, Object action, ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper) {
337         ScopesHashModel model = buildScopesHashModel(servletContext, request, response, wrapper, stack);
338         populateContext(model, stack, action, request, response);
339         model.put("s", new StrutsModels(stack, request, response));
340         return model;
341     }
342 }