001: /*
002: * Copyright 2006-2007 Dan Shellman
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.iscreen.impl.xml;
017:
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.util.ArrayList;
021: import java.util.HashSet;
022: import java.util.LinkedHashSet;
023: import java.util.List;
024: import java.util.Set;
025:
026: import javax.xml.parsers.DocumentBuilder;
027: import javax.xml.parsers.DocumentBuilderFactory;
028: import javax.xml.parsers.ParserConfigurationException;
029: import org.xml.sax.SAXException;
030: import org.w3c.dom.Document;
031: import org.w3c.dom.Element;
032: import org.w3c.dom.Node;
033: import org.w3c.dom.NodeList;
034:
035: import org.iscreen.ConfigurationException;
036:
037: /**
038: * Parses XML configuration files. This is the parser for the default
039: * XML configuration file format. It requires an instance of the
040: * OgnlXmlServiceFactory in order to work properly. It calls back into
041: * the factory to notify it of configuration elements. This parser
042: * uses DOM (not SAX), though it treats the service factory more like
043: * SAX in that it notifies it of config events.
044: *
045: *
046: * @author Shellman, Dan
047: */
048: public class XmlParser {
049: private static final String ATTR_NAMESPACE = "namespace";
050: private static final String ATTR_DEFAULT_RESOURCE = "default-resource";
051: private static final String ATTR_INCLUDE_FILE = "file";
052: private static final String ATTR_ID = "id";
053: private static final String ATTR_KEY = "key";
054: private static final String ATTR_RESOURCE_FILE = "file";
055: private static final String ATTR_REF = "ref";
056: private static final String ATTR_CLASS_NAME = "class";
057: private static final String ATTR_RESOURCE_REF = "resource-id";
058: private static final String ATTR_FROM = "from";
059: private static final String ATTR_TO = "to";
060: private static final String ATTR_PROPERTY = "property";
061: private static final String ATTR_SERVICE_REF = "service-id";
062: private static final String ATTR_FAIL_FAST = "fail-fast";
063: private static final String ATTR_IF_EXP = "if";
064: private static final String ATTR_ITERATE_EXP = "iterate";
065: private static final String ATTR_MAP_EXP = "map";
066: private static final String ATTR_NAME = "name";
067:
068: private static final String EL_ROOT = "validation-root";
069: private static final String EL_INCLUDE = "include";
070: private static final String EL_VALIDATOR = "validator";
071: private static final String EL_VALIDATION_SET = "validation-set";
072: private static final String EL_USE_VALIDATOR = "use-validator";
073: private static final String EL_USE_VALIDATION_SET = "use-validation-set";
074: private static final String EL_MAPPING = "mapping";
075: private static final String EL_RESOURCE = "resource";
076: private static final String EL_MESSAGE = "message";
077: private static final String EL_RESOURCE_FILE = "resource-file";
078: private static final String EL_LABEL = "label";
079: private static final String EL_CONSTRAINT = "constraint";
080: private static final String EL_FAILURE = "failure";
081: private static final String EL_DOC = "doc";
082:
083: private XmlServiceFactory factoryCallback;
084: private String namespace;
085: private String defaultResource;
086: private String location;
087: private PositionContext position;
088:
089: /**
090: * Default constructor.
091: */
092: public XmlParser(XmlServiceFactory factory) {
093: factoryCallback = factory;
094: } //end XmlParser()
095:
096: /**
097: * Parses the configuration file and calls back into the provided
098: * factory for configuration elements.
099: *
100: * @param configLocation The classpath-based location of the config file.
101: */
102: public void parse(String configLocation) {
103: Document doc;
104: Element root;
105: NodeList nodeList;
106:
107: location = configLocation;
108: position = new PositionContext(location);
109: doc = getDocument(getInput(configLocation));
110: root = doc.getDocumentElement();
111: if (!root.getTagName().equals(EL_ROOT)) {
112: throw new XmlConfigurationException(position,
113: "Element named " + EL_ROOT, "Element named "
114: + root.getTagName());
115: }
116:
117: position.pushNode(EL_ROOT);
118: namespace = root.getAttribute(ATTR_NAMESPACE);
119: if (namespace == null || namespace.trim().equals("")) {
120: throw new XmlConfigurationException(position,
121: "Attribute named " + ATTR_NAMESPACE,
122: "No namespace defined");
123: }
124:
125: position.pushAttr(ATTR_NAMESPACE, namespace);
126: defaultResource = root.getAttribute(ATTR_DEFAULT_RESOURCE);
127: defaultResource = buildRef(defaultResource);
128: if (defaultResource != null) {
129: position.pushAttr(ATTR_DEFAULT_RESOURCE, defaultResource);
130: }
131:
132: nodeList = root.getChildNodes();
133: for (int i = 0; i < nodeList.getLength(); i++) {
134: Node node;
135: Element element;
136:
137: node = nodeList.item(i);
138: if (node.getNodeType() == Node.ELEMENT_NODE) {
139: String tagName;
140:
141: element = (Element) node;
142: tagName = element.getTagName();
143:
144: if (EL_INCLUDE.equals(tagName)) {
145: position.pushNode(EL_INCLUDE);
146: handleInclude(element);
147: position.popNode();
148: } else if (EL_RESOURCE.equals(tagName)) {
149: position.pushNode(EL_RESOURCE);
150: handleResource(element);
151: position.popNode();
152: } else if (EL_VALIDATOR.equals(tagName)) {
153: position.pushNode(EL_VALIDATOR);
154: handleValidator(element);
155: position.popNode();
156: } else if (EL_VALIDATION_SET.equals(tagName)) {
157: position.pushNode(EL_VALIDATION_SET);
158: handleValidationSet(element);
159: position.popNode();
160: } else {
161: throw new XmlConfigurationException(position, null,
162: "Invalid element named " + tagName);
163: }
164: }
165: }
166: } //end parse()
167:
168: // ***
169: // Protected methods
170: // ***
171:
172: /**
173: * Given a "location" which is a classpath-based resource, return an
174: * InputStream to it.
175: *
176: * @param classpathLocation The location of the input (must be available
177: * on the classpath).
178: *
179: * @return Returns an InputStream of the resource.
180: */
181: protected InputStream getInput(String classpathLocation) {
182: InputStream stream;
183:
184: stream = Thread.currentThread().getContextClassLoader()
185: .getResourceAsStream(classpathLocation);
186: if (stream == null) {
187: throw new XmlConfigurationException(position, null,
188: "Unable to load/locate configuration file");
189: }
190:
191: return stream;
192: } //end getInput()
193:
194: /**
195: * Constructs a DOM object that represents the stream provided. The Document
196: * represents the XML of the stream.
197: *
198: * @param stream The input stream of the XML document.
199: *
200: * @return Returns the Document representing the DOM of the XML.
201: */
202: protected Document getDocument(InputStream stream) {
203: Document doc;
204: DocumentBuilderFactory factory;
205: DocumentBuilder builder;
206:
207: try {
208: factory = DocumentBuilderFactory.newInstance();
209: factory.setCoalescing(true);
210: factory.setIgnoringComments(true);
211: factory.setIgnoringElementContentWhitespace(true);
212: factory.setExpandEntityReferences(true);
213: factory.setValidating(false);
214: builder = factory.newDocumentBuilder();
215: doc = builder.parse(stream);
216: } catch (IOException e) {
217: throw new XmlConfigurationException(position, null,
218: "Error loading/reading XML file", e);
219: } catch (SAXException e) {
220: throw new XmlConfigurationException(position, null,
221: "Error parsing XML file", e);
222: } catch (ParserConfigurationException e) {
223: throw new XmlConfigurationException(position, null,
224: "Error parsing XML file", e);
225: }
226:
227: return doc;
228: } //end getDocument()
229:
230: // ***
231: // Private methods
232: // ***
233:
234: private void handleInclude(Element root) {
235: String includeFile;
236:
237: includeFile = root.getAttribute(ATTR_INCLUDE_FILE);
238: if (includeFile == null || includeFile.trim().equals("")) {
239: throw new XmlConfigurationException(position,
240: "Attribute named " + ATTR_INCLUDE_FILE,
241: "Attribute not defined");
242: }
243:
244: position.pushAttr(ATTR_INCLUDE_FILE, includeFile);
245: factoryCallback.registerInclude(includeFile);
246: } //end handleInclude()
247:
248: private void handleResource(Element root) {
249: String id;
250: String ref;
251: NodeList children;
252: Set messages = new HashSet();
253: Set files = new LinkedHashSet();
254:
255: id = buildId(root.getAttribute(ATTR_ID));
256: position.pushAttr(ATTR_ID, id);
257: ref = buildRef(root.getAttribute(ATTR_REF));
258: if (ref != null) {
259: position.pushAttr(ATTR_REF, ref);
260: }
261:
262: children = root.getChildNodes();
263: for (int i = 0; i < children.getLength(); i++) {
264: Element el;
265:
266: if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
267: el = (Element) children.item(i);
268: } else {
269: //It's probably an empty text node, so ignore it.
270: continue;
271: }
272:
273: if (EL_MESSAGE.equals(el.getTagName())) {
274: XmlConfigMessage msg;
275:
276: msg = new XmlConfigMessage();
277: msg.setKey(el.getAttribute(ATTR_KEY));
278: msg.setValue(getElementContent(el));
279:
280: messages.add(msg);
281: } else if (EL_RESOURCE_FILE.equals(el.getTagName())) {
282: files.add(el.getAttribute(ATTR_RESOURCE_FILE));
283: } else {
284: throw new XmlConfigurationException(position,
285: "Element named " + EL_RESOURCE_FILE + " or "
286: + EL_MESSAGE, "Invalid element named "
287: + el.getTagName());
288: }
289: }
290:
291: try {
292: factoryCallback.registerResource(id, ref, messages, files);
293: } catch (ConfigurationException e) {
294: throw new XmlConfigurationException(position, null, e
295: .getMessage(), e);
296: }
297: } //end handleResource()
298:
299: private void handleValidator(Element root) {
300: String id;
301: String ref;
302: String className;
303: String localDefaultResource;
304: XmlConfigLabel label = null;
305: XmlConfigDoc doc = null;
306: Set mappings = new HashSet();
307: Set constraints = new HashSet();
308: Set failures = new HashSet();
309: Set services = new HashSet();
310: Element el;
311: Element[] elements;
312:
313: id = buildId(root.getAttribute(ATTR_ID));
314: position.pushAttr(ATTR_ID, id);
315: ref = buildRef(root.getAttribute(ATTR_REF));
316: if (ref != null) {
317: position.pushAttr(ATTR_REF, ref);
318: }
319:
320: className = root.getAttribute(ATTR_CLASS_NAME);
321: if (className != null) {
322: position.pushAttr(ATTR_CLASS_NAME, className);
323: }
324:
325: localDefaultResource = buildRef(root
326: .getAttribute(ATTR_DEFAULT_RESOURCE));
327:
328: //Get the label, if there is one.
329: el = getChildElement(root, EL_LABEL);
330: if (el != null) {
331: label = new XmlConfigLabel();
332: label.setResourceId(buildRef(el
333: .getAttribute(ATTR_RESOURCE_REF)));
334: label.setResourceKey(el.getAttribute(ATTR_KEY));
335: label.setValue(getElementContent(el));
336: label.setId(el.getAttribute(ATTR_ID));
337: }
338:
339: //Get the mappings
340: elements = getChildElements(root, EL_MAPPING);
341: if (elements != null) {
342: for (int i = 0; i < elements.length; i++) {
343: XmlConfigMapping mapping;
344:
345: mapping = new XmlConfigMapping();
346: mapping.setFrom(elements[i].getAttribute(ATTR_FROM));
347: mapping.setTo(elements[i].getAttribute(ATTR_TO));
348:
349: mappings.add(mapping);
350: }
351: }
352:
353: //Get the constraints
354: elements = getChildElements(root, EL_CONSTRAINT);
355: if (elements != null) {
356: for (int i = 0; i < elements.length; i++) {
357: XmlConfigConstraint constraint;
358:
359: constraint = new XmlConfigConstraint();
360: constraint.setProperty(elements[i]
361: .getAttribute(ATTR_PROPERTY));
362: constraint.setValue(getElementContent(elements[i]));
363: constraint.setServiceId(elements[i]
364: .getAttribute(ATTR_SERVICE_REF));
365:
366: constraints.add(constraint);
367: }
368: }
369:
370: //Get the failures
371: elements = getChildElements(root, EL_FAILURE);
372: if (elements != null) {
373: for (int i = 0; i < elements.length; i++) {
374: XmlConfigFailure failure;
375:
376: failure = new XmlConfigFailure();
377: failure.setResource(buildRef(elements[i]
378: .getAttribute(ATTR_RESOURCE_REF)));
379: failure.setKey(elements[i].getAttribute(ATTR_KEY));
380: failure.setMessage(getElementContent(elements[i]));
381: failure.setProperty(elements[i]
382: .getAttribute(ATTR_PROPERTY));
383:
384: failures.add(failure);
385: }
386: }
387:
388: //Get the doc tag (for documentation)
389: el = getChildElement(root, EL_DOC);
390: if (el != null) {
391: doc = new XmlConfigDoc();
392: doc.setContent(getElementContent(el));
393: }
394:
395: try {
396: factoryCallback.registerValidator(defaultResource, id, ref,
397: className, localDefaultResource, label, doc,
398: mappings, constraints, failures);
399: } catch (ConfigurationException e) {
400: throw new XmlConfigurationException(position, null, e
401: .getMessage(), e);
402: }
403: } //end handleValidator()
404:
405: private void handleValidationSet(Element root) {
406: String id;
407: String localDefaultResource;
408: NodeList children;
409:
410: id = buildId(root.getAttribute(ATTR_ID));
411: position.pushAttr(ATTR_ID, id);
412: localDefaultResource = buildRef(root
413: .getAttribute(ATTR_DEFAULT_RESOURCE));
414:
415: factoryCallback.registerValidationSet(id);
416:
417: children = root.getChildNodes();
418: for (int i = 0; i < children.getLength(); i++) {
419: Element el;
420:
421: if (children.item(i).getNodeType() != Node.ELEMENT_NODE) {
422: //Probably just an empty text node.
423: continue;
424: }
425:
426: el = (Element) children.item(i);
427: if (EL_USE_VALIDATOR.equals(el.getTagName())) {
428: position.pushNode(EL_USE_VALIDATOR);
429: handleUseValidator(id, localDefaultResource, el);
430: position.popNode();
431: } else if (EL_USE_VALIDATION_SET.equals(el.getTagName())) {
432: position.pushNode(EL_USE_VALIDATION_SET);
433: handleUseValidationSet(id, localDefaultResource, el);
434: position.popNode();
435: } else {
436: throw new XmlConfigurationException(position,
437: "Element named " + EL_USE_VALIDATOR + " or "
438: + EL_USE_VALIDATION_SET,
439: "Invalid element found named "
440: + el.getTagName());
441: }
442: }
443: } //end handleValidationSet()
444:
445: private void handleUseValidator(String setId,
446: String localDefaultResource, Element root) {
447: String ref;
448: String failFast;
449: String name;
450: XmlConfigLabel label = null;
451: XmlConfigDoc doc = null;
452: Set mappings = new HashSet();
453: Set constraints = new HashSet();
454: Set failures = new HashSet();
455: Set services = new HashSet();
456: Element el;
457: Element[] elements;
458:
459: ref = buildRef(root.getAttribute(ATTR_REF));
460: position.pushAttr(ATTR_REF, ref);
461: failFast = root.getAttribute(ATTR_FAIL_FAST);
462: name = root.getAttribute(ATTR_NAME);
463:
464: //Get the label, if there is one.
465: el = getChildElement(root, EL_LABEL);
466: if (el != null) {
467: label = new XmlConfigLabel();
468: label.setResourceId(buildRef(el
469: .getAttribute(ATTR_RESOURCE_REF)));
470: label.setResourceKey(el.getAttribute(ATTR_KEY));
471: label.setValue(getElementContent(el));
472: label.setId(el.getAttribute(ATTR_ID));
473: }
474:
475: //Get the mappings
476: elements = getChildElements(root, EL_MAPPING);
477: if (elements != null) {
478: for (int i = 0; i < elements.length; i++) {
479: XmlConfigMapping mapping;
480:
481: mapping = new XmlConfigMapping();
482: mapping.setFrom(elements[i].getAttribute(ATTR_FROM));
483: mapping.setTo(elements[i].getAttribute(ATTR_TO));
484:
485: mappings.add(mapping);
486: }
487: }
488:
489: //Get the constraints
490: elements = getChildElements(root, EL_CONSTRAINT);
491: if (elements != null) {
492: for (int i = 0; i < elements.length; i++) {
493: XmlConfigConstraint constraint;
494:
495: constraint = new XmlConfigConstraint();
496: constraint.setProperty(elements[i]
497: .getAttribute(ATTR_PROPERTY));
498: constraint.setValue(getElementContent(elements[i]));
499: constraint.setServiceId(elements[i]
500: .getAttribute(ATTR_SERVICE_REF));
501:
502: constraints.add(constraint);
503: }
504: }
505:
506: //Get the failures
507: elements = getChildElements(root, EL_FAILURE);
508: if (elements != null) {
509: for (int i = 0; i < elements.length; i++) {
510: XmlConfigFailure failure;
511:
512: failure = new XmlConfigFailure();
513: failure.setResource(buildRef(elements[i]
514: .getAttribute(ATTR_RESOURCE_REF)));
515: failure.setKey(elements[i].getAttribute(ATTR_KEY));
516: failure.setMessage(getElementContent(elements[i]));
517: failure.setProperty(elements[i]
518: .getAttribute(ATTR_PROPERTY));
519:
520: failures.add(failure);
521: }
522: }
523:
524: //Get the doc tag (for documentation)
525: el = getChildElement(root, EL_DOC);
526: if (el != null) {
527: doc = new XmlConfigDoc();
528: doc.setContent(getElementContent(el));
529: }
530:
531: try {
532: factoryCallback.addValidatorToSet(setId, defaultResource,
533: localDefaultResource, ref, getBoolean(failFast),
534: name, label, doc, mappings, constraints, failures);
535: } catch (ConfigurationException e) {
536: throw new XmlConfigurationException(position, null, e
537: .getMessage(), e);
538: }
539: } //end handleUseValidator()
540:
541: private void handleUseValidationSet(String setId,
542: String localDefaultResource, Element root) {
543: String ref;
544: String failFast;
545:
546: ref = buildRef(root.getAttribute(ATTR_REF));
547: position.pushAttr(ATTR_REF, ref);
548: failFast = root.getAttribute(ATTR_FAIL_FAST);
549:
550: try {
551: factoryCallback.addValidationSetToSet(setId, ref,
552: getBoolean(failFast), root.getAttribute(ATTR_NAME),
553: root.getAttribute(ATTR_IF_EXP), root
554: .getAttribute(ATTR_ITERATE_EXP), root
555: .getAttribute(ATTR_MAP_EXP));
556: } catch (ConfigurationException e) {
557: throw new XmlConfigurationException(position, null, e
558: .getMessage(), e);
559: }
560: } //end handleUseValidationSet()
561:
562: private String buildId(String id) {
563: StringBuffer buf = new StringBuffer();
564:
565: buf.append(namespace);
566: buf.append('.');
567: buf.append(id);
568:
569: return buf.toString();
570: } //end buildId()
571:
572: private String buildRef(String ref) {
573: if (ref != null && !ref.trim().equals("")) {
574: if (ref.indexOf('.') == -1) {
575: return buildId(ref);
576: } else {
577: return ref;
578: }
579: }
580:
581: return null;
582: } //end buildRef()
583:
584: private Element getChildElement(Element root, String tagName) {
585: Element el = null;
586: NodeList nodes;
587:
588: nodes = root.getChildNodes();
589: for (int i = 0; i < nodes.getLength(); i++) {
590: Node node;
591:
592: node = nodes.item(i);
593: if (node.getNodeType() == Node.ELEMENT_NODE
594: && ((Element) node).getTagName().equals(tagName)) {
595: el = (Element) node;
596: break;
597: }
598: }
599:
600: return el;
601: } //end getChildElement()
602:
603: private Element[] getChildElements(Element root, String tagName) {
604: Element[] elements = null;
605: List elList = new ArrayList();
606: NodeList nodes;
607:
608: nodes = root.getChildNodes();
609: for (int i = 0; i < nodes.getLength(); i++) {
610: Node node;
611:
612: node = nodes.item(i);
613: if (node.getNodeType() == Node.ELEMENT_NODE
614: && ((Element) node).getTagName().equals(tagName)) {
615: elList.add(node);
616: }
617: }
618:
619: if (elList.size() > 0) {
620: elements = new Element[elList.size()];
621: elList.toArray(elements);
622: }
623:
624: return elements;
625: } //end getChildElements()
626:
627: private String getElementContent(Element el) {
628: StringBuffer buf = new StringBuffer();
629: NodeList nodes;
630:
631: nodes = el.getChildNodes();
632: for (int i = 0; i < nodes.getLength(); i++) {
633: Node node;
634:
635: node = nodes.item(i);
636: if (node.getNodeType() == Node.TEXT_NODE) {
637: buf.append(node.getNodeValue());
638: } else {
639: //We shouldn't have anything here.
640: //TODO: decide whether to throw an exception or not.
641: }
642: }
643:
644: //Only return a valid string if there's actually something there.
645: if (buf.length() > 0) {
646: return buf.toString();
647: }
648:
649: return null;
650: } //end getElementContent()
651:
652: private boolean getBoolean(String boolStr) {
653: boolean flag = false;
654:
655: if (boolStr != null
656: && (boolStr.equalsIgnoreCase("true") || boolStr
657: .equalsIgnoreCase("yes"))) {
658: flag = true;
659: }
660:
661: return flag;
662: } //end getBoolean()
663: } //end XmlParser
|