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 org.apache.ws.commons.schema.XmlSchemaSerializer.XmlSchemaSerializerException;
23  import org.apache.ws.commons.schema.constants.Constants;
24  import org.apache.ws.commons.schema.utils.NamespaceContextOwner;
25  import org.apache.ws.commons.schema.utils.NamespacePrefixList;
26  import org.w3c.dom.Document;
27  
28  import javax.xml.namespace.QName;
29  import javax.xml.transform.*;
30  import javax.xml.transform.dom.DOMSource;
31  import javax.xml.transform.stream.StreamResult;
32  import java.io.*;
33  import java.util.Map;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.Stack;
37  
38  // Ancient history, year unknown:-)
39  //Oct 15th - momo - initial impl
40  //Oct 17th - vidyanand - add SimpleType + element
41  //Oct 18th - momo - add ComplexType
42  //Oct 19th - vidyanand - handle external
43  //Dec 6th - Vidyanand - changed RuntimeExceptions thrown to XmlSchemaExceptions
44  //Jan 15th - Vidyanand - made changes to SchemaBuilder.handleElement to look for an element ref.
45  //Feb 20th - Joni - Change the getXmlSchemaFromLocation schema
46  //         variable to name s.
47  //Feb 21th - Joni - Port to XMLDomUtil and Tranformation.
48  /**
49   * Contains the definition of a schema. All XML Schema definition language (XSD)
50   * elements are children of the schema element. Represents the World Wide Web
51   * Consortium (W3C) schema element
52   */
53  public class XmlSchema extends XmlSchemaAnnotated implements NamespaceContextOwner {
54      private static final String UTF_8_ENCODING = "UTF-8";
55  	static final String SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";
56      XmlSchemaForm attributeFormDefault, elementFormDefault;
57  
58      XmlSchemaObjectTable attributeGroups,
59              attributes, elements, groups,
60              notations, schemaTypes;
61      XmlSchemaDerivationMethod blockDefault, finalDefault;
62      XmlSchemaObjectCollection includes, items;
63      boolean isCompiled;
64      String syntacticalTargetNamespace, logicalTargetNamespace, version;
65      String schema_ns_prefix = "";
66      XmlSchemaCollection parent;
67  
68      private NamespacePrefixList namespaceContext;
69      //keep the encoding of the input
70      private String inputEncoding;
71  
72      public void setInputEncoding(String encoding){
73          this.inputEncoding = encoding;
74      }
75      /**
76       * Creates new XmlSchema
77       * Create a new XmlSchema. The schema is <i>not</i> added to the parent collection,
78       * since it has no target namespace when created through this constructor.
79       * Call {@link XmlSchema#XmlSchema(String, XmlSchemaCollection)} instead.
80        *
81        * @param parent the parent XmlSchemaCollection
82       * @deprecated
83        */
84       public XmlSchema(XmlSchemaCollection parent) {
85      	this(null, null, parent);
86      }
87  
88      /**
89       * Create a schema that is not a member of a collection.
90       */
91      public XmlSchema() {
92      	this(null, null, null);
93      }
94  
95      /**
96       * Create a new schema and record it as a member of a schema collection.
97       * @param namespace the target namespace.
98       * @param systemId the system ID for the schema.
99       * @param parent the parent collection.
100      */
101     public XmlSchema(String namespace, String systemId, XmlSchemaCollection parent) {
102         this.parent = parent;
103         attributeFormDefault = new XmlSchemaForm(XmlSchemaForm.UNQUALIFIED);
104         elementFormDefault = new XmlSchemaForm(XmlSchemaForm.UNQUALIFIED);
105         blockDefault = new XmlSchemaDerivationMethod(Constants.BlockConstants.NONE);
106         finalDefault = new XmlSchemaDerivationMethod(Constants.BlockConstants.NONE);
107         items = new XmlSchemaObjectCollection();
108         includes = new XmlSchemaObjectCollection();
109         elements = new XmlSchemaObjectTable();
110         attributeGroups = new XmlSchemaObjectTable();
111         attributes = new XmlSchemaObjectTable();
112         groups = new XmlSchemaObjectTable();
113         notations = new XmlSchemaObjectTable();
114         schemaTypes = new XmlSchemaObjectTable();
115 
116         syntacticalTargetNamespace = logicalTargetNamespace = namespace;
117         if (logicalTargetNamespace == null) {
118              logicalTargetNamespace = "";
119          }
120         if(parent != null) {
121         	XmlSchemaCollection.SchemaKey schemaKey =
122         		new XmlSchemaCollection.SchemaKey(this.logicalTargetNamespace, systemId);
123         	if (parent.containsSchema(schemaKey)) {
124         		throw new XmlSchemaException("Schema name conflict in collection");
125         	} else {
126         		parent.addSchema(schemaKey, this);
127         	}
128         }
129     }
130 
131     public XmlSchema(String namespace, XmlSchemaCollection parent) {
132         this(namespace, namespace, parent);
133        
134     }
135 
136     public XmlSchemaForm getAttributeFormDefault() {
137         return attributeFormDefault;
138     }
139 
140     public void setAttributeFormDefault(XmlSchemaForm value) {
141         attributeFormDefault = value;
142     }
143 
144     public XmlSchemaObjectTable getAttributeGroups() {
145         return attributeGroups;
146     }
147 
148     public XmlSchemaObjectTable getAttributes() {
149         return attributes;
150     }
151 
152     public XmlSchemaDerivationMethod getBlockDefault() {
153         return blockDefault;
154     }
155 
156     public void setBlockDefault(XmlSchemaDerivationMethod blockDefault) {
157         this.blockDefault = blockDefault;
158     }
159 
160     public XmlSchemaForm getElementFormDefault() {
161         return elementFormDefault;
162     }
163 
164     public void setElementFormDefault(XmlSchemaForm elementFormDefault) {
165         this.elementFormDefault = elementFormDefault;
166     }
167 
168     public XmlSchemaObjectTable getElements() {
169         return elements;
170     }
171 
172     
173     protected XmlSchemaElement getElementByName(QName name, boolean deep,
174 			Stack schemaStack) {
175 		if (schemaStack != null && schemaStack.contains(this)) {
176 			// recursive schema - just return null
177 			return null;
178 		} else {
179 			XmlSchemaElement element = (XmlSchemaElement) elements
180 					.getItem(name);
181 			if (deep) {
182 				if (element == null) {
183 					// search the imports
184 					for (Iterator includedItems = includes.getIterator(); includedItems
185 							.hasNext();) {
186 						
187 						XmlSchema schema = getSchema(includedItems.next());
188 						
189 						if (schema != null) {
190 						// create an empty stack - push the current parent in
191 						// and
192 						// use the protected method to process the schema
193 						if (schemaStack == null) {
194 							schemaStack = new Stack();
195 						}
196 						schemaStack.push(this);
197 						element = schema.getElementByName(name, deep,
198 								schemaStack);
199 						if (element != null) {
200 							return element;
201 						}
202 						}
203 					}
204 				} else {
205 					return element;
206 				}
207 			}
208 
209 			return element;
210 		}
211 	}
212     
213     protected XmlSchemaAttribute getAttributeByName(QName name, boolean deep, Stack schemaStack) {
214                                         if (schemaStack != null && schemaStack.contains(this)) {
215                                                 // recursive schema - just return null
216                                                 return null;
217                                         } else {
218                                                 XmlSchemaAttribute attribute = (XmlSchemaAttribute) attributes
219                                                                 .getItem(name);
220                                                 if (deep) {
221                                                         if (attribute == null) {
222                                                                 // search the imports
223                                                                 for (Iterator includedItems = includes.getIterator(); includedItems
224                                                                                 .hasNext();) {
225                                                                         
226                                                                         XmlSchema schema = getSchema(includedItems.next());
227                                                                         
228                                                                         if (schema != null) {
229                                                                         // create an empty stack - push the current parent in
230                                                                         // and
231                                                                         // use the protected method to process the schema
232                                                                         if (schemaStack == null) {
233                                                                                 schemaStack = new Stack();
234                                                                         }
235                                                                         schemaStack.push(this);
236                                                                         attribute = schema.getAttributeByName(name, deep,
237                                                                                         schemaStack);
238                                                                         if (attribute != null) {
239                                                                                 return attribute;
240                                                                         }
241                                                                         }
242                                                                 }
243                                                         } else {
244                                                                 return attribute;
245                                                         }
246                                                 }
247 
248                                                 return attribute;
249                                         }
250                                 }
251 
252 	/**
253 	 * get an element by the name in the local schema
254 	 * 
255 	 * @param name
256 	 * @return the element.
257 	 */
258 	public XmlSchemaElement getElementByName(String name) {
259         QName nameToSearchFor = new QName(this.getTargetNamespace(),name);
260         return this.getElementByName(nameToSearchFor, false, null);
261 	}
262 
263 	/**
264 	 * Look for a element by its qname. Searches through all the schemas
265 	 * @param name
266 	 * @return the element.
267 	 */
268 	public XmlSchemaElement getElementByName(QName name) {
269 		return this.getElementByName(name, true, null);
270 	}
271 	
272 	/**
273 	 * Look for a global attribute by its QName. Searches through all schemas.
274 	 * @param name
275 	 * @return the attribute.
276 	 */
277 	public XmlSchemaAttribute getAttributeByName(QName name) {
278 	    return this.getAttributeByName(name, true, null);
279 	}
280 
281 	/**
282 	 * Protected method that allows safe (non-recursive schema loading). It looks for a type
283 	 * with constraints.
284          * 
285 	 * @param name
286 	 * @param deep
287 	 * @param schemaStack
288 	 * @return the type.
289 	 */
290 	protected XmlSchemaType getTypeByName(QName name, boolean deep,
291 			Stack schemaStack) {
292 		if (schemaStack != null && schemaStack.contains(this)) {
293 			// recursive schema - just return null
294 			return null;
295 		} else {
296 			XmlSchemaType type = (XmlSchemaType) schemaTypes.getItem(name);
297 
298 			if (deep) {
299 				if (type == null) {
300 					// search the imports
301 					for (Iterator includedItems = includes.getIterator(); includedItems
302 							.hasNext();) {
303 
304 						XmlSchema schema = getSchema(includedItems.next());
305 						
306 						if (schema != null) {
307 							// create an empty stack - push the current parent
308 							// use the protected method to process the schema
309 							if (schemaStack == null) {
310 								schemaStack = new Stack();
311 							}
312 							schemaStack.push(this);
313 							type = schema
314 									.getTypeByName(name, deep, schemaStack);
315 							if (type != null) {
316 								return type;
317 							}
318 						}
319 					}
320 				} else {
321 					return type;
322 				}
323 			}
324 
325 			return type;
326 		}
327 	}
328 
329 	/**
330 	 * Search this schema and all the imported/included ones
331          * for the given Qname
332 	 * @param name
333 	 * @return the type.
334 	 */
335 	public XmlSchemaType getTypeByName(QName name) {
336 		return getTypeByName(name, true, null);
337 	}
338 
339 	/**
340 	 * Search this schema for a type by qname.
341 	 * @param name
342 	 * @return the type.
343 	 */
344 	public XmlSchemaType getTypeByName(String name) {
345         QName nameToSearchFor = new QName(this.getTargetNamespace(),name);
346         return getTypeByName(nameToSearchFor, false, null);
347 	}
348 
349 	/**
350 	 * Get a schema from an import
351 	 * 
352 	 * @param includeOrImport
353 	 * @return return the schema object.
354 	 */
355 	private XmlSchema getSchema(Object includeOrImport) {
356 		XmlSchema schema;
357 		if (includeOrImport instanceof XmlSchemaImport) {
358 			schema = ((XmlSchemaImport) includeOrImport).getSchema();
359 		} else if (includeOrImport instanceof XmlSchemaInclude) {
360 			schema = ((XmlSchemaInclude) includeOrImport).getSchema();
361 		} else {
362 			// skip ?
363 			schema = null;
364 		}
365 
366 		return schema;
367 	}
368 
369     
370 
371     public XmlSchemaDerivationMethod getFinalDefault() {
372         return finalDefault;
373     }
374 
375     public void setFinalDefault(XmlSchemaDerivationMethod finalDefault) {
376         this.finalDefault = finalDefault;
377     }
378 
379     public XmlSchemaObjectTable getGroups() {
380         return groups;
381     }
382 
383     public XmlSchemaObjectCollection getIncludes() {
384         return includes;
385     }
386 
387     public boolean isCompiled() {
388         return isCompiled;
389     }
390 
391     public XmlSchemaObjectCollection getItems() {
392         return items;
393     }
394 
395     public XmlSchemaObjectTable getNotations() {
396         return notations;
397     }
398 
399     public XmlSchemaObjectTable getSchemaTypes() {
400         return schemaTypes;
401     }
402 
403     public String getTargetNamespace() {
404         return syntacticalTargetNamespace;
405     }
406 
407     public void setTargetNamespace(String targetNamespace) {
408         if (!targetNamespace.equals("")) {
409             syntacticalTargetNamespace = logicalTargetNamespace = targetNamespace;
410         }
411     }
412 
413     public String getVersion() {
414         return version;
415     }
416 
417     public void compile(ValidationEventHandler eh) {
418 
419     }
420 
421     /**
422      * Serialize the schema into the given output stream
423      * @param out - the output stream to write to
424      */
425     public void write(OutputStream out) {
426     try {
427         if (this.inputEncoding!= null &&
428                 !"".equals(this.inputEncoding)){
429                 write(new OutputStreamWriter(out,this.inputEncoding));
430         }else{
431         	//As per the XML spec the default is taken to be UTF 8
432             write(new OutputStreamWriter(out,UTF_8_ENCODING));
433         }
434     } catch (UnsupportedEncodingException e) {
435         //log the error and just write it without the encoding
436         write(new OutputStreamWriter(out));
437     }
438 
439     }
440 
441     /**
442      * Serialize the schema into the given output stream
443      * @param out - the output stream to write to
444      * @param options -  a map of options
445      */
446     public void write(OutputStream out, Map options) {
447     	try {
448 	        if (this.inputEncoding!= null &&
449 	                !"".equals(this.inputEncoding)){
450 	                write(new OutputStreamWriter(out,this.inputEncoding),options);
451 	        }else{
452 	            write(new OutputStreamWriter(out,UTF_8_ENCODING),options);
453 	        }
454     	} catch (UnsupportedEncodingException e) {
455             //log the error and just write it without the encoding
456             write(new OutputStreamWriter(out));
457         }
458 
459     }
460 
461     /**
462      * Serialie the schema to a given writer
463      * @param writer - the writer to write this
464      */
465     public void write(Writer writer,Map options) {
466         serialize_internal(this, writer,options);
467     }
468     /**
469      * Serialie the schema to a given writer
470      * @param writer - the writer to write this
471      */
472     public void write(Writer writer) {
473         serialize_internal(this, writer,null);
474     }
475 
476     public Document[] getAllSchemas() {
477         try {
478 
479             XmlSchemaSerializer xser = new XmlSchemaSerializer();
480             xser.setExtReg(this.parent.getExtReg());
481             return xser.serializeSchema(this, true);
482 
483         } catch (XmlSchemaSerializer.XmlSchemaSerializerException e) {
484             throw new XmlSchemaException(e.getMessage());
485         }
486     }
487 
488     /**
489      * serialize the schema - this is the method tht does to work
490      * @param schema
491      * @param out
492      * @param options
493      */
494     private  void serialize_internal(XmlSchema schema, Writer out, Map options) {
495 
496         try {
497             XmlSchemaSerializer xser = new XmlSchemaSerializer();
498             xser.setExtReg(this.parent.getExtReg());
499             Document[] serializedSchemas = xser.serializeSchema(schema, false);
500             TransformerFactory trFac = TransformerFactory.newInstance();
501 
502             try {
503                 trFac.setAttribute("indent-number", "4");
504             } catch (IllegalArgumentException e) {
505                 //do nothing - we'll just silently let this pass if it
506                 //was not compatible
507             }
508 
509             Source source = new DOMSource(serializedSchemas[0]);
510             Result result = new StreamResult(out);
511             javax.xml.transform.Transformer tr = trFac.newTransformer();
512 
513             //use the input encoding if there is one
514             if (schema.inputEncoding!= null &&
515                     !"".equals(schema.inputEncoding)){
516                 tr.setOutputProperty(OutputKeys.ENCODING,schema.inputEncoding);
517             }
518 
519             //let these be configured from outside  if any is present
520             //Note that one can enforce the encoding by passing the necessary
521             //property in options
522 
523             if (options==null){
524                 options = new HashMap();
525                 loadDefaultOptions(options);
526             }
527             Iterator keys = options.keySet().iterator();
528             while (keys.hasNext()) {
529                 Object key = keys.next();
530                 tr.setOutputProperty((String)key, (String)options.get(key));
531             }
532 
533             tr.transform(source, result);
534             out.flush();
535         } catch (TransformerConfigurationException e) {
536             throw new XmlSchemaException(e.getMessage());
537         } catch (TransformerException e) {
538             throw new XmlSchemaException(e.getMessage());
539         } catch (XmlSchemaSerializer.XmlSchemaSerializerException e) {
540             throw new XmlSchemaException(e.getMessage());
541         } catch (IOException e) {
542             throw new XmlSchemaException(e.getMessage());
543         }
544     }
545 
546     /**
547      * Load the default options
548      * @param options  - the map of
549      */
550     private void loadDefaultOptions(Map options) {
551         options.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
552         options.put(OutputKeys.INDENT, "yes");
553     }
554 
555     public void addType(XmlSchemaType type) {
556         QName qname = type.getQName();
557         if (schemaTypes.contains(qname)) {
558             throw new XmlSchemaException(" Schema for namespace '" +
559                     syntacticalTargetNamespace + "' already contains type '" +
560                     qname.getLocalPart() + "'");
561         }
562         schemaTypes.add(qname, type);
563     }
564 
565     public NamespacePrefixList getNamespaceContext() {
566         return namespaceContext;
567     }
568 
569     /**
570      * Sets the schema elements namespace context. This may be used for schema
571      * serialization, until a better mechanism was found.
572      */
573     public void setNamespaceContext(NamespacePrefixList namespaceContext) {
574         this.namespaceContext = namespaceContext;
575     }
576 
577     /**
578      * Override the equals(Object) method with equivalence checking
579      * that is specific to this class.
580      */
581     public boolean equals(Object what) {
582 
583         //Note: this method may no longer be required when line number/position are used correctly in XmlSchemaObject.
584         //Currently they are simply initialized to zero, but they are used in XmlSchemaObject.equals 
585         //which can result in a false positive (e.g. if a WSDL contains 2 inlined schemas).
586 
587         if (what == this) {
588             return true;
589         }
590 
591         //If the inherited behaviour determines that the objects are NOT equal, return false. 
592         //Otherwise, do some further equivalence checking.
593 
594         if(!super.equals(what)) {
595             return false;
596         }
597 
598         if (!(what instanceof XmlSchema)) {
599             return false;
600         }
601 
602         XmlSchema xs = (XmlSchema) what;
603 
604         if (this.id != null) {
605             if (!this.id.equals(xs.id)) {
606                 return false;
607             }
608         } else {
609             if (xs.id != null) {
610                 return false;
611             }
612         }
613 
614         if (this.syntacticalTargetNamespace != null) {
615             if (!this.syntacticalTargetNamespace.equals(xs.syntacticalTargetNamespace)) {
616                 return false;
617             }
618         } else {
619             if (xs.syntacticalTargetNamespace != null) {
620                 return false;
621             }
622         }
623 
624         //TODO decide if further schema content should be checked for equivalence.
625 
626         return true;
627     }
628     
629     /**
630      * Retrieve a DOM tree for this one schema, independent of any included or 
631      * related schemas.
632      * @return The DOM document.
633      * @throws XmlSchemaSerializerException
634      */
635     public Document getSchemaDocument() throws XmlSchemaSerializerException {
636         XmlSchemaSerializer xser = new XmlSchemaSerializer();
637         xser.setExtReg(this.parent.getExtReg());
638         return xser.serializeSchema(this, false)[0];
639     }
640     
641     public String getInputEncoding() {
642         return inputEncoding;
643     }
644     
645     public String toString() {
646         return super.toString() + "[" + logicalTargetNamespace + "]";
647     }
648 }