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