Java tutorial
/* * Copyright 2004-2008 the Seasar Foundation and the Others. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.seasar.velocity.tools; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import org.apache.commons.digester.RuleSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.velocity.tools.view.ToolInfo; import org.apache.velocity.tools.view.XMLToolboxManager; import org.apache.velocity.tools.view.context.ViewContext; import org.apache.velocity.tools.view.servlet.ServletToolInfo; import org.apache.velocity.tools.view.servlet.ServletToolboxManager; import org.apache.velocity.tools.view.servlet.ServletUtils; /** * NOTE ServletToolboxManager???????????????????? * <p>A toolbox manager for the servlet environment.</p> * * <p>A toolbox manager is responsible for automatically filling the Velocity * context with a set of view tools. This class provides the following * features:</p> * <ul> * <li>configurable through an XML-based configuration file</li> * <li>assembles a set of view tools (the toolbox) on request</li> * <li>handles different tool scopes (request, session, application)</li> * <li>supports any class with a public constructor without parameters * to be used as a view tool</li> * <li>supports adding primitive data values to the context(String,Number,Boolean)</li> * </ul> * * * <p><strong>Configuration</strong></p> * <p>The toolbox manager is configured through an XML-based configuration * file. The configuration file is passed to the {@link #load(java.io.InputStream input)} * method. The format is shown in the following example:</p> * <pre> * <?xml version="1.0"?> * * <toolbox> * <tool> * <key>link</key> * <scope>request</scope> * <class>org.apache.velocity.tools.view.tools.LinkTool</class> * </tool> * <tool> * <key>date</key> * <scope>application</scope> * <class>org.apache.velocity.tools.generic.DateTool</class> * </tool> * <data type="number"> * <key>luckynumber</key> * <value>1.37</value> * </data> * <data type="string"> * <key>greeting</key> * <value>Hello World!</value> * </data> * <xhtml>true</xhtml> * </toolbox> * </pre> * <p>The recommended location for the configuration file is the WEB-INF directory of the * web application.</p> * * @author <a href="mailto:sidler@teamup.com">Gabriel Sidler</a> * @author Nathan Bubna * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> * @author <a href="mailto:henning@schmiedehausen.org">Henning P. Schmiedehausen</a> * @version $Id: ServletToolboxManager.java 488460 2006-12-19 00:00:35Z nbubna $ */ public class S2ServletToolboxManager extends XMLToolboxManager { // --------------------------------------------------- Properties --------- public static final String SESSION_TOOLS_KEY = ServletToolboxManager.class.getName() + ":session-tools"; protected static final Log LOG = LogFactory.getLog(ServletToolboxManager.class); private ServletContext servletContext; private Map appTools; private ArrayList sessionToolInfo; private ArrayList requestToolInfo; private boolean createSession; private static HashMap managersMap = new HashMap(); // NOTE uses S2ServletToolboxRuleSet instead of ServletToolboxRuleSet private static RuleSet servletRuleSet = new S2ServletToolboxRuleSet(); // --------------------------------------------------- Constructor -------- /** * Use getInstance(ServletContext,String) instead * to ensure there is exactly one ServletToolboxManager * per xml toolbox configuration file. */ private S2ServletToolboxManager(ServletContext servletContext) { this.servletContext = servletContext; appTools = new HashMap(); sessionToolInfo = new ArrayList(); requestToolInfo = new ArrayList(); createSession = true; } // -------------------------------------------- Public Methods ------------ /** * ServletToolboxManager factory method. * This method will ensure there is exactly one ServletToolboxManager * per xml toolbox configuration file. */ public static synchronized S2ServletToolboxManager getInstance(ServletContext servletContext, String toolboxFile) { // little fix up if (!toolboxFile.startsWith("/")) { toolboxFile = "/" + toolboxFile; } // get the unique key for this toolbox file in this servlet context String uniqueKey = servletContext.hashCode() + ':' + toolboxFile; // check if a previous instance exists S2ServletToolboxManager toolboxManager = (S2ServletToolboxManager) managersMap.get(uniqueKey); if (toolboxManager == null) { // if not, build one InputStream is = null; try { // get the bits is = servletContext.getResourceAsStream(toolboxFile); if (is != null) { LOG.info("Using config file '" + toolboxFile + "'"); toolboxManager = new S2ServletToolboxManager(servletContext); toolboxManager.load(is); // remember it managersMap.put(uniqueKey, toolboxManager); LOG.debug("Toolbox setup complete."); } else { LOG.debug("No toolbox was found at '" + toolboxFile + "'"); } } catch (Exception e) { LOG.error("Problem loading toolbox '" + toolboxFile + "'", e); } finally { try { if (is != null) { is.close(); } } catch (Exception ee) { } } } return toolboxManager; } /** * <p>Sets whether or not to create a new session when none exists for the * current request and session-scoped tools have been defined for this * toolbox.</p> * * <p>If true, then a call to {@link #getToolbox(Object)} will * create a new session if none currently exists for this request and * the toolbox has one or more session-scoped tools designed.</p> * * <p>If false, then a call to getToolbox(Object) will never * create a new session for the current request. * This effectively means that no session-scoped tools will be added to * the ToolboxContext for a request that does not have a session object. * </p> * * The default value is true. */ public void setCreateSession(boolean b) { createSession = b; LOG.debug("create-session is set to " + b); } /** * <p>Sets an application attribute to tell velocimacros and tools * (especially the LinkTool) whether they should output XHTML or HTML.</p> * * @see ViewContext#XHTML * @since VelocityTools 1.1 */ public void setXhtml(Boolean value) { servletContext.setAttribute(ViewContext.XHTML, value); LOG.info(ViewContext.XHTML + " is set to " + value); } // ------------------------------ XMLToolboxManager Overrides ------------- /** * <p>Retrieves the rule set Digester should use to parse and load * the toolbox for this manager.</p> * * <p>The DTD corresponding to the ServletToolboxRuleSet is: * <pre> * <?xml version="1.0"?> * <!ELEMENT toolbox (create-session?,xhtml?,tool*,data*,#PCDATA)> * <!ELEMENT create-session (#CDATA)> * <!ELEMENT xhtml (#CDATA)> * <!ELEMENT tool (key,scope?,class,parameter*,#PCDATA)> * <!ELEMENT data (key,value)> * <!ATTLIST data type (string|number|boolean) "string"> * <!ELEMENT key (#CDATA)> * <!ELEMENT scope (#CDATA)> * <!ELEMENT class (#CDATA)> * <!ELEMENT parameter (EMPTY)> * <!ATTLIST parameter name CDATA #REQUIRED> * <!ATTLIST parameter value CDATA #REQUIRED> * <!ELEMENT value (#CDATA)> * </pre></p> * * @since VelocityTools 1.1 */ protected RuleSet getRuleSet() { return servletRuleSet; } /** * Ensures that application-scoped tools do not have request path * restrictions set for them, as those will not be enforced. * * @param info a ToolInfo object * @return true if the ToolInfo is valid * @since VelocityTools 1.3 */ protected boolean validateToolInfo(ToolInfo info) { if (!super.validateToolInfo(info)) { return false; } if (info instanceof ServletToolInfo) { ServletToolInfo sti = (ServletToolInfo) info; if (sti.getRequestPath() != null && !ViewContext.REQUEST.equalsIgnoreCase(sti.getScope())) { LOG.error(sti.getKey() + " must be a request-scoped tool to have a request path restriction!"); return false; } } return true; } /** * Overrides XMLToolboxManager to separate tools by scope. * For this to work, we obviously override getToolbox(Object) as well. */ public void addTool(ToolInfo info) { if (validateToolInfo(info)) { if (info instanceof ServletToolInfo) { ServletToolInfo sti = (ServletToolInfo) info; if (ViewContext.REQUEST.equalsIgnoreCase(sti.getScope())) { requestToolInfo.add(sti); return; } else if (ViewContext.SESSION.equalsIgnoreCase(sti.getScope())) { sessionToolInfo.add(sti); return; } else if (ViewContext.APPLICATION.equalsIgnoreCase(sti.getScope())) { /* add application scoped tools to appTools and * initialize them with the ServletContext */ appTools.put(sti.getKey(), sti.getInstance(servletContext)); return; } else { LOG.warn("Unknown scope '" + sti.getScope() + "' - " + sti.getKey() + " will be request scoped."); //default is request scope requestToolInfo.add(info); } } else { //default is request scope requestToolInfo.add(info); } } } /** * Overrides XMLToolboxManager to put data into appTools map */ public void addData(ToolInfo info) { if (validateToolInfo(info)) { appTools.put(info.getKey(), info.getInstance(null)); } } /** * Overrides XMLToolboxManager to handle the separate * scopes. * * Application scope tools were initialized when the toolbox was loaded. * Session scope tools are initialized once per session and stored in a * map in the session attributes. * Request scope tools are initialized on every request. * * @param initData the {@link ViewContext} for the current servlet request */ public Map getToolbox(Object initData) { //we know the initData is a ViewContext ViewContext ctx = (ViewContext) initData; String requestPath = ServletUtils.getPath(ctx.getRequest()); //create the toolbox map with the application tools in it Map toolbox = new HashMap(appTools); if (!sessionToolInfo.isEmpty()) { HttpSession session = ctx.getRequest().getSession(createSession); if (session != null) { // allow only one thread per session at a time synchronized (getMutex(session)) { // get the session tools Map stmap = (Map) session.getAttribute(SESSION_TOOLS_KEY); if (stmap == null) { // init and store session tools map stmap = new HashMap(sessionToolInfo.size()); Iterator i = sessionToolInfo.iterator(); while (i.hasNext()) { ServletToolInfo sti = (ServletToolInfo) i.next(); stmap.put(sti.getKey(), sti.getInstance(ctx)); } session.setAttribute(SESSION_TOOLS_KEY, stmap); } // add them to the toolbox toolbox.putAll(stmap); } } } //add and initialize request tools Iterator i = requestToolInfo.iterator(); while (i.hasNext()) { ToolInfo info = (ToolInfo) i.next(); if (info instanceof ServletToolInfo) { ServletToolInfo sti = (ServletToolInfo) info; if (!sti.allowsRequestPath(requestPath)) { continue; } } toolbox.put(info.getKey(), info.getInstance(ctx)); } return toolbox; } /** * Returns a mutex (lock object) unique to the specified session * to allow for reliable synchronization on the session. */ protected Object getMutex(HttpSession session) { // yes, this uses double-checked locking, but it is safe here // since partial initialization of the lock is not an issue Object lock = session.getAttribute("session.mutex"); if (lock == null) { // one thread per toolbox manager at a time synchronized (this) { // in case another thread already came thru lock = session.getAttribute("session.mutex"); if (lock == null) { // use a Boolean because it is serializable and small lock = new Boolean(true); session.setAttribute("session.mutex", lock); } } } return lock; } }