View Javadoc

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