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 }