blob: 523bebe8a06cc37685e52ee64cbba63ebc08d2ad [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The name "Apache Software Foundation" must not be used to endorse or
* promote products derived from this software without prior written
* permission. For written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999-2001, Sun Microsystems,
* Inc., http://www.sun.com. For more information on the Apache Software
* Foundation, please see <http://www.apache.org/>.
*/
package org.apache.xml.dtm;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Properties;
/**
* This class is based on the FactoryFinder classes in the JAXP subpackages
* in the xml-commons project (xml-apis.jar)
*
* This copy of FactoryFinder is for the DTMManager. It caches the class
* name after it is found the first time, if the System.property is not set.
* If the System.property is set, then it is always used.
*
* It does not use context class loaders, but we will probably need to add
* this support in the future. Question: If we use context class loaders, can
* we still cache the class (do we need to also cache the class loader for
* comparison purposes)?
*
* @author Edwin Goei, Ilene Seelemann
*/
class FactoryFinder {
/** Controls debugging output to stderr */
private static boolean debug;
/**
* Avoid reading all the files when the findFactory
* method is called the second time (cache the result of
* finding the default impl).
*/
private static String foundFactory = null;
// Define system property "jaxp.debug" to get output
static {
try {
String val =
SecuritySupport.getInstance().getSystemProperty("jaxp.debug");
// Allow simply setting the prop to turn on debug
debug = val != null && (! "false".equals(val));
} catch (SecurityException se) {
debug = false;
}
}
/**
* Main entry point. Finds and creates a new instance of a concrete
* factory implementation in the specified order as stated in the JAXP
* spec. This code attempts to find a factory implementation in
* serveral locations. If one fails, the next one is tried. To be
* more robust, this occurs even if a SecurityException is thrown, but
* perhaps it may be better to propogate the SecurityException instead,
* so SecurityException-s are not masked.
*
* @return A new instance of the concrete factory class, never null
*
* @param factoryId
* Name of the factory to find, same as a property name
*
* @param fallbackClassName
* Implementation class name, if nothing else is found. Use
* null to mean not to use a fallback.
*
* @throws FactoryFinder.ConfigurationError
* If a factory instance cannot be returned
*
* Package private so this code can be shared.
*/
static Object find(String factoryId, String fallbackClassName)
throws ConfigurationError
{
SecuritySupport ss = SecuritySupport.getInstance();
ClassLoader cl = FactoryFinder.class.getClassLoader();
dPrint("find factoryId=" + factoryId);
// Use the system property first
try {
String systemProp = ss.getSystemProperty(factoryId);
if (systemProp != null) {
dPrint("found system property, value=" + systemProp);
return newInstance(systemProp, cl, true);
}
} catch (SecurityException se) {
// Ignore and continue w/ next location
}
synchronized (FactoryFinder.class) {
// This block will only run once, and then foundFactory will
// be set and immutable. Currently there is no support in this
// class for context class loaders. If the contents of the
// xalan.properties file changes, or the class loader changes,
// this will *not* affect the cached class.
if (foundFactory == null) {
// Try to read from $java.home/lib/xalan.properties
Properties xalanProperties = null;
try {
String javah = ss.getSystemProperty("java.home");
String configFile = javah + File.separator +
"lib" + File.separator + "xalan.properties";
File f = new File(configFile);
FileInputStream fis = ss.getFileInputStream(f);
xalanProperties = new Properties();
xalanProperties.load(fis);
fis.close();
} catch (Exception x) {
// assert(x instanceof FileNotFoundException
// || x instanceof SecurityException)
// In both cases, ignore and continue w/ next location
}
if (xalanProperties != null) {
foundFactory = xalanProperties.getProperty(factoryId);
if (foundFactory != null) {
dPrint("found in xalan.properties, value=" + foundFactory);
}
} else {
// Try Jar Service Provider Mechanism
// (foundFactory gets set in findJarServiceProvider method)
findJarServiceProvider(factoryId);
if (foundFactory == null) {
if (fallbackClassName == null) {
throw new ConfigurationError(
"Provider for " + factoryId + " cannot be found", null);
}
dPrint("using fallback, value=" + fallbackClassName);
foundFactory = fallbackClassName;
}
}
}
}
return newInstance(foundFactory, cl, true);
}
private static void dPrint(String msg) {
if (debug) {
System.err.println("JAXP: " + msg);
}
}
/**
* Create an instance of a class using the specified ClassLoader and
* optionally fall back to the current ClassLoader if not found.
*
* @param className Name of the concrete class corresponding to the
* service provider
*
* @param cl ClassLoader to use to load the class, null means to use
* the bootstrap ClassLoader
*
* @param doFallback true if the current ClassLoader should be tried as
* a fallback if the class is not found using cl
*/
private static Object newInstance(String className, ClassLoader cl,
boolean doFallback)
throws ConfigurationError
{
// assert(className != null);
try {
Class providerClass;
if (cl == null) {
// XXX Use the bootstrap ClassLoader. There is no way to
// load a class using the bootstrap ClassLoader that works
// in both JDK 1.1 and Java 2. However, this should still
// work b/c the following should be true:
//
// (cl == null) iff current ClassLoader == null
//
// Thus Class.forName(String) will use the current
// ClassLoader which will be the bootstrap ClassLoader.
providerClass = Class.forName(className);
} else {
try {
providerClass = cl.loadClass(className);
} catch (ClassNotFoundException x) {
if (doFallback) {
// Fall back to current classloader
cl = FactoryFinder.class.getClassLoader();
providerClass = cl.loadClass(className);
} else {
throw x;
}
}
}
Object instance = providerClass.newInstance();
dPrint("created new instance of " + providerClass +
" using ClassLoader: " + cl);
return instance;
} catch (ClassNotFoundException x) {
throw new ConfigurationError(
"Provider " + className + " not found", x);
} catch (Exception x) {
throw new ConfigurationError(
"Provider " + className + " could not be instantiated: " + x,
x);
}
}
/*
* Try to find provider using Jar Service Provider Mechanism
*
* @return instance of provider class if found or null
*/
private static String findJarServiceProvider(String factoryId)
throws ConfigurationError
{
SecuritySupport ss = SecuritySupport.getInstance();
String serviceId = "META-INF/services/" + factoryId;
InputStream is = null;
// No support yet for context class loader
ClassLoader cl = FactoryFinder.class.getClassLoader();
is = ss.getResourceAsStream(cl, serviceId);
if (is == null) {
// No provider found
return null;
}
dPrint("found jar resource=" + serviceId +
" using ClassLoader: " + cl);
// Read the service provider name in UTF-8 as specified in
// the jar spec. Unfortunately this fails in Microsoft
// VJ++, which does not implement the UTF-8
// encoding. Theoretically, we should simply let it fail in
// that case, since the JVM is obviously broken if it
// doesn't support such a basic standard. But since there
// are still some users attempting to use VJ++ for
// development, we have dropped in a fallback which makes a
// second attempt using the platform's default encoding. In
// VJ++ this is apparently ASCII, which is a subset of
// UTF-8... and since the strings we'll be reading here are
// also primarily limited to the 7-bit ASCII range (at
// least, in English versions), this should work well
// enough to keep us on the air until we're ready to
// officially decommit from VJ++. [Edited comment from
// jkesselm]
BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (java.io.UnsupportedEncodingException e) {
rd = new BufferedReader(new InputStreamReader(is));
}
String factoryClassName = null;
try {
// XXX Does not handle all possible input as specified by the
// Jar Service Provider specification
factoryClassName = rd.readLine();
rd.close();
} catch (IOException x) {
// No provider found
return null;
}
if (factoryClassName != null &&
! "".equals(factoryClassName)) {
dPrint("found in resource, value="
+ factoryClassName);
// Note: here we do not want to fall back to the current
// ClassLoader because we want to avoid the case where the
// resource file was found using one ClassLoader and the
// provider class was instantiated using a different one.
return factoryClassName;
}
// No provider found
return null;
}
static class ConfigurationError extends Error {
private Exception exception;
/**
* Construct a new instance with the specified detail string and
* exception.
*/
ConfigurationError(String msg, Exception x) {
super(msg);
this.exception = x;
}
Exception getException() {
return exception;
}
}
}