View Javadoc
1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   // SAXCatalogReader.java - Read XML Catalog files
6   
7   /*
8    * Copyright 2001-2004 The Apache Software Foundation or its licensors,
9    * as applicable.
10   *
11   * Licensed under the Apache License, Version 2.0 (the "License");
12   * you may not use this file except in compliance with the License.
13   * You may obtain a copy of the License at
14   *
15   *      http://www.apache.org/licenses/LICENSE-2.0
16   *
17   * Unless required by applicable law or agreed to in writing, software
18   * distributed under the License is distributed on an "AS IS" BASIS,
19   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   * See the License for the specific language governing permissions and
21   * limitations under the License.
22   */
23  
24  package com.sun.org.apache.xml.internal.resolver.readers;
25  
26  import com.sun.org.apache.xml.internal.resolver.Catalog;
27  import com.sun.org.apache.xml.internal.resolver.CatalogException;
28  import com.sun.org.apache.xml.internal.resolver.CatalogManager;
29  import com.sun.org.apache.xml.internal.resolver.helpers.Debug;
30  import java.io.FileNotFoundException;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.net.MalformedURLException;
34  import java.net.URL;
35  import java.net.URLConnection;
36  import java.net.UnknownHostException;
37  import java.util.Hashtable;
38  import javax.xml.parsers.ParserConfigurationException;
39  import javax.xml.parsers.SAXParser;
40  import javax.xml.parsers.SAXParserFactory;
41  import org.xml.sax.AttributeList;
42  import org.xml.sax.Attributes;
43  import org.xml.sax.ContentHandler;
44  import org.xml.sax.DocumentHandler;
45  import org.xml.sax.EntityResolver;
46  import org.xml.sax.InputSource;
47  import org.xml.sax.Locator;
48  import org.xml.sax.Parser;
49  import org.xml.sax.SAXException;
50  import sun.reflect.misc.ReflectUtil;
51  
52  /**
53   * A SAX-based CatalogReader.
54   *
55   * <p>This class is used to read XML Catalogs using the SAX. This reader
56   * has an advantage over the DOM-based reader in that it functions on
57   * the stream of SAX events. It has the disadvantage
58   * that it cannot look around in the tree.</p>
59   *
60   * <p>Since the choice of CatalogReaders (in the InputStream case) can only
61   * be made on the basis of MIME type, the following problem occurs: only
62   * one CatalogReader can exist for all XML mime types. In order to get
63   * around this problem, the SAXCatalogReader relies on a set of external
64   * CatalogParsers to actually build the catalog.</p>
65   *
66   * <p>The selection of CatalogParsers is made on the basis of the QName
67   * of the root element of the document.</p>
68   *
69   * @see Catalog
70   * @see CatalogReader
71   * @see SAXCatalogReader
72   * @see TextCatalogReader
73   * @see DOMCatalogParser
74   *
75   * @author Norman Walsh
76   * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
77   *
78   */
79  public class SAXCatalogReader implements CatalogReader, ContentHandler, DocumentHandler {
80    /** The SAX Parser Factory */
81    protected SAXParserFactory parserFactory = null;
82  
83    /** The SAX Parser Class */
84    protected String parserClass = null;
85  
86    /**
87       * Mapping table from QNames to CatalogParser classes.
88       *
89       * <p>Each key in this hash table has the form "elementname"
90       * or "{namespaceuri}elementname". The former is used if the
91       * namespace URI is null.</p>
92       */
93    protected Hashtable namespaceMap = new Hashtable();
94  
95    /** The parser in use for the current catalog. */
96    private SAXCatalogParser saxParser = null;
97  
98    /** Set if something goes horribly wrong. It allows the class to
99       * ignore the rest of the events that are received.
100      */
101   private boolean abandonHope = false;
102 
103   /** The Catalog that we're working for. */
104   private Catalog catalog;
105 
106   /** Set the XML SAX Parser Factory.
107    */
108   public void setParserFactory(SAXParserFactory parserFactory) {
109     this.parserFactory = parserFactory;
110   }
111 
112   /** Set the XML SAX Parser Class
113    */
114   public void setParserClass(String parserClass) {
115     this.parserClass = parserClass;
116   }
117 
118   /** Get the parser factory currently in use. */
119   public SAXParserFactory getParserFactory() {
120     return parserFactory;
121   }
122 
123   /** Get the parser class currently in use. */
124   public String getParserClass() {
125     return parserClass;
126   }
127 
128   /** The debug class to use for this reader.
129    *
130    * This is a bit of a hack. Anyway, whenever we read for a catalog,
131    * we extract the debug object
132    * from the catalog's manager so that we can use it to print messages.
133    *
134    * In production, we don't really expect any messages so it doesn't
135    * really matter. But it's still a bit of a hack.
136    */
137   protected Debug debug = CatalogManager.getStaticManager().debug;
138 
139   /** The constructor */
140   public SAXCatalogReader() {
141     parserFactory = null;
142     parserClass = null;
143   }
144 
145   /** The constructor */
146   public SAXCatalogReader(SAXParserFactory parserFactory) {
147     this.parserFactory = parserFactory;
148   }
149 
150   /** The constructor */
151   public SAXCatalogReader(String parserClass) {
152     this.parserClass = parserClass;
153   }
154 
155   /** Set the SAXCatalogParser class for the given namespace/root
156      * element type.
157      */
158   public void setCatalogParser(String namespaceURI,
159                                String rootElement,
160                                String parserClass) {
161     if (namespaceURI == null) {
162       namespaceMap.put(rootElement, parserClass);
163     } else {
164       namespaceMap.put("{"+namespaceURI+"}"+rootElement, parserClass);
165     }
166   }
167 
168   /** Get the SAXCatalogParser class for the given namespace/root
169      * element type.
170      */
171   public String getCatalogParser(String namespaceURI,
172                                  String rootElement) {
173     if (namespaceURI == null) {
174       return (String) namespaceMap.get(rootElement);
175     } else {
176       return (String) namespaceMap.get("{"+namespaceURI+"}"+rootElement);
177     }
178   }
179 
180   /**
181    * Parse an XML Catalog file.
182    *
183    * @param catalog The catalog to which this catalog file belongs
184    * @param fileUrl The URL or filename of the catalog file to process
185    *
186    * @throws MalformedURLException Improper fileUrl
187    * @throws IOException Error reading catalog file
188    */
189   public void readCatalog(Catalog catalog, String fileUrl)
190     throws MalformedURLException, IOException,
191            CatalogException {
192 
193     URL url = null;
194 
195     try {
196       url = new URL(fileUrl);
197     } catch (MalformedURLException e) {
198       url = new URL("file:///" + fileUrl);
199     }
200 
201     debug = catalog.getCatalogManager().debug;
202 
203     try {
204       URLConnection urlCon = url.openConnection();
205       readCatalog(catalog, urlCon.getInputStream());
206     } catch (FileNotFoundException e) {
207       catalog.getCatalogManager().debug.message(1, "Failed to load catalog, file not found",
208                     url.toString());
209     }
210   }
211 
212   /**
213    * Parse an XML Catalog stream.
214    *
215    * @param catalog The catalog to which this catalog file belongs
216    * @param is The input stream from which the catalog will be read
217    *
218    * @throws MalformedURLException Improper fileUrl
219    * @throws IOException Error reading catalog file
220    * @throws CatalogException A Catalog exception
221    */
222   public void readCatalog(Catalog catalog, InputStream is)
223     throws IOException, CatalogException {
224 
225     // Create an instance of the parser
226     if (parserFactory == null && parserClass == null) {
227       debug.message(1, "Cannot read SAX catalog without a parser");
228       throw new CatalogException(CatalogException.UNPARSEABLE);
229     }
230 
231     debug = catalog.getCatalogManager().debug;
232     EntityResolver bResolver = catalog.getCatalogManager().getBootstrapResolver();
233 
234     this.catalog = catalog;
235 
236     try {
237       if (parserFactory != null) {
238         SAXParser parser = parserFactory.newSAXParser();
239         SAXParserHandler spHandler = new SAXParserHandler();
240         spHandler.setContentHandler(this);
241         if (bResolver != null) {
242           spHandler.setEntityResolver(bResolver);
243         }
244         parser.parse(new InputSource(is), spHandler);
245       } else {
246         Parser parser = (Parser) ReflectUtil.forName(parserClass).newInstance();
247         parser.setDocumentHandler(this);
248         if (bResolver != null) {
249           parser.setEntityResolver(bResolver);
250         }
251         parser.parse(new InputSource(is));
252       }
253     } catch (ClassNotFoundException cnfe) {
254       throw new CatalogException(CatalogException.UNPARSEABLE);
255     } catch (IllegalAccessException iae) {
256       throw new CatalogException(CatalogException.UNPARSEABLE);
257     } catch (InstantiationException ie) {
258       throw new CatalogException(CatalogException.UNPARSEABLE);
259     } catch (ParserConfigurationException pce) {
260       throw new CatalogException(CatalogException.UNKNOWN_FORMAT);
261     } catch (SAXException se) {
262       Exception e = se.getException();
263       // FIXME: there must be a better way
264       UnknownHostException uhe = new UnknownHostException();
265       FileNotFoundException fnfe = new FileNotFoundException();
266       if (e != null) {
267         if (e.getClass() == uhe.getClass()) {
268           throw new CatalogException(CatalogException.PARSE_FAILED,
269                                      e.toString());
270         } else if (e.getClass() == fnfe.getClass()) {
271           throw new CatalogException(CatalogException.PARSE_FAILED,
272                                      e.toString());
273         }
274       }
275       throw new CatalogException(se);
276     }
277   }
278 
279   // ----------------------------------------------------------------------
280   // Implement the SAX ContentHandler interface
281 
282   /** The SAX <code>setDocumentLocator</code> method. Does nothing. */
283   public void setDocumentLocator (Locator locator) {
284     if (saxParser != null) {
285       saxParser.setDocumentLocator(locator);
286     }
287   }
288 
289   /** The SAX <code>startDocument</code> method. Does nothing. */
290   public void startDocument () throws SAXException {
291     saxParser = null;
292     abandonHope = false;
293     return;
294   }
295 
296   /** The SAX <code>endDocument</code> method. Does nothing. */
297   public void endDocument ()throws SAXException {
298     if (saxParser != null) {
299       saxParser.endDocument();
300     }
301   }
302 
303   /**
304    * The SAX <code>startElement</code> method.
305    *
306    * <p>The catalog parser is selected based on the namespace of the
307    * first element encountered in the catalog.</p>
308    */
309   public void startElement (String name,
310                             AttributeList atts)
311     throws SAXException {
312 
313     if (abandonHope) {
314       return;
315     }
316 
317     if (saxParser == null) {
318       String prefix = "";
319       if (name.indexOf(':') > 0) {
320         prefix = name.substring(0, name.indexOf(':'));
321       }
322 
323       String localName = name;
324       if (localName.indexOf(':') > 0) {
325         localName = localName.substring(localName.indexOf(':')+1);
326       }
327 
328       String namespaceURI = null;
329       if (prefix.equals("")) {
330         namespaceURI = atts.getValue("xmlns");
331       } else {
332         namespaceURI = atts.getValue("xmlns:" + prefix);
333       }
334 
335       String saxParserClass = getCatalogParser(namespaceURI,
336                                                localName);
337 
338       if (saxParserClass == null) {
339         abandonHope = true;
340         if (namespaceURI == null) {
341           debug.message(2, "No Catalog parser for " + name);
342         } else {
343           debug.message(2, "No Catalog parser for "
344                         + "{" + namespaceURI + "}"
345                         + name);
346         }
347         return;
348       }
349 
350       try {
351         saxParser = (SAXCatalogParser)
352           ReflectUtil.forName(saxParserClass).newInstance();
353 
354         saxParser.setCatalog(catalog);
355         saxParser.startDocument();
356         saxParser.startElement(name, atts);
357       } catch (ClassNotFoundException cnfe) {
358         saxParser = null;
359         abandonHope = true;
360         debug.message(2, cnfe.toString());
361       } catch (InstantiationException ie) {
362         saxParser = null;
363         abandonHope = true;
364         debug.message(2, ie.toString());
365       } catch (IllegalAccessException iae) {
366         saxParser = null;
367         abandonHope = true;
368         debug.message(2, iae.toString());
369       } catch (ClassCastException cce ) {
370         saxParser = null;
371         abandonHope = true;
372         debug.message(2, cce.toString());
373       }
374     } else {
375       saxParser.startElement(name, atts);
376     }
377   }
378 
379   /**
380    * The SAX2 <code>startElement</code> method.
381    *
382    * <p>The catalog parser is selected based on the namespace of the
383    * first element encountered in the catalog.</p>
384    */
385   public void startElement (String namespaceURI,
386                             String localName,
387                             String qName,
388                             Attributes atts)
389     throws SAXException {
390 
391     if (abandonHope) {
392       return;
393     }
394 
395     if (saxParser == null) {
396       String saxParserClass = getCatalogParser(namespaceURI,
397                                                localName);
398 
399       if (saxParserClass == null) {
400         abandonHope = true;
401         if (namespaceURI == null) {
402           debug.message(2, "No Catalog parser for " + localName);
403         } else {
404           debug.message(2, "No Catalog parser for "
405                         + "{" + namespaceURI + "}"
406                         + localName);
407         }
408         return;
409       }
410 
411       try {
412         saxParser = (SAXCatalogParser)
413           ReflectUtil.forName(saxParserClass).newInstance();
414 
415         saxParser.setCatalog(catalog);
416         saxParser.startDocument();
417         saxParser.startElement(namespaceURI, localName, qName, atts);
418       } catch (ClassNotFoundException cnfe) {
419         saxParser = null;
420         abandonHope = true;
421         debug.message(2, cnfe.toString());
422       } catch (InstantiationException ie) {
423         saxParser = null;
424         abandonHope = true;
425         debug.message(2, ie.toString());
426       } catch (IllegalAccessException iae) {
427         saxParser = null;
428         abandonHope = true;
429         debug.message(2, iae.toString());
430       } catch (ClassCastException cce ) {
431         saxParser = null;
432         abandonHope = true;
433         debug.message(2, cce.toString());
434       }
435     } else {
436       saxParser.startElement(namespaceURI, localName, qName, atts);
437     }
438   }
439 
440   /** The SAX <code>endElement</code> method. Does nothing. */
441   public void endElement (String name) throws SAXException {
442     if (saxParser != null) {
443       saxParser.endElement(name);
444     }
445   }
446 
447   /** The SAX2 <code>endElement</code> method. Does nothing. */
448   public void endElement (String namespaceURI,
449                           String localName,
450                           String qName) throws SAXException {
451     if (saxParser != null) {
452       saxParser.endElement(namespaceURI, localName, qName);
453     }
454   }
455 
456   /** The SAX <code>characters</code> method. Does nothing. */
457   public void characters (char ch[], int start, int length)
458     throws SAXException {
459     if (saxParser != null) {
460       saxParser.characters(ch, start, length);
461     }
462   }
463 
464   /** The SAX <code>ignorableWhitespace</code> method. Does nothing. */
465   public void ignorableWhitespace (char ch[], int start, int length)
466     throws SAXException {
467     if (saxParser != null) {
468       saxParser.ignorableWhitespace(ch, start, length);
469     }
470   }
471 
472   /** The SAX <code>processingInstruction</code> method. Does nothing. */
473   public void processingInstruction (String target, String data)
474     throws SAXException {
475     if (saxParser != null) {
476       saxParser.processingInstruction(target, data);
477     }
478   }
479 
480   /** The SAX <code>startPrefixMapping</code> method. Does nothing. */
481   public void startPrefixMapping (String prefix, String uri)
482     throws SAXException {
483     if (saxParser != null) {
484       saxParser.startPrefixMapping (prefix, uri);
485     }
486   }
487 
488   /** The SAX <code>endPrefixMapping</code> method. Does nothing. */
489   public void endPrefixMapping (String prefix)
490     throws SAXException {
491     if (saxParser != null) {
492       saxParser.endPrefixMapping (prefix);
493     }
494   }
495 
496   /** The SAX <code>skippedentity</code> method. Does nothing. */
497   public void skippedEntity (String name)
498     throws SAXException {
499     if (saxParser != null) {
500       saxParser.skippedEntity(name);
501     }
502   }
503 }