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 }