001: // ResolvingXMLFilter.java - An XMLFilter that performs catalog resolution
002:
003: /*
004: * Copyright 2001-2004 The Apache Software Foundation or its licensors,
005: * as applicable.
006: *
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: package com.sun.org.apache.xml.internal.resolver.tools;
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.net.MalformedURLException;
025: import java.net.URL;
026:
027: import org.xml.sax.InputSource;
028: import org.xml.sax.SAXException;
029: import org.xml.sax.XMLReader;
030: import org.xml.sax.Attributes;
031: import org.xml.sax.helpers.XMLFilterImpl;
032:
033: import com.sun.org.apache.xml.internal.resolver.Catalog;
034: import com.sun.org.apache.xml.internal.resolver.CatalogManager;
035:
036: import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
037:
038: /**
039: * A SAX XMLFilter that performs catalog-based entity resolution.
040: *
041: * <p>This class implements a SAX XMLFilter that performs entity resolution
042: * using the CatalogResolver. The actual, underlying parser is obtained
043: * from a SAXParserFactory.</p>
044: * </p>
045: *
046: * @see CatalogResolver
047: * @see org.xml.sax.XMLFilter
048: *
049: * @author Norman Walsh
050: * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
051: *
052: * @version 1.0
053: */
054: public class ResolvingXMLFilter extends XMLFilterImpl {
055: /**
056: * Suppress explanatory message?
057: *
058: * @see #parse(InputSource)
059: */
060: public static boolean suppressExplanation = false;
061:
062: /** The manager for the underlying resolver. */
063: private CatalogManager catalogManager = CatalogManager
064: .getStaticManager();
065:
066: /** The underlying catalog resolver. */
067: private CatalogResolver catalogResolver = null;
068:
069: /** A separate resolver for oasis-xml-pi catalogs. */
070: private CatalogResolver piCatalogResolver = null;
071:
072: /** Are we in the prolog? Is an oasis-xml-catalog PI valid now? */
073: private boolean allowXMLCatalogPI = false;
074:
075: /** Has an oasis-xml-catalog PI been seen? */
076: private boolean oasisXMLCatalogPI = false;
077:
078: /** The base URI of the input document, if known. */
079: private URL baseURL = null;
080:
081: /** Construct an empty XML Filter with no parent. */
082: public ResolvingXMLFilter() {
083: super ();
084: catalogResolver = new CatalogResolver(catalogManager);
085: }
086:
087: /** Construct an XML filter with the specified parent. */
088: public ResolvingXMLFilter(XMLReader parent) {
089: super (parent);
090: catalogResolver = new CatalogResolver(catalogManager);
091: }
092:
093: /** Construct an XML filter with the specified parent. */
094: public ResolvingXMLFilter(CatalogManager manager) {
095: super ();
096: catalogManager = manager;
097: catalogResolver = new CatalogResolver(catalogManager);
098: }
099:
100: /** Construct an XML filter with the specified parent. */
101: public ResolvingXMLFilter(XMLReader parent, CatalogManager manager) {
102: super (parent);
103: catalogManager = manager;
104: catalogResolver = new CatalogResolver(catalogManager);
105: }
106:
107: /**
108: * Provide accessto the underlying Catalog.
109: */
110: public Catalog getCatalog() {
111: return catalogResolver.getCatalog();
112: }
113:
114: /**
115: * SAX XMLReader API.
116: *
117: * <p>Note that the JAXP 1.1ea2 parser crashes with an InternalError if
118: * it encounters a system identifier that appears to be a relative URI
119: * that begins with a slash. For example, the declaration:</p>
120: *
121: * <pre>
122: * <!DOCTYPE book SYSTEM "/path/to/dtd/on/my/system/docbookx.dtd">
123: * </pre>
124: *
125: * <p>would cause such an error. As a convenience, this method catches
126: * that error and prints an explanation. (Unfortunately, it's not possible
127: * to identify the particular system identifier that causes the problem.)
128: * </p>
129: *
130: * <p>The underlying error is forwarded after printing the explanatory
131: * message. The message is only every printed once and if
132: * <code>suppressExplanation</code> is set to <code>false</code> before
133: * parsing, it will never be printed.</p>
134: */
135: public void parse(InputSource input) throws IOException,
136: SAXException {
137: allowXMLCatalogPI = true;
138:
139: setupBaseURI(input.getSystemId());
140:
141: try {
142: super .parse(input);
143: } catch (InternalError ie) {
144: explain(input.getSystemId());
145: throw ie;
146: }
147: }
148:
149: /** SAX XMLReader API.
150: *
151: * @see #parse(InputSource)
152: */
153: public void parse(String systemId) throws IOException, SAXException {
154: allowXMLCatalogPI = true;
155:
156: setupBaseURI(systemId);
157:
158: try {
159: super .parse(systemId);
160: } catch (InternalError ie) {
161: explain(systemId);
162: throw ie;
163: }
164: }
165:
166: /**
167: * Implements the <code>resolveEntity</code> method
168: * for the SAX interface, using an underlying CatalogResolver
169: * to do the real work.
170: */
171: public InputSource resolveEntity(String publicId, String systemId) {
172: allowXMLCatalogPI = false;
173: String resolved = catalogResolver.getResolvedEntity(publicId,
174: systemId);
175:
176: if (resolved == null && piCatalogResolver != null) {
177: resolved = piCatalogResolver.getResolvedEntity(publicId,
178: systemId);
179: }
180:
181: if (resolved != null) {
182: try {
183: InputSource iSource = new InputSource(resolved);
184: iSource.setPublicId(publicId);
185:
186: // Ideally this method would not attempt to open the
187: // InputStream, but there is a bug (in Xerces, at least)
188: // that causes the parser to mistakenly open the wrong
189: // system identifier if the returned InputSource does
190: // not have a byteStream.
191: //
192: // It could be argued that we still shouldn't do this here,
193: // but since the purpose of calling the entityResolver is
194: // almost certainly to open the input stream, it seems to
195: // do little harm.
196: //
197: URL url = new URL(resolved);
198: InputStream iStream = url.openStream();
199: iSource.setByteStream(iStream);
200:
201: return iSource;
202: } catch (Exception e) {
203: catalogManager.debug.message(1,
204: "Failed to create InputSource", resolved);
205: return null;
206: }
207: } else {
208: return null;
209: }
210: }
211:
212: /** SAX DTDHandler API.
213: *
214: * <p>Captured here only to detect the end of the prolog so that
215: * we can ignore subsequent oasis-xml-catalog PIs. Otherwise
216: * the events are just passed through.</p>
217: */
218: public void notationDecl(String name, String publicId,
219: String systemId) throws SAXException {
220: allowXMLCatalogPI = false;
221: super .notationDecl(name, publicId, systemId);
222: }
223:
224: /** SAX DTDHandler API.
225: *
226: * <p>Captured here only to detect the end of the prolog so that
227: * we can ignore subsequent oasis-xml-catalog PIs. Otherwise
228: * the events are just passed through.</p>
229: */
230: public void unparsedEntityDecl(String name, String publicId,
231: String systemId, String notationName) throws SAXException {
232: allowXMLCatalogPI = false;
233: super
234: .unparsedEntityDecl(name, publicId, systemId,
235: notationName);
236: }
237:
238: /** SAX ContentHandler API.
239: *
240: * <p>Captured here only to detect the end of the prolog so that
241: * we can ignore subsequent oasis-xml-catalog PIs. Otherwise
242: * the events are just passed through.</p>
243: */
244: public void startElement(String uri, String localName,
245: String qName, Attributes atts) throws SAXException {
246: allowXMLCatalogPI = false;
247: super .startElement(uri, localName, qName, atts);
248: }
249:
250: /** SAX ContentHandler API.
251: *
252: * <p>Detect and use the oasis-xml-catalog PI if it occurs.</p>
253: */
254: public void processingInstruction(String target, String pidata)
255: throws SAXException {
256: if (target.equals("oasis-xml-catalog")) {
257: URL catalog = null;
258: String data = pidata;
259:
260: int pos = data.indexOf("catalog=");
261: if (pos >= 0) {
262: data = data.substring(pos + 8);
263: if (data.length() > 1) {
264: String quote = data.substring(0, 1);
265: data = data.substring(1);
266: pos = data.indexOf(quote);
267: if (pos >= 0) {
268: data = data.substring(0, pos);
269: try {
270: if (baseURL != null) {
271: catalog = new URL(baseURL, data);
272: } else {
273: catalog = new URL(data);
274: }
275: } catch (MalformedURLException mue) {
276: // nevermind
277: }
278: }
279: }
280: }
281:
282: if (allowXMLCatalogPI) {
283: if (catalogManager.getAllowOasisXMLCatalogPI()) {
284: catalogManager.debug.message(4,
285: "oasis-xml-catalog PI", pidata);
286:
287: if (catalog != null) {
288: try {
289: catalogManager.debug.message(4,
290: "oasis-xml-catalog", catalog
291: .toString());
292: oasisXMLCatalogPI = true;
293:
294: if (piCatalogResolver == null) {
295: piCatalogResolver = new CatalogResolver(
296: true);
297: }
298:
299: piCatalogResolver.getCatalog()
300: .parseCatalog(catalog.toString());
301: } catch (Exception e) {
302: catalogManager.debug.message(3,
303: "Exception parsing oasis-xml-catalog: "
304: + catalog.toString());
305: }
306: } else {
307: catalogManager.debug.message(3,
308: "PI oasis-xml-catalog unparseable: "
309: + pidata);
310: }
311: } else {
312: catalogManager.debug.message(4,
313: "PI oasis-xml-catalog ignored: " + pidata);
314: }
315: } else {
316: catalogManager.debug.message(3,
317: "PI oasis-xml-catalog occurred in an invalid place: "
318: + pidata);
319: }
320: } else {
321: super .processingInstruction(target, pidata);
322: }
323: }
324:
325: /** Save the base URI of the document being parsed. */
326: private void setupBaseURI(String systemId) {
327: URL cwd = null;
328:
329: try {
330: cwd = FileURL.makeURL("basename");
331: } catch (MalformedURLException mue) {
332: cwd = null;
333: }
334:
335: try {
336: baseURL = new URL(systemId);
337: } catch (MalformedURLException mue) {
338: if (cwd != null) {
339: try {
340: baseURL = new URL(cwd, systemId);
341: } catch (MalformedURLException mue2) {
342: // give up
343: baseURL = null;
344: }
345: } else {
346: // give up
347: baseURL = null;
348: }
349: }
350: }
351:
352: /** Provide one possible explanation for an InternalError. */
353: private void explain(String systemId) {
354: if (!suppressExplanation) {
355: System.out
356: .println("XMLReader probably encountered bad URI in "
357: + systemId);
358: System.out
359: .println("For example, replace '/some/uri' with 'file:/some/uri'.");
360: }
361: suppressExplanation = true;
362: }
363: }
|