From 91d58c63675b4b030b6ffb7a940b44dc56fa6eb7 Mon Sep 17 00:00:00 2001 From: "Craig R. McClanahan" Date: Wed, 13 Feb 2002 02:18:11 +0000 Subject: [PATCH] Add a new factory base class (LogFactory) and corresponding implementation (LogFactoryImpl) that is based on the principles of the JAXP API's approach to discovering SAXParserFactory and DocumentBuilderFactory instances. It addresses the technical concerns that Costin brought up in response to the original Commons Logging 1.0 vote. The primary new features: * Applications can select their own LogFactory implementations, not just their own Log implementations. The default LogFactoryImpl uses the same algorithm currently included in LogSource. * The LogFactory implementation class can be specified by either a system property (org.apache.commons.logging.LogFactory), or by a corresponding property in a "commons-logging.properties" file found somewhere in the class path. * LogFactory implementations possess optional configuration attributes, which are preloaded from the "commons-logging.properties" file if it is found. These can be used by the factory to customize its own behavior as needed. * LogFactory and Log implementation classes are loaded from the thread context class loader (if it is set) in a JDK 1.2 or later environment. Hwoever, the entire API and default implementation should still work on a JDK 1.1 system. * A specialized exception (LogConfigurationException) is thrown for things like missing LogFactory or Log implementation clases. This class extends RuntimeException, so you normally don't have to put everything in try/catch blocks unless you care about dealing with this in a special way. For applications currently using the pre-release version of the API, this will typically mean replacing calls like this: Log log = LogSource.getInstance("foo"); with calls like this: Log log = LogFactory.newFactory().getInstance("foo"); unless you want to take advantage of the new capabilities. If this factory approach is accepted, I propose that we take the actions: * Deprecate LogSource (but leave it there for now, to assist existing applications in their transition) * Consider adding a setLogFactory() method to the Log interface -- and the existing implementation classes -- to give them easy access to the configuration attributes associated with the factory. * Add unit tests for the new code (it's not really been tested yet). * Propose the revised APIs as Commons-Logging 1.0 so that apps waiting for a final release can know what API to depend on. Follow-up technical discussions on this proposal should take place on COMMONS-DEV. (If you want to argue about who can vote for what, please start your own thread someplace else so we can get some work done :-). git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/logging/trunk@138856 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/commons/logging/LogFactory.java | 330 ++++++++++++++ .../commons/logging/impl/LogFactoryImpl.java | 424 ++++++++++++++++++ 2 files changed, 754 insertions(+) create mode 100644 src/java/org/apache/commons/logging/LogFactory.java create mode 100644 src/java/org/apache/commons/logging/impl/LogFactoryImpl.java diff --git a/src/java/org/apache/commons/logging/LogFactory.java b/src/java/org/apache/commons/logging/LogFactory.java new file mode 100644 index 0000000..badf32d --- /dev/null +++ b/src/java/org/apache/commons/logging/LogFactory.java @@ -0,0 +1,330 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//logging/src/java/org/apache/commons/logging/LogFactory.java,v 1.1 2002/02/13 02:18:11 craigmcc Exp $ + * $Revision: 1.1 $ + * $Date: 2002/02/13 02:18:11 $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2002 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 acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "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 names without prior written + * permission of the Apache Group. + * + * 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. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.logging; + + +import java.io.InputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Properties; + + +/** + *

Factory for creating {@link Log} instances, with discovery and + * configuration features similar to that employed by standard Java APIs + * such as JAXP.

+ * + *

IMPLEMENTATION NOTE - This implementation is heavily + * based on the SAXParserFactory and DocumentBuilderFactory implementations + * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.

+ * + * @author Craig R. McClanahan + * @author Costin Manolache + * @version $Revision: 1.1 $ $Date: 2002/02/13 02:18:11 $ + */ + +public abstract class LogFactory { + + + // ----------------------------------------------------- Manifest Constants + + + /** + * The fully qualified class name of the fallback LogFactory + * implementation class to use, if no other can be found. + */ + protected static final String FACTORY_DEFAULT = + "org.apache.commons.logging.impl.LogFactoryImpl"; + + + /** + * The name of the properties file to search for. + */ + protected static final String FACTORY_PROPERTIES = + "commons-logging.properties"; + + + /** + * The name of the property used to identify the LogFactory implementation + * class name. + */ + public static final String FACTORY_PROPERTY = + "org.apache.commons.logging.LogFactory"; + + + // ----------------------------------------------------------- Constructors + + + /** + * Protected constructor that is not available for public use. + */ + protected LogFactory() { } + + + // --------------------------------------------------------- Public Methods + + + /** + * Return the configuration attribute with the specified name (if any), + * or null if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public abstract Object getAttribute(String name); + + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public abstract String[] getAttributeNames(); + + + /** + *

Construct (if necessary) and return a Log instance, + * using the factory's current set of configuration attributes.

+ * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public abstract Log getInstance(String name) + throws LogConfigurationException; + + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public abstract void removeAttribute(String name); + + + /** + * Set the configuration attribute with the specified name. Calling + * this with a null value is equivalent to calling + * removeAttribute(name). + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or null + * to remove any setting for this attribute + */ + public abstract void setAttribute(String name, Object value); + + + // --------------------------------------------------------- Static Methods + + + /** + *

Construct and return a new LogFactory instance, using + * the following ordered lookup procedure to determine the name of the + * implementation class to be loaded:

+ * + * + *

NOTE - If the properties file method of identifying the + * LogFactory implementation class is utilized, all of the + * properties defined in this file will be set as configuration attributes + * on the corresponding LogFactory instance.

+ * + * @exception LogConfigurationException if the implementation class is not + * available or cannot be instantiated. + */ + public static LogFactory newFactory() throws LogConfigurationException { + + // Identify the class loader we will be using + ClassLoader classLoader = findClassLoader(); + + // First, try the system property + try { + String factoryClass = System.getProperty(FACTORY_PROPERTY); + if (factoryClass != null) { + return (newInstance(factoryClass, classLoader)); + } + } catch (SecurityException e) { + ; + } + + // Second, try a properties file + try { + InputStream stream = + classLoader.getResourceAsStream(FACTORY_PROPERTIES); + if (stream != null) { + Properties props = new Properties(); + props.load(stream); + stream.close(); + String factoryClass = props.getProperty(FACTORY_PROPERTY); + if (factoryClass != null) { + LogFactory instance = + newInstance(factoryClass, classLoader); + Enumeration names = props.propertyNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + String value = props.getProperty(name); + instance.setAttribute(name, value); + } + return (instance); + } + } + } catch (IOException e) { + } catch (SecurityException e) { + } + + // Third, try the fallback implementation class + return (newInstance(FACTORY_DEFAULT, classLoader)); + + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * Return the class loader to be used for loading the selected + * LogFactory implementation class. On a JDK 1.2 or later + * system, the context class loader for the current thread will be used + * if it is present. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified + */ + protected static ClassLoader findClassLoader() + throws LogConfigurationException { + + // Are we running on a JDK 1.2 or later system? + Method method = null; + try { + method = Thread.class.getMethod("getContextClassLoader", null); + } catch (NoSuchMethodException e) { + // Assume we are running on JDK 1.1 + return (LogFactory.class.getClassLoader()); + } + + // Get the thread context class loader (if there is one) + ClassLoader classLoader = null; + try { + classLoader = (ClassLoader) + method.invoke(Thread.currentThread(), null); + if (classLoader == null) { + classLoader = LogFactory.class.getClassLoader(); + } + } catch (IllegalAccessException e) { + throw new LogConfigurationException + ("Unexpected IllegalAccessException", e); + } catch (InvocationTargetException e) { + throw new LogConfigurationException + ("Unexpected InvocationTargetException", e); + } + + // Return the selected class loader + return (classLoader); + + } + + + /** + * Return a new instance of the specified LogFactory + * implementation class, loaded by the specified class loader. + * + * @param factoryClass Fully qualified name of the LogFactory + * implementation class + * @param classLoader ClassLoader from which to load this class + * + * @exception LogConfigurationException if a suitable instance + * cannot be created + */ + private static LogFactory newInstance(String factoryClass, + ClassLoader classLoader) + throws LogConfigurationException { + + try { + Class clazz = null; + if (classLoader == null) { + clazz = Class.forName(factoryClass); + } else { + clazz = classLoader.loadClass(factoryClass); + } + return ((LogFactory) clazz.newInstance()); + } catch (Exception e) { + throw new LogConfigurationException(e); + } + + } + + +} diff --git a/src/java/org/apache/commons/logging/impl/LogFactoryImpl.java b/src/java/org/apache/commons/logging/impl/LogFactoryImpl.java new file mode 100644 index 0000000..b32036f --- /dev/null +++ b/src/java/org/apache/commons/logging/impl/LogFactoryImpl.java @@ -0,0 +1,424 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//logging/src/java/org/apache/commons/logging/impl/LogFactoryImpl.java,v 1.1 2002/02/13 02:18:11 craigmcc Exp $ + * $Revision: 1.1 $ + * $Date: 2002/02/13 02:18:11 $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2002 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 acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "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 names without prior written + * permission of the Apache Group. + * + * 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. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.logging.impl; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.logging.LogSource; + + +/** + *

Concrete subclass of {@link LogFactory} that implements the + * following algorithm to dynamically select a logging implementation + * class to instantiate a wrapper for:

+ *
    + *
  • Use the org.apache.commons.logging.Log system property + * to identify the requested implementation class.
  • + *
  • If Log4J is available, return an instance of + * org.apache.commons.logging.impl.Log4JCategoryLog.
  • + *
  • If JDK 1.4 or later is available, return an instance of + * org.apache.commons.logging.impl.Jdk14Logger.
  • + *
  • Otherwise, return an instance of + * org.apache.commons.logging.impl.NoOpLog.
  • + *
+ * + *

If the selected {@link Log} implementation class has a + * setLogFactory() method that accepts a {@link LogFactory} + * parameter, this method will be called on each newly created instance + * to identify the associated factory. This makes factory configuration + * attributes available to the Log instance, if it so desires.

+ * + *

This factory will remember previously created Log instances + * for the same name, and will return them on repeated requests to the + * getInstance() method. This implementation ignores any + * configured attributes.

+ * + * @author Rod Waldhoff + * @author Craig R. McClanahan + * @version $Revision: 1.1 $ $Date: 2002/02/13 02:18:11 $ + */ + +public class LogFactoryImpl extends LogFactory { + + + // ----------------------------------------------------------- Constructors + + + /** + * Public no-arguments constructor required by the lookup mechanism. + */ + public LogFactoryImpl() { + + super(); + + } + + + // ----------------------------------------------------- Manifest Constants + + + /** + * The fully qualified name of the default {@link Log} implementation. + */ + public static final String LOG_DEFAULT = + "org.apache.commons.logging.NoOpLog"; + + + /** + * The name of the system property identifying our {@link Log} + * implementation class. + */ + public static final String LOG_PROPERTY = + "org.apache.commons.logging.Log"; + + + /** + * The deprecated system property used for backwards compatibility with + * the old {@link LogSource} class. + */ + protected static final String LOG_PROPERTY_OLD = + "org.apache.commons.logging.log"; + + + // ----------------------------------------------------- Instance Variables + + + /** + * The configuration attributes for this {@link LogFactory}. + */ + protected Hashtable attributes = new Hashtable(); + + + /** + * The {@link Log} instances that have already been created, keyed by + * logger name. + */ + protected Hashtable instances = new Hashtable(); + + + /** + * The one-argument constructor of the {@link Log} implementation class + * that will be used to create new instances. This value is initialized + * by getLogConstructor(), and then returned repeatedly. + */ + protected Constructor logConstructor = null; + + + /** + * The signature of the Constructor to be used. + */ + protected Class logConstructorSignature[] = + { java.lang.String.class }; + + + /** + * The one-argument setLogFactory method of the selected + * {@link Log} method, if it exists. + */ + protected Method logMethod = null; + + + /** + * The signature of the setLogFactory method to be used. + */ + protected Class logMethodSignature[] = + { LogFactory.class }; + + + // --------------------------------------------------------- Public Methods + + + /** + * Return the configuration attribute with the specified name (if any), + * or null if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public Object getAttribute(String name) { + + return (attributes.get(name)); + + } + + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public String[] getAttributeNames() { + + Vector names = new Vector(); + Enumeration keys = attributes.keys(); + while (keys.hasMoreElements()) { + names.addElement((String) keys.nextElement()); + } + String results[] = new String[names.size()]; + for (int i = 0; i < results.length; i++) { + results[i] = (String) names.elementAt(i); + } + return (results); + + } + + + /** + *

Construct (if necessary) and return a Log instance, + * using the factory's current set of configuration attributes.

+ * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public Log getInstance(String name) + throws LogConfigurationException { + + Log instance = (Log) instances.get(name); + if (instance != null) { + instance = newInstance(name); + instances.put(name, instance); + } + return (instance); + + } + + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public void removeAttribute(String name) { + + attributes.remove(name); + + } + + + /** + * Set the configuration attribute with the specified name. Calling + * this with a null value is equivalent to calling + * removeAttribute(name). + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or null + * to remove any setting for this attribute + */ + public void setAttribute(String name, Object value) { + + if (value == null) { + attributes.remove(name); + } else { + attributes.put(name, value); + } + + } + + + // ------------------------------------------------------ Protected Methods + + + /** + *

Return the Constructor that can be called to instantiate + * new {@link Log} instances.

+ * + *

IMPLEMENTATION NOTE - Race conditions caused by + * calling this method from more than one thread are ignored, because + * the same Constructor instance will ultimately be derived + * in all circumstances.

+ * + * @exception LogConfigurationException if a suitable constructor + * cannot be returned + */ + protected Constructor getLogConstructor() + throws LogConfigurationException { + + // Return the previously identified Constructor (if any) + if (logConstructor != null) { + return (logConstructor); + } + + // Identify the Log implementation class we will be using + String logClassName = null; + try { + logClassName = System.getProperty(LOG_PROPERTY); + if (logClassName == null) { // @deprecated + logClassName = System.getProperty(LOG_PROPERTY_OLD); + } + if ((logClassName == null) && isLog4JAvailable()) { + logClassName = + "org.apache.commons.logging.impl.Log4JCategoryLog"; + } + if ((logClassName == null) && isJdk14Available()) { + logClassName = + "org.apache.commons.logging.impl.Jdk14Logger"; + } + if (logClassName == null) { + logClassName = + "org.apache.commons.logging.impl.NoOpLog"; + } + } catch (SecurityException e) { + } + + // Attempt to load the Log implementation class + Class logClass = null; + try { + logClass = findClassLoader().loadClass(logClassName); + if (!Log.class.isAssignableFrom(logClass)) { + throw new LogConfigurationException + ("Class " + logClassName + " does not implement Log"); + } + } catch (Throwable t) { + throw new LogConfigurationException(t); + } + + // Identify the setLogFactory method (if there is one) + try { + logMethod = logClass.getMethod("setLogFactory", + logMethodSignature); + } catch (Throwable t) { + logMethod = null; + } + + // Identify the corresponding constructor to be used + try { + logConstructor = logClass.getConstructor(logConstructorSignature); + return (logConstructor); + } catch (Throwable t) { + throw new LogConfigurationException + ("No suitable Log constructor", t); + } + + } + + + /** + * Is JDK 1.4 or later logging available? + */ + protected boolean isJdk14Available() { + + try { + findClassLoader().loadClass("java.util.logging.Logger"); + return (true); + } catch (Throwable t) { + return (false); + } + + } + + + /** + * Is a Log4J implementation available? + */ + protected boolean isLog4JAvailable() { + + try { + findClassLoader().loadClass("org.apache.log4j.Category"); + return (true); + } catch (Throwable t) { + return (false); + } + + } + + + /** + * Create and return a new {@link Log} instance for the specified name. + * + * @param name Name of the new logger + * + * @exception LogConfigurationException if a new instance cannot + * be created + */ + protected Log newInstance(String name) + throws LogConfigurationException { + + Log instance = null; + + try { + Object params[] = new Object[1]; + params[0] = name; + instance = (Log) getLogConstructor().newInstance(params); + if (logMethod != null) { + params[0] = this; + logMethod.invoke(instance, params); + } + return (instance); + } catch (Throwable t) { + throw new LogConfigurationException(t); + } + + } + + +}