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 
  21 package com.sun.org.apache.xml.internal.resolver.readers;
  22 
  23 import com.sun.org.apache.xml.internal.resolver.Catalog;
  24 import com.sun.org.apache.xml.internal.resolver.CatalogException;
  25 import com.sun.org.apache.xml.internal.resolver.CatalogManager;
  26 import com.sun.org.apache.xml.internal.resolver.helpers.Debug;
  27 import java.io.FileNotFoundException;
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.net.MalformedURLException;
  31 import java.net.URL;
  32 import java.net.URLConnection;
  33 import java.net.UnknownHostException;
  34 import java.util.HashMap;
  35 import java.util.Map;
  36 import javax.xml.parsers.ParserConfigurationException;
  37 import javax.xml.parsers.SAXParser;
  38 import javax.xml.parsers.SAXParserFactory;
  39 import org.xml.sax.AttributeList;
  40 import org.xml.sax.Attributes;
  41 import org.xml.sax.ContentHandler;
  42 import org.xml.sax.DocumentHandler;
  43 import org.xml.sax.EntityResolver;
  44 import org.xml.sax.InputSource;
  45 import org.xml.sax.Locator;
  46 import org.xml.sax.Parser;
  47 import org.xml.sax.SAXException;
  48 import 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  */
  77 public 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 }