1 /* 2 * Title: ConfigLoader 3 * Description: 4 * 5 * This software is published under the terms of the OpenSymphony Software 6 * License version 1.1, of which a copy has been included with this 7 * distribution in the LICENSE.txt file. 8 */ 9 10 package com.opensymphony.module.sitemesh.mapper; 11 12 import com.opensymphony.module.sitemesh.Config; 13 import com.opensymphony.module.sitemesh.Decorator; 14 import org.w3c.dom; 15 import org.xml.sax.SAXException; 16 17 import javax.servlet.ServletException; 18 import javax.xml.parsers.DocumentBuilder; 19 import javax.xml.parsers.DocumentBuilderFactory; 20 import javax.xml.parsers.ParserConfigurationException; 21 import java.io.File; 22 import java.io.IOException; 23 import java.util.HashMap; 24 import java.util.Map; 25 26 /** 27 * The ConfigLoader reads a configuration XML file that contains Decorator definitions 28 * (name, url, init-params) and path-mappings (pattern, name). 29 * 30 * <p>These can then be accessed by the getDecoratorByName() methods and getMappedName() 31 * methods respectively.</p> 32 * 33 * <p>The DTD for the configuration file in old (deprecated) format is located at 34 * <a href="http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd"> 35 * http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd 36 * </a>.</p> 37 * 38 * <p>The DTD for the configuration file in new format is located at 39 * <a href="http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd"> 40 * http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd 41 * </a>.</p> 42 * 43 * <p>Editing the config file will cause it to be auto-reloaded.</p> 44 * 45 * <p>This class is used by ConfigDecoratorMapper, and uses PathMapper for pattern matching.</p> 46 * 47 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a> 48 * @author <a href="mailto:pathos@pandora.be">Mathias Bogaert</a> 49 * @version $Revision: 1.6 $ 50 * 51 * @see com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper 52 * @see com.opensymphony.module.sitemesh.mapper.PathMapper 53 */ 54 public final class ConfigLoader { 55 private Map decorators = null; 56 private long configLastModified; 57 58 private File configFile = null; 59 private String configFileName = null; 60 private PathMapper pathMapper = null; 61 62 private Config config = null; 63 64 /** Create new ConfigLoader using supplied File. */ 65 public ConfigLoader(File configFile) throws ServletException { 66 this.configFile = configFile; 67 this.configFileName = configFile.getName(); 68 loadConfig(); 69 } 70 71 /** Create new ConfigLoader using supplied filename and config. */ 72 public ConfigLoader(String configFileName, Config config) throws ServletException { 73 this.config = config; 74 this.configFileName = configFileName; 75 if (config.getServletContext().getRealPath(configFileName) != null) { 76 this.configFile = new File(config.getServletContext().getRealPath(configFileName)); 77 } 78 loadConfig(); 79 } 80 81 /** Retrieve Decorator based on name specified in configuration file. */ 82 public Decorator getDecoratorByName(String name) throws ServletException { 83 refresh(); 84 return (Decorator)decorators.get(name); 85 } 86 87 /** Get name of Decorator mapped to given path. */ 88 public String getMappedName(String path) throws ServletException { 89 refresh(); 90 return pathMapper.get(path); 91 } 92 93 /** Load configuration from file. */ 94 private synchronized void loadConfig() throws ServletException { 95 try { 96 // Build a document from the file 97 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 98 DocumentBuilder builder = factory.newDocumentBuilder(); 99 100 Document document = null; 101 if (configFile != null && configFile.canRead()) { 102 // Keep time we read the file to check if the file was modified 103 configLastModified = configFile.lastModified(); 104 document = builder.parse(configFile); 105 } 106 else { 107 document = builder.parse(config.getServletContext().getResourceAsStream(configFileName)); 108 } 109 110 // Parse the configuration document 111 parseConfig(document); 112 } 113 catch (ParserConfigurationException e) { 114 throw new ServletException("Could not get XML parser", e); 115 } 116 catch (IOException e) { 117 throw new ServletException("Could not read the config file: " + configFileName, e); 118 } 119 catch (SAXException e) { 120 throw new ServletException("Could not parse the config file: " + configFileName, e); 121 } 122 catch (IllegalArgumentException e) { 123 throw new ServletException("Could not find the config file: " + configFileName, e); 124 } 125 } 126 127 /** Parse configuration from XML document. */ 128 private synchronized void parseConfig(Document document) { 129 Element root = document.getDocumentElement(); 130 131 // get the default directory for the decorators 132 String defaultDir = getAttribute(root, "defaultdir"); 133 if (defaultDir == null) defaultDir = getAttribute(root, "defaultDir"); 134 135 // Clear previous config 136 pathMapper = new PathMapper(); 137 decorators = new HashMap(); 138 139 // Get decorators 140 NodeList decoratorNodes = root.getElementsByTagName("decorator"); 141 Element decoratorElement = null; 142 143 for (int i = 0; i < decoratorNodes.getLength(); i++) { 144 String name = null, page = null, uriPath = null, role = null; 145 146 // get the current decorator element 147 decoratorElement = (Element) decoratorNodes.item(i); 148 149 if (getAttribute(decoratorElement, "name") != null) { 150 // The new format is used 151 name = getAttribute(decoratorElement, "name"); 152 page = getAttribute(decoratorElement, "page"); 153 uriPath = getAttribute(decoratorElement, "webapp"); 154 role = getAttribute(decoratorElement, "role"); 155 156 // Append the defaultDir 157 if (defaultDir != null && page != null && page.length() > 0 && !page.startsWith("/")) { 158 if (page.charAt(0) == '/') page = defaultDir + page; 159 else page = defaultDir + '/' + page; 160 } 161 162 // The uriPath must begin with a slash 163 if (uriPath != null && uriPath.length() > 0) { 164 if (uriPath.charAt(0) != '/') uriPath = '/' + uriPath; 165 } 166 167 // Get all <pattern>...</pattern> and <url-pattern>...</url-pattern> nodes and add a mapping 168 populatePathMapper(decoratorElement.getElementsByTagName("pattern"), role, name); 169 populatePathMapper(decoratorElement.getElementsByTagName("url-pattern"), role, name); 170 } 171 else { 172 // NOTE: Deprecated format 173 name = getContainedText(decoratorNodes.item(i), "decorator-name"); 174 page = getContainedText(decoratorNodes.item(i), "resource"); 175 // We have this here because the use of jsp-file is deprecated, but we still want 176 // it to work. 177 if (page == null) page = getContainedText(decoratorNodes.item(i), "jsp-file"); 178 } 179 180 Map params = new HashMap(); 181 182 NodeList paramNodes = decoratorElement.getElementsByTagName("init-param"); 183 for (int ii = 0; ii < paramNodes.getLength(); ii++) { 184 String paramName = getContainedText(paramNodes.item(ii), "param-name"); 185 String paramValue = getContainedText(paramNodes.item(ii), "param-value"); 186 params.put(paramName, paramValue); 187 } 188 storeDecorator(new DefaultDecorator(name, page, uriPath, role, params)); 189 } 190 191 // Get (deprecated format) decorator-mappings 192 NodeList mappingNodes = root.getElementsByTagName("decorator-mapping"); 193 for (int i = 0; i < mappingNodes.getLength(); i++) { 194 Element n = (Element)mappingNodes.item(i); 195 String name = getContainedText(mappingNodes.item(i), "decorator-name"); 196 197 // Get all <url-pattern>...</url-pattern> nodes and add a mapping 198 populatePathMapper(n.getElementsByTagName("url-pattern"), null, name); 199 } 200 } 201 202 /** 203 * Extracts each URL pattern and adds it to the pathMapper map. 204 */ 205 private void populatePathMapper(NodeList patternNodes, String role, String name) { 206 for (int j = 0; j < patternNodes.getLength(); j++) { 207 Element p = (Element)patternNodes.item(j); 208 Text patternText = (Text) p.getFirstChild(); 209 if (patternText != null) { 210 String pattern = patternText.getData().trim(); 211 if (pattern != null) { 212 if (role != null) { 213 // concatenate name and role to allow more 214 // than one decorator per role 215 pathMapper.put(name + role, pattern); 216 } 217 else { 218 pathMapper.put(name, pattern); 219 } 220 } 221 } 222 } 223 } 224 225 /** Override default behavior of element.getAttribute (returns the empty string) to return null. */ 226 private static String getAttribute(Element element, String name) { 227 if (element != null && element.getAttribute(name) != null && element.getAttribute(name).trim() != "") { 228 return element.getAttribute(name).trim(); 229 } 230 else { 231 return null; 232 } 233 } 234 235 /** 236 * With a given parent XML Element, find the text contents of the child element with 237 * supplied name. 238 */ 239 private static String getContainedText(Node parent, String childTagName) { 240 try { 241 Node tag = ((Element)parent).getElementsByTagName(childTagName).item(0); 242 String text = ((Text)tag.getFirstChild()).getData(); 243 return text; 244 } 245 catch (Exception e) { 246 return null; 247 } 248 } 249 250 /** Store Decorator in Map */ 251 private void storeDecorator(Decorator d) { 252 if (d.getRole() != null) { 253 decorators.put(d.getName() + d.getRole(), d); 254 } 255 else { 256 decorators.put(d.getName(), d); 257 } 258 } 259 260 /** Check if configuration file has been updated, and if so, reload. */ 261 private synchronized void refresh() throws ServletException { 262 if (configFile != null && configLastModified != configFile.lastModified()) loadConfig(); 263 } 264 }