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
21package com.sun.org.apache.xml.internal.resolver.readers;
22
23import com.sun.org.apache.xml.internal.resolver.Catalog;
24import com.sun.org.apache.xml.internal.resolver.CatalogException;
25import com.sun.org.apache.xml.internal.resolver.CatalogManager;
26import com.sun.org.apache.xml.internal.resolver.helpers.Debug;
27import java.io.FileNotFoundException;
28import java.io.IOException;
29import java.io.InputStream;
30import java.net.MalformedURLException;
31import java.net.URL;
32import java.net.URLConnection;
33import java.net.UnknownHostException;
34import java.util.HashMap;
35import java.util.Map;
36import javax.xml.parsers.ParserConfigurationException;
37import javax.xml.parsers.SAXParser;
38import javax.xml.parsers.SAXParserFactory;
39import org.xml.sax.AttributeList;
40import org.xml.sax.Attributes;
41import org.xml.sax.ContentHandler;
42import org.xml.sax.DocumentHandler;
43import org.xml.sax.EntityResolver;
44import org.xml.sax.InputSource;
45import org.xml.sax.Locator;
46import org.xml.sax.Parser;
47import org.xml.sax.SAXException;
48import sun.reflect.misc.ReflectUtil;
49
50/**
51 * A SAX-based CatalogReader.
52 *
53 * <p>This class is used to read XML Catalogs using the SAX. This reader
54 * has an advantage over the DOM-based reader in that it functions on
55 * the stream of SAX events. It has the disadvantage
56 * that it cannot look around in the tree.</p>
57 *
58 * <p>Since the choice of CatalogReaders (in the InputStream case) can only
59 * be made on the basis of MIME type, the following problem occurs: only
60 * one CatalogReader can exist for all XML mime types. In order to get
61 * around this problem, the SAXCatalogReader relies on a set of external
62 * CatalogParsers to actually build the catalog.</p>
63 *
64 * <p>The selection of CatalogParsers is made on the basis of the QName
65 * of the root element of the document.</p>
66 *
67 * @see Catalog
68 * @see CatalogReader
69 * @see SAXCatalogReader
70 * @see TextCatalogReader
71 * @see DOMCatalogParser
72 *
73 * @author Norman Walsh
74 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
75 *
76 */
77public class SAXCatalogReader implements CatalogReader, ContentHandler, DocumentHandler {
78    /** The SAX Parser Factory */
79    protected SAXParserFactory parserFactory = null;
80
81    /** The SAX Parser Class */
82    protected String parserClass = null;
83
84    /**
85     * Mapping table from QNames to CatalogParser classes.
86     *
87     * <p>Each key in this hash table has the form "elementname"
88     * or "{namespaceuri}elementname". The former is used if the
89     * namespace URI is null.</p>
90     */
91    protected Map<String, String> namespaceMap = new HashMap<>();
92
93    /** The parser in use for the current catalog. */
94    private SAXCatalogParser saxParser = null;
95
96    /** Set if something goes horribly wrong. It allows the class to
97     * ignore the rest of the events that are received.
98     */
99    private boolean abandonHope = false;
100
101    /** The Catalog that we're working for. */
102    private Catalog catalog;
103
104    /** Set the XML SAX Parser Factory.
105     */
106    public void setParserFactory(SAXParserFactory parserFactory) {
107        this.parserFactory = parserFactory;
108    }
109
110    /** Set the XML SAX Parser Class
111     */
112    public void setParserClass(String parserClass) {
113        this.parserClass = parserClass;
114    }
115
116    /** Get the parser factory currently in use. */
117    public SAXParserFactory getParserFactory() {
118        return parserFactory;
119    }
120
121    /** Get the parser class currently in use. */
122    public String getParserClass() {
123        return parserClass;
124    }
125
126    /** The debug class to use for this reader.
127     *
128     * This is a bit of a hack. Anyway, whenever we read for a catalog,
129     * we extract the debug object
130     * from the catalog's manager so that we can use it to print messages.
131     *
132     * In production, we don't really expect any messages so it doesn't
133     * really matter. But it's still a bit of a hack.
134     */
135    protected Debug debug = CatalogManager.getStaticManager().debug;
136
137    /** The constructor */
138    public SAXCatalogReader() {
139        parserFactory = null;
140        parserClass = null;
141    }
142
143    /** The constructor */
144    public SAXCatalogReader(SAXParserFactory parserFactory) {
145        this.parserFactory = parserFactory;
146    }
147
148    /** The constructor */
149    public SAXCatalogReader(String parserClass) {
150        this.parserClass = parserClass;
151    }
152
153    /**
154     * Set the SAXCatalogParser class for the given namespace/root
155     * element type.
156     */
157    public void setCatalogParser(String namespaceURI,
158            String rootElement,
159            String parserClass) {
160        namespaceURI = namespaceURI != null ? namespaceURI.trim() : "";
161        namespaceMap.put("{"+namespaceURI+"}"+rootElement, parserClass);
162    }
163
164    /**
165     * Get the SAXCatalogParser class for the given namespace/root
166     * element type.
167     */
168    public String getCatalogParser(String namespaceURI,
169            String rootElement) {
170        namespaceURI = namespaceURI != null ? namespaceURI.trim() : "";
171        return namespaceMap.get("{"+namespaceURI+"}"+rootElement);
172    }
173
174    /**
175     * Parse an XML Catalog file.
176     *
177     * @param catalog The catalog to which this catalog file belongs
178     * @param fileUrl The URL or filename of the catalog file to process
179     *
180     * @throws MalformedURLException Improper fileUrl
181     * @throws IOException Error reading catalog file
182     */
183    public void readCatalog(Catalog catalog, String fileUrl)
184            throws MalformedURLException, IOException,
185            CatalogException {
186
187        URL url = null;
188
189        try {
190            url = new URL(fileUrl);
191        } catch (MalformedURLException e) {
192            url = new URL("file:///" + fileUrl);
193        }
194
195        debug = catalog.getCatalogManager().debug;
196
197        try {
198            URLConnection urlCon = url.openConnection();
199            readCatalog(catalog, urlCon.getInputStream());
200        } catch (FileNotFoundException e) {
201            catalog.getCatalogManager().debug.message(1, "Failed to load catalog, file not found",
202                    url.toString());
203        }
204    }
205
206    /**
207     * Parse an XML Catalog stream.
208     *
209     * @param catalog The catalog to which this catalog file belongs
210     * @param is The input stream from which the catalog will be read
211     *
212     * @throws MalformedURLException Improper fileUrl
213     * @throws IOException Error reading catalog file
214     * @throws CatalogException A Catalog exception
215     */
216    public void readCatalog(Catalog catalog, InputStream is)
217            throws IOException, CatalogException {
218
219        // Create an instance of the parser
220        if (parserFactory == null && parserClass == null) {
221            debug.message(1, "Cannot read SAX catalog without a parser");
222            throw new CatalogException(CatalogException.UNPARSEABLE);
223        }
224
225        debug = catalog.getCatalogManager().debug;
226        EntityResolver bResolver = catalog.getCatalogManager().getBootstrapResolver();
227
228        this.catalog = catalog;
229
230        try {
231            if (parserFactory != null) {
232                SAXParser parser = parserFactory.newSAXParser();
233                SAXParserHandler spHandler = new SAXParserHandler();
234                spHandler.setContentHandler(this);
235                if (bResolver != null) {
236                    spHandler.setEntityResolver(bResolver);
237                }
238                parser.parse(new InputSource(is), spHandler);
239            } else {
240                Class<?> c =  ReflectUtil.forName(parserClass);
241                if (!Parser.class.isAssignableFrom(c)) {
242                    throw new ClassCastException(parserClass
243                                + " cannot be cast to "
244                                + Parser.class.getName());
245                }
246                Parser parser = (Parser) c.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.length() == 0) {
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}
504