View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2004 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * $Id$
44   */
45  
46  package org.exolab.castor.xml.schema.reader;
47  
48  //-- imported classes and packages
49  import org.exolab.castor.net.URIException;
50  import org.exolab.castor.net.URILocation;
51  import org.exolab.castor.net.URIResolver;
52  import org.exolab.castor.xml.AttributeSet;
53  import org.exolab.castor.xml.Namespaces;
54  import org.exolab.castor.xml.XMLException;
55  import org.exolab.castor.xml.schema.Annotation;
56  import org.exolab.castor.xml.schema.AttributeGroup;
57  import org.exolab.castor.xml.schema.AttributeGroupDecl;
58  import org.exolab.castor.xml.schema.ComplexType;
59  import org.exolab.castor.xml.schema.Group;
60  import org.exolab.castor.xml.schema.ModelGroup;
61  import org.exolab.castor.xml.schema.RedefineSchema;
62  import org.exolab.castor.xml.schema.Schema;
63  import org.exolab.castor.xml.schema.SchemaContext;
64  import org.exolab.castor.xml.schema.SchemaException;
65  import org.exolab.castor.xml.schema.SchemaNames;
66  import org.exolab.castor.xml.schema.SimpleType;
67  import org.exolab.castor.xml.schema.XMLType;
68  import org.xml.sax.InputSource;
69  import org.xml.sax.Locator;
70  import org.xml.sax.Parser;
71  
72  /**
73   * The purpose of this class is to read redefined elements in an XML schema.
74   * The following xml schema structure can be redefined:
75   * <ul>
76   *     <li>Complextypes</li>
77   *     <li>Simpletypes</li>
78   *     <li>AttributeGroup</li>
79   *     <li>Group</li>
80   * </ul>
81   * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
82   * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
83   **/
84  public class RedefineUnmarshaller extends ComponentReader
85  {
86  
87  	/**
88  	 * The current ComponentReader used to read nested structures
89  	 **/
90  	private ComponentReader _unmarshaller;
91  	
92  	/**
93  	 * The current branch depth
94  	 **/
95  	private int _depth = 0;
96  	
97  	/**
98  	 * The parent XML schema
99  	 */
100 	private Schema _schema;
101 	
102 	/**
103 	 * The imported XML Schema
104 	 */
105 	private Schema _importedSchema;
106 	
107 	private RedefineSchema _redefineSchema;
108 	
109 	/**
110 	 * The XML Schema imported
111 	 */
112 	
113     public RedefineUnmarshaller(
114             final SchemaContext schemaContext,
115             final Schema schema,
116             final AttributeSet atts,
117             final URIResolver uriResolver,
118             final Locator locator,
119             final SchemaUnmarshallerState state)
120     throws XMLException {
121         super(schemaContext);
122         if (schema == null) {
123         	String err = SchemaNames.REDEFINE + " must be used with an existing parent XML Schema.";
124         	throw new SchemaException(err);
125         }
126         setURIResolver(uriResolver);
127 
128         URILocation uri = null;
129 		//-- Get schemaLocation
130 		String schemaLocation = atts.getValue(SchemaNames.SCHEMALOCATION_ATTR);
131 		_schema = schema;
132 		
133 		if (schemaLocation == null) {
134 		    //-- <redefine/> or <redefine> <annotation>(*) </redefine>
135 		    _redefineSchema = new RedefineSchema(schema);
136 		    _schema.addRedefineSchema(_redefineSchema);
137 			return; 
138 		}
139 
140         
141         if (schemaLocation.indexOf("\\") != -1) {
142             String err = "'" + schemaLocation + 
143                 "' is not a valid URI as defined by IETF RFC 2396.";
144             err += "The URI mustn't contain '\\'.";
145             error(err);
146 	    }
147 
148         try {
149             String documentBase = locator.getSystemId();
150             if (documentBase != null) {
151                 if (!documentBase.endsWith("/"))
152                     documentBase = documentBase.substring(0, documentBase.lastIndexOf('/') +1 );
153             }
154 	        uri = getURIResolver().resolve(schemaLocation, documentBase);
155             if (uri != null) {
156                 schemaLocation = uri.getAbsoluteURI();
157             }
158         } 
159         catch (URIException urix) {
160             throw new XMLException(urix);
161         }
162 
163         //-- Schema object to hold import schema
164 		boolean addSchema = false;
165 		_redefineSchema = schema.getRedefineSchema(schemaLocation);
166 		Schema importedSchema = null;
167 		
168 		boolean alreadyLoaded = false;
169 		
170         //-- The schema is not yet loaded
171 		if (_redefineSchema == null) {
172             if (uri instanceof SchemaLocation) {
173                 importedSchema = ((SchemaLocation)uri).getSchema();
174                 //-- set the main schema in order to handle 
175                 //-- redefinition at runtime
176                 
177                 // importedSchema.addMainSchema(schema);
178                 
179                 _redefineSchema = new RedefineSchema(schema, importedSchema);
180 			    schema.addRedefineSchema(_redefineSchema);
181 			    alreadyLoaded = true;
182             }
183             else {
184 			    importedSchema = new Schema();
185 			    addSchema = true;
186 			}
187 		}
188 		else {
189 			//-- check schema location, if different, allow merge
190 		    String tmpLocation = _redefineSchema.getOriginalSchema().getSchemaLocation();
191 		    alreadyLoaded = schemaLocation.equals(tmpLocation);
192 		}
193 
194         state.markAsProcessed(schemaLocation, importedSchema);
195 
196         if (alreadyLoaded) return;
197         
198         //-- Parser Schema
199 		Parser parser = null;
200 		try {
201 		    parser = getSchemaContext().getParser();
202 		}
203 		catch(RuntimeException rte) {}
204 		if (parser == null) {
205 		    throw new SchemaException("Error failed to create parser for import");
206 		}
207 		//-- Create Schema object and setup unmarshaller
208 		SchemaUnmarshaller schemaUnmarshaller = new SchemaUnmarshaller(getSchemaContext(), state);
209 		schemaUnmarshaller.setURIResolver(getURIResolver());
210 		schemaUnmarshaller.setSchema(importedSchema);
211 		Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller);
212 		parser.setDocumentHandler(handler);
213 		parser.setErrorHandler(handler);
214 
215 		try {
216 		    InputSource source = new InputSource(uri.getReader());
217             source.setSystemId(uri.getAbsoluteURI());
218             parser.parse(source);
219 		}
220 		catch(java.io.IOException ioe) {
221 		    throw new SchemaException("Error reading import file '"+schemaLocation+"': "+ ioe);
222 		}
223 		catch(org.xml.sax.SAXException sx) {
224 		    throw new SchemaException(sx);
225 		}
226 		
227 		//-- namespace checking
228 		String namespace = importedSchema.getTargetNamespace();
229 		if ( namespace != null ) {
230 			//-- Make sure targetNamespace is not the same as the
231 			//-- importing schema, see section 4.2.2 in the
232 			//-- XML Schema Recommendation
233 			if (!namespace.equals(schema.getTargetNamespace()) ) {
234 			    String err = "The 'namespace' attribute in the <redefine> element must be the same of the targetNamespace of the global schema.\n"
235 			    	         +namespace+" is different from:"+schema.getTargetNamespace();
236 			    error (err);
237 			}
238 		} else {
239 			importedSchema.setTargetNamespace(schema.getTargetNamespace());
240 		}
241 		
242         //-- set the main schema in order to handle 
243 		//-- redefinition at runtime
244 		
245 		// importedSchema.addMainSchema(schema);
246 		
247 		_importedSchema = importedSchema;
248 		_redefineSchema = new RedefineSchema(schema, _importedSchema);
249 		//-- Add schema to list of redefine schemas (if not already present)
250 		if (addSchema)
251 		{
252             importedSchema.setSchemaLocation(schemaLocation);
253             _schema.addRedefineSchema(_redefineSchema);
254 		}
255 	}
256 
257     /**
258      * Signals the start of an element with the given name.
259      *
260      * @param name the NCName of the element. It is an error
261      * if the name is a QName (ie. contains a prefix).
262      * @param namespace the namespace of the element. This may be null.
263      * Note: A null namespace is not the same as the default namespace unless
264      * the default namespace is also null.
265      * @param atts the AttributeSet containing the attributes associated
266      * with the element.
267      * @param nsDecls the namespace declarations being declared for this
268      * element. This may be null.
269      **/
270     public void startElement(String name, String namespace, AttributeSet atts,
271     		Namespaces nsDecls)
272 	throws XMLException
273 	{
274 
275     	
276     	
277     	//-- DEBUG
278     	//System.out.println("#startElement: " + name + " {" + namespace + "}");
279     	//-- /DEBUG
280 
281     	//-- Do delagation if necessary
282     	if (_unmarshaller != null) {
283     		try {
284     			_unmarshaller.startElement(name, namespace, atts, nsDecls);
285     			_depth ++;
286     			return;
287     		} catch(RuntimeException rtx) {
288     			error(rtx);
289     		}
290     	}
291     	
292     	//-- <annotation>
293     	if (name.equals(SchemaNames.ANNOTATION)) {
294     		_unmarshaller = new AnnotationUnmarshaller(getSchemaContext(), atts);
295     	}
296     	//-- <attributeGroup>
297     	else if (name.equals(SchemaNames.ATTRIBUTE_GROUP)) {
298     		_unmarshaller = new AttributeGroupUnmarshaller(getSchemaContext(), _schema, atts);
299     	}
300     	//-- <complexType>
301     	else if (name.equals(SchemaNames.COMPLEX_TYPE)) {
302     		_unmarshaller
303 			= new ComplexTypeUnmarshaller(getSchemaContext(), _schema, atts);
304     	}
305     	//-- <simpleType>
306     	else if (name.equals(SchemaNames.SIMPLE_TYPE)) {
307     		_unmarshaller = new SimpleTypeUnmarshaller(getSchemaContext(), _schema, atts);
308     	}
309     	//-- <group>
310     	else if (name.equals(SchemaNames.GROUP)) {
311     		_unmarshaller = new ModelGroupUnmarshaller(getSchemaContext(), _schema, atts);
312     	}
313     	else {
314     		//--Exception here
315     		String err = "<" + name +"> elements cannot be used in a redefine.";
316             error(err);
317     	}
318 
319     	_unmarshaller.setDocumentLocator(getDocumentLocator());
320 
321     } //-- startElement
322 
323     /**
324      * Signals to end of the element with the given name.
325      *
326      * @param name the NCName of the element. It is an error
327      * if the name is a QName (ie. contains a prefix).
328      * @param namespace the namespace of the element.
329      **/
330     public void endElement(String name, String namespace)
331 	throws XMLException
332 	{
333     	
334     	//-- DEBUG
335     	//System.out.println("#endElement: " + name + " {" + namespace + "}");
336     	//-- /DEBUG
337 
338     	//-- Do delagation if necessary
339     	if ((_unmarshaller != null) && (_depth > 0)) {
340     		_unmarshaller.endElement(name, namespace);
341     		--_depth;
342     		return;
343     	}
344 
345 
346     	//-- use internal JVM String
347     	name = name.intern();
348 
349 
350     	//-- check for name mismatches
351     	if ((_unmarshaller != null)) {
352     		if (!name.equals(_unmarshaller.elementName())) {
353     			String err = "error: missing end element for ";
354     			err += _unmarshaller.elementName();
355     			error(err);
356     		}
357     	}
358     	else {
359     		String err = "error: missing start element for " + name;
360     		throw new SchemaException(err);
361     	}
362 
363     	//-- call unmarshaller.finish() to perform any necessary cleanup
364     	_unmarshaller.finish();
365 
366     	//-- <annotation>
367     	if (name.equals(SchemaNames.ANNOTATION)) {
368     		_redefineSchema.addAnnotation((Annotation)_unmarshaller.getObject());
369     	}
370     	//-- <attributeGroup>
371         else if (name.equals(SchemaNames.ATTRIBUTE_GROUP)) {
372         	if (_redefineSchema.getSchemaLocation() == "") {
373         		String err = "In a <redefine>, only annotations can be defined when no -schemaLocation- is specified.";
374         		error(err);
375         	}
376         	
377         	AttributeGroupDecl group = null;
378         	group = (AttributeGroupDecl)(((AttributeGroupUnmarshaller)_unmarshaller).getAttributeGroup());
379         	
380         	String structureName = group.getName();
381         	if (structureName == null) {
382         		String err = "When redefining an AttributeGroup, the group must have a name.\n";
383         		error(err);
384         	}
385         	
386         	//1-- the attributeGroup must exist in the imported schema
387         	AttributeGroup original = _importedSchema.getAttributeGroup(structureName);
388         	if (original == null) {
389         		String err = "When redefining an AttributeGroup, the AttributeGroup must be present in the imported XML schema.\n"
390         			+"AttributeGroup: "+structureName+" is not defined in XML Schema:" + _importedSchema.getSchemaLocation();
391         		error(err);
392         	}
393         	
394         	//-- todo: add code to check the Derivation Valid (Restriction, Complex) constraint.
395         	group.setRedefined();
396     		_redefineSchema.addAttributeGroup(group);
397     		
398     	}
399     	//-- <complexType>
400     	else if (name.equals(SchemaNames.COMPLEX_TYPE)) {
401     		if (_redefineSchema.getSchemaLocation() == "") {
402     			String err = "In a <redefine>, only annotations can be defined when no -schemaLocation- is specified.";
403     			error(err);
404     		}
405     		ComplexType complexType = null;
406     		complexType = ((ComplexTypeUnmarshaller)_unmarshaller).getComplexType();
407     		//-- Checks that the complexType exists in the imported schema
408     		String structureName = complexType.getName();
409     		if (structureName == null) {
410     			String err = "When redefining a complexType, the complexType must have a name.\n";
411     			error(err);
412     		}
413     		
414     		//1-- the complexType must exist in the imported schema
415     		ComplexType original = _importedSchema.getComplexType(structureName);
416     		if (original == null) {
417     		    String err = "When redefining a complexType, the complexType must be present in the imported XML schema.\n"
418     		    	         +"ComplexType: "+structureName+" is not defined in XML Schema:" + _importedSchema.getSchemaLocation();
419     		    error(err);
420     		}
421     		
422     		//2-- the base type must be itself
423     		XMLType baseType = complexType.getBaseType();
424     		//--just check the names since a top level complexType can only be defined once.
425     		if (baseType == null || !baseType.getName().equals(structureName)) {
426     			String err = "When redefining a complexType, the complexType must use itself as the base type definition.\n"
427     				+"ComplexType: "+structureName+" uses:" + baseType+ " as its base type.";
428     			error(err);
429     		}
430     		
431     		complexType.setRedefined();
432     		_redefineSchema.addComplexType(complexType);
433             getResolver().addResolvable(complexType.getReferenceId(), complexType);
434     	}
435     	//-- <simpleType>
436     	else if (name.equals(SchemaNames.SIMPLE_TYPE)) {
437     		if (_redefineSchema.getSchemaLocation() == "") {
438     			String err = "In a <redefine>, only annotations can be defined when no -schemaLocation- is specified.";
439     			error(err);
440     		}
441     		
442     		SimpleType simpleType = null;
443     		simpleType = ((SimpleTypeUnmarshaller)_unmarshaller).getSimpleType();
444             //-- Checks that the simpleType exists in the imported schema
445     		String structureName = simpleType.getName();
446     		if (structureName == null) {
447     			String err = "When redefining a simpleType, the simpleType must have a name.\n";
448     			error(err);
449     		}
450     		
451     		//1-- the simpleType must exist in the imported schema
452     		SimpleType original = _importedSchema.getSimpleType(structureName,_schema.getTargetNamespace() );
453     		if (original == null) {
454     			String err = "When redefining a simpleType, the simpleType must be present in the imported XML schema.\n"
455     				+"SimpleType: "+structureName+" is not defined in XML Schema:" + _importedSchema.getSchemaLocation();
456     			error(err);
457     		}
458     		
459     		//2-- the base type must be itself
460     		XMLType baseType = simpleType.getBaseType();
461     		//--just check the names since a top level complexType can only be defined once.
462     		if (!baseType.getName().equals(structureName)) {
463     			String err = "When redefining a simpleType, the simpleType must use itself as the base type definition.\n"
464     				+"SimpleType: "+structureName+" uses:" + baseType.getName() + " as its base type.";
465     			error(err);
466     		}	
467     		
468     		simpleType.setRedefined();
469     		_redefineSchema.addSimpleType(simpleType);
470     		getResolver().addResolvable(simpleType.getReferenceId(), simpleType);
471     	}
472     	//--<group>
473     	else if (name.equals(SchemaNames.GROUP)) {
474     		if (_redefineSchema.getSchemaLocation() == "") {
475     			String err = "In a <redefine>, only annotations can be defined when no -schemaLocation- is specified.";
476     			error(err);
477     		}
478     		
479     		ModelGroup group = null;
480     		group = (((ModelGroupUnmarshaller)_unmarshaller).getGroup());
481     		
482     		String structureName = group.getName();
483     		if (structureName == null) {
484     			String err = "When redefining a group, the group must have a name.\n";
485     			error(err);
486     		}
487     		
488     		//1-- the group must exist in the imported schema
489     		Group original = _importedSchema.getModelGroup(structureName);
490     		if (original == null) {
491     			String err = "When redefining a group, the group must be present in the imported XML schema.\n"
492     				+"Group: "+structureName+" is not defined in XML Schema:" + _importedSchema.getSchemaLocation();
493     			error(err);
494     		}
495     		
496     		//-- code needs to be added to check the Particle Valid (Restriction) constraint
497     		//--TBD
498     		
499     		group.setRedefined();
500     		_redefineSchema.addGroup(group);
501     	} else {
502     		String err = "In a <redefine>, only complexTypes|simpleTypes|groups or attributeGroups can be redefined.";
503 			error(err);	
504     	}
505 
506     	_unmarshaller = null;
507     } //-- endElement
508 
509     public void characters(char[] ch, int start, int length)
510 	throws XMLException
511 	{
512     	//-- Do delagation if necessary
513     	if (_unmarshaller != null) {
514     		_unmarshaller.characters(ch, start, length);
515     	}
516     } //-- characters
517     
518     
519     /**
520      * Sets the name of the element that this UnknownUnmarshaller handles
521     **/
522     public String elementName() {
523         return SchemaNames.REDEFINE;
524     } //-- elementName
525 
526     /**
527      * Returns the Object created by this ComponentReader
528      * @return the Object created by this ComponentReader
529     **/
530     public Object getObject() {
531         return _redefineSchema;
532     } //-- getObject
533 
534 }