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