001: /*
002: * $Id: FreemarkerManager.java 570513 2007-08-28 18:14:00Z jholmes $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.views.freemarker;
022:
023: import java.io.File;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.util.ArrayList;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.Properties;
032: import java.util.Set;
033:
034: import javax.servlet.GenericServlet;
035: import javax.servlet.ServletContext;
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.http.HttpServletResponse;
038: import javax.servlet.http.HttpSession;
039:
040: import org.apache.commons.logging.Log;
041: import org.apache.commons.logging.LogFactory;
042: import org.apache.struts2.StrutsConstants;
043: import org.apache.struts2.views.JspSupportServlet;
044: import org.apache.struts2.views.TagLibrary;
045: import org.apache.struts2.views.freemarker.tags.StrutsModels;
046: import org.apache.struts2.views.util.ContextUtil;
047:
048: import com.opensymphony.xwork2.inject.Container;
049: import com.opensymphony.xwork2.inject.Inject;
050: import com.opensymphony.xwork2.util.FileManager;
051: import com.opensymphony.xwork2.util.ValueStack;
052: import com.opensymphony.xwork2.ObjectFactory;
053:
054: import freemarker.cache.FileTemplateLoader;
055: import freemarker.cache.MultiTemplateLoader;
056: import freemarker.cache.TemplateLoader;
057: import freemarker.cache.WebappTemplateLoader;
058: import freemarker.ext.beans.BeansWrapper;
059: import freemarker.ext.jsp.TaglibFactory;
060: import freemarker.ext.servlet.HttpRequestHashModel;
061: import freemarker.ext.servlet.HttpRequestParametersHashModel;
062: import freemarker.ext.servlet.HttpSessionHashModel;
063: import freemarker.ext.servlet.ServletContextHashModel;
064: import freemarker.template.ObjectWrapper;
065: import freemarker.template.SimpleHash;
066: import freemarker.template.TemplateException;
067: import freemarker.template.TemplateExceptionHandler;
068: import freemarker.template.TemplateModel;
069:
070: /**
071: * Static Configuration Manager for the FreemarkerResult's configuration
072: *
073: * <p/>
074: *
075: * Possible extension points are :-
076: * <ul>
077: * <li>createConfiguration method</li>
078: * <li>loadSettings method</li>
079: * <li>getTemplateLoader method</li>
080: * <li>populateContext method</li>
081: * </ul>
082: *
083: * <p/>
084: * <b> createConfiguration method </b><br/>
085: * Create a freemarker Configuration.
086: * <p/>
087: *
088: * <b> loadSettings method </b><br/>
089: * Load freemarker settings, default to freemarker.properties (if found in classpath)
090: * <p/>
091: *
092: * <b> getTemplateLoader method</b><br/>
093: * create a freemarker TemplateLoader that loads freemarker template in the following order :-
094: * <ol>
095: * <li>path defined in ServletContext init parameter named 'templatePath' or 'TemplatePath' (must be an absolute path)</li>
096: * <li>webapp classpath</li>
097: * <li>struts's static folder (under [STRUT2_SOURCE]/org/apache/struts2/static/</li>
098: * </ol>
099: * <p/>
100: *
101: * <b> populateContext method</b><br/>
102: * populate the created model.
103: *
104: */
105: public class FreemarkerManager {
106:
107: private static final Log LOG = LogFactory
108: .getLog(FreemarkerManager.class);
109: public static final String CONFIG_SERVLET_CONTEXT_KEY = "freemarker.Configuration";
110: public static final String KEY_EXCEPTION = "exception";
111:
112: // coppied from freemarker servlet - since they are private
113: private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application";
114: private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs";
115: private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
116: private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters";
117:
118: // coppied from freemarker servlet - so that there is no dependency on it
119: public static final String KEY_APPLICATION = "Application";
120: public static final String KEY_REQUEST_MODEL = "Request";
121: public static final String KEY_SESSION_MODEL = "Session";
122: public static final String KEY_JSP_TAGLIBS = "JspTaglibs";
123: public static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters";
124:
125: private String encoding;
126: private boolean altMapWrapper;
127: private Map<String, TagLibrary> tagLibraries;
128:
129: @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
130: public void setEncoding(String encoding) {
131: this .encoding = encoding;
132: }
133:
134: @Inject(StrutsConstants.STRUTS_FREEMARKER_WRAPPER_ALT_MAP)
135: public void setWrapperAltMap(String val) {
136: altMapWrapper = "true".equals(val);
137: }
138:
139: @Inject
140: public void setContainer(Container container) {
141: Map<String, TagLibrary> map = new HashMap<String, TagLibrary>();
142: Set<String> prefixes = container
143: .getInstanceNames(TagLibrary.class);
144: for (String prefix : prefixes) {
145: map.put(prefix, container.getInstance(TagLibrary.class,
146: prefix));
147: }
148: this .tagLibraries = Collections.unmodifiableMap(map);
149: }
150:
151: public final synchronized freemarker.template.Configuration getConfiguration(
152: ServletContext servletContext) throws TemplateException {
153: freemarker.template.Configuration config = (freemarker.template.Configuration) servletContext
154: .getAttribute(CONFIG_SERVLET_CONTEXT_KEY);
155:
156: if (config == null) {
157: config = createConfiguration(servletContext);
158:
159: // store this configuration in the servlet context
160: servletContext.setAttribute(CONFIG_SERVLET_CONTEXT_KEY,
161: config);
162: }
163:
164: config.setWhitespaceStripping(true);
165:
166: return config;
167: }
168:
169: protected ScopesHashModel buildScopesHashModel(
170: ServletContext servletContext, HttpServletRequest request,
171: HttpServletResponse response, ObjectWrapper wrapper,
172: ValueStack stack) {
173: ScopesHashModel model = new ScopesHashModel(wrapper,
174: servletContext, request, stack);
175:
176: // Create hash model wrapper for servlet context (the application)
177: // only need one thread to do this once, per servlet context
178: synchronized (servletContext) {
179: ServletContextHashModel servletContextModel = (ServletContextHashModel) servletContext
180: .getAttribute(ATTR_APPLICATION_MODEL);
181:
182: if (servletContextModel == null) {
183:
184: GenericServlet servlet = JspSupportServlet.jspSupportServlet;
185: // TODO if the jsp support servlet isn't load-on-startup then it won't exist
186: // if it hasn't been accessed, and a JSP page is accessed
187: if (servlet != null) {
188: servletContextModel = new ServletContextHashModel(
189: servlet, wrapper);
190: servletContext.setAttribute(ATTR_APPLICATION_MODEL,
191: servletContextModel);
192: TaglibFactory taglibs = new TaglibFactory(
193: servletContext);
194: servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL,
195: taglibs);
196: }
197:
198: }
199:
200: model.put(KEY_APPLICATION, servletContextModel);
201: model.put(KEY_JSP_TAGLIBS, (TemplateModel) servletContext
202: .getAttribute(ATTR_JSP_TAGLIBS_MODEL));
203: }
204:
205: // Create hash model wrapper for session
206: HttpSession session = request.getSession(false);
207: if (session != null) {
208: model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(
209: session, wrapper));
210: } else {
211: // no session means no attributes ???
212: // model.put(KEY_SESSION_MODEL, new SimpleHash());
213: }
214:
215: // Create hash model wrapper for the request attributes
216: HttpRequestHashModel requestModel = (HttpRequestHashModel) request
217: .getAttribute(ATTR_REQUEST_MODEL);
218:
219: if ((requestModel == null)
220: || (requestModel.getRequest() != request)) {
221: requestModel = new HttpRequestHashModel(request, response,
222: wrapper);
223: request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
224: }
225:
226: model.put(KEY_REQUEST_MODEL, requestModel);
227:
228: // Create hash model wrapper for request parameters
229: HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel) request
230: .getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
231: if (reqParametersModel == null
232: || requestModel.getRequest() != request) {
233: reqParametersModel = new HttpRequestParametersHashModel(
234: request);
235: request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL,
236: reqParametersModel);
237: }
238: model.put(KEY_REQUEST_PARAMETER_MODEL, reqParametersModel);
239:
240: return model;
241: }
242:
243: protected void populateContext(ScopesHashModel model,
244: ValueStack stack, Object action,
245: HttpServletRequest request, HttpServletResponse response) {
246: // put the same objects into the context that the velocity result uses
247: Map standard = ContextUtil.getStandardContext(stack, request,
248: response);
249: model.putAll(standard);
250:
251: // support for JSP exception pages, exposing the servlet or JSP exception
252: Throwable exception = (Throwable) request
253: .getAttribute("javax.servlet.error.exception");
254:
255: if (exception == null) {
256: exception = (Throwable) request
257: .getAttribute("javax.servlet.error.JspException");
258: }
259:
260: if (exception != null) {
261: model.put(KEY_EXCEPTION, exception);
262: }
263: }
264:
265: protected BeansWrapper getObjectWrapper() {
266: return new StrutsBeanWrapper(altMapWrapper);
267: }
268:
269: /**
270: * The default template loader is a MultiTemplateLoader which includes
271: * a ClassTemplateLoader and a WebappTemplateLoader (and a FileTemplateLoader depending on
272: * the init-parameter 'TemplatePath').
273: * <p/>
274: * The ClassTemplateLoader will resolve fully qualified template includes
275: * that begin with a slash. for example /com/company/template/common.ftl
276: * <p/>
277: * The WebappTemplateLoader attempts to resolve templates relative to the web root folder
278: */
279: protected TemplateLoader getTemplateLoader(
280: ServletContext servletContext) {
281: // construct a FileTemplateLoader for the init-param 'TemplatePath'
282: FileTemplateLoader templatePathLoader = null;
283:
284: String templatePath = servletContext
285: .getInitParameter("TemplatePath");
286: if (templatePath == null) {
287: templatePath = servletContext
288: .getInitParameter("templatePath");
289: }
290:
291: if (templatePath != null) {
292: try {
293: templatePathLoader = new FileTemplateLoader(new File(
294: templatePath));
295: } catch (IOException e) {
296: LOG.error("Invalid template path specified: "
297: + e.getMessage(), e);
298: }
299: }
300:
301: // presume that most apps will require the class and webapp template loader
302: // if people wish to
303: return templatePathLoader != null ? new MultiTemplateLoader(
304: new TemplateLoader[] { templatePathLoader,
305: new WebappTemplateLoader(servletContext),
306: new StrutsClassTemplateLoader() })
307: : new MultiTemplateLoader(new TemplateLoader[] {
308: new WebappTemplateLoader(servletContext),
309: new StrutsClassTemplateLoader() });
310: }
311:
312: /**
313: * Create the instance of the freemarker Configuration object.
314: * <p/>
315: * this implementation
316: * <ul>
317: * <li>obtains the default configuration from Configuration.getDefaultConfiguration()
318: * <li>sets up template loading from a ClassTemplateLoader and a WebappTemplateLoader
319: * <li>sets up the object wrapper to be the BeansWrapper
320: * <li>loads settings from the classpath file /freemarker.properties
321: * </ul>
322: *
323: * @param servletContext
324: */
325: protected freemarker.template.Configuration createConfiguration(
326: ServletContext servletContext) throws TemplateException {
327: freemarker.template.Configuration configuration = new freemarker.template.Configuration();
328:
329: configuration
330: .setTemplateLoader(getTemplateLoader(servletContext));
331:
332: configuration
333: .setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
334:
335: configuration.setObjectWrapper(getObjectWrapper());
336:
337: if (encoding != null) {
338: configuration.setDefaultEncoding(encoding);
339: }
340:
341: loadSettings(servletContext, configuration);
342:
343: return configuration;
344: }
345:
346: /**
347: * Load the settings from the /freemarker.properties file on the classpath
348: *
349: * @see freemarker.template.Configuration#setSettings for the definition of valid settings
350: */
351: protected void loadSettings(ServletContext servletContext,
352: freemarker.template.Configuration configuration) {
353: InputStream in = null;
354:
355: try {
356: in = FileManager.loadFile("freemarker.properties",
357: FreemarkerManager.class);
358:
359: if (in != null) {
360: Properties p = new Properties();
361: p.load(in);
362: configuration.setSettings(p);
363: }
364: } catch (IOException e) {
365: LOG
366: .error(
367: "Error while loading freemarker settings from /freemarker.properties",
368: e);
369: } catch (TemplateException e) {
370: LOG
371: .error(
372: "Error while loading freemarker settings from /freemarker.properties",
373: e);
374: } finally {
375: if (in != null) {
376: try {
377: in.close();
378: } catch (IOException io) {
379: LOG.warn("Unable to close input stream", io);
380: }
381: }
382: }
383: }
384:
385: public SimpleHash buildTemplateModel(ValueStack stack,
386: Object action, ServletContext servletContext,
387: HttpServletRequest request, HttpServletResponse response,
388: ObjectWrapper wrapper) {
389: ScopesHashModel model = buildScopesHashModel(servletContext,
390: request, response, wrapper, stack);
391: populateContext(model, stack, action, request, response);
392: for (String prefix : tagLibraries.keySet()) {
393: model.put(prefix, tagLibraries.get(prefix)
394: .getFreemarkerModels(stack, request, response));
395: }
396: return model;
397: }
398: }
|