| /* |
| * $Id$ |
| * |
| * Copyright 2006 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.struts2.views.freemarker; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import javax.servlet.GenericServlet; |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.http.HttpSession; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.struts2.StrutsConstants; |
| import org.apache.struts2.config.Settings; |
| import org.apache.struts2.views.JspSupportServlet; |
| import org.apache.struts2.views.freemarker.tags.StrutsModels; |
| import org.apache.struts2.views.util.ContextUtil; |
| |
| import com.opensymphony.xwork2.util.FileManager; |
| import com.opensymphony.xwork2.ObjectFactory; |
| import com.opensymphony.xwork2.util.OgnlValueStack; |
| |
| import freemarker.cache.FileTemplateLoader; |
| import freemarker.cache.MultiTemplateLoader; |
| import freemarker.cache.TemplateLoader; |
| import freemarker.cache.WebappTemplateLoader; |
| import freemarker.ext.beans.BeansWrapper; |
| import freemarker.ext.jsp.TaglibFactory; |
| import freemarker.ext.servlet.HttpRequestHashModel; |
| import freemarker.ext.servlet.HttpRequestParametersHashModel; |
| import freemarker.ext.servlet.HttpSessionHashModel; |
| import freemarker.ext.servlet.ServletContextHashModel; |
| import freemarker.template.ObjectWrapper; |
| import freemarker.template.SimpleHash; |
| import freemarker.template.TemplateException; |
| import freemarker.template.TemplateExceptionHandler; |
| import freemarker.template.TemplateModel; |
| |
| |
| /** |
| * Static Configuration Manager for the FreemarkerResult's configuration |
| * |
| * <p/> |
| * |
| * Possible extension points are :- |
| * <ul> |
| * <li>createConfiguration method</li> |
| * <li>loadSettings method</li> |
| * <li>getTemplateLoader method</li> |
| * <li>populateContext method</li> |
| * </ul> |
| * |
| * <p/> |
| * <b> createConfiguration method </b><br/> |
| * Create a freemarker Configuration. |
| * <p/> |
| * |
| * <b> loadSettings method </b><br/> |
| * Load freemarker settings, default to freemarker.properties (if found in classpath) |
| * <p/> |
| * |
| * <b> getTemplateLoader method</b><br/> |
| * create a freemarker TemplateLoader that loads freemarker template in the following order :- |
| * <ol> |
| * <li>path defined in ServletContext init parameter named 'templatePath' or 'TemplatePath' (must be an absolute path)</li> |
| * <li>webapp classpath</li> |
| * <li>struts's static folder (under [STRUT2_SOURCE]/org/apache/struts2/static/</li> |
| * </ol> |
| * <p/> |
| * |
| * <b> populateContext method</b><br/> |
| * populate the created model. |
| * |
| */ |
| public class FreemarkerManager { |
| |
| private static final Log log = LogFactory.getLog(FreemarkerManager.class); |
| public static final String CONFIG_SERVLET_CONTEXT_KEY = "freemarker.Configuration"; |
| public static final String KEY_EXCEPTION = "exception"; |
| |
| // coppied from freemarker servlet - since they are private |
| private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application"; |
| private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs"; |
| private static final String ATTR_REQUEST_MODEL = ".freemarker.Request"; |
| private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters"; |
| |
| // coppied from freemarker servlet - so that there is no dependency on it |
| public static final String KEY_APPLICATION = "Application"; |
| public static final String KEY_REQUEST_MODEL = "Request"; |
| public static final String KEY_SESSION_MODEL = "Session"; |
| public static final String KEY_JSP_TAGLIBS = "JspTaglibs"; |
| public static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters"; |
| private static FreemarkerManager instance = null; |
| |
| |
| /** |
| * To allow for custom configuration of freemarker, sublcass this class "ConfigManager" and |
| * set the Struts configuration property |
| * <b>struts.freemarker.configmanager.classname</b> to the fully qualified classname. |
| * <p/> |
| * This allows you to override the protected methods in the ConfigMangaer |
| * to programatically create your own Configuration instance |
| */ |
| public final static synchronized FreemarkerManager getInstance() { |
| if (instance == null) { |
| String classname = FreemarkerManager.class.getName(); |
| |
| if (Settings.isSet(StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME)) { |
| classname = Settings.get(StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME).trim(); |
| } |
| |
| try { |
| log.info("Instantiating Freemarker ConfigManager!, " + classname); |
| // singleton instances shouldn't be built accessing request or session-specific context data |
| instance = (FreemarkerManager) ObjectFactory.getObjectFactory().buildBean(classname, null); |
| } catch (Exception e) { |
| log.fatal("Fatal exception occurred while trying to instantiate a Freemarker ConfigManager instance, " + classname, e); |
| } |
| } |
| |
| // if the instance creation failed, make sure there is a default instance |
| if (instance == null) { |
| instance = new FreemarkerManager(); |
| } |
| |
| return instance; |
| } |
| |
| public final synchronized freemarker.template.Configuration getConfiguration(ServletContext servletContext) throws TemplateException { |
| freemarker.template.Configuration config = (freemarker.template.Configuration) servletContext.getAttribute(CONFIG_SERVLET_CONTEXT_KEY); |
| |
| if (config == null) { |
| config = createConfiguration(servletContext); |
| |
| // store this configuration in the servlet context |
| servletContext.setAttribute(CONFIG_SERVLET_CONTEXT_KEY, config); |
| } |
| |
| config.setWhitespaceStripping(true); |
| |
| return config; |
| } |
| |
| protected ScopesHashModel buildScopesHashModel(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper, OgnlValueStack stack) { |
| ScopesHashModel model = new ScopesHashModel(wrapper, servletContext, request, stack); |
| |
| // Create hash model wrapper for servlet context (the application) |
| // only need one thread to do this once, per servlet context |
| synchronized (servletContext) { |
| ServletContextHashModel servletContextModel = (ServletContextHashModel) servletContext.getAttribute(ATTR_APPLICATION_MODEL); |
| |
| if (servletContextModel == null) { |
| |
| GenericServlet servlet = JspSupportServlet.jspSupportServlet; |
| // TODO if the jsp support servlet isn't load-on-startup then it won't exist |
| // if it hasn't been accessed, and a JSP page is accessed |
| if (servlet != null) { |
| servletContextModel = new ServletContextHashModel(servlet, wrapper); |
| servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel); |
| TaglibFactory taglibs = new TaglibFactory(servletContext); |
| servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs); |
| } |
| |
| } |
| |
| model.put(KEY_APPLICATION, servletContextModel); |
| model.put(KEY_JSP_TAGLIBS, (TemplateModel) servletContext.getAttribute(ATTR_JSP_TAGLIBS_MODEL)); |
| } |
| |
| // Create hash model wrapper for session |
| HttpSession session = request.getSession(false); |
| if (session != null) { |
| model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper)); |
| } else { |
| // no session means no attributes ??? |
| // model.put(KEY_SESSION_MODEL, new SimpleHash()); |
| } |
| |
| // Create hash model wrapper for the request attributes |
| HttpRequestHashModel requestModel = (HttpRequestHashModel) request.getAttribute(ATTR_REQUEST_MODEL); |
| |
| if ((requestModel == null) || (requestModel.getRequest() != request)) { |
| requestModel = new HttpRequestHashModel(request, response, wrapper); |
| request.setAttribute(ATTR_REQUEST_MODEL, requestModel); |
| } |
| |
| model.put(KEY_REQUEST_MODEL, requestModel); |
| |
| |
| // Create hash model wrapper for request parameters |
| HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel) request.getAttribute(ATTR_REQUEST_PARAMETERS_MODEL); |
| if (reqParametersModel == null || requestModel.getRequest() != request) { |
| reqParametersModel = new HttpRequestParametersHashModel(request); |
| request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL, reqParametersModel); |
| } |
| model.put(KEY_REQUEST_PARAMETER_MODEL, reqParametersModel); |
| |
| return model; |
| } |
| |
| protected void populateContext(ScopesHashModel model, OgnlValueStack stack, Object action, HttpServletRequest request, HttpServletResponse response) { |
| // put the same objects into the context that the velocity result uses |
| Map standard = ContextUtil.getStandardContext(stack, request, response); |
| model.putAll(standard); |
| |
| // support for JSP exception pages, exposing the servlet or JSP exception |
| Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception"); |
| |
| if (exception == null) { |
| exception = (Throwable) request.getAttribute("javax.servlet.error.JspException"); |
| } |
| |
| if (exception != null) { |
| model.put(KEY_EXCEPTION, exception); |
| } |
| } |
| |
| protected BeansWrapper getObjectWrapper() { |
| return new StrutsBeanWrapper(); |
| } |
| |
| /** |
| * The default template loader is a MultiTemplateLoader which includes |
| * a ClassTemplateLoader and a WebappTemplateLoader (and a FileTemplateLoader depending on |
| * the init-parameter 'TemplatePath'). |
| * <p/> |
| * The ClassTemplateLoader will resolve fully qualified template includes |
| * that begin with a slash. for example /com/company/template/common.ftl |
| * <p/> |
| * The WebappTemplateLoader attempts to resolve templates relative to the web root folder |
| */ |
| protected TemplateLoader getTemplateLoader(ServletContext servletContext) { |
| // construct a FileTemplateLoader for the init-param 'TemplatePath' |
| FileTemplateLoader templatePathLoader = null; |
| |
| String templatePath = servletContext.getInitParameter("TemplatePath"); |
| if (templatePath == null) { |
| templatePath = servletContext.getInitParameter("templatePath"); |
| } |
| |
| if (templatePath != null) { |
| try { |
| templatePathLoader = new FileTemplateLoader(new File(templatePath)); |
| } catch (IOException e) { |
| log.error("Invalid template path specified: " + e.getMessage(), e); |
| } |
| } |
| |
| // presume that most apps will require the class and webapp template loader |
| // if people wish to |
| return templatePathLoader != null ? |
| new MultiTemplateLoader(new TemplateLoader[]{ |
| templatePathLoader, |
| new WebappTemplateLoader(servletContext), |
| new StrutsClassTemplateLoader() |
| }) |
| : new MultiTemplateLoader(new TemplateLoader[]{ |
| new WebappTemplateLoader(servletContext), |
| new StrutsClassTemplateLoader() |
| }); |
| } |
| |
| /** |
| * Create the instance of the freemarker Configuration object. |
| * <p/> |
| * this implementation |
| * <ul> |
| * <li>obtains the default configuration from Configuration.getDefaultConfiguration() |
| * <li>sets up template loading from a ClassTemplateLoader and a WebappTemplateLoader |
| * <li>sets up the object wrapper to be the BeansWrapper |
| * <li>loads settings from the classpath file /freemarker.properties |
| * </ul> |
| * |
| * @param servletContext |
| */ |
| protected freemarker.template.Configuration createConfiguration(ServletContext servletContext) throws TemplateException { |
| freemarker.template.Configuration configuration = new freemarker.template.Configuration(); |
| |
| configuration.setTemplateLoader(getTemplateLoader(servletContext)); |
| |
| configuration.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); |
| |
| configuration.setObjectWrapper(getObjectWrapper()); |
| |
| if (Settings.isSet(StrutsConstants.STRUTS_I18N_ENCODING)) { |
| configuration.setDefaultEncoding(Settings.get(StrutsConstants.STRUTS_I18N_ENCODING)); |
| } |
| |
| loadSettings(servletContext, configuration); |
| |
| return configuration; |
| } |
| |
| /** |
| * Load the settings from the /freemarker.properties file on the classpath |
| * |
| * @see freemarker.template.Configuration#setSettings for the definition of valid settings |
| */ |
| protected void loadSettings(ServletContext servletContext, freemarker.template.Configuration configuration) { |
| try { |
| InputStream in = FileManager.loadFile("freemarker.properties", FreemarkerManager.class); |
| |
| if (in != null) { |
| Properties p = new Properties(); |
| p.load(in); |
| configuration.setSettings(p); |
| } |
| } catch (IOException e) { |
| log.error("Error while loading freemarker settings from /freemarker.properties", e); |
| } catch (TemplateException e) { |
| log.error("Error while loading freemarker settings from /freemarker.properties", e); |
| } |
| } |
| |
| public SimpleHash buildTemplateModel(OgnlValueStack stack, Object action, ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper) { |
| ScopesHashModel model = buildScopesHashModel(servletContext, request, response, wrapper, stack); |
| populateContext(model, stack, action, request, response); |
| model.put("s", new StrutsModels(stack, request, response)); |
| return model; |
| } |
| } |