001: /*
002: * Title: ConfigLoader
003: * Description:
004: *
005: * This software is published under the terms of the OpenSymphony Software
006: * License version 1.1, of which a copy has been included with this
007: * distribution in the LICENSE.txt file.
008: */
009:
010: package com.opensymphony.module.sitemesh.mapper;
011:
012: import com.opensymphony.module.sitemesh.Config;
013: import com.opensymphony.module.sitemesh.Decorator;
014: import org.w3c.dom.*;
015: import org.xml.sax.SAXException;
016:
017: import javax.servlet.ServletException;
018: import javax.xml.parsers.DocumentBuilder;
019: import javax.xml.parsers.DocumentBuilderFactory;
020: import javax.xml.parsers.ParserConfigurationException;
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.HashMap;
024: import java.util.Map;
025:
026: /**
027: * The ConfigLoader reads a configuration XML file that contains Decorator definitions
028: * (name, url, init-params) and path-mappings (pattern, name).
029: *
030: * <p>These can then be accessed by the getDecoratorByName() methods and getMappedName()
031: * methods respectively.</p>
032: *
033: * <p>The DTD for the configuration file in old (deprecated) format is located at
034: * <a href="http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd">
035: * http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd
036: * </a>.</p>
037: *
038: * <p>The DTD for the configuration file in new format is located at
039: * <a href="http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd">
040: * http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd
041: * </a>.</p>
042: *
043: * <p>Editing the config file will cause it to be auto-reloaded.</p>
044: *
045: * <p>This class is used by ConfigDecoratorMapper, and uses PathMapper for pattern matching.</p>
046: *
047: * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
048: * @author <a href="mailto:pathos@pandora.be">Mathias Bogaert</a>
049: * @version $Revision: 1.7 $
050: *
051: * @see com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper
052: * @see com.opensymphony.module.sitemesh.mapper.PathMapper
053: */
054: public class ConfigLoader {
055: private Map decorators = null;
056: private long configLastModified;
057:
058: private File configFile = null;
059: private String configFileName = null;
060: private PathMapper pathMapper = null;
061:
062: private Config config = null;
063:
064: /** Create new ConfigLoader using supplied File. */
065: public ConfigLoader(File configFile) throws ServletException {
066: this .configFile = configFile;
067: this .configFileName = configFile.getName();
068: loadConfig();
069: }
070:
071: /** Create new ConfigLoader using supplied filename and config. */
072: public ConfigLoader(String configFileName, Config config)
073: throws ServletException {
074: this .config = config;
075: this .configFileName = configFileName;
076: if (config.getServletContext().getRealPath(configFileName) != null) {
077: this .configFile = new File(config.getServletContext()
078: .getRealPath(configFileName));
079: }
080: loadConfig();
081: }
082:
083: /** Retrieve Decorator based on name specified in configuration file. */
084: public Decorator getDecoratorByName(String name)
085: throws ServletException {
086: refresh();
087: return (Decorator) decorators.get(name);
088: }
089:
090: /** Get name of Decorator mapped to given path. */
091: public String getMappedName(String path) throws ServletException {
092: refresh();
093: return pathMapper.get(path);
094: }
095:
096: /** Load configuration from file. */
097: private synchronized void loadConfig() throws ServletException {
098: try {
099: // Build a document from the file
100: DocumentBuilderFactory factory = DocumentBuilderFactory
101: .newInstance();
102: DocumentBuilder builder = factory.newDocumentBuilder();
103:
104: Document document = null;
105: if (configFile != null && configFile.canRead()) {
106: // Keep time we read the file to check if the file was modified
107: configLastModified = configFile.lastModified();
108: document = builder.parse(configFile);
109: } else {
110: document = builder.parse(config.getServletContext()
111: .getResourceAsStream(configFileName));
112: }
113:
114: // Parse the configuration document
115: parseConfig(document);
116: } catch (ParserConfigurationException e) {
117: throw new ServletException("Could not get XML parser", e);
118: } catch (IOException e) {
119: throw new ServletException(
120: "Could not read the config file: " + configFileName,
121: e);
122: } catch (SAXException e) {
123: throw new ServletException(
124: "Could not parse the config file: "
125: + configFileName, e);
126: } catch (IllegalArgumentException e) {
127: throw new ServletException(
128: "Could not find the config file: " + configFileName,
129: e);
130: }
131: }
132:
133: /** Parse configuration from XML document. */
134: private synchronized void parseConfig(Document document) {
135: Element root = document.getDocumentElement();
136:
137: // get the default directory for the decorators
138: String defaultDir = getAttribute(root, "defaultdir");
139: if (defaultDir == null)
140: defaultDir = getAttribute(root, "defaultDir");
141:
142: // Clear previous config
143: pathMapper = new PathMapper();
144: decorators = new HashMap();
145:
146: // Get decorators
147: NodeList decoratorNodes = root
148: .getElementsByTagName("decorator");
149: Element decoratorElement = null;
150:
151: for (int i = 0; i < decoratorNodes.getLength(); i++) {
152: String name = null, page = null, uriPath = null, role = null;
153:
154: // get the current decorator element
155: decoratorElement = (Element) decoratorNodes.item(i);
156:
157: if (getAttribute(decoratorElement, "name") != null) {
158: // The new format is used
159: name = getAttribute(decoratorElement, "name");
160: page = getAttribute(decoratorElement, "page");
161: uriPath = getAttribute(decoratorElement, "webapp");
162: role = getAttribute(decoratorElement, "role");
163:
164: // Append the defaultDir
165: if (defaultDir != null && page != null
166: && page.length() > 0 && !page.startsWith("/")) {
167: if (page.charAt(0) == '/')
168: page = defaultDir + page;
169: else
170: page = defaultDir + '/' + page;
171: }
172:
173: // The uriPath must begin with a slash
174: if (uriPath != null && uriPath.length() > 0) {
175: if (uriPath.charAt(0) != '/')
176: uriPath = '/' + uriPath;
177: }
178:
179: // Get all <pattern>...</pattern> and <url-pattern>...</url-pattern> nodes and add a mapping
180: populatePathMapper(decoratorElement
181: .getElementsByTagName("pattern"), role, name);
182: populatePathMapper(decoratorElement
183: .getElementsByTagName("url-pattern"), role,
184: name);
185: } else {
186: // NOTE: Deprecated format
187: name = getContainedText(decoratorNodes.item(i),
188: "decorator-name");
189: page = getContainedText(decoratorNodes.item(i),
190: "resource");
191: // We have this here because the use of jsp-file is deprecated, but we still want
192: // it to work.
193: if (page == null)
194: page = getContainedText(decoratorNodes.item(i),
195: "jsp-file");
196: }
197:
198: Map params = new HashMap();
199:
200: NodeList paramNodes = decoratorElement
201: .getElementsByTagName("init-param");
202: for (int ii = 0; ii < paramNodes.getLength(); ii++) {
203: String paramName = getContainedText(
204: paramNodes.item(ii), "param-name");
205: String paramValue = getContainedText(paramNodes
206: .item(ii), "param-value");
207: params.put(paramName, paramValue);
208: }
209: storeDecorator(new DefaultDecorator(name, page, uriPath,
210: role, params));
211: }
212:
213: // Get (deprecated format) decorator-mappings
214: NodeList mappingNodes = root
215: .getElementsByTagName("decorator-mapping");
216: for (int i = 0; i < mappingNodes.getLength(); i++) {
217: Element n = (Element) mappingNodes.item(i);
218: String name = getContainedText(mappingNodes.item(i),
219: "decorator-name");
220:
221: // Get all <url-pattern>...</url-pattern> nodes and add a mapping
222: populatePathMapper(n.getElementsByTagName("url-pattern"),
223: null, name);
224: }
225: }
226:
227: /**
228: * Extracts each URL pattern and adds it to the pathMapper map.
229: */
230: private void populatePathMapper(NodeList patternNodes, String role,
231: String name) {
232: for (int j = 0; j < patternNodes.getLength(); j++) {
233: Element p = (Element) patternNodes.item(j);
234: Text patternText = (Text) p.getFirstChild();
235: if (patternText != null) {
236: String pattern = patternText.getData().trim();
237: if (pattern != null) {
238: if (role != null) {
239: // concatenate name and role to allow more
240: // than one decorator per role
241: pathMapper.put(name + role, pattern);
242: } else {
243: pathMapper.put(name, pattern);
244: }
245: }
246: }
247: }
248: }
249:
250: /** Override default behavior of element.getAttribute (returns the empty string) to return null. */
251: private static String getAttribute(Element element, String name) {
252: if (element != null && element.getAttribute(name) != null
253: && element.getAttribute(name).trim() != "") {
254: return element.getAttribute(name).trim();
255: } else {
256: return null;
257: }
258: }
259:
260: /**
261: * With a given parent XML Element, find the text contents of the child element with
262: * supplied name.
263: */
264: private static String getContainedText(Node parent,
265: String childTagName) {
266: try {
267: Node tag = ((Element) parent).getElementsByTagName(
268: childTagName).item(0);
269: String text = ((Text) tag.getFirstChild()).getData();
270: return text;
271: } catch (Exception e) {
272: return null;
273: }
274: }
275:
276: /** Store Decorator in Map */
277: private void storeDecorator(Decorator d) {
278: if (d.getRole() != null) {
279: decorators.put(d.getName() + d.getRole(), d);
280: } else {
281: decorators.put(d.getName(), d);
282: }
283: }
284:
285: /** Check if configuration file has been updated, and if so, reload. */
286: private synchronized void refresh() throws ServletException {
287: if (configFile != null
288: && configLastModified != configFile.lastModified())
289: loadConfig();
290: }
291: }
|