001: // CatalogResolver.java - A SAX EntityResolver/JAXP URI Resolver
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.URL;
025: import java.net.MalformedURLException;
026:
027: import org.xml.sax.SAXException;
028: import org.xml.sax.XMLReader;
029: import org.xml.sax.InputSource;
030: import org.xml.sax.EntityResolver;
031:
032: import javax.xml.transform.sax.SAXSource;
033: import javax.xml.transform.Source;
034: import javax.xml.transform.URIResolver;
035: import javax.xml.transform.TransformerException;
036: import javax.xml.parsers.ParserConfigurationException;
037: import javax.xml.parsers.SAXParserFactory;
038:
039: import com.sun.org.apache.xml.internal.resolver.Catalog;
040: import com.sun.org.apache.xml.internal.resolver.CatalogManager;
041: import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
042:
043: /**
044: * A SAX EntityResolver/JAXP URIResolver that uses catalogs.
045: *
046: * <p>This class implements both a SAX EntityResolver and a JAXP URIResolver.
047: * </p>
048: *
049: * <p>This resolver understands OASIS TR9401 catalogs, XCatalogs, and the
050: * current working draft of the OASIS Entity Resolution Technical
051: * Committee specification.</p>
052: *
053: * @see Catalog
054: * @see org.xml.sax.EntityResolver
055: * @see javax.xml.transform.URIResolver
056: *
057: * @author Norman Walsh
058: * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
059: *
060: * @version 1.0
061: */
062: public class CatalogResolver implements EntityResolver, URIResolver {
063: /** Make the parser Namespace aware? */
064: public boolean namespaceAware = true;
065:
066: /** Make the parser validating? */
067: public boolean validating = false;
068:
069: /** The underlying catalog */
070: private Catalog catalog = null;
071:
072: /** The catalog manager */
073: private CatalogManager catalogManager = CatalogManager
074: .getStaticManager();
075:
076: /** Constructor */
077: public CatalogResolver() {
078: initializeCatalogs(false);
079: }
080:
081: /** Constructor */
082: public CatalogResolver(boolean privateCatalog) {
083: initializeCatalogs(privateCatalog);
084: }
085:
086: /** Constructor */
087: public CatalogResolver(CatalogManager manager) {
088: catalogManager = manager;
089: initializeCatalogs(!catalogManager.getUseStaticCatalog());
090: }
091:
092: /** Initialize catalog */
093: private void initializeCatalogs(boolean privateCatalog) {
094: catalog = catalogManager.getCatalog();
095: }
096:
097: /** Return the underlying catalog */
098: public Catalog getCatalog() {
099: return catalog;
100: }
101:
102: /**
103: * Implements the guts of the <code>resolveEntity</code> method
104: * for the SAX interface.
105: *
106: * <p>Presented with an optional public identifier and a system
107: * identifier, this function attempts to locate a mapping in the
108: * catalogs.</p>
109: *
110: * <p>If such a mapping is found, it is returned. If no mapping is
111: * found, null is returned.</p>
112: *
113: * @param publicId The public identifier for the entity in question.
114: * This may be null.
115: *
116: * @param systemId The system identifier for the entity in question.
117: * XML requires a system identifier on all external entities, so this
118: * value is always specified.
119: *
120: * @return The resolved identifier (a URI reference).
121: */
122: public String getResolvedEntity(String publicId, String systemId) {
123: String resolved = null;
124:
125: if (catalog == null) {
126: catalogManager.debug
127: .message(1,
128: "Catalog resolution attempted with null catalog; ignored");
129: return null;
130: }
131:
132: if (systemId != null) {
133: try {
134: resolved = catalog.resolveSystem(systemId);
135: } catch (MalformedURLException me) {
136: catalogManager.debug.message(1,
137: "Malformed URL exception trying to resolve",
138: publicId);
139: resolved = null;
140: } catch (IOException ie) {
141: catalogManager.debug.message(1,
142: "I/O exception trying to resolve", publicId);
143: resolved = null;
144: }
145: }
146:
147: if (resolved == null) {
148: if (publicId != null) {
149: try {
150: resolved = catalog
151: .resolvePublic(publicId, systemId);
152: } catch (MalformedURLException me) {
153: catalogManager.debug
154: .message(
155: 1,
156: "Malformed URL exception trying to resolve",
157: publicId);
158: } catch (IOException ie) {
159: catalogManager.debug
160: .message(1,
161: "I/O exception trying to resolve",
162: publicId);
163: }
164: }
165:
166: if (resolved != null) {
167: catalogManager.debug.message(2, "Resolved public",
168: publicId, resolved);
169: }
170: } else {
171: catalogManager.debug.message(2, "Resolved system",
172: systemId, resolved);
173: }
174:
175: return resolved;
176: }
177:
178: /**
179: * Implements the <code>resolveEntity</code> method
180: * for the SAX interface.
181: *
182: * <p>Presented with an optional public identifier and a system
183: * identifier, this function attempts to locate a mapping in the
184: * catalogs.</p>
185: *
186: * <p>If such a mapping is found, the resolver attempts to open
187: * the mapped value as an InputSource and return it. Exceptions are
188: * ignored and null is returned if the mapped value cannot be opened
189: * as an input source.</p>
190: *
191: * <p>If no mapping is found (or an error occurs attempting to open
192: * the mapped value as an input source), null is returned and the system
193: * will use the specified system identifier as if no entityResolver
194: * was specified.</p>
195: *
196: * @param publicId The public identifier for the entity in question.
197: * This may be null.
198: *
199: * @param systemId The system identifier for the entity in question.
200: * XML requires a system identifier on all external entities, so this
201: * value is always specified.
202: *
203: * @return An InputSource for the mapped identifier, or null.
204: */
205: public InputSource resolveEntity(String publicId, String systemId) {
206: String resolved = getResolvedEntity(publicId, systemId);
207:
208: if (resolved != null) {
209: try {
210: InputSource iSource = new InputSource(resolved);
211: iSource.setPublicId(publicId);
212:
213: // Ideally this method would not attempt to open the
214: // InputStream, but there is a bug (in Xerces, at least)
215: // that causes the parser to mistakenly open the wrong
216: // system identifier if the returned InputSource does
217: // not have a byteStream.
218: //
219: // It could be argued that we still shouldn't do this here,
220: // but since the purpose of calling the entityResolver is
221: // almost certainly to open the input stream, it seems to
222: // do little harm.
223: //
224: URL url = new URL(resolved);
225: InputStream iStream = url.openStream();
226: iSource.setByteStream(iStream);
227:
228: return iSource;
229: } catch (Exception e) {
230: catalogManager.debug.message(1,
231: "Failed to create InputSource", resolved);
232: return null;
233: }
234: }
235:
236: return null;
237: }
238:
239: /** JAXP URIResolver API */
240: public Source resolve(String href, String base)
241: throws TransformerException {
242:
243: String uri = href;
244: String fragment = null;
245: int hashPos = href.indexOf("#");
246: if (hashPos >= 0) {
247: uri = href.substring(0, hashPos);
248: fragment = href.substring(hashPos + 1);
249: }
250:
251: String result = null;
252:
253: try {
254: result = catalog.resolveURI(href);
255: } catch (Exception e) {
256: // nop;
257: }
258:
259: if (result == null) {
260: try {
261: URL url = null;
262:
263: if (base == null) {
264: url = new URL(uri);
265: result = url.toString();
266: } else {
267: URL baseURL = new URL(base);
268: url = (href.length() == 0 ? baseURL : new URL(
269: baseURL, uri));
270: result = url.toString();
271: }
272: } catch (java.net.MalformedURLException mue) {
273: // try to make an absolute URI from the current base
274: String absBase = makeAbsolute(base);
275: if (!absBase.equals(base)) {
276: // don't bother if the absBase isn't different!
277: return resolve(href, absBase);
278: } else {
279: throw new TransformerException("Malformed URL "
280: + href + "(base " + base + ")", mue);
281: }
282: }
283: }
284:
285: catalogManager.debug.message(2, "Resolved URI", href, result);
286:
287: SAXSource source = new SAXSource();
288: source.setInputSource(new InputSource(result));
289: setEntityResolver(source);
290: return source;
291: }
292:
293: /**
294: * <p>Establish an entityResolver for newly resolved URIs.</p>
295: *
296: * <p>This is called from the URIResolver to set an EntityResolver
297: * on the SAX parser to be used for new XML documents that are
298: * encountered as a result of the document() function, xsl:import,
299: * or xsl:include. This is done because the XSLT processor calls
300: * out to the SAXParserFactory itself to create a new SAXParser to
301: * parse the new document. The new parser does not automatically
302: * inherit the EntityResolver of the original (although arguably
303: * it should). See below:</p>
304: *
305: * <tt>"If an application wants to set the ErrorHandler or
306: * EntityResolver for an XMLReader used during a transformation,
307: * it should use a URIResolver to return the SAXSource which
308: * provides (with getXMLReader) a reference to the XMLReader"</tt>
309: *
310: * <p>...quoted from page 118 of the Java API for XML
311: * Processing 1.1 specification</p>
312: *
313: */
314: private void setEntityResolver(SAXSource source)
315: throws TransformerException {
316: XMLReader reader = source.getXMLReader();
317: if (reader == null) {
318: SAXParserFactory spFactory = SAXParserFactory.newInstance();
319: spFactory.setNamespaceAware(true);
320: try {
321: reader = spFactory.newSAXParser().getXMLReader();
322: } catch (ParserConfigurationException ex) {
323: throw new TransformerException(ex);
324: } catch (SAXException ex) {
325: throw new TransformerException(ex);
326: }
327: }
328: reader.setEntityResolver(this );
329: source.setXMLReader(reader);
330: }
331:
332: /** Attempt to construct an absolute URI */
333: private String makeAbsolute(String uri) {
334: if (uri == null) {
335: uri = "";
336: }
337:
338: try {
339: URL url = new URL(uri);
340: return url.toString();
341: } catch (MalformedURLException mue) {
342: try {
343: URL fileURL = FileURL.makeURL(uri);
344: return fileURL.toString();
345: } catch (MalformedURLException mue2) {
346: // bail
347: return uri;
348: }
349: }
350: }
351: }
|