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 1999-2002 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * $Id$
34   */
35  
36  package org.exolab.castor.xml.schema.reader;
37  
38  import java.io.IOException;
39  import java.io.Reader;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.exolab.castor.net.URIException;
44  import org.exolab.castor.net.URILocation;
45  import org.exolab.castor.net.URIResolver;
46  import org.exolab.castor.util.NestedIOException;
47  import org.exolab.castor.xml.XMLException;
48  import org.exolab.castor.xml.schema.Schema;
49  import org.exolab.castor.xml.schema.SchemaContext;
50  import org.exolab.castor.xml.schema.SchemaContextImpl;
51  import org.xml.sax.EntityResolver;
52  import org.xml.sax.ErrorHandler;
53  import org.xml.sax.InputSource;
54  import org.xml.sax.Parser;
55  import org.xml.sax.SAXException;
56  import org.xml.sax.SAXParseException;
57  
58  /**
59   * A class for reading XML Schemas.
60   * 
61   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
62   * @version $Revision$ $Date: 2004-10-05 14:27:10 -0600 (Tue, 05 Oct 2004) $
63   **/
64  @SuppressWarnings("deprecation")
65  public class SchemaReader {
66  
67    /**
68     * The {@link Log} instance to use.
69     */
70    private static final Log LOG = LogFactory.getLog(SchemaReader.class);
71  
72    /**
73     * The Castor XML Context... mother of all.
74     */
75    private SchemaContext _schemaContext;
76  
77    /**
78     * XML Parser instance
79     */
80    private Parser _parser = null;
81  
82    /**
83     * SAX InputSource to Schema
84     */
85    private InputSource _source = null;
86  
87    /**
88     * SAX EntityResolver
89     */
90    private EntityResolver _resolver = null;
91  
92    /**
93     * SAX ErrorHandler
94     */
95    private ErrorHandler _errorHandler = null;
96  
97    /**
98     * The resolver to be used for resolving href
99     */
100   private URIResolver _uriResolver;
101 
102   /**
103    * A flag that indicates that included schemas should be cached instead of being inlined [which is
104    * the default behavior as specified by the XML Schema Specification].
105    * 
106    */
107   private boolean _cacheIncludedSchemas = false;
108 
109   private Schema _schema = null;
110 
111   private boolean _validate = true;
112 
113   /**
114    * Old fashion style to create a SchemaReader instance.
115    * 
116    * @throws IOException if no Parser is available
117    */
118   private void init() throws IOException {
119     // get default parser from Configuration
120     _schemaContext = new SchemaContextImpl();
121 
122     Parser parser = _schemaContext.getParser();
123 
124     if (parser == null) {
125       String message = "fatal error: unable to create SAX parser.";
126       LOG.warn(message);
127       throw new IOException(message);
128     }
129 
130     _parser = parser;
131   }
132 
133   /**
134    * Creates a new SchemaReader for the given InputSource
135    * 
136    * @param source the InputSource to read the Schema from.
137    */
138   public SchemaReader(InputSource source) throws IOException {
139     init();
140 
141     if (source == null)
142       throw new IllegalArgumentException("InputSource cannot be null");
143 
144     _source = source;
145 
146   }
147 
148   /**
149    * Creates a new SchemaReader for the given Reader
150    * 
151    * @param reader the Reader to read the Schema from.
152    * @param filename for reporting errors.
153    **/
154   public SchemaReader(Reader reader, String filename) throws IOException {
155     init();
156 
157     if (reader == null) {
158       String err = "The argument 'reader' must not be null.";
159       throw new IllegalArgumentException(err);
160     }
161 
162     _source = new InputSource(reader);
163     if (filename == null)
164       filename = reader.toString();
165     _source.setPublicId(filename);
166 
167   }
168 
169   /**
170    * Creates a new SchemaReader for the given URL
171    * 
172    * @param url the URL string
173    **/
174   public SchemaReader(String url) throws IOException {
175     init();
176     if (url == null) {
177       String err = "The argument 'url' must not be null.";
178       throw new IllegalArgumentException(err);
179     }
180     _source = new InputSource(url);
181 
182   }
183 
184   /**
185    * New style how to create a SchemaReader instance, requiring that {@link SchemaContext} and
186    * InputSource are set before calling {@link read}.
187    */
188   public SchemaReader() {
189     super();
190   }
191 
192   /**
193    * To set the {@link SchemaContext} to be used. Also resets the parser as it depends of the
194    * {@link SchemaContext}.
195    * 
196    * @param schemaContext the {@link SchemaContext} to be used
197    */
198   public void setSchemaContext(final SchemaContext schemaContext) {
199     this._schemaContext = schemaContext;
200 
201     Parser p = _schemaContext.getParser();
202     if (p != null) {
203       _parser = p;
204     }
205   }
206 
207   /**
208    * A different way to create a SchemaReader by using an empty constructor and setting the
209    * InputSource afterwards.
210    * 
211    * @param inputSource the InputSource to read the schema from
212    */
213   public void setInputSource(final InputSource inputSource) {
214     if (inputSource == null) {
215       String message = "InputSource must not be null";
216       LOG.warn(message);
217       throw new IllegalArgumentException(message);
218     }
219     _source = inputSource;
220   }
221 
222   /**
223    * Reads the Schema from the source and returns the Schema object model.
224    * 
225    * <BR />
226    * <B>Note:</B> Subsequent calls to this method will simply return a cached copy of the Schema
227    * object. To read a new Schema object, create a new Reader.
228    * 
229    * @return the new Schema created from the source of this SchemaReader
230    **/
231   public Schema read() throws IOException {
232     if (_schema != null) {
233       return _schema;
234     }
235     if (_parser == null) {
236       String message = "Required Parser was not specified";
237       LOG.warn(message);
238       throw new IllegalStateException(message);
239     }
240     if (_source == null) {
241       String message = "Required Source was not specified";
242       LOG.warn(message);
243       throw new IllegalStateException(message);
244     }
245     SchemaUnmarshaller schemaUnmarshaller = null;
246 
247     try {
248       SchemaUnmarshallerState state = new SchemaUnmarshallerState();
249       // TODO[Joachim] state.setConfiguration(_config);
250       state.cacheIncludedSchemas = _cacheIncludedSchemas;
251       schemaUnmarshaller = new SchemaUnmarshaller(_schemaContext, state);
252       if (_uriResolver != null)
253         schemaUnmarshaller.setURIResolver(_uriResolver);
254 
255       // make sure we mark the URI as processed for cyclic imports/includes
256       String uri = _source.getSystemId();
257       if (uri != null) {
258         URIResolver resolver = schemaUnmarshaller.getURIResolver();
259         try {
260           URILocation location = resolver.resolve(uri, null);
261           if (location != null)
262             uri = location.toString();
263         } catch (URIException except) {
264           throw new NestedIOException(except);
265         }
266         state.markAsProcessed(uri, schemaUnmarshaller.getSchema());
267       }
268 
269       Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller);
270       _parser.setDocumentHandler(handler);
271 
272       if (_errorHandler == null)
273         _parser.setErrorHandler(handler);
274       else
275         _parser.setErrorHandler(_errorHandler);
276 
277       if (_resolver != null)
278         _parser.setEntityResolver(_resolver);
279       _parser.parse(_source);
280     } catch (XMLException ex) {
281       handleException(ex);
282     } catch (org.xml.sax.SAXException sx) {
283       handleException(sx);
284     }
285 
286     _schema = schemaUnmarshaller.getSchema();
287 
288     if (_validate) {
289       try {
290         _schema.validate();
291       } catch (org.exolab.castor.xml.ValidationException vx) {
292         throw new NestedIOException(vx);
293       }
294     }
295 
296     return _schema;
297 
298   }
299 
300   /**
301    * Sets the ErrorHandler.
302    * 
303    * @param errorHandler
304    **/
305   public void setErrorHandler(ErrorHandler errorHandler) {
306     _errorHandler = errorHandler;
307   }
308 
309   /**
310    * Sets wheter or not to cache the included xml schemas instead of inlining them as specified by
311    * the XML Schema specification.
312    * 
313    * @param cache true to cache the included XML Schemas.
314    **/
315   public void setCacheIncludedSchemas(boolean cache) {
316     _cacheIncludedSchemas = cache;
317   }
318 
319   /**
320    * Sets whether or not post-read validation should occur. By default, validation is enabled. Note
321    * that certain read validation cannot be disabled.
322    * 
323    * @param validate a boolean that when true will force a call to Schema#validate after the schema
324    *        is read.
325    **/
326   public void setValidation(boolean validate) {
327     _validate = validate;
328   }
329 
330   /**
331    * Sets the EntityResolver used to resolve SYSTEM Identifier. If the entity resolver is null, the
332    * default one will be used.
333    * 
334    * @param resolver the EntityResolver to use.
335    */
336   public void setEntityResolver(EntityResolver resolver) {
337     _resolver = resolver;
338   }
339 
340   /**
341    * Sets the URIResolver used to resolve hrefs. If the entity resolver is null, the default one
342    * will be used.
343    * 
344    * @param uriresolver the URIResolver to use.
345    */
346   public void setURIResolver(URIResolver uriresolver) {
347     _uriResolver = uriresolver;
348   }
349 
350   /**
351    * Handle an exception which is one of our own XMLExceptions.
352    * 
353    * @param xmlException the XMLException to handle.
354    * @throws IOException
355    */
356   private void handleException(XMLException xmlException) throws IOException {
357     throw new NestedIOException(xmlException);
358   }
359 
360   /**
361    * Handle an exception which is a foreign SAXException.
362    * 
363    * @param sx The SAXException to handle.
364    * @throws IOException
365    */
366   private void handleException(SAXException sx) throws IOException {
367     Exception except = sx.getException();
368     if (except == null) {
369       except = sx;
370     } else if (except instanceof SAXParseException) {
371       SAXParseException spe = (SAXParseException) except;
372       String filename = spe.getSystemId();
373       if (filename == null)
374         filename = spe.getPublicId();
375       if (filename == null)
376         filename = "<filename unavailable>";
377 
378       String err = spe.getMessage();
379 
380       err += "; " + filename + " [ line: " + spe.getLineNumber();
381       err += ", column: " + spe.getColumnNumber() + ']';
382       throw new NestedIOException(err, except);
383     } else if (except instanceof XMLException) {
384       handleException((XMLException) except);
385     }
386 
387     throw new NestedIOException(except);
388 
389   }
390 }