1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.sun.org.apache.xml.internal.resolver.tools;
19
20import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
21import java.io.IOException;
22import java.io.InputStream;
23import java.net.URL;
24import java.net.MalformedURLException;
25
26import org.xml.sax.SAXException;
27import org.xml.sax.XMLReader;
28import org.xml.sax.InputSource;
29import org.xml.sax.EntityResolver;
30
31import javax.xml.transform.sax.SAXSource;
32import javax.xml.transform.Source;
33import javax.xml.transform.URIResolver;
34import javax.xml.transform.TransformerException;
35import javax.xml.parsers.ParserConfigurationException;
36import javax.xml.parsers.SAXParserFactory;
37
38import com.sun.org.apache.xml.internal.resolver.Catalog;
39import com.sun.org.apache.xml.internal.resolver.CatalogManager;
40import com.sun.org.apache.xml.internal.resolver.helpers.FileURL;
41
42/**
43 * A SAX EntityResolver/JAXP URIResolver that uses catalogs.
44 *
45 * <p>This class implements both a SAX EntityResolver and a JAXP URIResolver.
46 * </p>
47 *
48 * <p>This resolver understands OASIS TR9401 catalogs, XCatalogs, and the
49 * current working draft of the OASIS Entity Resolution Technical
50 * Committee specification.</p>
51 *
52 * @see Catalog
53 * @see org.xml.sax.EntityResolver
54 * @see javax.xml.transform.URIResolver
55 * @deprecated The JDK internal Catalog API in package
56 * {@code com.sun.org.apache.xml.internal.resolver}
57 * is encapsulated in JDK 9. The entire implementation under the package is now
58 * deprecated and subject to removal in a future release. Users of the API
59 * should migrate to the {@linkplain javax.xml.catalog new public API}.
60 * <p>
61 * The new Catalog API is supported throughout the JDK XML Processors, which allows
62 * the use of Catalog by simply setting a path to a Catalog file as a property.
63 *
64 * @author Norman Walsh
65 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
66 *
67 * @version 1.0
68 */
69@Deprecated(since="9", forRemoval=true)
70public class CatalogResolver implements EntityResolver, URIResolver {
71  /** Make the parser Namespace aware? */
72  public boolean namespaceAware = true;
73
74  /** Make the parser validating? */
75  public boolean validating = false;
76
77  /** The underlying catalog */
78  private Catalog catalog = null;
79
80  /** The catalog manager */
81  private CatalogManager catalogManager = CatalogManager.getStaticManager();
82
83  /** Constructor */
84  public CatalogResolver() {
85    initializeCatalogs(false);
86  }
87
88  /** Constructor */
89  public CatalogResolver(boolean privateCatalog) {
90    initializeCatalogs(privateCatalog);
91  }
92
93  /** Constructor */
94  public CatalogResolver(CatalogManager manager) {
95    catalogManager = manager;
96    initializeCatalogs(!catalogManager.getUseStaticCatalog());
97  }
98
99  /** Initialize catalog */
100  private void initializeCatalogs(boolean privateCatalog) {
101    catalog = catalogManager.getCatalog();
102  }
103
104  /** Return the underlying catalog */
105  public Catalog getCatalog() {
106    return catalog;
107  }
108
109  /**
110   * Implements the guts of the <code>resolveEntity</code> method
111   * for the SAX interface.
112   *
113   * <p>Presented with an optional public identifier and a system
114   * identifier, this function attempts to locate a mapping in the
115   * catalogs.</p>
116   *
117   * <p>If such a mapping is found, it is returned.  If no mapping is
118   * found, null is returned.</p>
119   *
120   * @param publicId  The public identifier for the entity in question.
121   * This may be null.
122   *
123   * @param systemId  The system identifier for the entity in question.
124   * XML requires a system identifier on all external entities, so this
125   * value is always specified.
126   *
127   * @return The resolved identifier (a URI reference).
128   */
129  public String getResolvedEntity (String publicId, String systemId) {
130    String resolved = null;
131
132    if (catalog == null) {
133      catalogManager.debug.message(1, "Catalog resolution attempted with null catalog; ignored");
134      return null;
135    }
136
137    if (systemId != null) {
138      try {
139        resolved = catalog.resolveSystem(systemId);
140      } catch (MalformedURLException me) {
141        catalogManager.debug.message(1, "Malformed URL exception trying to resolve",
142                      publicId);
143        resolved = null;
144      } catch (IOException ie) {
145        catalogManager.debug.message(1, "I/O exception trying to resolve", publicId);
146        resolved = null;
147      }
148    }
149
150    if (resolved == null) {
151      if (publicId != null) {
152        try {
153          resolved = catalog.resolvePublic(publicId, systemId);
154        } catch (MalformedURLException me) {
155          catalogManager.debug.message(1, "Malformed URL exception trying to resolve",
156                        publicId);
157        } catch (IOException ie) {
158          catalogManager.debug.message(1, "I/O exception trying to resolve", publicId);
159        }
160      }
161
162      if (resolved != null) {
163        catalogManager.debug.message(2, "Resolved public", publicId, resolved);
164      }
165    } else {
166      catalogManager.debug.message(2, "Resolved system", systemId, resolved);
167    }
168
169    return resolved;
170  }
171
172  /**
173   * Implements the <code>resolveEntity</code> method
174   * for the SAX interface.
175   *
176   * <p>Presented with an optional public identifier and a system
177   * identifier, this function attempts to locate a mapping in the
178   * catalogs.</p>
179   *
180   * <p>If such a mapping is found, the resolver attempts to open
181   * the mapped value as an InputSource and return it. Exceptions are
182   * ignored and null is returned if the mapped value cannot be opened
183   * as an input source.</p>
184   *
185   * <p>If no mapping is found (or an error occurs attempting to open
186   * the mapped value as an input source), null is returned and the system
187   * will use the specified system identifier as if no entityResolver
188   * was specified.</p>
189   *
190   * @param publicId  The public identifier for the entity in question.
191   * This may be null.
192   *
193   * @param systemId  The system identifier for the entity in question.
194   * XML requires a system identifier on all external entities, so this
195   * value is always specified.
196   *
197   * @return An InputSource for the mapped identifier, or null.
198   */
199  public InputSource resolveEntity (String publicId, String systemId) {
200    String resolved = getResolvedEntity(publicId, systemId);
201
202    if (resolved != null) {
203      try {
204        InputSource iSource = new InputSource(resolved);
205        iSource.setPublicId(publicId);
206
207        // Ideally this method would not attempt to open the
208        // InputStream, but there is a bug (in Xerces, at least)
209        // that causes the parser to mistakenly open the wrong
210        // system identifier if the returned InputSource does
211        // not have a byteStream.
212        //
213        // It could be argued that we still shouldn't do this here,
214        // but since the purpose of calling the entityResolver is
215        // almost certainly to open the input stream, it seems to
216        // do little harm.
217        //
218        URL url = new URL(resolved);
219        InputStream iStream = url.openStream();
220        iSource.setByteStream(iStream);
221
222        return iSource;
223      } catch (Exception e) {
224        catalogManager.debug.message(1,
225                                     "Failed to create InputSource ("
226                                     + e.toString()
227                                     + ")", resolved);
228        return null;
229      }
230    }
231
232    return null;
233  }
234
235  /** JAXP URIResolver API */
236  public Source resolve(String href, String base)
237    throws TransformerException {
238
239    String uri = href;
240    String fragment = null;
241    int hashPos = href.indexOf("#");
242    if (hashPos >= 0) {
243      uri = href.substring(0, hashPos);
244      fragment = href.substring(hashPos+1);
245    }
246
247    String result = null;
248
249    try {
250      result = catalog.resolveURI(href);
251    } catch (Exception e) {
252      // nop;
253    }
254
255    if (result == null) {
256      try {
257        URL url = null;
258
259        if (base==null) {
260          url = new URL(uri);
261          result = url.toString();
262        } else {
263          URL baseURL = new URL(base);
264          url = (href.length()==0 ? baseURL : new URL(baseURL, uri));
265          result = url.toString();
266        }
267      } catch (java.net.MalformedURLException mue) {
268        // try to make an absolute URI from the current base
269        String absBase = makeAbsolute(base);
270        if (!absBase.equals(base)) {
271          // don't bother if the absBase isn't different!
272          return resolve(href, absBase);
273        } else {
274          throw new TransformerException("Malformed URL "
275                                         + href + "(base " + base + ")",
276                                         mue);
277        }
278      }
279    }
280
281    catalogManager.debug.message(2, "Resolved URI", href, result);
282
283    SAXSource source = new SAXSource();
284    source.setInputSource(new InputSource(result));
285    setEntityResolver(source);
286    return source;
287  }
288
289  /**
290   * <p>Establish an entityResolver for newly resolved URIs.</p>
291   *
292   * <p>This is called from the URIResolver to set an EntityResolver
293   * on the SAX parser to be used for new XML documents that are
294   * encountered as a result of the document() function, xsl:import,
295   * or xsl:include.  This is done because the XSLT processor calls
296   * out to the SAXParserFactory itself to create a new SAXParser to
297   * parse the new document.  The new parser does not automatically
298   * inherit the EntityResolver of the original (although arguably
299   * it should).  See below:</p>
300   *
301   * <tt>"If an application wants to set the ErrorHandler or
302   * EntityResolver for an XMLReader used during a transformation,
303   * it should use a URIResolver to return the SAXSource which
304   * provides (with getXMLReader) a reference to the XMLReader"</tt>
305   *
306   * <p>...quoted from page 118 of the Java API for XML
307   * Processing 1.1 specification</p>
308   *
309   */
310  private void setEntityResolver(SAXSource source) throws TransformerException {
311    XMLReader reader = source.getXMLReader();
312    if (reader == null) {
313      SAXParserFactory spFactory = catalogManager.useServicesMechanism() ?
314                    SAXParserFactory.newInstance() : new SAXParserFactoryImpl();
315      spFactory.setNamespaceAware(true);
316      try {
317        reader = spFactory.newSAXParser().getXMLReader();
318      }
319      catch (ParserConfigurationException ex) {
320        throw new TransformerException(ex);
321      }
322      catch (SAXException ex) {
323        throw new TransformerException(ex);
324      }
325    }
326    reader.setEntityResolver(this);
327    source.setXMLReader(reader);
328  }
329
330  /** Attempt to construct an absolute URI */
331  private String makeAbsolute(String uri) {
332    if (uri == null) {
333      uri = "";
334    }
335
336    try {
337      URL url = new URL(uri);
338      return url.toString();
339    } catch (MalformedURLException mue) {
340      try {
341        URL fileURL = FileURL.makeURL(uri);
342        return fileURL.toString();
343      } catch (MalformedURLException mue2) {
344        // bail
345        return uri;
346      }
347    }
348  }
349}
350