View Javadoc

1   /*
2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3    *
4    * This software is open source.
5    * See the bottom of this file for the licence.
6    */
7   
8   package org.dom4j.datatype;
9   
10  import com.sun.msv.datatype.xsd.DatatypeFactory;
11  import com.sun.msv.datatype.xsd.TypeIncubator;
12  import com.sun.msv.datatype.xsd.XSDatatype;
13  
14  import java.util.HashMap;
15  import java.util.Iterator;
16  import java.util.Map;
17  
18  import org.dom4j.Attribute;
19  import org.dom4j.Document;
20  import org.dom4j.DocumentFactory;
21  import org.dom4j.Element;
22  import org.dom4j.Namespace;
23  import org.dom4j.QName;
24  import org.dom4j.io.SAXReader;
25  import org.dom4j.util.AttributeHelper;
26  
27  import org.relaxng.datatype.DatatypeException;
28  import org.relaxng.datatype.ValidationContext;
29  
30  import org.xml.sax.EntityResolver;
31  import org.xml.sax.InputSource;
32  
33  /***
34   * <p>
35   * <code>SchemaParser</code> reads an XML Schema Document.
36   * </p>
37   * 
38   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
39   * @author Yuxin Ruan
40   * @version $Revision: 1.19 $
41   */
42  public class SchemaParser {
43      private static final Namespace XSD_NAMESPACE = Namespace.get("xsd",
44              "http://www.w3.org/2001/XMLSchema");
45  
46      // Use QNames for the elements
47      private static final QName XSD_ELEMENT = QName
48              .get("element", XSD_NAMESPACE);
49  
50      private static final QName XSD_ATTRIBUTE = QName.get("attribute",
51              XSD_NAMESPACE);
52  
53      private static final QName XSD_SIMPLETYPE = QName.get("simpleType",
54              XSD_NAMESPACE);
55  
56      private static final QName XSD_COMPLEXTYPE = QName.get("complexType",
57              XSD_NAMESPACE);
58  
59      private static final QName XSD_RESTRICTION = QName.get("restriction",
60              XSD_NAMESPACE);
61  
62      private static final QName XSD_SEQUENCE = QName.get("sequence",
63              XSD_NAMESPACE);
64  
65      private static final QName XSD_CHOICE = QName.get("choice", XSD_NAMESPACE);
66  
67      private static final QName XSD_ALL = QName.get("all", XSD_NAMESPACE);
68  
69      private static final QName XSD_INCLUDE = QName
70              .get("include", XSD_NAMESPACE);
71  
72      /*** Document factory used to register Element specific factories */
73      private DatatypeDocumentFactory documentFactory;
74  
75      /***
76       * Cache of <code>XSDatatype</code> instances loaded or created during
77       * this build
78       */
79      private Map dataTypeCache = new HashMap();
80  
81      /*** NamedTypeResolver */
82      private NamedTypeResolver namedTypeResolver;
83  
84      /*** target namespace */
85      private Namespace targetNamespace;
86  
87      public SchemaParser() {
88          this(DatatypeDocumentFactory.singleton);
89      }
90  
91      public SchemaParser(DatatypeDocumentFactory documentFactory) {
92          this.documentFactory = documentFactory;
93          this.namedTypeResolver = new NamedTypeResolver(documentFactory);
94      }
95  
96      /***
97       * Parses the given schema document
98       * 
99       * @param schemaDocument
100      *            is the document of the XML Schema
101      */
102     public void build(Document schemaDocument) {
103         this.targetNamespace = null;
104         internalBuild(schemaDocument);
105     }
106 
107     public void build(Document schemaDocument, Namespace namespace) {
108         this.targetNamespace = namespace;
109         internalBuild(schemaDocument);
110     }
111 
112     private synchronized void internalBuild(Document schemaDocument) {
113         Element root = schemaDocument.getRootElement();
114 
115         if (root != null) {
116             // handle schema includes
117             Iterator includeIter = root.elementIterator(XSD_INCLUDE);
118 
119             while (includeIter.hasNext()) {
120                 Element includeElement = (Element) includeIter.next();
121                 String inclSchemaInstanceURI = includeElement
122                         .attributeValue("schemaLocation");
123                 EntityResolver resolver = schemaDocument.getEntityResolver();
124 
125                 try {
126                     if (resolver == null) {
127                         String msg = "No EntityResolver available";
128                         throw new InvalidSchemaException(msg);
129                     }
130 
131                     InputSource inputSource = resolver.resolveEntity(null,
132                             inclSchemaInstanceURI);
133 
134                     if (inputSource == null) {
135                         String msg = "Could not resolve the schema URI: "
136                                 + inclSchemaInstanceURI;
137                         throw new InvalidSchemaException(msg);
138                     }
139 
140                     SAXReader reader = new SAXReader();
141                     Document inclSchemaDocument = reader.read(inputSource);
142                     build(inclSchemaDocument);
143                 } catch (Exception e) {
144                     System.out.println("Failed to load schema: "
145                             + inclSchemaInstanceURI);
146                     System.out.println("Caught: " + e);
147                     e.printStackTrace();
148                     throw new InvalidSchemaException("Failed to load schema: "
149                             + inclSchemaInstanceURI);
150                 }
151             }
152 
153             // handle elements
154             Iterator iter = root.elementIterator(XSD_ELEMENT);
155 
156             while (iter.hasNext()) {
157                 onDatatypeElement((Element) iter.next(), documentFactory);
158             }
159 
160             // handle named simple types
161             iter = root.elementIterator(XSD_SIMPLETYPE);
162 
163             while (iter.hasNext()) {
164                 onNamedSchemaSimpleType((Element) iter.next());
165             }
166 
167             // hanlde named complex types
168             iter = root.elementIterator(XSD_COMPLEXTYPE);
169 
170             while (iter.hasNext()) {
171                 onNamedSchemaComplexType((Element) iter.next());
172             }
173 
174             namedTypeResolver.resolveNamedTypes();
175         }
176     }
177 
178     // Implementation methods
179     // -------------------------------------------------------------------------
180 
181     /***
182      * processes an XML Schema &lt;element&gt; tag
183      * 
184      * @param xsdElement
185      *            DOCUMENT ME!
186      * @param parentFactory
187      *            DOCUMENT ME!
188      */
189     private void onDatatypeElement(Element xsdElement,
190             DocumentFactory parentFactory) {
191         String name = xsdElement.attributeValue("name");
192         String type = xsdElement.attributeValue("type");
193         QName qname = getQName(name);
194 
195         DatatypeElementFactory factory = getDatatypeElementFactory(qname);
196 
197         if (type != null) {
198             // register type with this element name
199             XSDatatype dataType = getTypeByName(type);
200 
201             if (dataType != null) {
202                 factory.setChildElementXSDatatype(qname, dataType);
203             } else {
204                 QName typeQName = getQName(type);
205                 namedTypeResolver.registerTypedElement(xsdElement, typeQName,
206                         parentFactory);
207             }
208 
209             return;
210         }
211 
212         // handle element types derrived from simpleTypes
213         Element xsdSimpleType = xsdElement.element(XSD_SIMPLETYPE);
214 
215         if (xsdSimpleType != null) {
216             XSDatatype dataType = loadXSDatatypeFromSimpleType(xsdSimpleType);
217 
218             if (dataType != null) {
219                 factory.setChildElementXSDatatype(qname, dataType);
220             }
221         }
222 
223         Element schemaComplexType = xsdElement.element(XSD_COMPLEXTYPE);
224 
225         if (schemaComplexType != null) {
226             onSchemaComplexType(schemaComplexType, factory);
227         }
228 
229         Iterator iter = xsdElement.elementIterator(XSD_ATTRIBUTE);
230 
231         if (iter.hasNext()) {
232             do {
233                 onDatatypeAttribute(xsdElement, factory, (Element) iter
234                         .next());
235             } while (iter.hasNext());
236         }
237     }
238 
239     /***
240      * processes an named XML Schema &lt;complexTypegt; tag
241      * 
242      * @param schemaComplexType
243      *            DOCUMENT ME!
244      */
245     private void onNamedSchemaComplexType(Element schemaComplexType) {
246         Attribute nameAttr = schemaComplexType.attribute("name");
247 
248         if (nameAttr == null) {
249             return;
250         }
251 
252         String name = nameAttr.getText();
253         QName qname = getQName(name);
254 
255         DatatypeElementFactory factory = getDatatypeElementFactory(qname);
256 
257         onSchemaComplexType(schemaComplexType, factory);
258         namedTypeResolver.registerComplexType(qname, factory);
259     }
260 
261     /***
262      * processes an XML Schema &lt;complexTypegt; tag
263      * 
264      * @param schemaComplexType
265      *            DOCUMENT ME!
266      * @param elementFactory
267      *            DOCUMENT ME!
268      */
269     private void onSchemaComplexType(Element schemaComplexType,
270             DatatypeElementFactory elementFactory) {
271         Iterator iter = schemaComplexType.elementIterator(XSD_ATTRIBUTE);
272 
273         while (iter.hasNext()) {
274             Element xsdAttribute = (Element) iter.next();
275             String name = xsdAttribute.attributeValue("name");
276             QName qname = getQName(name);
277 
278             XSDatatype dataType = dataTypeForXsdAttribute(xsdAttribute);
279 
280             if (dataType != null) {
281                 // register the XSDatatype for the given Attribute
282                 // #### should both these be done?
283                 // elementFactory.setChildElementXSDatatype( qname, dataType );
284                 elementFactory.setAttributeXSDatatype(qname, dataType);
285             }
286         }
287 
288         // handle sequence definition
289         Element schemaSequence = schemaComplexType.element(XSD_SEQUENCE);
290 
291         if (schemaSequence != null) {
292             onChildElements(schemaSequence, elementFactory);
293         }
294 
295         // handle choice definition
296         Element schemaChoice = schemaComplexType.element(XSD_CHOICE);
297 
298         if (schemaChoice != null) {
299             onChildElements(schemaChoice, elementFactory);
300         }
301 
302         // handle all definition
303         Element schemaAll = schemaComplexType.element(XSD_ALL);
304 
305         if (schemaAll != null) {
306             onChildElements(schemaAll, elementFactory);
307         }
308     }
309 
310     private void onChildElements(Element element, DatatypeElementFactory fact) {
311         Iterator iter = element.elementIterator(XSD_ELEMENT);
312 
313         while (iter.hasNext()) {
314             Element xsdElement = (Element) iter.next();
315             onDatatypeElement(xsdElement, fact);
316         }
317     }
318 
319     /***
320      * processes an XML Schema &lt;attribute&gt; tag
321      * 
322      * @param xsdElement
323      *            DOCUMENT ME!
324      * @param elementFactory
325      *            DOCUMENT ME!
326      * @param xsdAttribute
327      *            DOCUMENT ME!
328      */
329     private void onDatatypeAttribute(Element xsdElement,
330             DatatypeElementFactory elementFactory, Element xsdAttribute) {
331         String name = xsdAttribute.attributeValue("name");
332         QName qname = getQName(name);
333         XSDatatype dataType = dataTypeForXsdAttribute(xsdAttribute);
334 
335         if (dataType != null) {
336             // register the XSDatatype for the given Attribute
337             elementFactory.setAttributeXSDatatype(qname, dataType);
338         } else {
339             String type = xsdAttribute.attributeValue("type");
340             System.out.println("Warning: Couldn't find XSDatatype for type: "
341                     + type + " attribute: " + name);
342         }
343     }
344 
345     /***
346      * processes an XML Schema &lt;attribute&gt; tag
347      * 
348      * @param xsdAttribute
349      *            DOCUMENT ME!
350      * 
351      * @return DOCUMENT ME!
352      * 
353      * @throws InvalidSchemaException
354      *             DOCUMENT ME!
355      */
356     private XSDatatype dataTypeForXsdAttribute(Element xsdAttribute) {
357         String type = xsdAttribute.attributeValue("type");
358         XSDatatype dataType = null;
359 
360         if (type != null) {
361             dataType = getTypeByName(type);
362         } else {
363             // must parse the <simpleType> element
364             Element xsdSimpleType = xsdAttribute.element(XSD_SIMPLETYPE);
365 
366             if (xsdSimpleType == null) {
367                 String name = xsdAttribute.attributeValue("name");
368                 String msg = "The attribute: " + name
369                         + " has no type attribute and does not contain a "
370                         + "<simpleType/> element";
371                 throw new InvalidSchemaException(msg);
372             }
373 
374             dataType = loadXSDatatypeFromSimpleType(xsdSimpleType);
375         }
376 
377         return dataType;
378     }
379 
380     /***
381      * processes an named XML Schema &lt;simpleTypegt; tag
382      * 
383      * @param schemaSimpleType
384      *            DOCUMENT ME!
385      */
386     private void onNamedSchemaSimpleType(Element schemaSimpleType) {
387         Attribute nameAttr = schemaSimpleType.attribute("name");
388 
389         if (nameAttr == null) {
390             return;
391         }
392 
393         String name = nameAttr.getText();
394         QName qname = getQName(name);
395         XSDatatype datatype = loadXSDatatypeFromSimpleType(schemaSimpleType);
396         namedTypeResolver.registerSimpleType(qname, datatype);
397     }
398 
399     /***
400      * Loads a XSDatatype object from a &lt;simpleType&gt; attribute schema
401      * element
402      * 
403      * @param xsdSimpleType
404      *            DOCUMENT ME!
405      * 
406      * @return DOCUMENT ME!
407      */
408     private XSDatatype loadXSDatatypeFromSimpleType(Element xsdSimpleType) {
409         Element xsdRestriction = xsdSimpleType.element(XSD_RESTRICTION);
410 
411         if (xsdRestriction != null) {
412             String base = xsdRestriction.attributeValue("base");
413 
414             if (base != null) {
415                 XSDatatype baseType = getTypeByName(base);
416 
417                 if (baseType == null) {
418                     onSchemaError("Invalid base type: " + base
419                             + " when trying to build restriction: "
420                             + xsdRestriction);
421                 } else {
422                     return deriveSimpleType(baseType, xsdRestriction);
423                 }
424             } else {
425                 // simpleType and base are mutually exclusive and you
426                 // must have one within a <restriction> tag
427                 Element xsdSubType = xsdSimpleType.element(XSD_SIMPLETYPE);
428 
429                 if (xsdSubType == null) {
430                     String msg = "The simpleType element: " + xsdSimpleType
431                             + " must contain a base attribute or simpleType"
432                             + " element";
433                     onSchemaError(msg);
434                 } else {
435                     return loadXSDatatypeFromSimpleType(xsdSubType);
436                 }
437             }
438         } else {
439             onSchemaError("No <restriction>. Could not create XSDatatype for"
440                     + " simpleType: " + xsdSimpleType);
441         }
442 
443         return null;
444     }
445 
446     /***
447      * Derives a new type from a base type and a set of restrictions
448      * 
449      * @param baseType
450      *            DOCUMENT ME!
451      * @param xsdRestriction
452      *            DOCUMENT ME!
453      * 
454      * @return DOCUMENT ME!
455      */
456     private XSDatatype deriveSimpleType(XSDatatype baseType,
457             Element xsdRestriction) {
458         TypeIncubator incubator = new TypeIncubator(baseType);
459         ValidationContext context = null;
460 
461         try {
462             for (Iterator iter = xsdRestriction.elementIterator(); iter
463                     .hasNext();) {
464                 Element element = (Element) iter.next();
465                 String name = element.getName();
466                 String value = element.attributeValue("value");
467                 boolean fixed = AttributeHelper.booleanValue(element, "fixed");
468 
469                 // add facet
470                 incubator.addFacet(name, value, fixed, context);
471             }
472 
473             // derive a new type by those facets
474             String newTypeName = null;
475 
476             return incubator.derive("", newTypeName);
477         } catch (DatatypeException e) {
478             onSchemaError("Invalid restriction: " + e.getMessage()
479                     + " when trying to build restriction: " + xsdRestriction);
480 
481             return null;
482         }
483     }
484 
485     /***
486      * DOCUMENT ME!
487      * 
488      * @param name
489      *            The name of the element
490      * 
491      * @return the <code>DatatypeElementFactory</code> for the given element
492      *         QName, creating one if it does not already exist
493      */
494     private DatatypeElementFactory getDatatypeElementFactory(QName name) {
495         DatatypeElementFactory factory = documentFactory
496                 .getElementFactory(name);
497 
498         if (factory == null) {
499             factory = new DatatypeElementFactory(name);
500             name.setDocumentFactory(factory);
501         }
502 
503         return factory;
504     }
505 
506     private XSDatatype getTypeByName(String type) {
507         XSDatatype dataType = (XSDatatype) dataTypeCache.get(type);
508 
509         if (dataType == null) {
510             // first check to see if it is a built-in type
511             // maybe a prefix is being used
512             int idx = type.indexOf(':');
513 
514             if (idx >= 0) {
515                 String localName = type.substring(idx + 1);
516 
517                 try {
518                     dataType = DatatypeFactory.getTypeByName(localName);
519                 } catch (DatatypeException e) {
520                 }
521             }
522 
523             if (dataType == null) {
524                 try {
525                     dataType = DatatypeFactory.getTypeByName(type);
526                 } catch (DatatypeException e) {
527                 }
528             }
529 
530             if (dataType == null) {
531                 // it's no built-in type, maybe it's a type we defined ourself
532                 QName typeQName = getQName(type);
533                 dataType = (XSDatatype) namedTypeResolver.simpleTypeMap
534                         .get(typeQName);
535             }
536 
537             if (dataType != null) {
538                 // store in cache for later
539                 dataTypeCache.put(type, dataType);
540             }
541         }
542 
543         return dataType;
544     }
545 
546     private QName getQName(String name) {
547         if (targetNamespace == null) {
548             return documentFactory.createQName(name);
549         } else {
550             return documentFactory.createQName(name, targetNamespace);
551         }
552     }
553 
554     /***
555      * Called when there is a problem with the schema and the builder cannot
556      * handle the XML Schema Data Types correctly
557      * 
558      * @param message
559      *            DOCUMENT ME!
560      * 
561      * @throws InvalidSchemaException
562      *             DOCUMENT ME!
563      */
564     private void onSchemaError(String message) {
565         // Some users may wish to disable exception throwing
566         // and instead use some kind of listener for errors and continue
567         // System.out.println( "WARNING: " + message );
568         throw new InvalidSchemaException(message);
569     }
570 }
571 
572 /*
573  * Redistribution and use of this software and associated documentation
574  * ("Software"), with or without modification, are permitted provided that the
575  * following conditions are met:
576  * 
577  * 1. Redistributions of source code must retain copyright statements and
578  * notices. Redistributions must also contain a copy of this document.
579  * 
580  * 2. Redistributions in binary form must reproduce the above copyright notice,
581  * this list of conditions and the following disclaimer in the documentation
582  * and/or other materials provided with the distribution.
583  * 
584  * 3. The name "DOM4J" must not be used to endorse or promote products derived
585  * from this Software without prior written permission of MetaStuff, Ltd. For
586  * written permission, please contact dom4j-info@metastuff.com.
587  * 
588  * 4. Products derived from this Software may not be called "DOM4J" nor may
589  * "DOM4J" appear in their names without prior written permission of MetaStuff,
590  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
591  * 
592  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
593  * 
594  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
595  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
596  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
597  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
598  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
599  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
600  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
601  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
602  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
603  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
604  * POSSIBILITY OF SUCH DAMAGE.
605  * 
606  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
607  */