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