View Javadoc

1   package org.apache.velocity.tools.view.servlet;
2   
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  
22  import java.io.InputStream;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  
28  import javax.servlet.ServletContext;
29  import javax.servlet.http.HttpSession;
30  
31  import org.apache.commons.digester.RuleSet;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.velocity.tools.view.ToolInfo;
35  import org.apache.velocity.tools.view.XMLToolboxManager;
36  import org.apache.velocity.tools.view.context.ViewContext;
37  import org.apache.velocity.tools.view.ServletUtils;
38  
39  
40  /**
41   * <p>A toolbox manager for the servlet environment.</p>
42   *
43   * <p>A toolbox manager is responsible for automatically filling the Velocity
44   * context with a set of view tools. This class provides the following
45   * features:</p>
46   * <ul>
47   *   <li>configurable through an XML-based configuration file</li>
48   *   <li>assembles a set of view tools (the toolbox) on request</li>
49   *   <li>handles different tool scopes (request, session, application)</li>
50   *   <li>supports any class with a public constructor without parameters
51   *     to be used as a view tool</li>
52   *   <li>supports adding primitive data values to the context(String,Number,Boolean)</li>
53   * </ul>
54   *
55   *
56   * <p><strong>Configuration</strong></p>
57   * <p>The toolbox manager is configured through an XML-based configuration
58   * file. The configuration file is passed to the {@link #load(java.io.InputStream input)}
59   * method. The format is shown in the following example:</p>
60   * <pre>
61   * &lt;?xml version="1.0"?&gt;
62   *
63   * &lt;toolbox&gt;
64   *   &lt;tool&gt;
65   *      &lt;key&gt;link&lt;/key&gt;
66   *      &lt;scope&gt;request&lt;/scope&gt;
67   *      &lt;class&gt;org.apache.velocity.tools.view.tools.LinkTool&lt;/class&gt;
68   *   &lt;/tool&gt;
69   *   &lt;tool&gt;
70   *      &lt;key&gt;date&lt;/key&gt;
71   *      &lt;scope&gt;application&lt;/scope&gt;
72   *      &lt;class&gt;org.apache.velocity.tools.generic.DateTool&lt;/class&gt;
73   *   &lt;/tool&gt;
74   *   &lt;data type="number"&gt;
75   *      &lt;key&gt;luckynumber&lt;/key&gt;
76   *      &lt;value&gt;1.37&lt;/value&gt;
77   *   &lt;/data&gt;
78   *   &lt;data type="string"&gt;
79   *      &lt;key&gt;greeting&lt;/key&gt;
80   *      &lt;value&gt;Hello World!&lt;/value&gt;
81   *   &lt;/data&gt;
82   *   &lt;xhtml&gt;true&lt;/xhtml&gt;
83   * &lt;/toolbox&gt;
84   * </pre>
85   * <p>The recommended location for the configuration file is the WEB-INF directory of the
86   * web application.</p>
87   *
88   * @author <a href="mailto:sidler@teamup.com">Gabriel Sidler</a>
89   * @author Nathan Bubna
90   * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
91   * @author <a href="mailto:henning@schmiedehausen.org">Henning P. Schmiedehausen</a>
92   * @version $Id: ServletToolboxManager.java 651470 2008-04-25 00:47:52Z nbubna $
93   * @deprecated Use {@link org.apache.velocity.tools.config.XmlFactoryConfiguration}
94   */
95  @Deprecated
96  public class ServletToolboxManager extends XMLToolboxManager
97  {
98  
99      // --------------------------------------------------- Properties ---------
100 
101     public static final String SESSION_TOOLS_KEY =
102         ServletToolboxManager.class.getName() + ":session-tools";
103 
104     protected static final Log LOG = LogFactory.getLog(ServletToolboxManager.class);
105 
106     private ServletContext servletContext;
107     private Map appTools;
108     private ArrayList sessionToolInfo;
109     private ArrayList requestToolInfo;
110     private boolean createSession;
111 
112     private static HashMap managersMap = new HashMap();
113     private static RuleSet servletRuleSet = new ServletToolboxRuleSet();
114 
115 
116     // --------------------------------------------------- Constructor --------
117 
118     /**
119      * Use getInstance(ServletContext,String) instead
120      * to ensure there is exactly one ServletToolboxManager
121      * per xml toolbox configuration file.
122      */
123     private ServletToolboxManager(ServletContext servletContext)
124     {
125         this.servletContext = servletContext;
126         appTools = new HashMap();
127         sessionToolInfo = new ArrayList();
128         requestToolInfo = new ArrayList();
129         createSession = true;
130 
131         LOG.warn("ServletToolboxManager has been deprecated. Please use "+
132                  "org.apache.velocity.tools.ToolboxFactory instead.");
133     }
134 
135 
136     // -------------------------------------------- Public Methods ------------
137 
138     /**
139      * ServletToolboxManager factory method.
140      * This method will ensure there is exactly one ServletToolboxManager
141      * per xml toolbox configuration file.
142      */
143     public static synchronized ServletToolboxManager getInstance(ServletContext servletContext,
144                                                                  String toolboxFile)
145     {
146         // little fix up
147         if (!toolboxFile.startsWith("/"))
148         {
149             toolboxFile = "/" + toolboxFile;
150         }
151 
152         // get the unique key for this toolbox file in this servlet context
153         String uniqueKey = servletContext.hashCode() + ':' + toolboxFile;
154 
155         // check if an instance already exists
156         ServletToolboxManager toolboxManager =
157             (ServletToolboxManager)managersMap.get(uniqueKey);
158 
159         if (toolboxManager == null)
160         {
161             // if not, build one
162             InputStream is = null;
163             try
164             {
165                 // get the bits
166                 is = servletContext.getResourceAsStream(toolboxFile);
167 
168                 if (is != null)
169                 {
170                     LOG.info("Using config file '" + toolboxFile + "'");
171 
172                     toolboxManager = new ServletToolboxManager(servletContext);
173                     toolboxManager.load(is);
174 
175                     // remember it
176                     managersMap.put(uniqueKey, toolboxManager);
177 
178                     LOG.debug("Toolbox setup complete.");
179                 }
180                 else
181                 {
182                     LOG.debug("No toolbox was found at '" + toolboxFile + "'");
183                 }
184             }
185             catch(Exception e)
186             {
187                 LOG.error("Problem loading toolbox '" + toolboxFile + "'", e);
188             }
189             finally
190             {
191                 try
192                 {
193                     if (is != null)
194                     {
195                         is.close();
196                     }
197                 }
198                 catch(Exception ee) {}
199             }
200         }
201         return toolboxManager;
202     }
203 
204 
205     /**
206      * <p>Sets whether or not to create a new session when none exists for the
207      * current request and session-scoped tools have been defined for this
208      * toolbox.</p>
209      *
210      * <p>If true, then a call to {@link #getToolbox(Object)} will
211      * create a new session if none currently exists for this request and
212      * the toolbox has one or more session-scoped tools designed.</p>
213      *
214      * <p>If false, then a call to getToolbox(Object) will never
215      * create a new session for the current request.
216      * This effectively means that no session-scoped tools will be added to
217      * the ToolboxContext for a request that does not have a session object.
218      * </p>
219      *
220      * The default value is true.
221      */
222     public void setCreateSession(boolean b)
223     {
224         createSession = b;
225         LOG.debug("create-session is set to " + b);
226     }
227 
228 
229     /**
230      * <p>Sets an application attribute to tell velocimacros and tools
231      * (especially the LinkTool) whether they should output XHTML or HTML.</p>
232      *
233      * @see ViewContext#XHTML
234      * @since VelocityTools 1.1
235      */
236     public void setXhtml(Boolean value)
237     {
238         servletContext.setAttribute(ViewContext.XHTML, value);
239         LOG.info(ViewContext.XHTML + " is set to " + value);
240     }
241 
242 
243     // ------------------------------ XMLToolboxManager Overrides -------------
244 
245     /**
246      * <p>Retrieves the rule set Digester should use to parse and load
247      * the toolbox for this manager.</p>
248      *
249      * <p>The DTD corresponding to the ServletToolboxRuleSet is:
250      * <pre>
251      *  &lt;?xml version="1.0"?&gt;
252      *  &lt;!ELEMENT toolbox (create-session?,xhtml?,tool*,data*,#PCDATA)&gt;
253      *  &lt;!ELEMENT create-session (#CDATA)&gt;
254      *  &lt;!ELEMENT xhtml          (#CDATA)&gt;
255      *  &lt;!ELEMENT tool           (key,scope?,class,parameter*,#PCDATA)&gt;
256      *  &lt;!ELEMENT data           (key,value)&gt;
257      *      &lt;!ATTLIST data type (string|number|boolean) "string"&gt;
258      *  &lt;!ELEMENT key            (#CDATA)&gt;
259      *  &lt;!ELEMENT scope          (#CDATA)&gt;
260      *  &lt;!ELEMENT class          (#CDATA)&gt;
261      *  &lt;!ELEMENT parameter (EMPTY)&gt;
262      *      &lt;!ATTLIST parameter name CDATA #REQUIRED&gt;
263      *      &lt;!ATTLIST parameter value CDATA #REQUIRED&gt;
264      *  &lt;!ELEMENT value          (#CDATA)&gt;
265      * </pre></p>
266      *
267      * @since VelocityTools 1.1
268      */
269     protected RuleSet getRuleSet()
270     {
271         return servletRuleSet;
272     }
273 
274     /**
275      * Ensures that application-scoped tools do not have request path
276      * restrictions set for them, as those will not be enforced.
277      *
278      * @param info a ToolInfo object
279      * @return true if the ToolInfo is valid
280      * @since VelocityTools 1.3
281      */
282     protected boolean validateToolInfo(ToolInfo info)
283     {
284         if (!super.validateToolInfo(info))
285         {
286             return false;
287         }
288         if (info instanceof ServletToolInfo)
289         {
290             ServletToolInfo sti = (ServletToolInfo)info;
291             if (sti.getRequestPath() != null &&
292                 !ViewContext.REQUEST.equalsIgnoreCase(sti.getScope()))
293             {
294                 LOG.error(sti.getKey() + " must be a request-scoped tool to have a request path restriction!");
295                 return false;
296             }
297         }
298         return true;
299     }
300 
301 
302     /**
303      * Overrides XMLToolboxManager to separate tools by scope.
304      * For this to work, we obviously override getToolbox(Object) as well.
305      */
306     public void addTool(ToolInfo info)
307     {
308         if (validateToolInfo(info))
309         {
310             if (info instanceof ServletToolInfo)
311             {
312                 ServletToolInfo sti = (ServletToolInfo)info;
313 
314                 if (ViewContext.REQUEST.equalsIgnoreCase(sti.getScope()))
315                 {
316                     requestToolInfo.add(sti);
317                     return;
318                 }
319                 else if (ViewContext.SESSION.equalsIgnoreCase(sti.getScope()))
320                 {
321                     sessionToolInfo.add(sti);
322                     return;
323                 }
324                 else if (ViewContext.APPLICATION.equalsIgnoreCase(sti.getScope()))
325                 {
326                     /* add application scoped tools to appTools and
327                      * initialize them with the ServletContext */
328                     appTools.put(sti.getKey(), sti.getInstance(servletContext));
329                     return;
330                 }
331                 else
332                 {
333                     LOG.warn("Unknown scope '" + sti.getScope() + "' - " +
334                             sti.getKey() + " will be request scoped.");
335 
336                     //default is request scope
337                     requestToolInfo.add(info);
338                 }
339             }
340             else
341             {
342                 //default is request scope
343                 requestToolInfo.add(info);
344             }
345         }
346     }
347 
348     /**
349      * Overrides XMLToolboxManager to put data into appTools map
350      */
351     public void addData(ToolInfo info)
352     {
353         if (validateToolInfo(info))
354         {
355             appTools.put(info.getKey(), info.getInstance(null));
356         }
357     }
358 
359     /**
360      * Overrides XMLToolboxManager to handle the separate
361      * scopes.
362      *
363      * Application scope tools were initialized when the toolbox was loaded.
364      * Session scope tools are initialized once per session and stored in a
365      * map in the session attributes.
366      * Request scope tools are initialized on every request.
367      *
368      * @param initData the {@link ViewContext} for the current servlet request
369      */
370     public Map getToolbox(Object initData)
371     {
372         //we know the initData is a ViewContext
373         ViewContext ctx = (ViewContext)initData;
374         String requestPath = ServletUtils.getPath(ctx.getRequest());
375 
376         //create the toolbox map with the application tools in it
377         Map toolbox = new HashMap(appTools);
378 
379         if (!sessionToolInfo.isEmpty())
380         {
381             HttpSession session = ctx.getRequest().getSession(createSession);
382             if (session != null)
383             {
384                 // allow only one thread per session at a time
385                 synchronized(getMutex(session))
386                 {
387                     // get the session tools
388                     Map stmap = (Map)session.getAttribute(SESSION_TOOLS_KEY);
389                     if (stmap == null)
390                     {
391                         // init and store session tools map
392                         stmap = new HashMap(sessionToolInfo.size());
393                         Iterator i = sessionToolInfo.iterator();
394                         while(i.hasNext())
395                         {
396                             ServletToolInfo sti = (ServletToolInfo)i.next();
397                             stmap.put(sti.getKey(), sti.getInstance(ctx));
398                         }
399                         session.setAttribute(SESSION_TOOLS_KEY, stmap);
400                     }
401                     // add them to the toolbox
402                     toolbox.putAll(stmap);
403                 }
404             }
405         }
406 
407         //add and initialize request tools
408         Iterator i = requestToolInfo.iterator();
409         while(i.hasNext())
410         {
411             ToolInfo info = (ToolInfo)i.next();
412             if (info instanceof ServletToolInfo)
413             {
414                 ServletToolInfo sti = (ServletToolInfo)info;
415                 if (!sti.allowsRequestPath(requestPath))
416                 {
417                     continue;
418                 }
419             }
420             toolbox.put(info.getKey(), info.getInstance(ctx));
421         }
422 
423         return toolbox;
424     }
425 
426 
427     /**
428      * Returns a mutex (lock object) unique to the specified session
429      * to allow for reliable synchronization on the session.
430      */
431     protected Object getMutex(HttpSession session)
432     {
433         // yes, this uses double-checked locking, but it is safe here
434         // since partial initialization of the lock is not an issue
435         Object lock = session.getAttribute("session.mutex");
436         if (lock == null)
437         {
438             // one thread per toolbox manager at a time
439             synchronized(this)
440             {
441                 // in case another thread already came thru
442                 lock = session.getAttribute("session.mutex");
443                 if (lock == null)
444                 {
445                     // use a Boolean because it is serializable and small
446                     lock = new Boolean(true);
447                     session.setAttribute("session.mutex", lock);
448                 }
449             }
450         }
451         return lock;
452     }
453 
454 }