
org.enhydra.xml.io.XMLEntityResolver Maven / Gradle / Ivy
/*
* Enhydra Java Application Server Project
*
* The contents of this file are subject to the Enhydra Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License on
* the Enhydra web site ( http://www.enhydra.org/ ).
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific terms governing rights and limitations
* under the License.
*
* The Initial Developer of the Enhydra Application Server is Lutris
* Technologies, Inc. The Enhydra Application Server and portions created
* by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
* All Rights Reserved.
*
* Contributor(s):
*
* $Id: XMLEntityResolver.java,v 1.3 2005/01/26 08:29:24 jkjome Exp $
*/
package org.enhydra.xml.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Vector;
import org.enhydra.apache.xerces.readers.XCatalog;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A flexable entity resolver for use with parsing XML files.
* This implements:
*
* - XCatalog support.
*
- Resolving entities from class loaders.
*
- Resolving standard URL entities.
*
- A debug output stream, useful for debugging resolution problems.
*
*
* A list of class loaders maybe associated with this object. If so
* an attempt will be made to resolve file:
URLs from
* the class path.
*
* @see org.enhydra.apache.xerces.readers.XCatalog
*/
public class XMLEntityResolver implements EntityResolver {
/**
* Protocol to use in specifying a URL pointing to the CLASSPATH.
*/
public static final String CLASSPATH_PROTOCOL = "file";
/**
* Protocol prefix.
*/
public static final String CLASSPATH_PROTOCOL_PREFIX
= CLASSPATH_PROTOCOL + ":";
/**
* URL for Enhydra default entity catalog.
* @see #setDefaultResolving
*/
public static final String DEFAULT_ENTITY_CATALOG
= CLASSPATH_PROTOCOL_PREFIX + "/org/enhydra/xml/catalog/default.xcat";
/**
* URL for Lutrus Enhydra default entity catalog. Only loaded if
* available.
* @see #setDefaultResolving
*/
public static final String LUTRIS_ENTITY_CATALOG
= CLASSPATH_PROTOCOL_PREFIX + "/org/enhydra/xml/catalog/lutris.xcat";
/**
* If not null, write debug information to this writer.
*/
private PrintWriter fDebugWriter = null;
/**
* XCatalog catalog and entity resolver..
*/
private XCatalog fXCatalog = new XCatalog();
/**
* List of ClassLoaders use to resolve entities.
*/
private Vector fClassLoaders = new Vector();
/**
* Set a writer used to output debug messages.
*/
public void setDebugWriter(PrintWriter writer) {
fDebugWriter = writer;
}
/**
* Get the debug writer.
*/
public PrintWriter getDebugWriter() {
return fDebugWriter;
}
/**
* Loads a catalog specified by the given input source and
* appends the contents to the catalog object.
*
* @param source The catalog source.
*
* @exception org.xml.sax.SAXException Throws exception on SAX error.
* @exception java.io.IOException Throws exception on i/o error.
*/
public void loadCatalog(InputSource source)
throws SAXException, IOException {
if (fDebugWriter != null) {
fDebugWriter.println("loadCatalog(" + InputSourceOps.getName(source) + ")");
}
// Check for catalog on the CLASSPATH
InputSource loadSource = searchClassPath(source);
if (loadSource == null) {
loadSource = source;
}
if (fDebugWriter != null) {
fDebugWriter.println("loading catalog from(" + InputSourceOps.getName(loadSource) + ")");
}
fXCatalog.loadCatalog(loadSource);
}
/**
* Load the default entity catalog and enable loading entities the
* classloader for the classloader that loaded this class.
*/
public void setDefaultResolving() throws SAXException, IOException {
// JDeveloper JVM doesn't correctly return system class loader
// if it loaded this class, so we do it explictly.
ClassLoader classLoader = XMLEntityResolver.class.getClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
addClassLoader(classLoader);
loadCatalog(new InputSource(DEFAULT_ENTITY_CATALOG));
// Check for optional default catalog
InputSource optSource = searchClassPath(LUTRIS_ENTITY_CATALOG);
if (optSource != null) {
loadCatalog(optSource);
}
}
/**
* Adds a public to system identifier mapping.
*
* @param publicId The public identifier, or "key".
* @param systemId The system identifier, or "value".
*/
public void addPublicMapping(String publicId, String systemId) {
fXCatalog.addPublicMapping(publicId, systemId);
}
/**
* Removes a public identifier mapping.
*
* @param publicId The public identifier to remove.
*/
public void removePublicMapping(System publicId) {
fXCatalog.removePublicMapping(publicId);
}
/** Returns an enumeration of public identifier mapping keys. */
public Enumeration getPublicMappingKeys() {
return fXCatalog.getPublicMappingKeys();
}
/**
* Returns a public identifier mapping.
*
* @param publicId The public identifier, or "key".
*
* @return Returns the system identifier value or null if there
* is no mapping defined.
*/
public String getPublicMapping(String publicId) {
return fXCatalog.getPublicMapping(publicId);
}
/**
* Adds a system identifier alias.
*
* @param publicId The system identifier "key".
* @param systemId The system identifier "value".
*/
public void addSystemMapping(String systemId1, String systemId2) {
fXCatalog.addSystemMapping(systemId1, systemId2);
}
/**
* Removes a system identifier alias.
*
* @param systemId The system identifier to remove.
*/
public void removeSystemMapping(String systemId) {
fXCatalog.removeSystemMapping(systemId);
}
/** Returns an enumeration of system identifier mapping keys. */
public Enumeration getSystemMappingKeys() {
return fXCatalog.getSystemMappingKeys();
}
/**
* Returns a system identifier alias.
*
* @param systemId The system identifier "key".
*
* @return Returns the system identifier alias value or null if there
* is no alias defined.
*/
public String getSystemMapping(String systemId) {
return fXCatalog.getSystemMapping(systemId);
}
/**
* Add a classloader to the end of the order list of classloaders to
* search for entities.
*/
public void addClassLoader(ClassLoader classLoader) {
fClassLoaders.add(classLoader);
}
/**
* Get an enumeration of the class loaders.
*/
public Enumeration getClassLoaders() {
return fClassLoaders.elements();
}
/**
* Attempt to load a systemId from a classloader.
*/
private InputSource loadFromClassLoader(ClassLoader loader,
String resourceId)
throws IOException {
// Drop leading `/' from path.
if (resourceId.startsWith("/")) {
resourceId = resourceId.substring(1);
}
InputStream in = loader.getResourceAsStream(resourceId);
if (in != null) {
return new ClosingInputSource(in);
} else {
return null;
}
}
/**
* Determine if a a system id is a CLASSPATH URL.
*/
private boolean isClassPathURL(String systemId) {
return ((systemId != null) && systemId.startsWith(CLASSPATH_PROTOCOL_PREFIX));
}
/**
* Get the file name from a CLASSPATH URL.
*/
private String parseClassPathURL(String url) {
return url.substring(CLASSPATH_PROTOCOL_PREFIX.length());
}
/**
* Attempt to load a systemId from a classpath. Will return an
* InputSource with a contained byte stream (doesn't need closed). If
* systemId is null or dosen't have the file: protocal, then null is
* returned.
*/
private InputSource searchClassPath(String systemId)
throws IOException {
InputSource source = null;
if ((systemId != null) && isClassPathURL(systemId)) {
String resourceName = parseClassPathURL(systemId);
int len = fClassLoaders.size();
for (int idx = 0; (idx < len) && (source == null); idx++) {
source = loadFromClassLoader((ClassLoader)fClassLoaders.get(idx),
resourceName);
}
if (source != null) {
source.setSystemId(systemId);
}
}
if (fDebugWriter != null) {
fDebugWriter.println("Entity " + ((source == null) ? "not " : "")
+ "found searching " + fClassLoaders.size()
+ " class loader(s): " + systemId
+ " => " + InputSourceOps.getName(source));
}
return source;
}
/**
* @see #searchClassPath#String
*/
private InputSource searchClassPath(InputSource source)
throws IOException {
if ((source.getByteStream() != null)
|| (source.getCharacterStream() != null)) {
return null; // Already open.
} else {
return searchClassPath(source.getSystemId());
}
}
/**
* @see org.xml.sax.EntityResolver
*/
public InputSource resolveEntity(String publicId,
String systemId)
throws SAXException, IOException {
if (fDebugWriter != null) {
fDebugWriter.println("resolveEntity(" + publicId + ", " + systemId + ")");
}
InputSource source;
InputSource catalogSource = fXCatalog.resolveEntity(publicId,
systemId);
if (catalogSource != null) {
if (fDebugWriter != null) {
fDebugWriter.println("XMLCatalog maps entity to: " + catalogSource.getSystemId()); //
}
source = searchClassPath(catalogSource);
if (source == null) {
source = catalogSource; // Use unchanged.
}
} else {
source = searchClassPath(systemId);
}
if (fDebugWriter != null) {
fDebugWriter.println("Resolved: " + InputSourceOps.getName(source));
}
return source;
}
}