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 * <?xml version="1.0"?>
62 *
63 * <toolbox>
64 * <tool>
65 * <key>link</key>
66 * <scope>request</scope>
67 * <class>org.apache.velocity.tools.view.tools.LinkTool</class>
68 * </tool>
69 * <tool>
70 * <key>date</key>
71 * <scope>application</scope>
72 * <class>org.apache.velocity.tools.generic.DateTool</class>
73 * </tool>
74 * <data type="number">
75 * <key>luckynumber</key>
76 * <value>1.37</value>
77 * </data>
78 * <data type="string">
79 * <key>greeting</key>
80 * <value>Hello World!</value>
81 * </data>
82 * <xhtml>true</xhtml>
83 * </toolbox>
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 * <?xml version="1.0"?>
252 * <!ELEMENT toolbox (create-session?,xhtml?,tool*,data*,#PCDATA)>
253 * <!ELEMENT create-session (#CDATA)>
254 * <!ELEMENT xhtml (#CDATA)>
255 * <!ELEMENT tool (key,scope?,class,parameter*,#PCDATA)>
256 * <!ELEMENT data (key,value)>
257 * <!ATTLIST data type (string|number|boolean) "string">
258 * <!ELEMENT key (#CDATA)>
259 * <!ELEMENT scope (#CDATA)>
260 * <!ELEMENT class (#CDATA)>
261 * <!ELEMENT parameter (EMPTY)>
262 * <!ATTLIST parameter name CDATA #REQUIRED>
263 * <!ATTLIST parameter value CDATA #REQUIRED>
264 * <!ELEMENT value (#CDATA)>
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 }