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