001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.views.freemarker;
006:
007: import com.opensymphony.util.FileManager;
008: import com.opensymphony.webwork.config.Configuration;
009: import com.opensymphony.webwork.views.JspSupportServlet;
010: import com.opensymphony.webwork.views.freemarker.tags.WebWorkModels;
011: import com.opensymphony.webwork.views.sitemesh.SitemeshModel;
012: import com.opensymphony.webwork.views.util.ContextUtil;
013: import com.opensymphony.webwork.WebWorkConstants;
014: import com.opensymphony.xwork.ObjectFactory;
015: import com.opensymphony.xwork.util.OgnlValueStack;
016: import freemarker.cache.FileTemplateLoader;
017: import freemarker.cache.MultiTemplateLoader;
018: import freemarker.cache.TemplateLoader;
019: import freemarker.cache.WebappTemplateLoader;
020: import freemarker.ext.beans.BeansWrapper;
021: import freemarker.ext.jsp.TaglibFactory;
022: import freemarker.ext.servlet.HttpRequestHashModel;
023: import freemarker.ext.servlet.HttpRequestParametersHashModel;
024: import freemarker.ext.servlet.HttpSessionHashModel;
025: import freemarker.ext.servlet.ServletContextHashModel;
026: import freemarker.template.*;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import javax.servlet.GenericServlet;
031: import javax.servlet.ServletContext;
032: import javax.servlet.http.HttpServletRequest;
033: import javax.servlet.http.HttpServletResponse;
034: import javax.servlet.http.HttpSession;
035: import java.io.File;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.util.Map;
039: import java.util.Properties;
040:
041: /**
042: * Static Configuration Manager for the FreemarkerResult's configuration
043: * <p/>
044: *
045: * Possible extension points are :-
046: * <ul>
047: * <li>createConfiguration method</li>
048: * <li>loadSettings method</li>
049: * <li>getTemplateLoader method</li>
050: * <li>populateContext method</li>
051: * </ul>
052: *
053: * <p/>
054: * <b> createConfiguration method </b><br/>
055: * Create a freemarker Configuration.
056: * <p/>
057: *
058: * <b> loadSettings method </b><br/>
059: * Load freemarker settings, default to freemarker.properties (if found in classpath)
060: * <p/>
061: *
062: * <b> getTemplateLoader method</b><br/>
063: * create a freemarker TemplateLoader that loads freemarker template in the following order :-
064: * <ol>
065: * <li>path defined in ServletContext init parameter named 'templatePath' or 'TemplatePath' (must be an absolute path)</li>
066: * <li>webapp classpath</li>
067: * <li>webwork's static folder (under [WEBWORK_SOURCE]/com/opensymphony/webwork/static/</li>
068: * </ol>
069: * <p/>
070: *
071: * <b> populateContext method</b><br/>
072: * populate the created model.
073: *
074: *
075: * @author CameronBraid
076: * @author tm_jee
077: * @version $Date: 2007-03-10 12:42:13 +0100 (Sat, 10 Mar 2007) $ $Id: FreemarkerManager.java 2859 2007-03-10 11:42:13Z tm_jee $
078: */
079: public class FreemarkerManager {
080:
081: private static final Log log = LogFactory
082: .getLog(FreemarkerManager.class);
083: public static final String CONFIG_SERVLET_CONTEXT_KEY = "freemarker.Configuration";
084: public static final String KEY_EXCEPTION = "exception";
085:
086: // coppied from freemarker servlet - since they are private
087: private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application";
088: private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs";
089: private static final String ATTR_SESSION_MODEL = ".freemarker.Session";
090: private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
091: private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters";
092:
093: // coppied from freemarker servlet - so that there is no dependency on it
094: public static final String KEY_APPLICATION = "Application";
095: public static final String KEY_REQUEST_MODEL = "Request";
096: public static final String KEY_SESSION_MODEL = "Session";
097: public static final String KEY_JSP_TAGLIBS = "JspTaglibs";
098: public static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters";
099: private static FreemarkerManager instance = null;
100:
101: /**
102: * To allow for custom configuration of freemarker, sublcass this class "ConfigManager" and
103: * set the webwork configuration property
104: * <b>webwork.freemarker.configmanager.classname</b> to the fully qualified classname.
105: * <p/>
106: * This allows you to override the protected methods in the ConfigMangaer
107: * to programatically create your own Configuration instance
108: */
109: public final static synchronized FreemarkerManager getInstance() {
110: if (instance == null) {
111: String classname = FreemarkerManager.class.getName();
112:
113: if (Configuration
114: .isSet(WebWorkConstants.WEBWORK_FREEMARKER_MANAGER_CLASSNAME)) {
115: classname = Configuration
116: .getString(
117: WebWorkConstants.WEBWORK_FREEMARKER_MANAGER_CLASSNAME)
118: .trim();
119: }
120:
121: try {
122: log.info("Instantiating Freemarker ConfigManager!, "
123: + classname);
124: // singleton instances shouldn't be built accessing request or session-specific context data
125: instance = (FreemarkerManager) ObjectFactory
126: .getObjectFactory().buildBean(classname, null);
127: } catch (Exception e) {
128: log
129: .fatal(
130: "Fatal exception occurred while trying to instantiate a Freemarker ConfigManager instance, "
131: + classname, e);
132: }
133: }
134:
135: // if the instance creation failed, make sure there is a default instance
136: if (instance == null) {
137: instance = new FreemarkerManager();
138: }
139:
140: return instance;
141: }
142:
143: public final synchronized freemarker.template.Configuration getConfiguration(
144: ServletContext servletContext) throws TemplateException {
145: freemarker.template.Configuration config = (freemarker.template.Configuration) servletContext
146: .getAttribute(CONFIG_SERVLET_CONTEXT_KEY);
147:
148: if (config == null) {
149: config = createConfiguration(servletContext);
150:
151: // store this configuration in the servlet context
152: servletContext.setAttribute(CONFIG_SERVLET_CONTEXT_KEY,
153: config);
154: }
155:
156: config.setWhitespaceStripping(true);
157:
158: return config;
159: }
160:
161: protected ScopesHashModel buildScopesHashModel(
162: ServletContext servletContext, HttpServletRequest request,
163: HttpServletResponse response, ObjectWrapper wrapper,
164: OgnlValueStack stack) {
165: ScopesHashModel model = new ScopesHashModel(wrapper,
166: 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
172: .getAttribute(ATTR_APPLICATION_MODEL);
173:
174: if (servletContextModel == null) {
175:
176: GenericServlet servlet = JspSupportServlet.jspSupportServlet;
177: // TODO if the jsp support servlet isn't load-on-startup then it won't exist
178: // if it hasn't been accessed, and a JSP page is accessed
179: if (servlet != null) {
180: servletContextModel = new ServletContextHashModel(
181: servlet, wrapper);
182: servletContext.setAttribute(ATTR_APPLICATION_MODEL,
183: servletContextModel);
184: TaglibFactory taglibs = new TaglibFactory(
185: servletContext);
186: servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL,
187: taglibs);
188: }
189:
190: }
191:
192: model.put(KEY_APPLICATION, servletContextModel);
193: model.put(KEY_JSP_TAGLIBS, (TemplateModel) servletContext
194: .getAttribute(ATTR_JSP_TAGLIBS_MODEL));
195: }
196:
197: // Create hash model wrapper for session
198: TemplateHashModel sessionModel;
199:
200: HttpSession session = request.getSession(false);
201: if (session != null) {
202: model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(
203: session, wrapper));
204: } else {
205: // no session means no attributes ???
206: // model.put(KEY_SESSION_MODEL, new SimpleHash());
207: }
208:
209: // Create hash model wrapper for the request
210: HttpRequestHashModel requestModel = (HttpRequestHashModel) request
211: .getAttribute(ATTR_REQUEST_MODEL);
212:
213: if ((requestModel == null)
214: || (requestModel.getRequest() != request)) {
215: requestModel = new HttpRequestHashModel(request, response,
216: wrapper);
217: request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
218: }
219:
220: model.put(KEY_REQUEST_MODEL, requestModel);
221:
222: // Create hash model wrapper for request parameters
223: HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel) request
224: .getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
225: if (reqParametersModel == null
226: || requestModel.getRequest() != request) {
227: reqParametersModel = new HttpRequestParametersHashModel(
228: request);
229: request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL,
230: reqParametersModel);
231: }
232: model.put(KEY_REQUEST_PARAMETER_MODEL, reqParametersModel);
233:
234: return model;
235: }
236:
237: public void populateContext(ScopesHashModel model,
238: OgnlValueStack stack, Object action,
239: HttpServletRequest request, HttpServletResponse response) {
240: // put the same objects into the context that the velocity result uses
241: Map standard = ContextUtil.getStandardContext(stack, request,
242: response);
243: model.putAll(standard);
244:
245: // support for JSP exception pages, exposing the servlet or JSP exception
246: Throwable exception = (Throwable) request
247: .getAttribute("javax.servlet.error.exception");
248:
249: if (exception == null) {
250: exception = (Throwable) request
251: .getAttribute("javax.servlet.error.JspException");
252: }
253:
254: if (exception != null) {
255: model.put(KEY_EXCEPTION, exception);
256: }
257: }
258:
259: protected BeansWrapper getObjectWrapper() {
260: return new WebWorkBeanWrapper();
261: }
262:
263: /**
264: * The default template loader is a MultiTemplateLoader which includes
265: * a ClassTemplateLoader and a WebappTemplateLoader (and a FileTemplateLoader depending on
266: * the init-parameter 'TemplatePath').
267: * <p/>
268: * The ClassTemplateLoader will resolve fully qualified template includes
269: * that begin with a slash. for example /com/company/template/common.ftl
270: * <p/>
271: * The WebappTemplateLoader attempts to resolve templates relative to the web root folder
272: */
273: protected TemplateLoader getTemplateLoader(
274: ServletContext servletContext) {
275: // construct a FileTemplateLoader for the init-param 'TemplatePath'
276: FileTemplateLoader templatePathLoader = null;
277:
278: String templatePath = servletContext
279: .getInitParameter("TemplatePath");
280: if (templatePath == null) {
281: templatePath = servletContext
282: .getInitParameter("templatePath");
283: }
284:
285: if (templatePath != null) {
286: try {
287: templatePathLoader = new FileTemplateLoader(new File(
288: templatePath));
289: } catch (IOException e) {
290: log.error("Invalid template path specified: "
291: + e.getMessage(), e);
292: }
293: }
294:
295: // presume that most apps will require the class and webapp template loader
296: // if people wish to
297: return templatePathLoader != null ? new MultiTemplateLoader(
298: new TemplateLoader[] { templatePathLoader,
299: new WebappTemplateLoader(servletContext),
300: new WebWorkClassTemplateLoader() })
301: : new MultiTemplateLoader(new TemplateLoader[] {
302: new WebappTemplateLoader(servletContext),
303: new WebWorkClassTemplateLoader() });
304: }
305:
306: /**
307: * Create the instance of the freemarker Configuration object.
308: * <p/>
309: * this implementation
310: * <ul>
311: * <li>obtains the default configuration from Configuration.getDefaultConfiguration()
312: * <li>sets up template loading from a ClassTemplateLoader and a WebappTemplateLoader
313: * <li>sets up the object wrapper to be the BeansWrapper
314: * <li>loads settings from the classpath file /freemarker.properties
315: * </ul>
316: *
317: * @param servletContext
318: */
319: protected freemarker.template.Configuration createConfiguration(
320: ServletContext servletContext) throws TemplateException {
321: freemarker.template.Configuration configuration = new freemarker.template.Configuration();
322:
323: configuration
324: .setTemplateLoader(getTemplateLoader(servletContext));
325:
326: configuration
327: .setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
328:
329: configuration.setObjectWrapper(getObjectWrapper());
330:
331: if (Configuration.isSet(WebWorkConstants.WEBWORK_I18N_ENCODING)) {
332: configuration.setDefaultEncoding(Configuration
333: .getString(WebWorkConstants.WEBWORK_I18N_ENCODING));
334: }
335:
336: loadSettings(servletContext, configuration);
337:
338: return configuration;
339: }
340:
341: /**
342: * Load the settings from the /freemarker.properties file on the classpath
343: *
344: * @see freemarker.template.Configuration#setSettings for the definition of valid settings
345: */
346: protected void loadSettings(ServletContext servletContext,
347: freemarker.template.Configuration configuration) {
348: try {
349: InputStream in = FileManager.loadFile(
350: "freemarker.properties", FreemarkerManager.class);
351:
352: if (in != null) {
353: Properties p = new Properties();
354: p.load(in);
355: configuration.setSettings(p);
356: }
357: } catch (IOException e) {
358: log
359: .error(
360: "Error while loading freemarker settings from /freemarker.properties",
361: e);
362: } catch (TemplateException e) {
363: log
364: .error(
365: "Error while loading freemarker settings from /freemarker.properties",
366: e);
367: }
368: }
369:
370: public SimpleHash buildTemplateModel(OgnlValueStack stack,
371: Object action, ServletContext servletContext,
372: HttpServletRequest request, HttpServletResponse response,
373: ObjectWrapper wrapper) {
374: ScopesHashModel model = buildScopesHashModel(servletContext,
375: request, response, wrapper, stack);
376: populateContext(model, stack, action, request, response);
377: model.put("ww", new WebWorkModels(stack, request, response));
378: if (Configuration
379: .isSet(WebWorkConstants.WEBWORK_FREEMARKER_SITEMESH_APPLY_DECORATOR_TRANSFORM)) {
380: if ("true"
381: .equals(Configuration
382: .get(WebWorkConstants.WEBWORK_FREEMARKER_SITEMESH_APPLY_DECORATOR_TRANSFORM))) {
383: model.put("sitemesh", new SitemeshModel(stack, request,
384: response));
385: }
386: }
387: return model;
388: }
389: }
|