View Javadoc

    1   /*
    2    * $Id: VelocityManager.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.velocity;
    22
    23  import java.io.File;
    24  import java.io.FileInputStream;
    25  import java.io.IOException;
    26  import java.io.InputStream;
    27  import java.util.ArrayList;
    28  import java.util.Collections;
    29  import java.util.HashMap;
    30  import java.util.Iterator;
    31  import java.util.List;
    32  import java.util.Map;
    33  import java.util.Properties;
    34  import java.util.Set;
    35  import java.util.StringTokenizer;
    36
    37  import javax.servlet.ServletContext;
    38  import javax.servlet.http.HttpServletRequest;
    39  import javax.servlet.http.HttpServletResponse;
    40
    41  import org.apache.commons.logging.Log;
    42  import org.apache.commons.logging.LogFactory;
    43  import org.apache.struts2.ServletActionContext;
    44  import org.apache.struts2.StrutsConstants;
    45  import org.apache.struts2.StrutsException;
    46  import org.apache.struts2.util.VelocityStrutsUtil;
    47  import org.apache.struts2.views.TagLibrary;
    48  import org.apache.struts2.views.jsp.ui.OgnlTool;
    49  import org.apache.struts2.views.util.ContextUtil;
    50  import org.apache.velocity.VelocityContext;
    51  import org.apache.velocity.app.Velocity;
    52  import org.apache.velocity.app.VelocityEngine;
    53  import org.apache.velocity.context.Context;
    54  import org.apache.velocity.tools.view.ToolboxManager;
    55  import org.apache.velocity.tools.view.context.ChainedContext;
    56  import org.apache.velocity.tools.view.servlet.ServletToolboxManager;
    57
    58  import com.opensymphony.xwork2.ObjectFactory;
    59  import com.opensymphony.xwork2.inject.Container;
    60  import com.opensymphony.xwork2.inject.Inject;
    61  import com.opensymphony.xwork2.util.ValueStack;
    62
    63
    64  /***
    65   * Manages the environment for Velocity result types
    66   *
    67   */
    68  public class VelocityManager {
    69      private static final Log log = LogFactory.getLog(VelocityManager.class);
    70      public static final String STRUTS = "struts";
    71      private ObjectFactory objectFactory;
    72
    73      /***
    74       * the parent JSP tag
    75       */
    76      public static final String PARENT = "parent";
    77
    78      /***
    79       * the current JSP tag
    80       */
    81      public static final String TAG = "tag";
    82
    83      private VelocityEngine velocityEngine;
    84
    85      /***
    86       * A reference to the toolbox manager.
    87       */
    88      protected ToolboxManager toolboxManager = null;
    89      private String toolBoxLocation;
    90
    91
    92      /***
    93       * Names of contexts that will be chained on every request
    94       */
    95      private String[] chainedContextNames;
    96
    97      private Properties velocityProperties;
    98
    99      private String customConfigFile;
    100
    101     private List<TagLibrary> tagLibraries;
    102
    103     public VelocityManager() {
    104     }
    105
    106     @Inject
    107     public void setObjectFactory(ObjectFactory fac) {
    108         this.objectFactory = fac;
    109     }
    110
    111     @Inject
    112     public void setContainer(Container container) {
    113         List<TagLibrary> list = new ArrayList<TagLibrary>();
    114         Set<String> prefixes = container.getInstanceNames(TagLibrary.class);
    115         for (String prefix : prefixes) {
    116             list.add(container.getInstance(TagLibrary.class, prefix));
    117         }
    118         this.tagLibraries = Collections.unmodifiableList(list);
    119     }
    120
    121     /***
    122      * retrieve an instance to the current VelocityManager
    123      */
    124     /*public synchronized static VelocityManager getInstance() {
    125         if (instance == null) {
    126             String classname = VelocityManager.class.getName();
    127
    128             if (Settings.isSet(StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME)) {
    129                 classname = Settings.get(StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME).trim();
    130             }
    131
    132             if (!classname.equals(VelocityManager.class.getName())) {
    133                 try {
    134                     log.info("Instantiating VelocityManager!, " + classname);
    135                     // singleton instances shouldn't be built accessing request or session-specific context data
    136                     instance = (VelocityManager) ObjectFactory.getObjectFactory().buildBean(classname, null);
    137                 } catch (Exception e) {
    138                     log.fatal("Fatal exception occurred while trying to instantiate a VelocityManager instance, " + classname, e);
    139                     instance = new VelocityManager();
    140                 }
    141             } else {
    142                 instance = new VelocityManager();
    143             }
    144         }
    145
    146         return instance;
    147     }
    148     */
    149
    150     /***
    151      * @return a reference to the VelocityEngine used by <b>all</b> struts velocity thingies with the exception of
    152      *         directly accessed *.vm pages
    153      */
    154     public VelocityEngine getVelocityEngine() {
    155         return velocityEngine;
    156     }
    157
    158     /***
    159      * This method is responsible for creating the standard VelocityContext used by all WW2 velocity views.  The
    160      * following context parameters are defined:
    161      * <p/>
    162      * <ul>
    163      * <li><strong>request</strong> - the current HttpServletRequest</li>
    164      * <li><strong>response</strong> - the current HttpServletResponse</li>
    165      * <li><strong>stack</strong> - the current {@link ValueStack}</li>
    166      * <li><strong>ognl</strong> - an {@link OgnlTool}</li>
    167      * <li><strong>struts</strong> - an instance of {@link org.apache.struts2.util.StrutsUtil}</li>
    168      * <li><strong>action</strong> - the current Struts action</li>
    169      * </ul>
    170      *
    171      * @return a new StrutsVelocityContext
    172      */
    173     public Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
    174         VelocityContext[] chainedContexts = prepareChainedContexts(req, res, stack.getContext());
    175         StrutsVelocityContext context = new StrutsVelocityContext(chainedContexts, stack);
    176         Map standardMap = ContextUtil.getStandardContext(stack, req, res);
    177         for (Iterator iterator = standardMap.entrySet().iterator(); iterator.hasNext();) {
    178             Map.Entry entry = (Map.Entry) iterator.next();
    179             context.put((String) entry.getKey(), entry.getValue());
    180         }
    181         context.put(STRUTS, new VelocityStrutsUtil(velocityEngine, context, stack, req, res));
    182
    183
    184         ServletContext ctx = null;
    185         try {
    186             ctx = ServletActionContext.getServletContext();
    187         } catch (NullPointerException npe) {
    188             // in case this was used outside the lifecycle of struts servlet
    189             log.debug("internal toolbox context ignored");
    190         }
    191
    192         if (toolboxManager != null && ctx != null) {
    193             ChainedContext chained = new ChainedContext(context, req, res, ctx);
    194             chained.setToolbox(toolboxManager.getToolboxContext(chained));
    195             return chained;
    196         } else {
    197             return context;
    198         }
    199
    200     }
    201
    202     /***
    203      * constructs contexts for chaining on this request.  This method does not
    204      * perform any initialization of the contexts.  All that must be done in the
    205      * context itself.
    206      *
    207      * @param servletRequest
    208      * @param servletResponse
    209      * @param extraContext
    210      * @return an VelocityContext[] of contexts to chain
    211      */
    212     protected VelocityContext[] prepareChainedContexts(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Map extraContext) {
    213         if (this.chainedContextNames == null) {
    214             return null;
    215         }
    216         List contextList = new ArrayList();
    217         for (int i = 0; i < chainedContextNames.length; i++) {
    218             String className = chainedContextNames[i];
    219             try {
    220                 VelocityContext velocityContext = (VelocityContext) objectFactory.buildBean(className, null);
    221                 contextList.add(velocityContext);
    222             } catch (Exception e) {
    223                 log.warn("Warning.  " + e.getClass().getName() + " caught while attempting to instantiate a chained VelocityContext, " + className + " -- skipping");
    224             }
    225         }
    226         if (contextList.size() > 0) {
    227             VelocityContext[] extraContexts = new VelocityContext[contextList.size()];
    228             contextList.toArray(extraContexts);
    229             return extraContexts;
    230         } else {
    231             return null;
    232         }
    233     }
    234
    235     /***
    236      * initializes the VelocityManager.  this should be called during the initialization process, say by
    237      * ServletDispatcher.  this may be called multiple times safely although calls beyond the first won't do anything
    238      *
    239      * @param context the current servlet context
    240      */
    241     public synchronized void init(ServletContext context) {
    242         if (velocityEngine == null) {
    243             velocityEngine = newVelocityEngine(context);
    244         }
    245         this.initToolbox(context);
    246     }
    247
    248     /***
    249      * load optional velocity properties using the following loading strategy
    250      * <ul>
    251      * <li>relative to the servlet context path</li>
    252      * <li>relative to the WEB-INF directory</li>
    253      * <li>on the classpath</li>
    254      * </ul>
    255      *
    256      * @param context the current ServletContext.  may <b>not</b> be null
    257      * @return the optional properties if struts.velocity.configfile was specified, an empty Properties file otherwise
    258      */
    259     public Properties loadConfiguration(ServletContext context) {
    260         if (context == null) {
    261             String gripe = "Error attempting to create a loadConfiguration from a null ServletContext!";
    262             log.error(gripe);
    263             throw new IllegalArgumentException(gripe);
    264         }
    265
    266         Properties properties = new Properties();
    267
    268         // now apply our systemic defaults, then allow user to override
    269         applyDefaultConfiguration(context, properties);
    270
    271
    272         String defaultUserDirective = properties.getProperty("userdirective");
    273
    274         /***
    275          * if the user has specified an external velocity configuration file, we'll want to search for it in the
    276          * following order
    277          *
    278          * 1. relative to the context path
    279          * 2. relative to /WEB-INF
    280          * 3. in the class path
    281          */
    282         String configfile;
    283
    284         if (customConfigFile != null) {
    285             configfile = customConfigFile;
    286         } else {
    287             configfile = "velocity.properties";
    288         }
    289
    290         configfile = configfile.trim();
    291
    292         InputStream in = null;
    293         String resourceLocation = null;
    294
    295         try {
    296             if (context.getRealPath(configfile) != null) {
    297                 // 1. relative to context path, i.e. /velocity.properties
    298                 String filename = context.getRealPath(configfile);
    299
    300                 if (filename != null) {
    301                     File file = new File(filename);
    302
    303                     if (file.isFile()) {
    304                         resourceLocation = file.getCanonicalPath() + " from file system";
    305                         in = new FileInputStream(file);
    306                     }
    307
    308                     // 2. if nothing was found relative to the context path, search relative to the WEB-INF directory
    309                     if (in == null) {
    310                         file = new File(context.getRealPath("/WEB-INF/" + configfile));
    311
    312                         if (file.isFile()) {
    313                             resourceLocation = file.getCanonicalPath() + " from file system";
    314                             in = new FileInputStream(file);
    315                         }
    316                     }
    317                 }
    318             }
    319
    320             // 3. finally, if there's no physical file, how about something in our classpath
    321             if (in == null) {
    322                 in = VelocityManager.class.getClassLoader().getResourceAsStream(configfile);
    323                 if (in != null) {
    324                     resourceLocation = configfile + " from classloader";
    325                 }
    326             }
    327
    328             // if we've got something, load 'er up
    329             if (in != null) {
    330                 log.info("Initializing velocity using " + resourceLocation);
    331                 properties.load(in);
    332             }
    333         } catch (IOException e) {
    334             log.warn("Unable to load velocity configuration " + resourceLocation, e);
    335         } finally {
    336             if (in != null) {
    337                 try {
    338                     in.close();
    339                 } catch (IOException e) {
    340                 }
    341             }
    342         }
    343
    344         // overide with programmatically set properties
    345         if (this.velocityProperties != null) {
    346             Iterator keys = this.velocityProperties.keySet().iterator();
    347             while (keys.hasNext()) {
    348                 String key = (String) keys.next();
    349                 properties.setProperty(key, this.velocityProperties.getProperty(key));
    350             }
    351         }
    352
    353         String userdirective = properties.getProperty("userdirective");
    354
    355         if ((userdirective == null) || userdirective.trim().equals("")) {
    356             userdirective = defaultUserDirective;
    357         } else {
    358             userdirective = userdirective.trim() + "," + defaultUserDirective;
    359         }
    360
    361         properties.setProperty("userdirective", userdirective);
    362
    363
    364         // for debugging purposes, allows users to dump out the properties that have been configured
    365         if (log.isDebugEnabled()) {
    366             log.debug("Initializing Velocity with the following properties ...");
    367
    368             for (Iterator iter = properties.keySet().iterator();
    369                  iter.hasNext();) {
    370                 String key = (String) iter.next();
    371                 String value = properties.getProperty(key);
    372
    373                 if (log.isDebugEnabled()) {
    374                     log.debug("    '" + key + "' = '" + value + "'");
    375                 }
    376             }
    377         }
    378
    379         return properties;
    380     }
    381
    382     @Inject(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE)
    383     public void setCustomConfigFile(String val) {
    384         this.customConfigFile = val;
    385     }
    386
    387     @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION)
    388     public void setToolBoxLocation(String toolboxLocation) {
    389         this.toolBoxLocation = toolboxLocation;
    390     }
    391
    392     /***
    393      * allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to the
    394      * the StrutsVelocityContext.  The intent is to allow these contexts to store helper objects that the ui
    395      * developer may want access to.  Examples of reasonable VelocityContexts would be an IoCVelocityContext, a
    396      * SpringReferenceVelocityContext, and a ToolboxVelocityContext
    397      */
    398     @Inject(StrutsConstants.STRUTS_VELOCITY_CONTEXTS)
    399     public void setChainedContexts(String contexts) {
    400         // we expect contexts to be a comma separated list of classnames
    401         StringTokenizer st = new StringTokenizer(contexts, ",");
    402         List contextList = new ArrayList();
    403
    404         while (st.hasMoreTokens()) {
    405             String classname = st.nextToken();
    406             contextList.add(classname);
    407         }
    408         if (contextList.size() > 0) {
    409             String[] chainedContexts = new String[contextList.size()];
    410             contextList.toArray(chainedContexts);
    411             this.chainedContextNames = chainedContexts;
    412         }
    413     }
    414
    415     /***
    416      * Initializes the ServletToolboxManager for this servlet's
    417      * toolbox (if any).
    418      */
    419     protected void initToolbox(ServletContext context) {
    420         /* if we have a toolbox, get a manager for it */
    421         if (toolBoxLocation != null) {
    422             toolboxManager = ServletToolboxManager.getInstance(context, toolBoxLocation);
    423         } else {
    424             Velocity.info("VelocityViewServlet: No toolbox entry in configuration.");
    425         }
    426     }
    427
    428
    429
    430
    431     /***
    432      * <p/>
    433      * Instantiates a new VelocityEngine.
    434      * </p>
    435      * <p/>
    436      * The following is the default Velocity configuration
    437      * </p>
    438      * <pre>
    439      *  resource.loader = file, class
    440      *  file.resource.loader.path = real path of webapp
    441      *  class.resource.loader.description = Velocity Classpath Resource Loader
    442      *  class.resource.loader.class = org.apache.struts2.views.velocity.StrutsResourceLoader
    443      * </pre>
    444      * <p/>
    445      * this default configuration can be overridden by specifying a struts.velocity.configfile property in the
    446      * struts.properties file.  the specified config file will be searched for in the following order:
    447      * </p>
    448      * <ul>
    449      * <li>relative to the servlet context path</li>
    450      * <li>relative to the WEB-INF directory</li>
    451      * <li>on the classpath</li>
    452      * </ul>
    453      *
    454      * @param context the current ServletContext.  may <b>not</b> be null
    455      */
    456     protected VelocityEngine newVelocityEngine(ServletContext context) {
    457         if (context == null) {
    458             String gripe = "Error attempting to create a new VelocityEngine from a null ServletContext!";
    459             log.error(gripe);
    460             throw new IllegalArgumentException(gripe);
    461         }
    462
    463         Properties p = loadConfiguration(context);
    464
    465         VelocityEngine velocityEngine = new VelocityEngine();
    466
    467         //  Set the velocity attribute for the servlet context
    468         //  if this is not set the webapp loader WILL NOT WORK
    469         velocityEngine.setApplicationAttribute(ServletContext.class.getName(),
    470                 context);
    471
    472         try {
    473             velocityEngine.init(p);
    474         } catch (Exception e) {
    475             String gripe = "Unable to instantiate VelocityEngine!";
    476             throw new StrutsException(gripe, e);
    477         }
    478
    479         return velocityEngine;
    480     }
    481
    482     /***
    483      * once we've loaded up the user defined configurations, we will want to apply Struts specification configurations.
    484      * <ul>
    485      * <li>if Velocity.RESOURCE_LOADER has not been defined, then we will use the defaults which is a joined file,
    486      * class loader for unpackaed wars and a straight class loader otherwise</li>
    487      * <li>we need to define the various Struts custom user directives such as #param, #tag, and #bodytag</li>
    488      * </ul>
    489      *
    490      * @param context
    491      * @param p
    492      */
    493     private void applyDefaultConfiguration(ServletContext context, Properties p) {
    494         // ensure that caching isn't overly aggressive
    495
    496         /***
    497          * Load a default resource loader definition if there isn't one present.
    498          * Ben Hall (22/08/2003)
    499          */
    500         if (p.getProperty(Velocity.RESOURCE_LOADER) == null) {
    501             p.setProperty(Velocity.RESOURCE_LOADER, "strutsfile, strutsclass");
    502         }
    503
    504         /***
    505          * If there's a "real" path add it for the strutsfile resource loader.
    506          * If there's no real path and they haven't configured a loader then we change
    507          * resource loader property to just use the strutsclass loader
    508          * Ben Hall (22/08/2003)
    509          */
    510         if (context.getRealPath("") != null) {
    511             p.setProperty("strutsfile.resource.loader.description", "Velocity File Resource Loader");
    512             p.setProperty("strutsfile.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
    513             p.setProperty("strutsfile.resource.loader.path", context.getRealPath(""));
    514             p.setProperty("strutsfile.resource.loader.modificationCheckInterval", "2");
    515             p.setProperty("strutsfile.resource.loader.cache", "true");
    516         } else {
    517             // remove strutsfile from resource loader property
    518             String prop = p.getProperty(Velocity.RESOURCE_LOADER);
    519             if (prop.indexOf("strutsfile,") != -1) {
    520                 prop = replace(prop, "strutsfile,", "");
    521             } else if (prop.indexOf(", strutsfile") != -1) {
    522                 prop = replace(prop, ", strutsfile", "");
    523             } else if (prop.indexOf("strutsfile") != -1) {
    524                 prop = replace(prop, "strutsfile", "");
    525             }
    526
    527             p.setProperty(Velocity.RESOURCE_LOADER, prop);
    528         }
    529
    530         /***
    531          * Refactored the Velocity templates for the Struts taglib into the classpath from the web path.  This will
    532          * enable Struts projects to have access to the templates by simply including the Struts jar file.
    533          * Unfortunately, there does not appear to be a macro for the class loader keywords
    534          * Matt Ho - Mon Mar 17 00:21:46 PST 2003
    535          */
    536         p.setProperty("strutsclass.resource.loader.description", "Velocity Classpath Resource Loader");
    537         p.setProperty("strutsclass.resource.loader.class", "org.apache.struts2.views.velocity.StrutsResourceLoader");
    538         p.setProperty("strutsclass.resource.loader.modificationCheckInterval", "2");
    539         p.setProperty("strutsclass.resource.loader.cache", "true");
    540
    541         // components
    542         StringBuffer sb = new StringBuffer();
    543
    544         for (TagLibrary tagLibrary : tagLibraries) {
    545             List<Class> directives = tagLibrary.getVelocityDirectiveClasses();
    546             for (Class directive : directives) {
    547                 addDirective(sb, directive);
    548             }
    549         }
    550
    551         String directives = sb.toString();
    552
    553         String userdirective = p.getProperty("userdirective");
    554         if ((userdirective == null) || userdirective.trim().equals("")) {
    555             userdirective = directives;
    556         } else {
    557             userdirective = userdirective.trim() + "," + directives;
    558         }
    559
    560         p.setProperty("userdirective", userdirective);
    561     }
    562
    563     private void addDirective(StringBuffer sb, Class clazz) {
    564         sb.append(clazz.getName()).append(",");
    565     }
    566
    567     private static final String replace(String string, String oldString, String newString) {
    568         if (string == null) {
    569             return null;
    570         }
    571         // If the newString is null, just return the string since there's nothing to replace.
    572         if (newString == null) {
    573             return string;
    574         }
    575         int i = 0;
    576         // Make sure that oldString appears at least once before doing any processing.
    577         if ((i = string.indexOf(oldString, i)) >= 0) {
    578             // Use char []'s, as they are more efficient to deal with.
    579             char[] string2 = string.toCharArray();
    580             char[] newString2 = newString.toCharArray();
    581             int oLength = oldString.length();
    582             StringBuffer buf = new StringBuffer(string2.length);
    583             buf.append(string2, 0, i).append(newString2);
    584             i += oLength;
    585             int j = i;
    586             // Replace all remaining instances of oldString with newString.
    587             while ((i = string.indexOf(oldString, i)) > 0) {
    588                 buf.append(string2, j, i - j).append(newString2);
    589                 i += oLength;
    590                 j = i;
    591             }
    592             buf.append(string2, j, string2.length - j);
    593             return buf.toString();
    594         }
    595         return string;
    596     }
    597
    598     /***
    599      * @return the velocityProperties
    600      */
    601     public Properties getVelocityProperties() {
    602         return velocityProperties;
    603     }
    604
    605     /***
    606      * @param velocityProperties the velocityProperties to set
    607      */
    608     public void setVelocityProperties(Properties velocityProperties) {
    609         this.velocityProperties = velocityProperties;
    610     }
    611 }