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  */