001: package org.drools.xml;
002:
003: /*
004: * Copyright 2005 JBoss Inc
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.io.BufferedInputStream;
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.Reader;
025: import java.net.URL;
026: import java.text.MessageFormat;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.LinkedList;
030: import java.util.ListIterator;
031: import java.util.Map;
032: import java.util.Set;
033:
034: import javax.xml.parsers.ParserConfigurationException;
035: import javax.xml.parsers.SAXParser;
036: import javax.xml.parsers.SAXParserFactory;
037:
038: import org.drools.lang.descr.PackageDescr;
039: import org.drools.lang.descr.RuleDescr;
040: import org.xml.sax.Attributes;
041: import org.xml.sax.EntityResolver;
042: import org.xml.sax.InputSource;
043: import org.xml.sax.Locator;
044: import org.xml.sax.SAXException;
045: import org.xml.sax.SAXNotRecognizedException;
046: import org.xml.sax.SAXParseException;
047: import org.xml.sax.helpers.DefaultHandler;
048:
049: /**
050: * <code>RuleSet</code> loader.
051: *
052: * Note you can override the default entity resolver by setting the System property of:
053: * <code>org.drools.io.EntityResolve</code> to your own custom entity resolver.
054: * This can be done using -Dorg.drools.io.EntityResolver=YourClassHere on the command line, for instance.
055: *
056: * @author <a href="mailto:bob@werken.com">bob mcwhirter </a>
057: */
058: public class XmlPackageReader extends DefaultHandler {
059: // ----------------------------------------------------------------------
060: // Constants
061: // ----------------------------------------------------------------------
062: public static final String ENTITY_RESOLVER_PROPERTY_NAME = "org.drools.io.EntityResolver";
063:
064: /** Namespace URI for the general tags. */
065: public static final String RULES_NAMESPACE_URI = "http://drools.org/rules";
066:
067: private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
068:
069: private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
070:
071: // ----------------------------------------------------------------------
072: // Instance members
073: // ----------------------------------------------------------------------
074: /** SAX parser. */
075: private SAXParser parser;
076:
077: /** isValidating */
078: private boolean isValidating = true;
079:
080: /** Locator for errors. */
081: private Locator locator;
082:
083: // private Map repo;
084:
085: /** Stack of configurations. */
086: private LinkedList configurationStack;
087:
088: /** Current configuration text. */
089: private StringBuffer characters;
090:
091: private Map handlers;
092:
093: private boolean lastWasEndElement;
094:
095: private LinkedList parents;
096:
097: private Object peer;
098:
099: private Object current;
100:
101: private PackageDescr packageDescr;
102:
103: private boolean inHandledRuleSubElement;
104:
105: private final MessageFormat message = new MessageFormat(
106: "({0}: {1}, {2}): {3}");
107:
108: private final Map namespaces = new HashMap();
109:
110: EntityResolver entityResolver;
111:
112: // ----------------------------------------------------------------------
113: // Constructors
114: // ----------------------------------------------------------------------
115:
116: /**
117: * Construct.
118: *
119: * <p>
120: * Uses the default JAXP SAX parser and the default classpath-based
121: * <code>DefaultSemanticModule</code>.
122: * </p>
123: */
124: public XmlPackageReader() {
125: // init
126: this .configurationStack = new LinkedList();
127: this .parents = new LinkedList();
128:
129: this .handlers = new HashMap();
130:
131: this .handlers.put("package", new PackageHandler(this ));
132: this .handlers.put("rule", new RuleHandler(this ));
133: this .handlers.put("query", new QueryHandler(this ));
134: this .handlers.put("attribute", null);
135: this .handlers.put("function", new FunctionHandler(this ));
136:
137: // Conditional Elements
138: this .handlers.put("lhs", new AndHandler(this ));
139:
140: this .handlers.put("and-restriction-connective",
141: new RestrictionConnectiveHandler(this ));
142:
143: this .handlers.put("or-restriction-connective",
144: new RestrictionConnectiveHandler(this ));
145:
146: this .handlers.put("and-conditional-element", new AndHandler(
147: this ));
148:
149: this .handlers
150: .put("or-conditional-element", new OrHandler(this ));
151:
152: this .handlers.put("and-constraint-connective", new AndHandler(
153: this ));
154: this .handlers.put("or-constraint-connective", new OrHandler(
155: this ));
156:
157: this .handlers.put("not", new NotHandler(this ));
158: this .handlers.put("exists", new ExistsHandler(this ));
159: this .handlers.put("eval", new EvalHandler(this ));
160: this .handlers.put("pattern", new PatternHandler(this ));
161:
162: this .handlers.put("from", new FromHandler(this ));
163: this .handlers.put("forall", new ForallHandler(this ));
164: this .handlers.put("collect", new CollectHandler(this ));
165: this .handlers.put("accumulate", new AccumulateHandler(this ));
166:
167: // Field Constraints
168: this .handlers.put("field-constraint",
169: new FieldConstraintHandler(this ));
170: this .handlers.put("literal-restriction",
171: new LiteralRestrictionHandler(this ));
172: this .handlers.put("variable-restriction",
173: new VariableRestrictionsHandler(this ));
174: this .handlers.put("predicate", new PredicateHandler(this ));
175:
176: this .handlers.put("return-value-restriction",
177: new ReturnValueRestrictionHandler(this ));
178: this .handlers.put("qualified-identifier-restriction",
179: new QualifiedIdentifierRestrictionHandler(this ));
180:
181: this .handlers.put("field-binding",
182: new FieldBindingHandler(this ));
183:
184: this .handlers.put("field-binding",
185: new FieldBindingHandler(this ));
186:
187: this .handlers.put("init", new AccumulateHelperHandler(this ));
188: this .handlers.put("action", new AccumulateHelperHandler(this ));
189: this .handlers.put("result", new AccumulateHelperHandler(this ));
190: this .handlers.put("reverse", new AccumulateHelperHandler(this ));
191:
192: this .handlers.put("external-function",
193: new AccumulateHelperHandler(this ));
194:
195: this .handlers.put("expression", new ExpressionHandler(this ));
196:
197: initEntityResolver();
198: }
199:
200: /**
201: * Construct.
202: *
203: * <p>
204: * Uses the default classpath-based <code>DefaultSemanticModule</code>.
205: * </p>
206: *
207: * @param parser
208: * The SAX parser.
209: */
210: public XmlPackageReader(final SAXParser parser) {
211: this .parser = parser;
212: }
213:
214: // ----------------------------------------------------------------------
215: // Instance methods
216: // ----------------------------------------------------------------------
217:
218: /**
219: * Read a <code>RuleSet</code> from a <code>Reader</code>.
220: *
221: * @param reader
222: * The reader containing the rule-set.
223: *
224: * @return The rule-set.
225: */
226: public PackageDescr read(final Reader reader) throws SAXException,
227: IOException {
228: return read(new InputSource(reader));
229: }
230:
231: /**
232: * Read a <code>RuleSet</code> from an <code>InputStream</code>.
233: *
234: * @param inputStream
235: * The input-stream containing the rule-set.
236: *
237: * @return The rule-set.
238: */
239: public PackageDescr read(final InputStream inputStream)
240: throws SAXException, IOException {
241: return read(new InputSource(inputStream));
242: }
243:
244: /**
245: * Read a <code>RuleSet</code> from an <code>InputSource</code>.
246: *
247: * @param in
248: * The rule-set input-source.
249: *
250: * @return The rule-set.
251: */
252: public PackageDescr read(final InputSource in) throws SAXException,
253: IOException {
254: SAXParser localParser = null;
255: if (this .parser == null) {
256: final SAXParserFactory factory = SAXParserFactory
257: .newInstance();
258: factory.setNamespaceAware(true);
259:
260: final String isValidatingString = System
261: .getProperty("drools.schema.validating");
262: if (System.getProperty("drools.schema.validating") != null) {
263: this .isValidating = Boolean
264: .getBoolean("drools.schema.validating");
265: }
266:
267: if (this .isValidating == true) {
268: factory.setValidating(true);
269: try {
270: localParser = factory.newSAXParser();
271: } catch (final ParserConfigurationException e) {
272: throw new RuntimeException(e.getMessage());
273: }
274:
275: try {
276: localParser.setProperty(
277: XmlPackageReader.JAXP_SCHEMA_LANGUAGE,
278: XmlPackageReader.W3C_XML_SCHEMA);
279: } catch (final SAXNotRecognizedException e) {
280: boolean hideWarnings = Boolean
281: .getBoolean("drools.schema.hidewarnings");
282: if (!hideWarnings) {
283: System.err
284: .println("Your SAX parser is not JAXP 1.2 compliant - turning off validation.");
285: }
286: localParser = null;
287: }
288: }
289:
290: if (localParser == null) {
291: // not jaxp1.2 compliant so turn off validation
292: try {
293: this .isValidating = false;
294: factory.setValidating(this .isValidating);
295: localParser = factory.newSAXParser();
296: } catch (final ParserConfigurationException e) {
297: throw new RuntimeException(e.getMessage());
298: }
299: }
300: } else {
301: localParser = this .parser;
302: }
303:
304: if (!localParser.isNamespaceAware()) {
305: throw new RuntimeException("parser must be namespace-aware");
306: }
307:
308: localParser.parse(in, this );
309:
310: return this .packageDescr;
311: }
312:
313: void setPackageDescr(final PackageDescr packageDescr) {
314: this .packageDescr = packageDescr;
315: }
316:
317: public PackageDescr getPackageDescr() {
318: return this .packageDescr;
319: }
320:
321: /**
322: * @see org.xml.sax.ContentHandler
323: */
324: public void setDocumentLocator(final Locator locator) {
325: this .locator = locator;
326: }
327:
328: /**
329: * Get the <code>Locator</code>.
330: *
331: * @return The locator.
332: */
333: public Locator getLocator() {
334: return this .locator;
335: }
336:
337: public void startDocument() {
338: this .isValidating = true;
339: this .packageDescr = null;
340: this .current = null;
341: this .peer = null;
342: this .lastWasEndElement = false;
343: this .parents.clear();
344: this .characters = null;
345: this .configurationStack.clear();
346: this .namespaces.clear();
347: }
348:
349: /**
350: * @param uri
351: * @param localName
352: * @param qname
353: * @param attrs
354: * @throws SAXException
355: * @see org.xml.sax.ContentHandler
356: *
357: * @todo: better way to manage unhandled elements
358: */
359: public void startElement(final String uri, final String localName,
360: final String qname, final Attributes attrs)
361: throws SAXException {
362: // going down so no peer
363: if (!this .lastWasEndElement) {
364: this .peer = null;
365: }
366:
367: final Handler handler = getHandler(localName);
368:
369: if ((handler != null)
370: && (!this .parents.isEmpty() && this .parents.getLast() instanceof RuleDescr)) {
371: this .inHandledRuleSubElement = true;
372: }
373:
374: if (handler == null) {
375: startConfiguration(localName, attrs);
376: return;
377: }
378:
379: validate(uri, localName, handler);
380:
381: final Object node = handler.start(uri, localName, attrs);
382:
383: if (node != null) {
384: this .parents.add(node);
385: this .current = node;
386: }
387: this .lastWasEndElement = false;
388: }
389:
390: /**
391: * @param uri
392: * @param localName
393: * @param qname
394: * @throws SAXException
395: * @see org.xml.sax.ContentHandler
396: */
397:
398: public void endElement(final String uri, final String localName,
399: final String qname) throws SAXException {
400: final Handler handler = getHandler(localName);
401:
402: if ((handler != null)
403: && (!this .parents.isEmpty() && this .parents.getLast() instanceof RuleDescr)) {
404: this .inHandledRuleSubElement = false;
405: }
406:
407: if (handler == null) {
408: if (this .configurationStack.size() >= 1) {
409: endConfiguration();
410: }
411: return;
412: }
413:
414: this .current = getParent(handler.generateNodeFor());
415:
416: final Object node = handler.end(uri, localName);
417:
418: // next
419: if (node != null && !this .lastWasEndElement) {
420: this .peer = node;
421: }
422: // up or no children
423: else if (this .lastWasEndElement
424: || (this .parents.getLast()).getClass().isInstance(
425: this .current)) {
426: this .peer = this .parents.removeLast();
427: }
428:
429: this .lastWasEndElement = true;
430: }
431:
432: private void validate(final String uri, final String localName,
433: final Handler handler) throws SAXParseException {
434: boolean validParent = false;
435: boolean validPeer = false;
436: boolean invalidNesting = false;
437:
438: final Set validParents = handler.getValidParents();
439: final Set validPeers = handler.getValidPeers();
440: boolean allowNesting = handler.allowNesting();
441:
442: // get parent
443: Object parent;
444: if (this .parents.size() != 0) {
445: parent = this .parents.getLast();
446: } else {
447: parent = null;
448: }
449:
450: // check valid parents
451: // null parent means localname is rule-set
452: // dont process if elements are the same
453: // instead check for allowed nesting
454: final Class nodeClass = getHandler(localName).generateNodeFor();
455: if (!nodeClass.isInstance(parent)) {
456: Object allowedParent;
457: final Iterator it = validParents.iterator();
458: while (!validParent && it.hasNext()) {
459: allowedParent = it.next();
460: if (parent == null && allowedParent == null) {
461: validParent = true;
462: } else if (allowedParent != null
463: && ((Class) allowedParent).isInstance(parent)) {
464: validParent = true;
465: }
466: }
467: if (!validParent) {
468: throw new SAXParseException("<" + localName
469: + "> has an invalid parent element [" + parent
470: + "]", getLocator());
471: }
472: }
473:
474: // check valid peers
475: // null peer means localname is rule-set
476: final Object peer = this .peer;
477:
478: Object allowedPeer;
479: Iterator it = validPeers.iterator();
480: while (!validPeer && it.hasNext()) {
481: allowedPeer = it.next();
482: if (peer == null && allowedPeer == null) {
483: validPeer = true;
484: } else if (allowedPeer != null
485: && ((Class) allowedPeer).isInstance(peer)) {
486: validPeer = true;
487: }
488: }
489: if (!validPeer) {
490: throw new SAXParseException("<" + localName
491: + "> is after an invalid element: "
492: + Handler.class.getName(), getLocator());
493: }
494:
495: if (!allowNesting) {
496: it = this .parents.iterator();
497: while (!invalidNesting && it.hasNext()) {
498: if (nodeClass.isInstance(it.next())) {
499: invalidNesting = true;
500: }
501: }
502: }
503: if (invalidNesting) {
504: throw new SAXParseException("<" + localName
505: + "> may not be nested", getLocator());
506: }
507:
508: }
509:
510: /**
511: * Start a configuration node.
512: *
513: * @param name
514: * Tag name.
515: * @param attrs
516: * Tag attributes.
517: */
518: protected void startConfiguration(final String name,
519: final Attributes attrs) {
520: this .characters = new StringBuffer();
521:
522: final DefaultConfiguration config = new DefaultConfiguration(
523: name);
524:
525: final int numAttrs = attrs.getLength();
526:
527: for (int i = 0; i < numAttrs; ++i) {
528: config.setAttribute(attrs.getLocalName(i), attrs
529: .getValue(i));
530: }
531:
532: // lets add the namespaces as attributes
533: for (final Iterator iter = this .namespaces.entrySet()
534: .iterator(); iter.hasNext();) {
535: final Map.Entry entry = (Map.Entry) iter.next();
536: String ns = (String) entry.getKey();
537: final String value = (String) entry.getValue();
538: if (ns == null || ns.length() == 0) {
539: ns = "xmlns";
540: } else {
541: ns = "xmlns:" + ns;
542: }
543: config.setAttribute(ns, value);
544: }
545:
546: if (this .configurationStack.isEmpty()) {
547: this .configurationStack.addLast(config);
548: } else {
549: ((DefaultConfiguration) this .configurationStack.getLast())
550: .addChild(config);
551: this .configurationStack.addLast(config);
552: }
553: }
554:
555: Handler getHandler(final String localName) {
556: return (Handler) this .handlers.get(localName);
557: }
558:
559: /**
560: * @param chars
561: * @param start
562: * @param len
563: * @see org.xml.sax.ContentHandler
564: */
565: public void characters(final char[] chars, final int start,
566: final int len) {
567: if (this .characters != null) {
568: this .characters.append(chars, start, len);
569: }
570: }
571:
572: /**
573: * End a configuration node.
574: *
575: * @return The configuration.
576: */
577: protected Configuration endConfiguration() {
578: final DefaultConfiguration config = (DefaultConfiguration) this .configurationStack
579: .removeLast();
580: if (this .characters != null) {
581: config.setText(this .characters.toString());
582: }
583:
584: this .characters = null;
585:
586: return config;
587: }
588:
589: LinkedList getParents() {
590: return this .parents;
591: }
592:
593: Object getParent(final Class parent) {
594: final ListIterator it = this .parents.listIterator(this .parents
595: .size());
596: Object node = null;
597: while (it.hasPrevious()) {
598: node = it.previous();
599: if (parent.isInstance(node)) {
600: break;
601: }
602: }
603: return node;
604: }
605:
606: Object getPeer() {
607: return this .peer;
608: }
609:
610: Object getCurrent() {
611: return this .current;
612: }
613:
614: public InputSource resolveEntity(final String publicId,
615: final String systemId) throws SAXException {
616: try {
617: final InputSource inputSource = resolveSchema(publicId,
618: systemId);
619: if (inputSource != null) {
620: return inputSource;
621: }
622: if (this .entityResolver != null) {
623: return this .entityResolver.resolveEntity(publicId,
624: systemId);
625: }
626: } catch (final IOException ioe) {
627: }
628: return null;
629: }
630:
631: public void startPrefixMapping(final String prefix, final String uri)
632: throws SAXException {
633: super .startPrefixMapping(prefix, uri);
634: this .namespaces.put(prefix, uri);
635: }
636:
637: public void endPrefixMapping(final String prefix)
638: throws SAXException {
639: super .endPrefixMapping(prefix);
640: this .namespaces.remove(prefix);
641: }
642:
643: private void print(final SAXParseException x) {
644: final String msg = this .message.format(new Object[] {
645: x.getSystemId(), new Integer(x.getLineNumber()),
646: new Integer(x.getColumnNumber()), x.getMessage() });
647: System.out.println(msg);
648: }
649:
650: public void warning(final SAXParseException x) {
651: print(x);
652: }
653:
654: public void error(final SAXParseException x) {
655: print(x);
656: }
657:
658: public void fatalError(final SAXParseException x)
659: throws SAXParseException {
660: print(x);
661: throw x;
662: }
663:
664: private InputSource resolveSchema(final String publicId,
665: final String systemId) throws SAXException, IOException {
666: // Schema files must end with xsd
667: if (!systemId.toLowerCase().endsWith("xsd")) {
668: return null;
669: }
670:
671: // try the actual location given by systemId
672: try {
673: final URL url = new URL(systemId);
674: return new InputSource(url.openStream());
675: } catch (final Exception e) {
676: }
677:
678: // Try and get the index for the filename, else return null
679: String xsd;
680: int index = systemId.lastIndexOf("/");
681: if (index == -1) {
682: index = systemId.lastIndexOf("\\");
683: }
684: if (index != -1) {
685: xsd = systemId.substring(index + 1);
686: } else {
687: xsd = systemId;
688: }
689:
690: ClassLoader cl = Thread.currentThread().getContextClassLoader();
691:
692: if (cl == null) {
693: cl = XmlPackageReader.class.getClassLoader();
694: }
695:
696: // Try looking in META-INF
697: {
698: final InputStream is = cl.getResourceAsStream("META-INF/"
699: + xsd);
700: if (is != null) {
701: return new InputSource(is);
702: }
703: }
704:
705: // Try looking in /META-INF
706: {
707: final InputStream is = cl.getResourceAsStream("/META-INF/"
708: + xsd);
709: if (is != null) {
710: return new InputSource(is);
711: }
712: }
713:
714: // Try looking at root of classpath
715: {
716: final InputStream is = cl.getResourceAsStream("/" + xsd);
717: if (is != null) {
718: return new InputSource(is);
719: }
720: }
721:
722: // Try current working directory
723: {
724: final File file = new File(xsd);
725: if (file.exists()) {
726: return new InputSource(new BufferedInputStream(
727: new FileInputStream(file)));
728: }
729: }
730:
731: cl = ClassLoader.getSystemClassLoader();
732:
733: // Try looking in META-INF
734: {
735: final InputStream is = cl.getResourceAsStream("META-INF/"
736: + xsd);
737: if (is != null) {
738: return new InputSource(is);
739: }
740: }
741:
742: // Try looking in /META-INF
743: {
744: final InputStream is = cl.getResourceAsStream("/META-INF/"
745: + xsd);
746: if (is != null) {
747: return new InputSource(is);
748: }
749: }
750:
751: // Try looking at root of classpath
752: {
753: final InputStream is = cl.getResourceAsStream("/" + xsd);
754: if (is != null) {
755: return new InputSource(is);
756: }
757: }
758:
759: return null;
760: }
761:
762: /**
763: * Intializes EntityResolver that is configured via system property ENTITY_RESOLVER_PROPERTY_NAME.
764: */
765: private void initEntityResolver() {
766: final String entityResolveClazzName = System
767: .getProperty(XmlPackageReader.ENTITY_RESOLVER_PROPERTY_NAME);
768: if (entityResolveClazzName != null
769: && entityResolveClazzName.length() > 0) {
770: try {
771: final Class entityResolverClazz = Thread
772: .currentThread().getContextClassLoader()
773: .loadClass(entityResolveClazzName);
774: this .entityResolver = (EntityResolver) entityResolverClazz
775: .newInstance();
776: } catch (final Exception ignoreIt) {
777: }
778: }
779: }
780:
781: }
|