View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.commons.schema;
21  
22  import java.io.IOException;
23  import java.io.Reader;
24  import java.security.PrivilegedActionException;
25  import java.security.PrivilegedExceptionAction;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Stack;
33  
34  import javax.xml.namespace.QName;
35  import javax.xml.parsers.DocumentBuilder;
36  import javax.xml.parsers.DocumentBuilderFactory;
37  import javax.xml.parsers.ParserConfigurationException;
38  import javax.xml.transform.Source;
39  import javax.xml.transform.dom.DOMSource;
40  import javax.xml.transform.sax.SAXSource;
41  import javax.xml.transform.stream.StreamSource;
42  
43  import org.apache.ws.commons.schema.constants.Constants;
44  import org.apache.ws.commons.schema.extensions.ExtensionRegistry;
45  import org.apache.ws.commons.schema.resolver.DefaultURIResolver;
46  import org.apache.ws.commons.schema.resolver.URIResolver;
47  import org.apache.ws.commons.schema.utils.DOMUtil;
48  import org.apache.ws.commons.schema.utils.NamespacePrefixList;
49  import org.apache.ws.commons.schema.utils.TargetNamespaceValidator;
50  import org.w3c.dom.Document;
51  import org.w3c.dom.Element;
52  import org.w3c.dom.Node;
53  import org.xml.sax.InputSource;
54  import org.xml.sax.SAXException;
55  
56  /**
57   * Contains a cache of XML Schema definition language (XSD).
58   *
59   */
60  public final class XmlSchemaCollection {
61  
62      // the default extension registry
63      private ExtensionRegistry extReg = new ExtensionRegistry();
64  
65      public ExtensionRegistry getExtReg() {
66          return extReg;
67      }
68  
69      public void setExtReg(ExtensionRegistry extReg) {
70          this.extReg = extReg;
71      }
72  
73      /**
74       * This map contains a list of Schema objects keyed in by their namespaces
75       * When resolving schemas, this map will be checked for the presence of the schema
76       * first
77       */
78      private Map knownNamespaceMap = new HashMap();
79  
80      /**
81       * get the namespace map
82       * @return a map of previously known XMLSchema objects keyed by their namespace (String)
83       */
84      public Map getKnownNamespaceMap() {
85  		return knownNamespaceMap;
86  	}
87  
88      /**
89       * sets the known namespace map
90       * @param knownNamespaceMap a map of previously known XMLSchema objects keyed by their namespace (String)
91       */
92  	public void setKnownNamespaceMap(Map knownNamespaceMap) {
93  		this.knownNamespaceMap = knownNamespaceMap;
94  	}
95  	
96  	
97  	
98      static class SchemaKey {
99          private final String namespace;
100         private final String systemId;
101         SchemaKey(String pNamespace, String pSystemId) {
102             namespace = pNamespace == null ? Constants.NULL_NS_URI : pNamespace;
103             systemId = pSystemId == null ? "" : pSystemId;
104         }
105         String getNamespace() { return namespace; }
106         String getSystemId() { return systemId; }
107         public int hashCode() {
108             final int PRIME = 31;
109             return (PRIME + namespace.hashCode()) * PRIME + systemId.hashCode();
110         }
111         public boolean equals(Object obj) {
112             if (this == obj)
113                 return true;
114             if (obj == null)
115                 return false;
116             if (getClass() != obj.getClass())
117                 return false;
118             final SchemaKey other = (SchemaKey) obj;
119             return namespace.equals(other.namespace)  &&  systemId.equals(other.systemId);
120         }
121         public String toString() {
122             return Constants.NULL_NS_URI.equals(namespace) ?
123                     systemId : ("{" + namespace + "}" + systemId);
124         }
125     }
126 
127     /**
128      * Map of included schemas.
129      */
130     private Map schemas = new HashMap();
131 
132 
133     /**
134      * base URI is used as the base for loading the
135      * imports
136      */
137     String baseUri = null;
138     /**
139      * In-scope namespaces for XML processing
140      */
141     private NamespacePrefixList namespaceContext;
142 
143     /**
144      * An org.xml.sax.EntityResolver that is used to
145      * resolve the imports/includes
146      */
147     private URIResolver schemaResolver = new DefaultURIResolver();
148 
149 	XmlSchema xsd = new XmlSchema(XmlSchema.SCHEMA_NS, this);
150 
151     /**
152      * stack to track imports (to prevent recursion)
153      */
154     Stack stack = new Stack();
155 
156     /**
157      * Set the base URI. This is used when schemas need to be
158      * loaded from relative locations
159      * @param baseUri  baseUri for this
160      */
161     public void setBaseUri(String baseUri){
162         this.baseUri = baseUri;
163     }
164 
165     /**
166      * Register a custom URI resolver
167      * @param schemaResolver   resolver
168      */
169     public void setSchemaResolver(URIResolver schemaResolver) {
170         this.schemaResolver = schemaResolver;
171     }
172 
173     /**
174      * Retrieve the custom URI resolver, if any.
175      * @return the current resolver.
176      */
177     public URIResolver getSchemaResolver() {
178 		return schemaResolver;
179 	}
180 
181     /**
182      * This section should comply to the XMLSchema specification; see
183      * <a href="http://www.w3.org/TR/2004/PER-xmlschema-2-20040318/datatypes.html#built-in-datatypes">
184      *  http://www.w3.org/TR/2004/PER-xmlschema-2-20040318/datatypes.html#built-in-datatypes</a>.
185      * This needs to be inspected by another pair of eyes
186      */
187     public void init() {
188     	
189     	/*
190     	 * Defined in section 4.
191     	 */
192     	addSimpleType(xsd, Constants.XSD_ANYSIMPLETYPE.getLocalPart());
193     	addSimpleType(xsd, Constants.XSD_ANYTYPE.getLocalPart());
194     	
195         /*
196         Primitive types
197 
198         3.2.1 string
199         3.2.2 boolean
200         3.2.3 decimal
201         3.2.4 float
202         3.2.5 double
203         3.2.6 duration
204         3.2.7 dateTime
205         3.2.8 time
206         3.2.9 date
207         3.2.10 gYearMonth
208         3.2.11 gYear
209         3.2.12 gMonthDay
210         3.2.13 gDay
211         3.2.14 gMonth
212         3.2.15 hexBinary
213         3.2.16 base64Binary
214         3.2.17 anyURI
215         3.2.18 QName
216         3.2.19 NOTATION
217         */
218         addSimpleType(xsd, Constants.XSD_STRING.getLocalPart());
219         addSimpleType(xsd, Constants.XSD_BOOLEAN.getLocalPart());
220         addSimpleType(xsd, Constants.XSD_FLOAT.getLocalPart());
221         addSimpleType(xsd, Constants.XSD_DOUBLE.getLocalPart());
222         addSimpleType(xsd, Constants.XSD_QNAME.getLocalPart());
223         addSimpleType(xsd, Constants.XSD_DECIMAL.getLocalPart());
224         addSimpleType(xsd, Constants.XSD_DURATION.getLocalPart());
225         addSimpleType(xsd, Constants.XSD_DATE.getLocalPart());
226         addSimpleType(xsd, Constants.XSD_TIME.getLocalPart());
227         addSimpleType(xsd, Constants.XSD_DATETIME.getLocalPart());
228         addSimpleType(xsd, Constants.XSD_DAY.getLocalPart());
229         addSimpleType(xsd, Constants.XSD_MONTH.getLocalPart());
230         addSimpleType(xsd, Constants.XSD_MONTHDAY.getLocalPart());
231         addSimpleType(xsd, Constants.XSD_YEAR.getLocalPart());
232         addSimpleType(xsd, Constants.XSD_YEARMONTH.getLocalPart());
233         addSimpleType(xsd, Constants.XSD_NOTATION.getLocalPart());
234         addSimpleType(xsd, Constants.XSD_HEXBIN.getLocalPart());
235         addSimpleType(xsd, Constants.XSD_BASE64.getLocalPart());
236         addSimpleType(xsd, Constants.XSD_ANYURI.getLocalPart());
237 
238 
239         /*
240          3.3.1 normalizedString
241         3.3.2 token
242         3.3.3 language
243         3.3.4 NMTOKEN
244         3.3.5 NMTOKENS
245         3.3.6 Name
246         3.3.7 NCName
247         3.3.8 ID
248         3.3.9 IDREF
249         3.3.10 IDREFS
250         3.3.11 ENTITY
251         3.3.12 ENTITIES
252         3.3.13 integer
253         3.3.14 nonPositiveInteger
254         3.3.15 negativeInteger
255         3.3.16 long
256         3.3.17 int
257         3.3.18 short
258         3.3.19 byte
259         3.3.20 nonNegativeInteger
260         3.3.21 unsignedLong
261         3.3.22 unsignedInt
262         3.3.23 unsignedShort
263         3.3.24 unsignedByte
264         3.3.25 positiveInteger
265         */
266 
267          //derived types from decimal
268         addSimpleType(xsd, Constants.XSD_LONG.getLocalPart());
269         addSimpleType(xsd, Constants.XSD_SHORT.getLocalPart());
270         addSimpleType(xsd, Constants.XSD_BYTE.getLocalPart());
271         addSimpleType(xsd, Constants.XSD_INTEGER.getLocalPart());
272         addSimpleType(xsd, Constants.XSD_INT.getLocalPart());
273         addSimpleType(xsd, Constants.XSD_POSITIVEINTEGER.getLocalPart());
274         addSimpleType(xsd, Constants.XSD_NEGATIVEINTEGER.getLocalPart());
275         addSimpleType(xsd, Constants.XSD_NONPOSITIVEINTEGER.getLocalPart());
276         addSimpleType(xsd, Constants.XSD_NONNEGATIVEINTEGER.getLocalPart());
277         addSimpleType(xsd, Constants.XSD_UNSIGNEDBYTE.getLocalPart());
278         addSimpleType(xsd, Constants.XSD_UNSIGNEDINT.getLocalPart());
279         addSimpleType(xsd, Constants.XSD_UNSIGNEDLONG.getLocalPart());
280         addSimpleType(xsd, Constants.XSD_UNSIGNEDSHORT.getLocalPart());
281 
282         //derived types from string
283         addSimpleType(xsd, Constants.XSD_NAME.getLocalPart());
284         addSimpleType(xsd, Constants.XSD_NORMALIZEDSTRING.getLocalPart());
285         addSimpleType(xsd, Constants.XSD_NCNAME.getLocalPart());
286         addSimpleType(xsd, Constants.XSD_NMTOKEN.getLocalPart());
287         addSimpleType(xsd, Constants.XSD_NMTOKENS.getLocalPart());
288         addSimpleType(xsd, Constants.XSD_ENTITY.getLocalPart());
289         addSimpleType(xsd, Constants.XSD_ENTITIES.getLocalPart());
290         addSimpleType(xsd, Constants.XSD_ID.getLocalPart());
291         addSimpleType(xsd, Constants.XSD_IDREF.getLocalPart());
292         addSimpleType(xsd, Constants.XSD_IDREFS.getLocalPart());
293         addSimpleType(xsd, Constants.XSD_LANGUAGE.getLocalPart());
294         addSimpleType(xsd, Constants.XSD_TOKEN.getLocalPart());
295 
296         //SchemaKey key = new SchemaKey(XmlSchema.SCHEMA_NS, null);
297         //addSchema(key, xsd);
298 
299         // look for a system property to see whether we have a registered
300         // extension registry class. if so we'll instantiate a new one
301         // and set it as the extension registry
302         //if there is an error, we'll just print out a message and move on.
303 
304         if (System.getProperty(Constants.SystemConstants.EXTENSION_REGISTRY_KEY)!= null){
305             try {
306                 Class clazz = Class.forName(System.getProperty(Constants.SystemConstants.EXTENSION_REGISTRY_KEY));
307                 this.extReg = (ExtensionRegistry)clazz.newInstance();
308             } catch (ClassNotFoundException e) {
309                 System.err.println("The specified extension registry class cannot be found!");
310             } catch (InstantiationException e) {
311                 System.err.println("The specified extension registry class cannot be instantiated!");
312             } catch (IllegalAccessException e) {
313                 System.err.println("The specified extension registry class cannot be accessed!");
314             }
315         }
316     }
317 
318     boolean containsSchema(SchemaKey pKey) {
319         return schemas.containsKey(pKey);
320     }
321 
322     
323     /**
324      * gets a schema from the external namespace map
325      * @param namespace
326      * @return
327      */
328     XmlSchema getKnownSchema(String namespace) {
329         return (XmlSchema) knownNamespaceMap.get(namespace);
330     }
331     
332     /**
333      * Get a schema given a SchemaKey
334      * @param pKey
335      * @return
336      */
337     XmlSchema getSchema(SchemaKey pKey) {
338         return (XmlSchema) schemas.get(pKey);
339     }
340 
341     void addSchema(SchemaKey pKey, XmlSchema pSchema) {
342         if (schemas.containsKey(pKey)) {
343             throw new IllegalStateException("A schema with target namespace "
344                     + pKey.getNamespace() + " and system ID " + pKey.getSystemId()
345                     + " is already present.");
346         }
347         schemas.put(pKey, pSchema);
348     }
349 
350     private void addSimpleType(XmlSchema schema,String typeName){
351         XmlSchemaSimpleType type;
352         type = new XmlSchemaSimpleType(schema);
353         type.setName(typeName);
354         schema.addType(type);
355     }
356     public XmlSchema read(Reader r, ValidationEventHandler veh) {
357         return read(new InputSource(r), veh);
358     }
359 
360     XmlSchema read(final InputSource inputSource, ValidationEventHandler veh,
361             TargetNamespaceValidator namespaceValidator) {
362         try {
363             DocumentBuilderFactory docFac = DocumentBuilderFactory.newInstance();
364             docFac.setNamespaceAware(true);
365             final DocumentBuilder builder = docFac.newDocumentBuilder();
366             Document doc = null;
367             doc = parse_doPriv(inputSource, builder, doc);
368             return read(doc, inputSource.getSystemId(), veh, namespaceValidator);
369         } catch (ParserConfigurationException e) {
370             throw new XmlSchemaException(e.getMessage());
371         } catch (IOException e) {
372             throw new XmlSchemaException(e.getMessage());
373         } catch (SAXException e) {
374             throw new XmlSchemaException(e.getMessage());
375         }
376     }
377 
378     private Document parse_doPriv(final InputSource inputSource, final DocumentBuilder builder, Document doc) throws IOException, SAXException {
379         try {
380             doc = (Document) java.security.AccessController.doPrivileged(
381                     new PrivilegedExceptionAction() {
382                         public Object run() throws IOException, SAXException {
383                             return builder.parse(inputSource);
384                         }
385                     }
386             );
387         } catch (PrivilegedActionException e) {
388             Exception exception = e.getException();
389             if(exception instanceof IOException) {
390                 throw (IOException) exception;
391             }
392             if(exception instanceof SAXException) {
393                 throw (SAXException) exception;
394             }
395         }
396         return doc;
397     }
398 
399     public XmlSchema read(InputSource inputSource, ValidationEventHandler veh) {
400         return read(inputSource, veh, null);
401     }
402 
403     public XmlSchema read(Source source, ValidationEventHandler veh) {
404         if (source instanceof SAXSource) {
405             return read(((SAXSource) source).getInputSource(), veh);
406         } else if (source instanceof DOMSource) {
407             Node node = ((DOMSource) source).getNode();
408             if (node instanceof Document) {
409                 node = ((Document) node).getDocumentElement();
410             }
411             return read((Document) node, veh);
412         } else if (source instanceof StreamSource) {
413             StreamSource ss = (StreamSource) source;
414             InputSource isource = new InputSource(ss.getSystemId());
415             isource.setByteStream(ss.getInputStream());
416             isource.setCharacterStream(ss.getReader());
417             isource.setPublicId(ss.getPublicId());
418             return read(isource, veh);
419         } else {
420             InputSource isource = new InputSource(source.getSystemId());
421             return read(isource, veh);
422         }
423     }
424 
425     public XmlSchema read(Document doc, ValidationEventHandler veh) {
426         SchemaBuilder builder = new SchemaBuilder(this, null);
427         return builder.build(doc, null, veh);
428     }
429 
430    
431     public XmlSchema read(Element elem) {
432         SchemaBuilder builder = new SchemaBuilder(this, null);
433         XmlSchema xmlSchema = builder.handleXmlSchemaElement(elem, null);
434         xmlSchema.setInputEncoding(DOMUtil.getXmlEncoding(elem.getOwnerDocument()));
435         return xmlSchema;
436     }
437 
438     public XmlSchema read(Document doc, String uri, ValidationEventHandler veh) {
439         return read(doc, uri, veh, null);
440     }
441 
442     public XmlSchema read(Document doc, String uri, ValidationEventHandler veh,
443             TargetNamespaceValidator validator) {
444         SchemaBuilder builder = new SchemaBuilder(this, validator);
445         XmlSchema schema = builder.build(doc, uri, veh);
446         schema.setInputEncoding(DOMUtil.getInputEncoding(doc));
447 		return schema;
448     }
449 
450     public XmlSchema read(Element elem, String uri) {
451         SchemaBuilder builder = new SchemaBuilder(this, null);
452         XmlSchema xmlSchema = builder.handleXmlSchemaElement(elem, uri);
453         xmlSchema.setInputEncoding(DOMUtil.getInputEncoding(elem.getOwnerDocument()));
454         return xmlSchema;
455     }
456 
457     /**
458      * Creates new XmlSchemaCollection
459      */
460     public XmlSchemaCollection() {
461         init();
462     }
463 
464     /**
465      * Retrieve a set of XmlSchema instances with the given its system ID.
466      * In general, this will return a single instance, or none. However,
467      * if the schema has no targetNamespace attribute and was included
468      * from schemata with different target namespaces, then it may
469      * occur, that multiple schema instances with different logical
470      * target namespaces may be returned.
471      * @param systemId  the system id for this  schema
472      * @return array of XmlSchema objects
473      */
474     public XmlSchema[] getXmlSchema(String systemId) {
475         if (systemId == null) {
476             systemId = "";
477         }
478         final List result = new ArrayList();
479         for (Iterator iter = schemas.entrySet().iterator();  iter.hasNext();  ) {
480             Map.Entry entry = (Map.Entry) iter.next();
481             if (((SchemaKey) entry.getKey()).getSystemId().equals(systemId)) {
482                 result.add(entry.getValue());
483             }
484         }
485         return (XmlSchema[]) result.toArray(new XmlSchema[result.size()]);
486     }
487 
488     /**
489      * Returns an array of all the XmlSchemas in this collection.
490      * @return the list of XmlSchema objects
491      */
492     public XmlSchema[] getXmlSchemas() {
493         Collection c = schemas.values();
494         return (XmlSchema[]) c.toArray(new XmlSchema[c.size()]);
495     }
496 
497     public XmlSchemaElement getElementByQName(QName qname) {
498         String uri = qname.getNamespaceURI();
499         for (Iterator iter = schemas.entrySet().iterator();  iter.hasNext();  ) {
500             Map.Entry entry = (Map.Entry) iter.next();
501             if (((SchemaKey) entry.getKey()).getNamespace().equals(uri)) {
502                 XmlSchemaElement element = ((XmlSchema) entry.getValue()).getElementByName(qname);
503                 if (element != null) {
504                     return element;
505                 }
506         }
507         }
508         return null;
509     }
510 
511     public XmlSchemaType getTypeByQName(QName schemaTypeName) {
512         String uri = schemaTypeName.getNamespaceURI();
513         for (Iterator iter = schemas.entrySet().iterator();  iter.hasNext();  ) {
514             Map.Entry entry = (Map.Entry) iter.next();
515             if (((SchemaKey) entry.getKey()).getNamespace().equals(uri)) {
516                 XmlSchemaType type = ((XmlSchema) entry.getValue()).getTypeByName(schemaTypeName);
517                 if (type != null) {
518                     return type;
519                 }
520         }
521         }
522         return null;
523     }
524     
525     /**
526      * Find a global attribute by QName in this collection of schemas.
527      * @param schemaAttributeName the name of the attribute.
528      * @return the attribute or null.
529      */
530     public XmlSchemaAttribute getAttributeByQName(QName schemaAttributeName) {
531         String uri = schemaAttributeName.getNamespaceURI();
532         for (Iterator iter = schemas.entrySet().iterator();  iter.hasNext();  ) {
533             Map.Entry entry = (Map.Entry) iter.next();
534             if (((SchemaKey) entry.getKey()).getNamespace().equals(uri)) {
535                 XmlSchemaAttribute attribute = ((XmlSchema) entry.getValue()).getAttributeByName(schemaAttributeName);
536                 if (attribute != null) {
537                     return attribute;
538                 }
539         }
540         }
541         return null;
542     }
543     
544     /**
545      * Return the schema from this collection for a particular targetNamespace.
546      * @param uri target namespace URI.
547      * @return the schema.
548      */
549     public XmlSchema schemaForNamespace(String uri) {
550         for (Iterator iter = schemas.entrySet().iterator();  iter.hasNext();  ) {
551             Map.Entry entry = (Map.Entry) iter.next();
552             if (((SchemaKey) entry.getKey()).getNamespace().equals(uri)) {
553                 return (XmlSchema) entry.getValue();
554             }
555         }
556         return null;
557     }
558 
559     Map unresolvedTypes = new HashMap();
560 
561     void addUnresolvedType(QName type, TypeReceiver receiver) {
562         ArrayList receivers = (ArrayList)unresolvedTypes.get(type);
563         if (receivers == null) {
564             receivers = new ArrayList();
565             unresolvedTypes.put(type, receivers);
566         }
567         receivers.add(receiver);
568     }
569 
570     void resolveType(QName typeName, XmlSchemaType type) {
571         ArrayList receivers = (ArrayList)unresolvedTypes.get(typeName);
572         if (receivers == null)
573             return;
574         for (Iterator i = receivers.iterator(); i.hasNext();) {
575             TypeReceiver receiver = (TypeReceiver) i.next();
576             receiver.setType(type);
577         }
578         unresolvedTypes.remove(typeName);
579     }
580 
581     public NamespacePrefixList getNamespaceContext() {
582         return namespaceContext;
583     }
584 
585     public void setNamespaceContext(NamespacePrefixList namespaceContext) {
586         this.namespaceContext = namespaceContext;
587     }
588 
589     public void push(SchemaKey pKey){
590         stack.push(pKey);
591     }
592 
593     public void pop(){
594         stack.pop();
595     }
596 
597     public boolean check(SchemaKey pKey){
598         return (stack.indexOf(pKey)==-1);
599     }
600 
601 	public String toString() {
602     	return super.toString() + "[" + schemas.toString() + "]";
603     }
604 }