001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.util;
019:
020: import java.io.IOException;
021:
022: import org.xml.sax.InputSource;
023: import org.xml.sax.SAXException;
024: import org.xml.sax.ext.EntityResolver2;
025:
026: import org.w3c.dom.ls.LSInput;
027: import org.w3c.dom.ls.LSResourceResolver;
028:
029: import javax.xml.parsers.SAXParserFactory;
030:
031: import org.apache.xerces.dom.DOMInputImpl;
032: import org.apache.xerces.jaxp.SAXParserFactoryImpl;
033:
034: import org.apache.xerces.xni.XNIException;
035: import org.apache.xerces.xni.XMLResourceIdentifier;
036:
037: import org.apache.xerces.xni.parser.XMLEntityResolver;
038: import org.apache.xerces.xni.parser.XMLInputSource;
039:
040: import org.apache.xml.resolver.Catalog;
041: import org.apache.xml.resolver.CatalogManager;
042: import org.apache.xml.resolver.readers.OASISXMLCatalogReader;
043: import org.apache.xml.resolver.readers.SAXCatalogReader;
044:
045: /**
046: * <p>The catalog resolver handles the resolution of external
047: * identifiers and URI references through XML catalogs. This
048: * component supports XML catalogs defined by the
049: * <a href="http://www.oasis-open.org/committees/entity/spec.html">
050: * OASIS XML Catalogs Specification</a>. It encapsulates the
051: * <a href="http://xml.apache.org/commons/">XML Commons</a> resolver.
052: * An instance of this class may be registered on the parser
053: * as a SAX entity resolver, as a DOM LSResourceResolver or
054: * as an XNI entity resolver by setting the property
055: * (http://apache.org/xml/properties/internal/entity-resolver).</p>
056: *
057: * <p>It is intended that this class may be used standalone to perform
058: * catalog resolution outside of a parsing context. It may be shared
059: * between several parsers and the application.</p>
060: *
061: * @author Michael Glavassevich, IBM
062: *
063: * @version $Id: XMLCatalogResolver.java 570138 2007-08-27 14:21:51Z mrglavas $
064: */
065: public class XMLCatalogResolver implements XMLEntityResolver,
066: EntityResolver2, LSResourceResolver {
067:
068: /** Internal catalog manager for Apache catalogs. **/
069: private CatalogManager fResolverCatalogManager = null;
070:
071: /** Internal catalog structure. **/
072: private Catalog fCatalog = null;
073:
074: /** An array of catalog URIs. **/
075: private String[] fCatalogsList = null;
076:
077: /**
078: * Indicates whether the list of catalogs has
079: * changed since it was processed.
080: */
081: private boolean fCatalogsChanged = true;
082:
083: /** Application specified prefer public setting. **/
084: private boolean fPreferPublic = true;
085:
086: /**
087: * Indicates whether the application desires that
088: * the parser or some other component performing catalog
089: * resolution should use the literal system identifier
090: * instead of the expanded system identifier.
091: */
092: private boolean fUseLiteralSystemId = true;
093:
094: /**
095: * <p>Constructs a catalog resolver with a default configuration.</p>
096: */
097: public XMLCatalogResolver() {
098: this (null, true);
099: }
100:
101: /**
102: * <p>Constructs a catalog resolver with the given
103: * list of entry files.</p>
104: *
105: * @param catalogs an ordered array list of absolute URIs
106: */
107: public XMLCatalogResolver(String[] catalogs) {
108: this (catalogs, true);
109: }
110:
111: /**
112: * <p>Constructs a catalog resolver with the given
113: * list of entry files and the preference for whether
114: * system or public matches are preferred.</p>
115: *
116: * @param catalogs an ordered array list of absolute URIs
117: * @param preferPublic the prefer public setting
118: */
119: public XMLCatalogResolver(String[] catalogs, boolean preferPublic) {
120: init(catalogs, preferPublic);
121: }
122:
123: /**
124: * <p>Returns the initial list of catalog entry files.</p>
125: *
126: * @return the initial list of catalog entry files
127: */
128: public final synchronized String[] getCatalogList() {
129: return (fCatalogsList != null) ? (String[]) fCatalogsList
130: .clone() : null;
131: }
132:
133: /**
134: * <p>Sets the initial list of catalog entry files.
135: * If there were any catalog mappings cached from
136: * the previous list they will be replaced by catalog
137: * mappings from the new list the next time the catalog
138: * is queried.</p>
139: *
140: * @param catalogs an ordered array list of absolute URIs
141: */
142: public final synchronized void setCatalogList(String[] catalogs) {
143: fCatalogsChanged = true;
144: fCatalogsList = (catalogs != null) ? (String[]) catalogs
145: .clone() : null;
146: }
147:
148: /**
149: * <p>Forces the cache of catalog mappings to be cleared.</p>
150: */
151: public final synchronized void clear() {
152: fCatalog = null;
153: }
154:
155: /**
156: * <p>Returns the preference for whether system or public
157: * matches are preferred. This is used in the absence
158: * of any occurrence of the <code>prefer</code> attribute
159: * on the <code>catalog</code> entry of a catalog. If this
160: * property has not yet been explicitly set its value is
161: * <code>true</code>.</p>
162: *
163: * @return the prefer public setting
164: */
165: public final boolean getPreferPublic() {
166: return fPreferPublic;
167: }
168:
169: /**
170: * <p>Sets the preference for whether system or public
171: * matches are preferred. This is used in the absence
172: * of any occurrence of the <code>prefer</code> attribute
173: * on the <code>catalog</code> entry of a catalog.</p>
174: *
175: * @param preferPublic the prefer public setting
176: */
177: public final void setPreferPublic(boolean preferPublic) {
178: fPreferPublic = preferPublic;
179: fResolverCatalogManager.setPreferPublic(preferPublic);
180: }
181:
182: /**
183: * <p>Returns the preference for whether the literal system
184: * identifier should be used when resolving system
185: * identifiers when both it and the expanded system
186: * identifier are available. If this property has not yet
187: * been explicitly set its value is <code>true</code>.</p>
188: *
189: * @return the preference for using literal system identifiers
190: * for catalog resolution
191: *
192: * @see #setUseLiteralSystemId
193: */
194: public final boolean getUseLiteralSystemId() {
195: return fUseLiteralSystemId;
196: }
197:
198: /**
199: * <p>Sets the preference for whether the literal system
200: * identifier should be used when resolving system
201: * identifiers when both it and the expanded system
202: * identifier are available.</p>
203: *
204: * <p>The literal system identifier is the URI as it was
205: * provided before absolutization. It may be embedded within
206: * an entity. It may be provided externally or it may be the
207: * result of redirection. For example, redirection may
208: * have come from the protocol level through HTTP or from
209: * an application's entity resolver.</p>
210: *
211: * <p>The expanded system identifier is an absolute URI
212: * which is the result of resolving the literal system
213: * identifier against a base URI.</p>
214: *
215: * @param useLiteralSystemId the preference for using
216: * literal system identifiers for catalog resolution
217: */
218: public final void setUseLiteralSystemId(boolean useLiteralSystemId) {
219: fUseLiteralSystemId = useLiteralSystemId;
220: }
221:
222: /**
223: * <p>Resolves an external entity. If the entity cannot be
224: * resolved, this method should return <code>null</code>. This
225: * method returns an input source if an entry was found in the
226: * catalog for the given external identifier. It should be
227: * overridden if other behaviour is required.</p>
228: *
229: * @param publicId the public identifier, or <code>null</code> if none was supplied
230: * @param systemId the system identifier
231: *
232: * @throws SAXException any SAX exception, possibly wrapping another exception
233: * @throws IOException thrown if some i/o error occurs
234: */
235: public InputSource resolveEntity(String publicId, String systemId)
236: throws SAXException, IOException {
237:
238: String resolvedId = null;
239: if (publicId != null && systemId != null) {
240: resolvedId = resolvePublic(publicId, systemId);
241: } else if (systemId != null) {
242: resolvedId = resolveSystem(systemId);
243: }
244:
245: if (resolvedId != null) {
246: InputSource source = new InputSource(resolvedId);
247: source.setPublicId(publicId);
248: return source;
249: }
250: return null;
251: }
252:
253: /**
254: * <p>Resolves an external entity. If the entity cannot be
255: * resolved, this method should return <code>null</code>. This
256: * method returns an input source if an entry was found in the
257: * catalog for the given external identifier. It should be
258: * overridden if other behaviour is required.</p>
259: *
260: * @param name the identifier of the external entity
261: * @param publicId the public identifier, or <code>null</code> if none was supplied
262: * @param baseURI the URI with respect to which relative systemIDs are interpreted.
263: * @param systemId the system identifier
264: *
265: * @throws SAXException any SAX exception, possibly wrapping another exception
266: * @throws IOException thrown if some i/o error occurs
267: */
268: public InputSource resolveEntity(String name, String publicId,
269: String baseURI, String systemId) throws SAXException,
270: IOException {
271:
272: String resolvedId = null;
273:
274: if (!getUseLiteralSystemId() && baseURI != null) {
275: // Attempt to resolve the system identifier against the base URI.
276: try {
277: URI uri = new URI(new URI(baseURI), systemId);
278: systemId = uri.toString();
279: }
280: // Ignore the exception. Fallback to the literal system identifier.
281: catch (URI.MalformedURIException ex) {
282: }
283: }
284:
285: if (publicId != null && systemId != null) {
286: resolvedId = resolvePublic(publicId, systemId);
287: } else if (systemId != null) {
288: resolvedId = resolveSystem(systemId);
289: }
290:
291: if (resolvedId != null) {
292: InputSource source = new InputSource(resolvedId);
293: source.setPublicId(publicId);
294: return source;
295: }
296: return null;
297: }
298:
299: /**
300: * <p>Locates an external subset for documents which do not explicitly
301: * provide one. This method always returns <code>null</code>. It
302: * should be overrided if other behaviour is required.</p>
303: *
304: * @param name the identifier of the document root element
305: * @param baseURI the document's base URI
306: *
307: * @throws SAXException any SAX exception, possibly wrapping another exception
308: * @throws IOException thrown if some i/o error occurs
309: */
310: public InputSource getExternalSubset(String name, String baseURI)
311: throws SAXException, IOException {
312: return null;
313: }
314:
315: /**
316: * <p>Resolves a resource using the catalog. This method interprets that
317: * the namespace URI corresponds to uri entries in the catalog.
318: * Where both a namespace and an external identifier exist, the namespace
319: * takes precedence.</p>
320: *
321: * @param type the type of the resource being resolved
322: * @param namespaceURI the namespace of the resource being resolved,
323: * or <code>null</code> if none was supplied
324: * @param publicId the public identifier of the resource being resolved,
325: * or <code>null</code> if none was supplied
326: * @param systemId the system identifier of the resource being resolved,
327: * or <code>null</code> if none was supplied
328: * @param baseURI the absolute base URI of the resource being parsed,
329: * or <code>null</code> if there is no base URI
330: */
331: public LSInput resolveResource(String type, String namespaceURI,
332: String publicId, String systemId, String baseURI) {
333:
334: String resolvedId = null;
335:
336: try {
337: // The namespace is useful for resolving namespace aware
338: // grammars such as XML schema. Let it take precedence over
339: // the external identifier if one exists.
340: if (namespaceURI != null) {
341: resolvedId = resolveURI(namespaceURI);
342: }
343:
344: if (!getUseLiteralSystemId() && baseURI != null) {
345: // Attempt to resolve the system identifier against the base URI.
346: try {
347: URI uri = new URI(new URI(baseURI), systemId);
348: systemId = uri.toString();
349: }
350: // Ignore the exception. Fallback to the literal system identifier.
351: catch (URI.MalformedURIException ex) {
352: }
353: }
354:
355: // Resolve against an external identifier if one exists. This
356: // is useful for resolving DTD external subsets and other
357: // external entities. For XML schemas if there was no namespace
358: // mapping we might be able to resolve a system identifier
359: // specified as a location hint.
360: if (resolvedId == null) {
361: if (publicId != null && systemId != null) {
362: resolvedId = resolvePublic(publicId, systemId);
363: } else if (systemId != null) {
364: resolvedId = resolveSystem(systemId);
365: }
366: }
367: }
368: // Ignore IOException. It cannot be thrown from this method.
369: catch (IOException ex) {
370: }
371:
372: if (resolvedId != null) {
373: return new DOMInputImpl(publicId, resolvedId, baseURI);
374: }
375: return null;
376: }
377:
378: /**
379: * <p>Resolves an external entity. If the entity cannot be
380: * resolved, this method should return <code>null</code>. This
381: * method only calls <code>resolveIdentifier</code> and returns
382: * an input source if an entry was found in the catalog. It
383: * should be overridden if other behaviour is required.</p>
384: *
385: * @param resourceIdentifier location of the XML resource to resolve
386: *
387: * @throws XNIException thrown on general error
388: * @throws IOException thrown if some i/o error occurs
389: */
390: public XMLInputSource resolveEntity(
391: XMLResourceIdentifier resourceIdentifier)
392: throws XNIException, IOException {
393:
394: String resolvedId = resolveIdentifier(resourceIdentifier);
395: if (resolvedId != null) {
396: return new XMLInputSource(resourceIdentifier.getPublicId(),
397: resolvedId, resourceIdentifier.getBaseSystemId());
398: }
399: return null;
400: }
401:
402: /**
403: * <p>Resolves an identifier using the catalog. This method interprets that
404: * the namespace of the identifier corresponds to uri entries in the catalog.
405: * Where both a namespace and an external identifier exist, the namespace
406: * takes precedence.</p>
407: *
408: * @param resourceIdentifier the identifier to resolve
409: *
410: * @throws XNIException thrown on general error
411: * @throws IOException thrown if some i/o error occurs
412: */
413: public String resolveIdentifier(
414: XMLResourceIdentifier resourceIdentifier)
415: throws IOException, XNIException {
416:
417: String resolvedId = null;
418:
419: // The namespace is useful for resolving namespace aware
420: // grammars such as XML schema. Let it take precedence over
421: // the external identifier if one exists.
422: String namespace = resourceIdentifier.getNamespace();
423: if (namespace != null) {
424: resolvedId = resolveURI(namespace);
425: }
426:
427: // Resolve against an external identifier if one exists. This
428: // is useful for resolving DTD external subsets and other
429: // external entities. For XML schemas if there was no namespace
430: // mapping we might be able to resolve a system identifier
431: // specified as a location hint.
432: if (resolvedId == null) {
433: String publicId = resourceIdentifier.getPublicId();
434: String systemId = getUseLiteralSystemId() ? resourceIdentifier
435: .getLiteralSystemId()
436: : resourceIdentifier.getExpandedSystemId();
437: if (publicId != null && systemId != null) {
438: resolvedId = resolvePublic(publicId, systemId);
439: } else if (systemId != null) {
440: resolvedId = resolveSystem(systemId);
441: }
442: }
443: return resolvedId;
444: }
445:
446: /**
447: * <p>Returns the URI mapping in the catalog for the given
448: * external identifier or <code>null</code> if no mapping
449: * exists. If the system identifier is an URN in the
450: * <code>publicid</code> namespace it is converted into
451: * a public identifier by URN "unwrapping" as specified
452: * in the XML Catalogs specification.</p>
453: *
454: * @param systemId the system identifier to locate in the catalog
455: *
456: * @return the mapped URI or <code>null</code> if no mapping
457: * was found in the catalog
458: *
459: * @throws IOException if an i/o error occurred while reading
460: * the catalog
461: */
462: public final synchronized String resolveSystem(String systemId)
463: throws IOException {
464:
465: if (fCatalogsChanged) {
466: parseCatalogs();
467: fCatalogsChanged = false;
468: }
469: return (fCatalog != null) ? fCatalog.resolveSystem(systemId)
470: : null;
471: }
472:
473: /**
474: * <p>Returns the URI mapping in the catalog for the given
475: * external identifier or <code>null</code> if no mapping
476: * exists. Public identifiers are normalized before
477: * comparison.</p>
478: *
479: * @param publicId the public identifier to locate in the catalog
480: * @param systemId the system identifier to locate in the catalog
481: *
482: * @return the mapped URI or <code>null</code> if no mapping
483: * was found in the catalog
484: *
485: * @throws IOException if an i/o error occurred while reading
486: * the catalog
487: */
488: public final synchronized String resolvePublic(String publicId,
489: String systemId) throws IOException {
490:
491: if (fCatalogsChanged) {
492: parseCatalogs();
493: fCatalogsChanged = false;
494: }
495: return (fCatalog != null) ? fCatalog.resolvePublic(publicId,
496: systemId) : null;
497: }
498:
499: /**
500: * <p>Returns the URI mapping in the catalog for the given URI
501: * reference or <code>null</code> if no mapping exists.
502: * URI comparison is case sensitive. If the URI reference
503: * is an URN in the <code>publicid</code> namespace
504: * it is converted into a public identifier by URN "unwrapping"
505: * as specified in the XML Catalogs specification and then
506: * resolution is performed following the semantics of
507: * external identifier resolution.</p>
508: *
509: * @param uri the URI to locate in the catalog
510: *
511: * @return the mapped URI or <code>null</code> if no mapping
512: * was found in the catalog
513: *
514: * @throws IOException if an i/o error occurred while reading
515: * the catalog
516: */
517: public final synchronized String resolveURI(String uri)
518: throws IOException {
519:
520: if (fCatalogsChanged) {
521: parseCatalogs();
522: fCatalogsChanged = false;
523: }
524: return (fCatalog != null) ? fCatalog.resolveURI(uri) : null;
525: }
526:
527: /**
528: * Initialization. Create a CatalogManager and set all
529: * the properties upfront. This prevents JVM wide system properties
530: * or a property file somewhere in the environment from affecting
531: * the behaviour of this catalog resolver.
532: */
533: private void init(String[] catalogs, boolean preferPublic) {
534: fCatalogsList = (catalogs != null) ? (String[]) catalogs
535: .clone() : null;
536: fPreferPublic = preferPublic;
537: fResolverCatalogManager = new CatalogManager();
538: fResolverCatalogManager.setAllowOasisXMLCatalogPI(false);
539: fResolverCatalogManager
540: .setCatalogClassName("org.apache.xml.resolver.Catalog");
541: fResolverCatalogManager.setCatalogFiles("");
542: fResolverCatalogManager.setIgnoreMissingProperties(true);
543: fResolverCatalogManager.setPreferPublic(fPreferPublic);
544: fResolverCatalogManager.setRelativeCatalogs(false);
545: fResolverCatalogManager.setUseStaticCatalog(false);
546: fResolverCatalogManager.setVerbosity(0);
547: }
548:
549: /**
550: * Instruct the <code>Catalog</code> to parse each of the
551: * catalogs in the list. Only the first catalog will actually be
552: * parsed immediately. The others will be queued and read if
553: * they are needed later.
554: */
555: private void parseCatalogs() throws IOException {
556: if (fCatalogsList != null) {
557: fCatalog = new Catalog(fResolverCatalogManager);
558: attachReaderToCatalog(fCatalog);
559: for (int i = 0; i < fCatalogsList.length; ++i) {
560: String catalog = fCatalogsList[i];
561: if (catalog != null && catalog.length() > 0) {
562: fCatalog.parseCatalog(catalog);
563: }
564: }
565: } else {
566: fCatalog = null;
567: }
568: }
569:
570: /**
571: * Attaches the reader to the catalog.
572: */
573: private void attachReaderToCatalog(Catalog catalog) {
574:
575: SAXParserFactory spf = new SAXParserFactoryImpl();
576: spf.setNamespaceAware(true);
577: spf.setValidating(false);
578:
579: SAXCatalogReader saxReader = new SAXCatalogReader(spf);
580: saxReader
581: .setCatalogParser(OASISXMLCatalogReader.namespaceName,
582: "catalog",
583: "org.apache.xml.resolver.readers.OASISXMLCatalogReader");
584: catalog.addReader("application/xml", saxReader);
585: }
586: }
|