1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 // ResolvingXMLFilter.java - An XMLFilter that performs catalog resolution 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.tools; 25 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.net.MalformedURLException; 29 import java.net.URL; 30 31 import org.xml.sax.InputSource; 32 import org.xml.sax.SAXException; 33 import org.xml.sax.XMLReader; 34 import org.xml.sax.Attributes; 35 import org.xml.sax.helpers.XMLFilterImpl; 36 37 import com.sun.org.apache.xml.internal.resolver.Catalog; 38 import com.sun.org.apache.xml.internal.resolver.CatalogManager; 39 40 import com.sun.org.apache.xml.internal.resolver.helpers.FileURL; 41 42 /** 43 * A SAX XMLFilter that performs catalog-based entity resolution. 44 * 45 * <p>This class implements a SAX XMLFilter that performs entity resolution 46 * using the CatalogResolver. The actual, underlying parser is obtained 47 * from a SAXParserFactory.</p> 48 * </p> 49 * 50 * @see CatalogResolver 51 * @see org.xml.sax.XMLFilter 52 * 53 * @author Norman Walsh 54 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a> 55 * 56 * @version 1.0 57 */ 58 public class ResolvingXMLFilter extends XMLFilterImpl { 59 /** 60 * Suppress explanatory message? 61 * 62 * @see #parse(InputSource) 63 */ 64 public static boolean suppressExplanation = false; 65 66 /** The manager for the underlying resolver. */ 67 CatalogManager catalogManager = CatalogManager.getStaticManager(); 68 69 /** The underlying catalog resolver. */ 70 private CatalogResolver catalogResolver = null; 71 72 /** A separate resolver for oasis-xml-pi catalogs. */ 73 private CatalogResolver piCatalogResolver = null; 74 75 /** Are we in the prolog? Is an oasis-xml-catalog PI valid now? */ 76 private boolean allowXMLCatalogPI = false; 77 78 /** Has an oasis-xml-catalog PI been seen? */ 79 private boolean oasisXMLCatalogPI = false; 80 81 /** The base URI of the input document, if known. */ 82 private URL baseURL = null; 83 84 /** Construct an empty XML Filter with no parent. */ 85 public ResolvingXMLFilter() { 86 super(); 87 catalogResolver = new CatalogResolver(catalogManager); 88 } 89 90 /** Construct an XML filter with the specified parent. */ 91 public ResolvingXMLFilter(XMLReader parent) { 92 super(parent); 93 catalogResolver = new CatalogResolver(catalogManager); 94 } 95 96 /** Construct an XML filter with the specified parent. */ 97 public ResolvingXMLFilter(CatalogManager manager) { 98 super(); 99 catalogManager = manager; 100 catalogResolver = new CatalogResolver(catalogManager); 101 } 102 103 /** Construct an XML filter with the specified parent. */ 104 public ResolvingXMLFilter(XMLReader parent, CatalogManager manager) { 105 super(parent); 106 catalogManager = manager; 107 catalogResolver = new CatalogResolver(catalogManager); 108 } 109 110 /** 111 * Provide accessto the underlying Catalog. 112 */ 113 public Catalog getCatalog() { 114 return catalogResolver.getCatalog(); 115 } 116 117 /** 118 * SAX XMLReader API. 119 * 120 * <p>Note that the JAXP 1.1ea2 parser crashes with an InternalError if 121 * it encounters a system identifier that appears to be a relative URI 122 * that begins with a slash. For example, the declaration:</p> 123 * 124 * <pre> 125 * <!DOCTYPE book SYSTEM "/path/to/dtd/on/my/system/docbookx.dtd"> 126 * </pre> 127 * 128 * <p>would cause such an error. As a convenience, this method catches 129 * that error and prints an explanation. (Unfortunately, it's not possible 130 * to identify the particular system identifier that causes the problem.) 131 * </p> 132 * 133 * <p>The underlying error is forwarded after printing the explanatory 134 * message. The message is only every printed once and if 135 * <code>suppressExplanation</code> is set to <code>false</code> before 136 * parsing, it will never be printed.</p> 137 */ 138 public void parse(InputSource input) 139 throws IOException, SAXException { 140 allowXMLCatalogPI = true; 141 142 setupBaseURI(input.getSystemId()); 143 144 try { 145 super.parse(input); 146 } catch (InternalError ie) { 147 explain(input.getSystemId()); 148 throw ie; 149 } 150 } 151 152 /** SAX XMLReader API. 153 * 154 * @see #parse(InputSource) 155 */ 156 public void parse(String systemId) 157 throws IOException, SAXException { 158 allowXMLCatalogPI = true; 159 160 setupBaseURI(systemId); 161 162 try { 163 super.parse(systemId); 164 } catch (InternalError ie) { 165 explain(systemId); 166 throw ie; 167 } 168 } 169 170 /** 171 * Implements the <code>resolveEntity</code> method 172 * for the SAX interface, using an underlying CatalogResolver 173 * to do the real work. 174 */ 175 public InputSource resolveEntity (String publicId, String systemId) { 176 allowXMLCatalogPI = false; 177 String resolved = catalogResolver.getResolvedEntity(publicId, systemId); 178 179 if (resolved == null && piCatalogResolver != null) { 180 resolved = piCatalogResolver.getResolvedEntity(publicId, systemId); 181 } 182 183 if (resolved != null) { 184 try { 185 InputSource iSource = new InputSource(resolved); 186 iSource.setPublicId(publicId); 187 188 // Ideally this method would not attempt to open the 189 // InputStream, but there is a bug (in Xerces, at least) 190 // that causes the parser to mistakenly open the wrong 191 // system identifier if the returned InputSource does 192 // not have a byteStream. 193 // 194 // It could be argued that we still shouldn't do this here, 195 // but since the purpose of calling the entityResolver is 196 // almost certainly to open the input stream, it seems to 197 // do little harm. 198 // 199 URL url = new URL(resolved); 200 InputStream iStream = url.openStream(); 201 iSource.setByteStream(iStream); 202 203 return iSource; 204 } catch (Exception e) { 205 catalogManager.debug.message(1, "Failed to create InputSource", resolved); 206 return null; 207 } 208 } else { 209 return null; 210 } 211 } 212 213 /** SAX DTDHandler API. 214 * 215 * <p>Captured here only to detect the end of the prolog so that 216 * we can ignore subsequent oasis-xml-catalog PIs. Otherwise 217 * the events are just passed through.</p> 218 */ 219 public void notationDecl (String name, String publicId, String systemId) 220 throws SAXException { 221 allowXMLCatalogPI = false; 222 super.notationDecl(name,publicId,systemId); 223 } 224 225 /** SAX DTDHandler API. 226 * 227 * <p>Captured here only to detect the end of the prolog so that 228 * we can ignore subsequent oasis-xml-catalog PIs. Otherwise 229 * the events are just passed through.</p> 230 */ 231 public void unparsedEntityDecl (String name, 232 String publicId, 233 String systemId, 234 String notationName) 235 throws SAXException { 236 allowXMLCatalogPI = false; 237 super.unparsedEntityDecl (name, publicId, systemId, notationName); 238 } 239 240 /** SAX ContentHandler API. 241 * 242 * <p>Captured here only to detect the end of the prolog so that 243 * we can ignore subsequent oasis-xml-catalog PIs. Otherwise 244 * the events are just passed through.</p> 245 */ 246 public void startElement (String uri, String localName, String qName, 247 Attributes atts) 248 throws SAXException { 249 allowXMLCatalogPI = false; 250 super.startElement(uri,localName,qName,atts); 251 } 252 253 /** SAX ContentHandler API. 254 * 255 * <p>Detect and use the oasis-xml-catalog PI if it occurs.</p> 256 */ 257 public void processingInstruction(String target, String pidata) 258 throws SAXException { 259 if (target.equals("oasis-xml-catalog")) { 260 URL catalog = null; 261 String data = pidata; 262 263 int pos = data.indexOf("catalog="); 264 if (pos >= 0) { 265 data = data.substring(pos+8); 266 if (data.length() > 1) { 267 String quote = data.substring(0,1); 268 data = data.substring(1); 269 pos = data.indexOf(quote); 270 if (pos >= 0) { 271 data = data.substring(0, pos); 272 try { 273 if (baseURL != null) { 274 catalog = new URL(baseURL, data); 275 } else { 276 catalog = new URL(data); 277 } 278 } catch (MalformedURLException mue) { 279 // nevermind 280 } 281 } 282 } 283 } 284 285 if (allowXMLCatalogPI) { 286 if (catalogManager.getAllowOasisXMLCatalogPI()) { 287 catalogManager.debug.message(4,"oasis-xml-catalog PI", pidata); 288 289 if (catalog != null) { 290 try { 291 catalogManager.debug.message(4,"oasis-xml-catalog", catalog.toString()); 292 oasisXMLCatalogPI = true; 293 294 if (piCatalogResolver == null) { 295 piCatalogResolver = new CatalogResolver(true); 296 } 297 298 piCatalogResolver.getCatalog().parseCatalog(catalog.toString()); 299 } catch (Exception e) { 300 catalogManager.debug.message(3, "Exception parsing oasis-xml-catalog: " 301 + catalog.toString()); 302 } 303 } else { 304 catalogManager.debug.message(3, "PI oasis-xml-catalog unparseable: " + pidata); 305 } 306 } else { 307 catalogManager.debug.message(4,"PI oasis-xml-catalog ignored: " + pidata); 308 } 309 } else { 310 catalogManager.debug.message(3, "PI oasis-xml-catalog occurred in an invalid place: " 311 + pidata); 312 } 313 } else { 314 super.processingInstruction(target, pidata); 315 } 316 } 317 318 /** Save the base URI of the document being parsed. */ 319 private void setupBaseURI(String systemId) { 320 URL cwd = null; 321 322 try { 323 cwd = FileURL.makeURL("basename"); 324 } catch (MalformedURLException mue) { 325 cwd = null; 326 } 327 328 try { 329 baseURL = new URL(systemId); 330 } catch (MalformedURLException mue) { 331 if (cwd != null) { 332 try { 333 baseURL = new URL(cwd, systemId); 334 } catch (MalformedURLException mue2) { 335 // give up 336 baseURL = null; 337 } 338 } else { 339 // give up 340 baseURL = null; 341 } 342 } 343 } 344 345 /** Provide one possible explanation for an InternalError. */ 346 private void explain(String systemId) { 347 if (!suppressExplanation) { 348 System.out.println("XMLReader probably encountered bad URI in " + systemId); 349 System.out.println("For example, replace '/some/uri' with 'file:/some/uri'."); 350 } 351 suppressExplanation = true; 352 } 353 }