View Javadoc

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