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 }