package org.cdlib.xtf.servletBase; /** * Copyright (c) 2004, Regents of the University of California * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the University of California nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.cdlib.xtf.util.*; /** Common members and methods for servlet configuration classes */ public abstract class TextConfig { /** Servlet we are part of */ public TextServlet servlet; /** Logging level: "silent", "errors", "warnings", "info", or "debug" */ public String logLevel = "info"; /** Max # of stylesheets to cache */ public int stylesheetCacheSize = 10; /** Max length of time (in seconds) to cache a stylesheet. */ public int stylesheetCacheExpire = 0; /** * Filesystem path to a stylesheet used to generate error pages * (no permission, invalid document, general exceptions, etc.) */ public String errorGenSheet; /** * Turns on dependency checking for the caches, so that (for instance) * changing a stylesheet forces it to be reloaded. */ public boolean dependencyCheckingEnabled = true; /** * Turns on latency reporting for the servlet. */ public boolean reportLatency = false; /** * Enables a cutoff size for latency reporting (if {@link #reportLatency} * is true.) Specifies the number of bytes after which a latency message * will be printed, even if the output is not complete. */ public int latencyCutoffSize = 0; /** * Amount of time (in seconds) that a request is allowed to run * before we consider it a possible "runaway" and start logging warning * messages. Default: 10 seconds */ public long runawayNormalTime = 0; /** * Amount of time (in seconds) after which a request should * voluntarily kill itself. Default: 5 minutes (300 seconds) */ public long runawayKillTime = 0; /** Whether session tracking is enabled. Default: true */ public boolean trackSessions = true; /** * Which URLs to apply encoding to, if session tracking enabled and * user doesn't allow cookies. If null, no URL rewriting will be * performed. */ public Pattern sessionEncodeURLPattern = null; /** Whether to print out a stylesheet profile after each request */ public boolean stylesheetProfiling = false; /** * Whether to allow browsers to cache XTF pages. Defaults to true * for backward compatibility but set to false in new XTF * installations, which is safer. */ public boolean allowBrowserCaching = true; /** * List of parameters to tokenize specially. Default: empty (meaning use * default tokenizer for all parameters.) */ public Map tokenizerMap = new HashMap(); /** All the configuration attributes in the form of name/value pairs */ public AttribList attribs = new AttribList(); /* Private temporaries for use during parsing */ private String tokenizeParam; private String tokenizeTokenizer; /** Create a configuration and attach it to a servlet */ public TextConfig(TextServlet servlet) { this.servlet = servlet; } /** * Constructor - Reads and parses the global configuration file (XML) for * the servlet. * * @param path Filesystem path to the config file. */ public void read(String expectedRootTag, String path) throws GeneralException { try { // Read in the document (it's in XML format) EasyNode root = EasyNode.readXMLFile(path); // Make sure the root tag is correct. String rootTag = root.name(); if (rootTag.equals("") && root.nChildren() == 1) { root = root.child(0); rootTag = root.name(); } if (!rootTag.equals(expectedRootTag)) throw new GeneralException( "Config file \"" + path + "\" " + "should contain <" + expectedRootTag + ">"); // Pick out the elements for (int i = 0; i < root.nChildren(); i++) { EasyNode el = root.child(i); if (!el.isElement()) continue; String tagName = el.name(); // Scan each attribute of each element. for (int j = 0; j < el.nAttrs(); j++) { String attrName = el.attrName(j); String strVal = el.attrValue(j); // See if we recognize the property (if so, handle it). if (!handleProperty(tagName + "." + attrName, strVal)) { // It is perfectly acceptable (and often necessary) for // the user to define pass-through tags. Somehow this got // forgotten in the onslaught of new features. So, if we // don't recognize the tag, just pass it by. // ; // do nothing } // Also put this tag in the attribute list so it can be // passed to the stylesheets. attribs.put(tagName + "." + attrName, strVal); } } // for i } catch (Exception e) { throw new GeneralException("Error reading config file " + path + ": " + e); } // Make sure that required parameters were specified. requireOrElse(errorGenSheet, "Config file error: errorGen stylesheet not specified"); } // constructor /** * Called when a property is encountered. Derived classes, if they don't * recognize the property, should call the base class version of * handleProperty(). * * @param tagAttr Combined element/attribute name being considered * @param strVal It's string value * @return true if handled, false if unrecognized */ protected boolean handleProperty(String tagAttr, String strVal) { if (tagAttr.equalsIgnoreCase("logging.level")) { if (strVal.equals("0")) strVal = "silent"; else if (strVal.equals("1")) strVal = "info"; else if (strVal.equals("2")) strVal = "debug"; logLevel = strVal; return true; } else if (tagAttr.equalsIgnoreCase("stylesheetCache.size")) { stylesheetCacheSize = parseInt(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("stylesheetCache.expire")) { stylesheetCacheExpire = parseInt(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("errorGen.path")) { errorGenSheet = servlet.getRealPath(strVal); return true; } else if (tagAttr.equalsIgnoreCase("dependencyChecking.check")) { dependencyCheckingEnabled = parseBoolean(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("reportLatency.report") || tagAttr.equalsIgnoreCase("reportLatency.enable") /* old, for backward compat */) { reportLatency = parseBoolean(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("reportLatency.cutoffSize")) { latencyCutoffSize = parseInt(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("runawayTimer.normalTime")) { runawayNormalTime = parseInt(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("runawayTimer.killTime")) { runawayKillTime = parseInt(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("trackSessions.track")) { trackSessions = parseBoolean(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("trackSessions.encodeURLPattern") && strVal.length() > 0) { try { sessionEncodeURLPattern = Pattern.compile(strVal); } catch (PatternSyntaxException e) { throw new GeneralException( "Config file property " + tagAttr + " must be a valid regular expression"); } return true; } else if (tagAttr.equalsIgnoreCase("tokenize.param") || tagAttr.equalsIgnoreCase("tokenize.tokenizer")) { if (tagAttr.equalsIgnoreCase("tokenize.param")) tokenizeParam = strVal; else tokenizeTokenizer = strVal; if (tokenizeParam != null && tokenizeTokenizer != null) { tokenizerMap.put(tokenizeParam, tokenizeTokenizer); tokenizeParam = null; tokenizeTokenizer = null; } return true; } else if (tagAttr.equalsIgnoreCase("stylesheetProfiling.profile")) { stylesheetProfiling = parseBoolean(tagAttr, strVal); return true; } else if (tagAttr.equalsIgnoreCase("cacheControl.allowBrowserCaching")) { allowBrowserCaching = parseBoolean(tagAttr, strVal); return true; } // Not recognized. return false; } // handleProperty() /** * Utility function - parse an integer value. If a valid integer isn't * specified, throws an exception. * * @param tagAttr Name of the element/attribute being considered * @param strVal It's string value */ public static int parseInt(String tagAttr, String strVal) throws GeneralException { try { return Integer.parseInt(strVal); } catch (NumberFormatException e) { throw new GeneralException("Integer value expected for " + tagAttr); } } // parseBoolean() /** * Utility function - parse a boolean value. Allows "true", "false", * "yes", "no", "1", or "0". If not one of these, throws an exception. * * @param tagAttr Name of the element/attribute being considered * @param strVal It's string value */ public static boolean parseBoolean(String tagAttr, String strVal) throws GeneralException { if (strVal.equalsIgnoreCase("true") || strVal.equalsIgnoreCase("yes") || strVal.equals("1")) { return true; } if (strVal.equalsIgnoreCase("false") || strVal.equalsIgnoreCase("no") || strVal.equals("0")) { return false; } throw new GeneralException( "Boolean value expected for " + tagAttr + " (value must be one of: " + "'true', 'false', 'yes', 'no', '1', or '0')"); } // parseBoolean() /** * Utility function - if the value is empty, throws an exception. * * @param value Value to check for null or "" * @param descrip If thrown, the exception uses this as the message. */ public static void requireOrElse(String value, String descrip) throws GeneralException { if (value == null || value.equals("")) throw new GeneralException(descrip); } // requireOrElse() } // class TextConfig