001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package de.schlund.pfixxml.config;
020:
021: import java.io.IOException;
022: import java.util.ArrayList;
023: import java.util.Properties;
024:
025: import javax.xml.namespace.QName;
026: import javax.xml.parsers.DocumentBuilderFactory;
027: import javax.xml.parsers.ParserConfigurationException;
028: import javax.xml.xpath.XPath;
029: import javax.xml.xpath.XPathConstants;
030: import javax.xml.xpath.XPathExpressionException;
031: import javax.xml.xpath.XPathFactory;
032: import javax.xml.xpath.XPathVariableResolver;
033:
034: import org.w3c.dom.Document;
035: import org.w3c.dom.Node;
036: import org.xml.sax.Attributes;
037: import org.xml.sax.InputSource;
038: import org.xml.sax.Locator;
039: import org.xml.sax.SAXException;
040: import org.xml.sax.SAXParseException;
041: import org.xml.sax.helpers.DefaultHandler;
042:
043: /**
044: * Handler for conditional processing. This handler takes care that only the
045: * parts matching certain conditions are parsed, where applicable. Please note
046: * that this implementation is NOT thread-safe,
047: *
048: * @author Sebastian Marsching <sebastian.marsching@1und1.de>
049: */
050: public class CustomizationHandler extends DefaultHandler {
051: private final static String DEFAULT_CUS_NS = "http://www.schlund.de/pustefix/customize";
052:
053: private final static String DEFAULT_CHOOSE_ELEMENTNAME = "choose";
054:
055: private final static String DEFAULT_DOCROOT_ELEMENTNAME = "docroot";
056:
057: private final static String DEFAULT_FQDN_ELEMENTNAME = "fqdn";
058:
059: private final static String DEFAULT_UID_ELEMENTNAME = "uid";
060:
061: private final static String DEFAULT_MACHINE_ELEMENTNAME = "machine";
062:
063: private class ParsingInfo implements Cloneable {
064: private boolean parsingActive = true;
065:
066: private boolean inChoose = false;
067:
068: private boolean foundActiveTree = false;
069:
070: private boolean triggerEndElement = false;
071:
072: public Object clone() {
073: try {
074: return super .clone();
075: } catch (CloneNotSupportedException e) {
076: throw new RuntimeException(e);
077: }
078: }
079: }
080:
081: private class PropertiesVariableResolver implements
082: XPathVariableResolver {
083:
084: private Properties props;
085:
086: public PropertiesVariableResolver(Properties props) {
087: this .props = props;
088: }
089:
090: public Object resolveVariable(QName variableName) {
091: return props.getProperty(variableName.getLocalPart());
092: }
093:
094: }
095:
096: private DefaultHandler targetHandler;
097:
098: private ArrayList<ParsingInfo> stack;
099:
100: private Node dummyNode;
101:
102: private XPathFactory xpfac;
103:
104: private String docroot;
105:
106: private String fqdn;
107:
108: private String machine;
109:
110: private String uid;
111:
112: private String namespace;
113:
114: private String namespaceContent;
115:
116: private String elementChoose = DEFAULT_CHOOSE_ELEMENTNAME;
117:
118: private String elementDocroot = DEFAULT_DOCROOT_ELEMENTNAME;
119:
120: private String elementFqdn = DEFAULT_FQDN_ELEMENTNAME;
121:
122: private String elementMachine = DEFAULT_MACHINE_ELEMENTNAME;
123:
124: private String elementUid = DEFAULT_UID_ELEMENTNAME;
125:
126: private StringBuffer xmlPath = new StringBuffer();
127:
128: private String[] matchingPaths = null;
129:
130: /**
131: * Creates a new customization handler with default configuration
132: *
133: * @param targetHandler Handler XML events are forwarded to
134: */
135: public CustomizationHandler(DefaultHandler targetHandler) {
136: this (targetHandler, DEFAULT_CUS_NS);
137: }
138:
139: /**
140: * Creates a new customization handler using the supplied arguments
141: *
142: * @param targetHandler Handler events are forwared to
143: * @param namespace URI specifying the namespace to expect the customization
144: * elements within
145: */
146: public CustomizationHandler(DefaultHandler targetHandler,
147: String namespace) {
148: this (targetHandler, BuildTimeProperties.getProperties(),
149: namespace, namespace);
150: }
151:
152: /**
153: * Creates a new customization handler using the supplied arguments
154: *
155: * @param targetHandler Handler events are forwared to
156: * @param namespace URI specifying the namespace to expect the customization
157: * elements within
158: * @param namespaceContent URI expected for docroot and fqdn tag, can be
159: * <code>null</code> if this tags should not be
160: * matched
161: */
162: public CustomizationHandler(DefaultHandler targetHandler,
163: String namespace, String namespaceContent) {
164: this (targetHandler, BuildTimeProperties.getProperties(),
165: namespace, namespaceContent);
166: }
167:
168: /**
169: * Creates a new customization handler using the supplied arguments
170: *
171: * @param targetHandler Handler events are forwared to
172: * @param namespace URI specifying the namespace to expect the customization
173: * elements within
174: * @param namespaceContent URI expected for docroot and fqdn tag
175: * @param pathsToMatch Array containing paths where the choose element is
176: * expected (e.g. "/root/element/otherlement")
177: */
178: public CustomizationHandler(DefaultHandler targetHandler,
179: String namespace, String namespaceContent,
180: String[] pathsToMatch) {
181: this (targetHandler, BuildTimeProperties.getProperties(),
182: namespace, namespaceContent);
183: this .matchingPaths = pathsToMatch;
184: }
185:
186: /**
187: * Creates a new customization handler using the supplied arguments
188: *
189: * @param targetHandler Handler events are forwared to
190: * @param buildTimeProps Properties to read variables from
191: * @param namespace URI specifying the namespace to expect the customization
192: * elements within
193: * @param namespaceContent URI expected for docroot and fqdn tag, can be
194: * <code>null</code> if this tags should not be
195: * matched
196: */
197: public CustomizationHandler(DefaultHandler targetHandler,
198: Properties buildTimeProps, String namespace,
199: String namespaceContent) {
200: this .namespace = namespace;
201: this .namespaceContent = namespaceContent;
202: this .targetHandler = targetHandler;
203: try {
204: Document doc = DocumentBuilderFactory.newInstance()
205: .newDocumentBuilder().newDocument();
206: doc.appendChild(doc.createElement("dummyElement"));
207: this .dummyNode = doc.getDocumentElement();
208: } catch (ParserConfigurationException e) {
209: throw new RuntimeException(e);
210: }
211: Properties props = new Properties(buildTimeProps);
212: String docroot = GlobalConfig.getDocroot() + "/";
213: if (docroot != null) {
214: props.setProperty("docroot", docroot);
215: }
216: this .xpfac = XPathFactory.newInstance();
217: this .xpfac
218: .setXPathVariableResolver(new PropertiesVariableResolver(
219: props));
220: this .docroot = docroot;
221: this .fqdn = props.getProperty("fqdn");
222: this .machine = props.getProperty("machine");
223: this .uid = props.getProperty("uid");
224: }
225:
226: private ParsingInfo peekParsingInfo() {
227: return (ParsingInfo) this .stack.get(0);
228: }
229:
230: private void popParsingInfo() {
231: this .stack.remove(0);
232: }
233:
234: private void pushParsingInfo() {
235: this .stack.add(0, (ParsingInfo) peekParsingInfo().clone());
236: this .peekParsingInfo().triggerEndElement = false;
237: }
238:
239: public void setDocumentLocator(Locator locator) {
240: this .targetHandler.setDocumentLocator(locator);
241: }
242:
243: public void startDocument() throws SAXException {
244: // Do initialization
245: this .stack = new ArrayList<ParsingInfo>();
246: this .stack.add(0, new ParsingInfo());
247:
248: this .targetHandler.startDocument();
249: }
250:
251: public void endDocument() throws SAXException {
252: this .targetHandler.endDocument();
253: }
254:
255: public void startPrefixMapping(String prefix, String uri)
256: throws SAXException {
257: if (this .peekParsingInfo().parsingActive) {
258: this .targetHandler.startPrefixMapping(prefix, uri);
259: }
260: }
261:
262: public void endPrefixMapping(String prefix) throws SAXException {
263: if (this .peekParsingInfo().parsingActive) {
264: this .targetHandler.endPrefixMapping(prefix);
265: }
266: }
267:
268: public void startElement(String uri, String localName,
269: String qName, Attributes atts) throws SAXException {
270: // Always push stack for new element
271: ParsingInfo parentInfo = this .peekParsingInfo();
272: this .pushParsingInfo();
273:
274: if (this .peekParsingInfo().parsingActive) {
275: if (this .peekParsingInfo().inChoose) {
276: if (localName.equals("when")
277: && uri.equals(this .namespace)) {
278: if (!this .peekParsingInfo().foundActiveTree) {
279: String testExp = atts.getValue("test");
280: if (testExp == null) {
281: throw new SAXException(
282: "Element \"when\" must have \"test\" attribute set!");
283: }
284: if (this .evalXPathExpression(testExp)) {
285: parentInfo.foundActiveTree = true;
286: this .peekParsingInfo().inChoose = false;
287: } else {
288: this .peekParsingInfo().parsingActive = false;
289: this .peekParsingInfo().inChoose = false;
290: }
291: } else {
292: this .peekParsingInfo().parsingActive = false;
293: this .peekParsingInfo().inChoose = false;
294: }
295: } else if (localName.equals("otherwise")
296: && uri.equals(this .namespace)) {
297: if (!this .peekParsingInfo().foundActiveTree) {
298: parentInfo.foundActiveTree = true;
299: this .peekParsingInfo().inChoose = false;
300: } else {
301: this .peekParsingInfo().parsingActive = false;
302: this .peekParsingInfo().inChoose = false;
303: }
304: } else {
305: this .peekParsingInfo().inChoose = false;
306: throw new SAXException(
307: "Illegal element \""
308: + qName
309: + "\": Only elements \"when\" and \"otherwise\" are allowed as children of element \""
310: + this .elementChoose + "\"!");
311: }
312: } else if (localName.equals(this .elementChoose)
313: && uri.equals(this .namespace)
314: && currentPathMatches()) {
315: this .peekParsingInfo().inChoose = true;
316: this .peekParsingInfo().foundActiveTree = false;
317: } else if (this .namespaceContent != null
318: && localName.equals(this .elementDocroot)
319: && uri.equals(this .namespaceContent)) {
320: if (docroot != null) {
321: targetHandler.characters(docroot.toCharArray(), 0,
322: docroot.length());
323: } else {
324: throw new SAXException(
325: "Element \""
326: + qName
327: + "\" is not allowed in packed WAR mode. Please change your configuration to use relative paths instead.");
328: }
329: } else if (this .namespaceContent != null
330: && localName.equals(this .elementFqdn)
331: && uri.equals(this .namespaceContent)) {
332: targetHandler.characters(fqdn.toCharArray(), 0, fqdn
333: .length());
334: } else if (this .namespaceContent != null
335: && localName.equals(this .elementMachine)
336: && uri.equals(this .namespaceContent)) {
337: targetHandler.characters(machine.toCharArray(), 0,
338: machine.length());
339: } else if (this .namespaceContent != null
340: && localName.equals(this .elementUid)
341: && uri.equals(this .namespaceContent)) {
342: targetHandler.characters(uid.toCharArray(), 0, uid
343: .length());
344: } else {
345: this .targetHandler.startElement(uri, localName, qName,
346: atts);
347: this .peekParsingInfo().triggerEndElement = true;
348: this .xmlPath.append("/");
349: this .xmlPath.append(localName);
350: }
351: }
352: }
353:
354: private boolean evalXPathExpression(String testExp)
355: throws SAXException {
356: XPath xpath = this .xpfac.newXPath();
357: try {
358: Boolean ret = (Boolean) xpath.evaluate(testExp,
359: this .dummyNode, XPathConstants.BOOLEAN);
360: return ret.booleanValue();
361: } catch (XPathExpressionException e) {
362: throw new SAXException("Invalid XPath expression: \""
363: + testExp + "\"", e);
364: }
365: }
366:
367: private boolean currentPathMatches() {
368: if (matchingPaths == null) {
369: return true;
370: }
371: for (int i = 0; i < matchingPaths.length; i++) {
372: if (xmlPath.toString().equals(matchingPaths[i])) {
373: return true;
374: }
375: }
376: return false;
377: }
378:
379: public void endElement(String uri, String localName, String qName)
380: throws SAXException {
381: if (this .peekParsingInfo().triggerEndElement) {
382: // Pass through
383: this .targetHandler.endElement(uri, localName, qName);
384: this .xmlPath.delete(xmlPath.lastIndexOf("/"), xmlPath
385: .length());
386: }
387: // Always pop stack
388: this .popParsingInfo();
389: }
390:
391: public void characters(char[] ch, int start, int length)
392: throws SAXException {
393: if (this .peekParsingInfo().parsingActive
394: && !this .peekParsingInfo().inChoose) {
395: this .targetHandler.characters(ch, start, length);
396: }
397: }
398:
399: public void ignorableWhitespace(char[] ch, int start, int length)
400: throws SAXException {
401: if (this .peekParsingInfo().parsingActive) {
402: this .targetHandler.ignorableWhitespace(ch, start, length);
403: }
404: }
405:
406: public void processingInstruction(String target, String data)
407: throws SAXException {
408: if (this .peekParsingInfo().parsingActive) {
409: this .targetHandler.processingInstruction(target, data);
410: }
411: }
412:
413: public void skippedEntity(String name) throws SAXException {
414: if (this .peekParsingInfo().parsingActive) {
415: this .targetHandler.skippedEntity(name);
416: }
417: }
418:
419: /*
420: * (non-Javadoc)
421: *
422: * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
423: */
424: public void error(SAXParseException e) throws SAXException {
425: if (this .peekParsingInfo().parsingActive) {
426: this .targetHandler.error(e);
427: } else {
428: super .fatalError(e);
429: }
430: }
431:
432: /*
433: * (non-Javadoc)
434: *
435: * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
436: */
437: public void fatalError(SAXParseException e) throws SAXException {
438: if (this .peekParsingInfo().parsingActive) {
439: this .targetHandler.fatalError(e);
440: } else {
441: super .fatalError(e);
442: }
443: }
444:
445: /*
446: * (non-Javadoc)
447: *
448: * @see org.xml.sax.helpers.DefaultHandler#notationDecl(java.lang.String,
449: * java.lang.String, java.lang.String)
450: */
451: public void notationDecl(String name, String publicId,
452: String systemId) throws SAXException {
453: if (this .peekParsingInfo().parsingActive) {
454: this .targetHandler.notationDecl(name, publicId, systemId);
455: } else {
456: super .notationDecl(name, publicId, systemId);
457: }
458: }
459:
460: /*
461: * (non-Javadoc)
462: *
463: * @see org.xml.sax.helpers.DefaultHandler#resolveEntity(java.lang.String,
464: * java.lang.String)
465: */
466: public InputSource resolveEntity(String publicId, String systemId)
467: throws IOException, SAXException {
468: if (this .peekParsingInfo().parsingActive) {
469: return this .targetHandler.resolveEntity(publicId, systemId);
470: } else {
471: return super .resolveEntity(publicId, systemId);
472: }
473: }
474:
475: /*
476: * (non-Javadoc)
477: *
478: * @see org.xml.sax.helpers.DefaultHandler#unparsedEntityDecl(java.lang.String,
479: * java.lang.String, java.lang.String, java.lang.String)
480: */
481: public void unparsedEntityDecl(String name, String publicId,
482: String systemId, String notationName) throws SAXException {
483: if (this .peekParsingInfo().parsingActive) {
484: this .targetHandler.unparsedEntityDecl(name, publicId,
485: systemId, notationName);
486: } else {
487: super .unparsedEntityDecl(name, publicId, systemId,
488: notationName);
489: }
490: }
491:
492: /*
493: * (non-Javadoc)
494: *
495: * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
496: */
497: public void warning(SAXParseException e) throws SAXException {
498: if (this.peekParsingInfo().parsingActive) {
499: this.targetHandler.warning(e);
500: } else {
501: super.warning(e);
502: }
503: }
504:
505: }
|