001: /*
002: * Title: DefaultFactory
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.factory;
011:
012: import com.opensymphony.module.sitemesh.Config;
013: import com.opensymphony.module.sitemesh.DecoratorMapper;
014: import com.opensymphony.module.sitemesh.PageParser;
015:
016: import org.w3c.dom.Document;
017: import org.w3c.dom.Element;
018: import org.w3c.dom.NodeList;
019: import org.w3c.dom.Text;
020: import org.xml.sax.SAXException;
021:
022: import javax.xml.parsers.DocumentBuilder;
023: import javax.xml.parsers.DocumentBuilderFactory;
024: import javax.xml.parsers.ParserConfigurationException;
025: import java.io.File;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.util.*;
029:
030: /**
031: * DefaultFactory, reads configuration from the <code>sitemesh.configfile</code> init param,
032: * or <code>/WEB-INF/sitemesh.xml</code> if not specified, or uses the
033: * default configuration if <code>sitemesh.xml</code> does not exist.
034: *
035: * <p>To use the <code>sitemesh.configfile</code> parameter, add the following to your web.xml:
036: * <pre>
037: * <context-param>
038: * <param-name>sitemesh.configfile</param-name>
039: * <param-value>/WEB-INF/etc/sitemesh.xml</param-value>
040: * </context-param>
041: * </pre>
042: * </p>
043: *
044: * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
045: * @author <a href="mailto:pathos@pandora.be">Mathias Bogaert</a>
046: * @version $Revision: 1.6 $
047: */
048: public class DefaultFactory extends BaseFactory {
049: String configFileName;
050: private static final String DEFAULT_CONFIG_FILENAME = "/WEB-INF/sitemesh.xml";
051:
052: File configFile;
053: long configLastModified;
054: Map configProps = new HashMap();
055:
056: String excludesFileName;
057: File excludesFile;
058:
059: public DefaultFactory(Config config) {
060: super (config);
061:
062: configFileName = config.getServletContext().getInitParameter(
063: "sitemesh.configfile");
064: if (configFileName == null) {
065: configFileName = DEFAULT_CONFIG_FILENAME;
066: }
067:
068: // configFilePath is null if loaded from war file
069: String initParamConfigFile = config.getConfigFile();
070: if (initParamConfigFile != null) {
071: configFileName = initParamConfigFile;
072: }
073:
074: String configFilePath = config.getServletContext().getRealPath(
075: configFileName);
076:
077: if (configFilePath != null) { // disable config auto reloading for .war files
078: configFile = new File(configFilePath);
079: }
080:
081: loadConfig();
082: }
083:
084: /** Load configuration from file. */
085: private synchronized void loadConfig() {
086: try {
087: // Load and parse the sitemesh.xml file
088: Element root = loadSitemeshXML();
089:
090: NodeList sections = root.getChildNodes();
091: // Loop through child elements of root node
092: for (int i = 0; i < sections.getLength(); i++) {
093: if (sections.item(i) instanceof Element) {
094: Element curr = (Element) sections.item(i);
095: NodeList children = curr.getChildNodes();
096:
097: if ("property".equalsIgnoreCase(curr.getTagName())) {
098: String name = curr.getAttribute("name");
099: String value = curr.getAttribute("value");
100: if (!"".equals(name) && !"".equals(value)) {
101: configProps.put("${" + name + "}", value);
102: }
103: } else if ("page-parsers".equalsIgnoreCase(curr
104: .getTagName())) {
105: // handle <page-parsers>
106: loadPageParsers(children);
107: } else if ("decorator-mappers"
108: .equalsIgnoreCase(curr.getTagName())) {
109: // handle <decorator-mappers>
110: loadDecoratorMappers(children);
111: } else if ("excludes".equalsIgnoreCase(curr
112: .getTagName())) {
113: // handle <excludes>
114: String fileName = replaceProperties(curr
115: .getAttribute("file"));
116: if (!"".equals(fileName)) {
117: excludesFileName = fileName;
118: loadExcludes();
119: }
120: }
121: }
122: }
123: } catch (ParserConfigurationException e) {
124: throw new FactoryException("Could not get XML parser", e);
125: } catch (IOException e) {
126: throw new FactoryException("Could not read config file : "
127: + configFileName, e);
128: } catch (SAXException e) {
129: throw new FactoryException("Could not parse config file : "
130: + configFileName, e);
131: }
132: }
133:
134: private Element loadSitemeshXML()
135: throws ParserConfigurationException, IOException,
136: SAXException {
137: DocumentBuilderFactory factory = DocumentBuilderFactory
138: .newInstance();
139: DocumentBuilder builder = factory.newDocumentBuilder();
140:
141: InputStream is = null;
142:
143: if (configFile == null) {
144: is = config.getServletContext().getResourceAsStream(
145: configFileName);
146: } else if (configFile.exists() && configFile.canRead()) {
147: is = configFile.toURL().openStream();
148: }
149:
150: if (is == null) { // load the default sitemesh configuration
151: is = getClass()
152: .getClassLoader()
153: .getResourceAsStream(
154: "com/opensymphony/module/sitemesh/factory/sitemesh-default.xml");
155: }
156:
157: if (is == null) { // load the default sitemesh configuration using another classloader
158: is = Thread
159: .currentThread()
160: .getContextClassLoader()
161: .getResourceAsStream(
162: "com/opensymphony/module/sitemesh/factory/sitemesh-default.xml");
163: }
164:
165: if (is == null) {
166: throw new IllegalStateException(
167: "Cannot load default configuration from jar");
168: }
169:
170: if (configFile != null)
171: configLastModified = configFile.lastModified();
172:
173: Document doc = builder.parse(is);
174: Element root = doc.getDocumentElement();
175: // Verify root element
176: if (!"sitemesh".equalsIgnoreCase(root.getTagName())) {
177: throw new FactoryException(
178: "Root element of sitemesh configuration file not <sitemesh>",
179: null);
180: }
181: return root;
182: }
183:
184: private void loadExcludes() throws ParserConfigurationException,
185: IOException, SAXException {
186: DocumentBuilderFactory factory = DocumentBuilderFactory
187: .newInstance();
188: DocumentBuilder builder = factory.newDocumentBuilder();
189:
190: InputStream is = null;
191:
192: if (excludesFile == null) {
193: is = config.getServletContext().getResourceAsStream(
194: excludesFileName);
195: } else if (excludesFile.exists() && excludesFile.canRead()) {
196: is = excludesFile.toURL().openStream();
197: }
198:
199: if (is == null) {
200: throw new IllegalStateException(
201: "Cannot load excludes configuration file from jar");
202: }
203:
204: Document document = builder.parse(is);
205: Element root = document.getDocumentElement();
206: NodeList sections = root.getChildNodes();
207:
208: // Loop through child elements of root node looking for the <excludes> block
209: for (int i = 0; i < sections.getLength(); i++) {
210: if (sections.item(i) instanceof Element) {
211: Element curr = (Element) sections.item(i);
212: if ("excludes".equalsIgnoreCase(curr.getTagName())) {
213: loadExcludeUrls(curr.getChildNodes());
214: }
215: }
216: }
217: }
218:
219: /** Loop through children of 'page-parsers' element and add all 'parser' mappings. */
220: private void loadPageParsers(NodeList nodes) {
221: clearParserMappings();
222: for (int i = 0; i < nodes.getLength(); i++) {
223: if (nodes.item(i) instanceof Element) {
224: Element curr = (Element) nodes.item(i);
225:
226: if ("parser".equalsIgnoreCase(curr.getTagName())) {
227: String className = curr.getAttribute("class");
228: String contentType = curr
229: .getAttribute("content-type");
230: mapParser(contentType, className);
231: }
232: }
233: }
234: }
235:
236: private void loadDecoratorMappers(NodeList nodes) {
237: clearDecoratorMappers();
238: Properties emptyProps = new Properties();
239:
240: pushDecoratorMapper(
241: "com.opensymphony.module.sitemesh.mapper.NullDecoratorMapper",
242: emptyProps);
243:
244: // note, this works from the bottom node up.
245: for (int i = nodes.getLength() - 1; i > 0; i--) {
246: if (nodes.item(i) instanceof Element) {
247: Element curr = (Element) nodes.item(i);
248: if ("mapper".equalsIgnoreCase(curr.getTagName())) {
249: String className = curr.getAttribute("class");
250: Properties props = new Properties();
251: // build properties from <param> tags.
252: NodeList children = curr.getChildNodes();
253: for (int j = 0; j < children.getLength(); j++) {
254: if (children.item(j) instanceof Element) {
255: Element currC = (Element) children.item(j);
256: if ("param".equalsIgnoreCase(currC
257: .getTagName())) {
258: String value = currC
259: .getAttribute("value");
260: props.put(currC.getAttribute("name"),
261: replaceProperties(value));
262: }
263: }
264: }
265: // add mapper
266: pushDecoratorMapper(className, props);
267: }
268: }
269: }
270:
271: pushDecoratorMapper(
272: "com.opensymphony.module.sitemesh.mapper.InlineDecoratorMapper",
273: emptyProps);
274: }
275:
276: /**
277: * Reads in all the url patterns to exclude from decoration.
278: */
279: private void loadExcludeUrls(NodeList nodes) {
280: clearExcludeUrls();
281: for (int i = 0; i < nodes.getLength(); i++) {
282: if (nodes.item(i) instanceof Element) {
283: Element p = (Element) nodes.item(i);
284: if ("pattern".equalsIgnoreCase(p.getTagName())
285: || "url-pattern".equalsIgnoreCase(p
286: .getTagName())) {
287: Text patternText = (Text) p.getFirstChild();
288: if (patternText != null) {
289: String pattern = patternText.getData().trim();
290: if (pattern != null) {
291: addExcludeUrl(pattern);
292: }
293: }
294: }
295: }
296: }
297: }
298:
299: /** Check if configuration file has been modified, and if so reload it. */
300: public void refresh() {
301: if (configFile != null
302: && configLastModified != configFile.lastModified())
303: loadConfig();
304: }
305:
306: /**
307: * Replaces any properties that appear in the supplied string
308: * with their actual values
309: *
310: * @param str the string to replace the properties in
311: * @return the same string but with any properties expanded out to their
312: * actual values
313: */
314: private String replaceProperties(String str) {
315: Set props = configProps.entrySet();
316: for (Iterator it = props.iterator(); it.hasNext();) {
317: Map.Entry entry = (Map.Entry) it.next();
318: String key = (String) entry.getKey();
319: int idx;
320: while ((idx = str.indexOf(key)) >= 0) {
321: StringBuffer buf = new StringBuffer(100);
322: buf.append(str.substring(0, idx));
323: buf.append(entry.getValue());
324: buf.append(str.substring(idx + key.length()));
325: str = buf.toString();
326: }
327: }
328: return str;
329: }
330: }
|