diff options
Diffstat (limited to 'src/org/apache/commons/logging/impl')
-rw-r--r-- | src/org/apache/commons/logging/impl/Jdk14Logger.java | 303 | ||||
-rw-r--r-- | src/org/apache/commons/logging/impl/LogFactoryImpl.java | 1400 | ||||
-rw-r--r-- | src/org/apache/commons/logging/impl/NoOpLog.java | 106 | ||||
-rw-r--r-- | src/org/apache/commons/logging/impl/SimpleLog.java | 709 | ||||
-rw-r--r-- | src/org/apache/commons/logging/impl/WeakHashtable.java | 478 | ||||
-rw-r--r-- | src/org/apache/commons/logging/impl/package.html | 21 |
6 files changed, 3017 insertions, 0 deletions
diff --git a/src/org/apache/commons/logging/impl/Jdk14Logger.java b/src/org/apache/commons/logging/impl/Jdk14Logger.java new file mode 100644 index 0000000..d4f840c --- /dev/null +++ b/src/org/apache/commons/logging/impl/Jdk14Logger.java @@ -0,0 +1,303 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + + +import java.io.Serializable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.logging.Log; + + +/** + * <p>Implementation of the <code>org.apache.commons.logging.Log</code> + * interface that wraps the standard JDK logging mechanisms that were + * introduced in the Merlin release (JDK 1.4).</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a> + * @author <a href="mailto:donaldp@apache.org">Peter Donald</a> + * @version $Revision: 370652 $ $Date: 2006-01-19 22:23:48 +0000 (Thu, 19 Jan 2006) $ + */ + +public class Jdk14Logger implements Log, Serializable { + + /** + * This member variable simply ensures that any attempt to initialise + * this class in a pre-1.4 JVM will result in an ExceptionInInitializerError. + * It must not be private, as an optimising compiler could detect that it + * is not used and optimise it away. + */ + protected static final Level dummyLevel = Level.FINE; + + // ----------------------------------------------------------- Constructors + + + /** + * Construct a named instance of this Logger. + * + * @param name Name of the logger to be constructed + */ + public Jdk14Logger(String name) { + + this.name = name; + logger = getLogger(); + + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The underlying Logger implementation we are using. + */ + protected transient Logger logger = null; + + + /** + * The name of the logger we are wrapping. + */ + protected String name = null; + + + // --------------------------------------------------------- Public Methods + + private void log( Level level, String msg, Throwable ex ) { + + Logger logger = getLogger(); + if (logger.isLoggable(level)) { + // Hack (?) to get the stack trace. + Throwable dummyException=new Throwable(); + StackTraceElement locations[]=dummyException.getStackTrace(); + // Caller will be the third element + String cname="unknown"; + String method="unknown"; + if( locations!=null && locations.length >2 ) { + StackTraceElement caller=locations[2]; + cname=caller.getClassName(); + method=caller.getMethodName(); + } + if( ex==null ) { + logger.logp( level, cname, method, msg ); + } else { + logger.logp( level, cname, method, msg, ex ); + } + } + + } + + /** + * Logs a message with <code>java.util.logging.Level.FINE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + log(Level.FINE, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.FINE</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable exception) { + log(Level.FINE, String.valueOf(message), exception); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + log(Level.SEVERE, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.SEVERE</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable exception) { + log(Level.SEVERE, String.valueOf(message), exception); + } + + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(name); + } + return (logger); + } + + + /** + * Logs a message with <code>java.util.logging.Level.INFO</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + log(Level.INFO, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.INFO</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable exception) { + log(Level.INFO, String.valueOf(message), exception); + } + + + /** + * Is debug logging currently enabled? + */ + public boolean isDebugEnabled() { + return (getLogger().isLoggable(Level.FINE)); + } + + + /** + * Is error logging currently enabled? + */ + public boolean isErrorEnabled() { + return (getLogger().isLoggable(Level.SEVERE)); + } + + + /** + * Is fatal logging currently enabled? + */ + public boolean isFatalEnabled() { + return (getLogger().isLoggable(Level.SEVERE)); + } + + + /** + * Is info logging currently enabled? + */ + public boolean isInfoEnabled() { + return (getLogger().isLoggable(Level.INFO)); + } + + + /** + * Is trace logging currently enabled? + */ + public boolean isTraceEnabled() { + return (getLogger().isLoggable(Level.FINEST)); + } + + + /** + * Is warn logging currently enabled? + */ + public boolean isWarnEnabled() { + return (getLogger().isLoggable(Level.WARNING)); + } + + + /** + * Logs a message with <code>java.util.logging.Level.FINEST</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + log(Level.FINEST, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.FINEST</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable exception) { + log(Level.FINEST, String.valueOf(message), exception); + } + + + /** + * Logs a message with <code>java.util.logging.Level.WARNING</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + log(Level.WARNING, String.valueOf(message), null); + } + + + /** + * Logs a message with <code>java.util.logging.Level.WARNING</code>. + * + * @param message to log + * @param exception log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable exception) { + log(Level.WARNING, String.valueOf(message), exception); + } + + +} diff --git a/src/org/apache/commons/logging/impl/LogFactoryImpl.java b/src/org/apache/commons/logging/impl/LogFactoryImpl.java new file mode 100644 index 0000000..8937b2f --- /dev/null +++ b/src/org/apache/commons/logging/impl/LogFactoryImpl.java @@ -0,0 +1,1400 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.logging.impl; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +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; + + +/** + * <p>Concrete subclass of {@link LogFactory} that implements the + * following algorithm to dynamically select a logging implementation + * class to instantiate a wrapper for.</p> + * <ul> + * <li>Use a factory configuration attribute named + * <code>org.apache.commons.logging.Log</code> to identify the + * requested implementation class.</li> + * <li>Use the <code>org.apache.commons.logging.Log</code> system property + * to identify the requested implementation class.</li> + * <li>If <em>Log4J</em> is available, return an instance of + * <code>org.apache.commons.logging.impl.Log4JLogger</code>.</li> + * <li>If <em>JDK 1.4 or later</em> is available, return an instance of + * <code>org.apache.commons.logging.impl.Jdk14Logger</code>.</li> + * <li>Otherwise, return an instance of + * <code>org.apache.commons.logging.impl.SimpleLog</code>.</li> + * </ul> + * + * <p>If the selected {@link Log} implementation class has a + * <code>setLogFactory()</code> 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.</p> + * + * <p>This factory will remember previously created <code>Log</code> instances + * for the same name, and will return them on repeated requests to the + * <code>getInstance()</code> method.</p> + * + * @author Rod Waldhoff + * @author Craig R. McClanahan + * @author Richard A. Sitze + * @author Brian Stansberry + * @version $Revision: 399224 $ $Date: 2006-05-03 10:25:54 +0100 (Wed, 03 May 2006) $ + */ + +public class LogFactoryImpl extends LogFactory { + + + /** Log4JLogger class name */ + private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger"; + /** Jdk14Logger class name */ + private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger"; + /** Jdk13LumberjackLogger class name */ + private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger"; + /** SimpleLog class name */ + private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog"; + + private static final String PKG_IMPL="org.apache.commons.logging.impl."; + private static final int PKG_LEN = PKG_IMPL.length(); + + // ----------------------------------------------------------- Constructors + + + + /** + * Public no-arguments constructor required by the lookup mechanism. + */ + public LogFactoryImpl() { + super(); + initDiagnostics(); // method on this object + if (isDiagnosticsEnabled()) { + logDiagnostic("Instance created."); + } + } + + + // ----------------------------------------------------- Manifest Constants + + + /** + * The name (<code>org.apache.commons.logging.Log</code>) 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 + * old versions of JCL. + */ + protected static final String LOG_PROPERTY_OLD = + "org.apache.commons.logging.log"; + + /** + * The name (<code>org.apache.commons.logging.Log.allowFlawedContext</code>) + * of the system property which can be set true/false to + * determine system behaviour when a bad context-classloader is encountered. + * When set to false, a LogConfigurationException is thrown if + * LogFactoryImpl is loaded via a child classloader of the TCCL (this + * should never happen in sane systems). + * + * Default behaviour: true (tolerates bad context classloaders) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_CONTEXT_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedContext"; + + /** + * The name (<code>org.apache.commons.logging.Log.allowFlawedDiscovery</code>) + * of the system property which can be set true/false to + * determine system behaviour when a bad logging adapter class is + * encountered during logging discovery. When set to false, an + * exception will be thrown and the app will fail to start. When set + * to true, discovery will continue (though the user might end up + * with a different logging implementation than they expected). + * + * Default behaviour: true (tolerates bad logging adapters) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedDiscovery"; + + /** + * The name (<code>org.apache.commons.logging.Log.allowFlawedHierarchy</code>) + * of the system property which can be set true/false to + * determine system behaviour when a logging adapter class is + * encountered which has bound to the wrong Log class implementation. + * When set to false, an exception will be thrown and the app will fail + * to start. When set to true, discovery will continue (though the user + * might end up with a different logging implementation than they expected). + * + * Default behaviour: true (tolerates bad Log class hierarchy) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedHierarchy"; + + + /** + * The names of classes that will be tried (in order) as logging + * adapters. Each class is expected to implement the Log interface, + * and to throw NoClassDefFound or ExceptionInInitializerError when + * loaded if the underlying logging library is not available. Any + * other error indicates that the underlying logging library is available + * but broken/unusable for some reason. + */ + private static final String[] classesToDiscover = { + LOGGING_IMPL_LOG4J_LOGGER, + "org.apache.commons.logging.impl.Jdk14Logger", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger", + "org.apache.commons.logging.impl.SimpleLog" + }; + + + // ----------------------------------------------------- Instance Variables + + /** + * Determines whether logging classes should be loaded using the thread-context + * classloader, or via the classloader that loaded this LogFactoryImpl class. + */ + private boolean useTCCL = true; + + /** + * The string prefixed to every message output by the logDiagnostic method. + */ + private String diagnosticPrefix; + + + /** + * Configuration attributes. + */ + protected Hashtable attributes = new Hashtable(); + + + /** + * The {@link org.apache.commons.logging.Log} instances that have + * already been created, keyed by logger name. + */ + protected Hashtable instances = new Hashtable(); + + + /** + * Name of the class implementing the Log interface. + */ + private String logClassName; + + + /** + * The one-argument constructor of the + * {@link org.apache.commons.logging.Log} + * implementation class that will be used to create new instances. + * This value is initialized by <code>getLogConstructor()</code>, + * 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 <code>setLogFactory</code> method of the selected + * {@link org.apache.commons.logging.Log} method, if it exists. + */ + protected Method logMethod = null; + + + /** + * The signature of the <code>setLogFactory</code> method to be used. + */ + protected Class logMethodSignature[] = + { LogFactory.class }; + + /** + * See getBaseClassLoader and initConfiguration. + */ + private boolean allowFlawedContext; + + /** + * See handleFlawedDiscovery and initConfiguration. + */ + private boolean allowFlawedDiscovery; + + /** + * See handleFlawedHierarchy and initConfiguration. + */ + private boolean allowFlawedHierarchy; + + // --------------------------------------------------------- Public Methods + + + /** + * Return the configuration attribute with the specified name (if any), + * or <code>null</code> 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); + + } + + + /** + * Convenience method to derive a name from the specified class and + * call <code>getInstance(String)</code> with it. + * + * @param clazz Class for which a suitable Log name will be derived + * + * @exception LogConfigurationException if a suitable <code>Log</code> + * instance cannot be returned + */ + public Log getInstance(Class clazz) throws LogConfigurationException { + + return (getInstance(clazz.getName())); + + } + + + /** + * <p>Construct (if necessary) and return a <code>Log</code> instance, + * using the factory's current set of configuration attributes.</p> + * + * <p><strong>NOTE</strong> - Depending upon the implementation of + * the <code>LogFactory</code> you are using, the <code>Log</code> + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument.</p> + * + * @param name Logical name of the <code>Log</code> 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 <code>Log</code> + * 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); + + } + + + /** + * Release any internal references to previously created + * {@link org.apache.commons.logging.Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public void release() { + + logDiagnostic("Releasing all known loggers"); + instances.clear(); + } + + + /** + * 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 <code>null</code> value is equivalent to calling + * <code>removeAttribute(name)</code>. + * <p> + * This method can be used to set logging configuration programmatically + * rather than via system properties. It can also be used in code running + * within a container (such as a webapp) to configure behaviour on a + * per-component level instead of globally as system properties would do. + * To use this method instead of a system property, call + * <pre> + * LogFactory.getFactory().setAttribute(...) + * </pre> + * This must be done before the first Log object is created; configuration + * changes after that point will be ignored. + * <p> + * This method is also called automatically if LogFactory detects a + * commons-logging.properties file; every entry in that file is set + * automatically as an attribute here. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or <code>null</code> + * to remove any setting for this attribute + */ + public void setAttribute(String name, Object value) { + + if (logConstructor != null) { + logDiagnostic("setAttribute: call too late; configuration already performed."); + } + + if (value == null) { + attributes.remove(name); + } else { + attributes.put(name, value); + } + + if (name.equals(TCCL_KEY)) { + useTCCL = Boolean.valueOf(value.toString()).booleanValue(); + } + + } + + + // ------------------------------------------------------ + // Static Methods + // + // These methods only defined as workarounds for a java 1.2 bug; + // theoretically none of these are needed. + // ------------------------------------------------------ + + /** + * Gets the context classloader. + * This method is a workaround for a java 1.2 compiler bug. + * @since 1.1 + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return LogFactory.getContextClassLoader(); + } + + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.isDiagnosticsEnabled. + */ + protected static boolean isDiagnosticsEnabled() { + return LogFactory.isDiagnosticsEnabled(); + } + + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.getClassLoader. + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + return LogFactory.getClassLoader(clazz); + } + + + // ------------------------------------------------------ Protected Methods + + /** + * Calculate and cache a string that uniquely identifies this instance, + * including which classloader the object was loaded from. + * <p> + * This string will later be prefixed to each "internal logging" message + * emitted, so that users can clearly see any unexpected behaviour. + * <p> + * Note that this method does not detect whether internal logging is + * enabled or not, nor where to output stuff if it is; that is all + * handled by the parent LogFactory class. This method just computes + * its own unique prefix for log messages. + */ + private void initDiagnostics() { + // It would be nice to include an identifier of the context classloader + // that this LogFactoryImpl object is responsible for. However that + // isn't possible as that information isn't available. It is possible + // to figure this out by looking at the logging from LogFactory to + // see the context & impl ids from when this object was instantiated, + // in order to link the impl id output as this object's prefix back to + // the context it is intended to manage. + // Note that this prefix should be kept consistent with that + // in LogFactory. + Class clazz = this.getClass(); + ClassLoader classLoader = getClassLoader(clazz); + String classLoaderName; + try { + if (classLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch(SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] "; + } + + + /** + * Output a diagnostic message to a user-specified destination (if the + * user has enabled diagnostic logging). + * + * @param msg diagnostic message + * @since 1.1 + */ + protected void logDiagnostic(String msg) { + if (isDiagnosticsEnabled()) { + logRawDiagnostic(diagnosticPrefix + msg); + } + } + + /** + * Return the fully qualified Java classname of the {@link Log} + * implementation we will be using. + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected String getLogClassName() { + + if (logClassName == null) { + discoverLogImplementation(getClass().getName()); + } + + return logClassName; + } + + + /** + * <p>Return the <code>Constructor</code> that can be called to instantiate + * new {@link org.apache.commons.logging.Log} instances.</p> + * + * <p><strong>IMPLEMENTATION NOTE</strong> - Race conditions caused by + * calling this method from more than one thread are ignored, because + * the same <code>Constructor</code> instance will ultimately be derived + * in all circumstances.</p> + * + * @exception LogConfigurationException if a suitable constructor + * cannot be returned + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected Constructor getLogConstructor() + throws LogConfigurationException { + + // Return the previously identified Constructor (if any) + if (logConstructor == null) { + discoverLogImplementation(getClass().getName()); + } + + return logConstructor; + } + + + /** + * Is <em>JDK 1.3 with Lumberjack</em> logging available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk13LumberjackAvailable() { + return isLogLibraryAvailable( + "Jdk13Lumberjack", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); + } + + + /** + * <p>Return <code>true</code> if <em>JDK 1.4 or later</em> logging + * is available. Also checks that the <code>Throwable</code> class + * supports <code>getStackTrace()</code>, which is required by + * Jdk14Logger.</p> + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk14Available() { + return isLogLibraryAvailable( + "Jdk14", + "org.apache.commons.logging.impl.Jdk14Logger"); + } + + + /** + * Is a <em>Log4J</em> implementation available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isLog4JAvailable() { + return isLogLibraryAvailable( + "Log4J", + LOGGING_IMPL_LOG4J_LOGGER); + } + + + /** + * Create and return a new {@link org.apache.commons.logging.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 { + if (logConstructor == null) { + instance = discoverLogImplementation(name); + } + else { + Object params[] = { name }; + instance = (Log) logConstructor.newInstance(params); + } + + if (logMethod != null) { + Object params[] = { this }; + logMethod.invoke(instance, params); + } + + return (instance); + + } catch (LogConfigurationException lce) { + + // this type of exception means there was a problem in discovery + // and we've already output diagnostics about the issue, etc.; + // just pass it on + throw (LogConfigurationException) lce; + + } catch (InvocationTargetException e) { + // A problem occurred invoking the Constructor or Method + // previously discovered + Throwable c = e.getTargetException(); + if (c != null) { + throw new LogConfigurationException(c); + } else { + throw new LogConfigurationException(e); + } + } catch (Throwable t) { + // A problem occurred invoking the Constructor or Method + // previously discovered + throw new LogConfigurationException(t); + } + } + + + // ------------------------------------------------------ Private Methods + + /** + * Utility method to check whether a particular logging library is + * present and available for use. Note that this does <i>not</i> + * affect the future behaviour of this class. + */ + private boolean isLogLibraryAvailable(String name, String classname) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Checking for '" + name + "'."); + } + try { + Log log = createLogFromClass( + classname, + this.getClass().getName(), // dummy category + false); + + if (log == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Did not find '" + name + "'."); + } + return false; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("Found '" + name + "'."); + } + return true; + } + } catch(LogConfigurationException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Logging system '" + name + "' is available but not useable."); + } + return false; + } + } + + /** + * Attempt to find an attribute (see method setAttribute) or a + * system property with the provided name and return its value. + * <p> + * The attributes associated with this object are checked before + * system properties in case someone has explicitly called setAttribute, + * or a configuration property has been set in a commons-logging.properties + * file. + * + * @return the value associated with the property, or null. + */ + private String getConfigurationValue(String property) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Trying to get configuration for item " + property); + } + + Object valueObj = getAttribute(property); + if (valueObj != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property); + } + return valueObj.toString(); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No LogFactory attribute found for " + property); + } + + try { + String value = System.getProperty(property); + if (value != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found system property [" + value + "] for " + property); + } + return value; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No system property found for property " + property); + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Security prevented reading system property " + property); + } + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No configuration defined for item " + property); + } + + return null; + } + + /** + * Get the setting for the user-configurable behaviour specified by key. + * If nothing has explicitly been set, then return dflt. + */ + private boolean getBooleanConfiguration(String key, boolean dflt) { + String val = getConfigurationValue(key); + if (val == null) + return dflt; + return Boolean.valueOf(val).booleanValue(); + } + + /** + * Initialize a number of variables that control the behaviour of this + * class and that can be tweaked by the user. This is done when the first + * logger is created, not in the constructor of this class, because we + * need to give the user a chance to call method setAttribute in order to + * configure this object. + */ + private void initConfiguration() { + allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true); + allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true); + allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true); + } + + + /** + * Attempts to create a Log instance for the given category name. + * Follows the discovery process described in the class javadoc. + * + * @param logCategory the name of the log category + * + * @throws LogConfigurationException if an error in discovery occurs, + * or if no adapter at all can be instantiated + */ + private Log discoverLogImplementation(String logCategory) + throws LogConfigurationException + { + if (isDiagnosticsEnabled()) { + logDiagnostic("Discovering a Log implementation..."); + } + + initConfiguration(); + + Log result = null; + + // See if the user specified the Log implementation to use + String specifiedLogClassName = findUserSpecifiedLogClassName(); + + if (specifiedLogClassName != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to load user-specified log class '" + + specifiedLogClassName + "'..."); + } + + result = createLogFromClass(specifiedLogClassName, + logCategory, + true); + if (result == null) { + StringBuffer messageBuffer = new StringBuffer("User-specified log class '"); + messageBuffer.append(specifiedLogClassName); + messageBuffer.append("' cannot be found or is not useable."); + + // Mistyping or misspelling names is a common fault. + // Construct a good error message, if we can + if (specifiedLogClassName != null) { + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); + } + throw new LogConfigurationException(messageBuffer.toString()); + } + + return result; + } + + // No user specified log; try to discover what's on the classpath + // + // Note that we deliberately loop here over classesToDiscover and + // expect method createLogFromClass to loop over the possible source + // classloaders. The effect is: + // for each discoverable log adapter + // for each possible classloader + // see if it works + // + // It appears reasonable at first glance to do the opposite: + // for each possible classloader + // for each discoverable log adapter + // see if it works + // + // The latter certainly has advantages for user-installable logging + // libraries such as log4j; in a webapp for example this code should + // first check whether the user has provided any of the possible + // logging libraries before looking in the parent classloader. + // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, + // and SimpleLog will always work in any JVM. So the loop would never + // ever look for logging libraries in the parent classpath. Yet many + // users would expect that putting log4j there would cause it to be + // detected (and this is the historical JCL behaviour). So we go with + // the first approach. A user that has bundled a specific logging lib + // in a webapp should use a commons-logging.properties file or a + // service file in META-INF to force use of that logging lib anyway, + // rather than relying on discovery. + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "No user-specified Log implementation; performing discovery" + + " using the standard supported logging implementations..."); + } + for(int i=0; (i<classesToDiscover.length) && (result == null); ++i) { + result = createLogFromClass(classesToDiscover[i], logCategory, true); + } + + if (result == null) { + throw new LogConfigurationException + ("No suitable Log implementation"); + } + + return result; + } + + + /** + * Appends message if the given name is similar to the candidate. + * @param messageBuffer <code>StringBuffer</code> the message should be appended to, + * not null + * @param name the (trimmed) name to be test against the candidate, not null + * @param candidate the candidate name (not null) + */ + private void informUponSimilarName(final StringBuffer messageBuffer, final String name, + final String candidate) { + if (name.equals(candidate)) { + // Don't suggest a name that is exactly the same as the one the + // user tried... + return; + } + + // If the user provides a name that is in the right package, and gets + // the first 5 characters of the adapter class right (ignoring case), + // then suggest the candidate adapter class name. + if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { + messageBuffer.append(" Did you mean '"); + messageBuffer.append(candidate); + messageBuffer.append("'?"); + } + } + + + /** + * Checks system properties and the attribute map for + * a Log implementation specified by the user under the + * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. + * + * @return classname specified by the user, or <code>null</code> + */ + private String findUserSpecifiedLogClassName() + { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'"); + } + String specifiedClass = (String) getAttribute(LOG_PROPERTY); + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + + LOG_PROPERTY_OLD + "'"); + } + specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); + } + + if (specifiedClass == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY + "'"); + } + try { + specifiedClass = System.getProperty(LOG_PROPERTY); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY + "' - " + e.getMessage()); + } + } + } + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY_OLD + "'"); + } + try { + specifiedClass = System.getProperty(LOG_PROPERTY_OLD); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY_OLD + "' - " + e.getMessage()); + } + } + } + + // Remove any whitespace; it's never valid in a classname so its + // presence just means a user mistake. As we know what they meant, + // we may as well strip the spaces. + if (specifiedClass != null) { + specifiedClass = specifiedClass.trim(); + } + + return specifiedClass; + } + + + /** + * Attempts to load the given class, find a suitable constructor, + * and instantiate an instance of Log. + * + * @param logAdapterClassName classname of the Log implementation + * + * @param logCategory argument to pass to the Log implementation's + * constructor + * + * @param affectState <code>true</code> if this object's state should + * be affected by this method call, <code>false</code> otherwise. + * + * @return an instance of the given class, or null if the logging + * library associated with the specified adapter is not available. + * + * @throws LogConfigurationException if there was a serious error with + * configuration and the handleFlawedDiscovery method decided this + * problem was fatal. + */ + private Log createLogFromClass(String logAdapterClassName, + String logCategory, + boolean affectState) + throws LogConfigurationException { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'"); + } + + Object[] params = { logCategory }; + Log logAdapter = null; + Constructor constructor = null; + + Class logAdapterClass = null; + ClassLoader currentCL = getBaseClassLoader(); + + for(;;) { + // Loop through the classloader hierarchy trying to find + // a viable classloader. + logDiagnostic( + "Trying to load '" + + logAdapterClassName + + "' from classloader " + + objectId(currentCL)); + try { + if (isDiagnosticsEnabled()) { + // Show the location of the first occurrence of the .class file + // in the classpath. This is the location that ClassLoader.loadClass + // will load the class from -- unless the classloader is doing + // something weird. + URL url; + String resourceName = logAdapterClassName.replace('.', '/') + ".class"; + if (currentCL != null) { + url = currentCL.getResource(resourceName ); + } else { + url = ClassLoader.getSystemResource(resourceName + ".class"); + } + + if (url == null) { + logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found."); + } else { + logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'"); + } + } + + Class c = null; + try { + c = Class.forName(logAdapterClassName, true, currentCL); + } catch (ClassNotFoundException originalClassNotFoundException) { + // The current classloader was unable to find the log adapter + // in this or any ancestor classloader. There's no point in + // trying higher up in the hierarchy in this case.. + String msg = "" + originalClassNotFoundException.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is not available via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + try { + // Try the class classloader. + // This may work in cases where the TCCL + // does not contain the code executed or JCL. + // This behaviour indicates that the application + // classloading strategy is not consistent with the + // Java 1.2 classloading guidelines but JCL can + // and so should handle this case. + c = Class.forName(logAdapterClassName); + } catch (ClassNotFoundException secondaryClassNotFoundException) { + // no point continuing: this adapter isn't available + msg = "" + secondaryClassNotFoundException.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is not available via the LogFactoryImpl class classloader: " + + msg.trim()); + break; + } + } + + constructor = c.getConstructor(logConstructorSignature); + Object o = constructor.newInstance(params); + + // Note that we do this test after trying to create an instance + // [rather than testing Log.class.isAssignableFrom(c)] so that + // we don't complain about Log hierarchy problems when the + // adapter couldn't be instantiated anyway. + if (o instanceof Log) { + logAdapterClass = c; + logAdapter = (Log) o; + break; + } + + // Oops, we have a potential problem here. An adapter class + // has been found and its underlying lib is present too, but + // there are multiple Log interface classes available making it + // impossible to cast to the type the caller wanted. We + // certainly can't use this logger, but we need to know whether + // to keep on discovering or terminate now. + // + // The handleFlawedHierarchy method will throw + // LogConfigurationException if it regards this problem as + // fatal, and just return if not. + handleFlawedHierarchy(currentCL, c); + } catch (NoClassDefFoundError e) { + // We were able to load the adapter but it had references to + // other classes that could not be found. This simply means that + // the underlying logger library is not present in this or any + // ancestor classloader. There's no point in trying higher up + // in the hierarchy in this case.. + String msg = "" + e.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is missing dependencies when loaded via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + break; + } catch (ExceptionInInitializerError e) { + // A static initializer block or the initializer code associated + // with a static variable on the log adapter class has thrown + // an exception. + // + // We treat this as meaning the adapter's underlying logging + // library could not be found. + String msg = "" + e.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is unable to initialize itself when loaded via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + break; + } catch(LogConfigurationException e) { + // call to handleFlawedHierarchy above must have thrown + // a LogConfigurationException, so just throw it on + throw e; + } catch(Throwable t) { + // handleFlawedDiscovery will determine whether this is a fatal + // problem or not. If it is fatal, then a LogConfigurationException + // will be thrown. + handleFlawedDiscovery(logAdapterClassName, currentCL, t); + } + + if (currentCL == null) { + break; + } + + // try the parent classloader + currentCL = currentCL.getParent(); + } + + if ((logAdapter != null) && affectState) { + // We've succeeded, so set instance fields + this.logClassName = logAdapterClassName; + this.logConstructor = constructor; + + // Identify the <code>setLogFactory</code> method (if there is one) + try { + this.logMethod = logAdapterClass.getMethod("setLogFactory", + logMethodSignature); + logDiagnostic("Found method setLogFactory(LogFactory) in '" + + logAdapterClassName + "'"); + } catch (Throwable t) { + this.logMethod = null; + logDiagnostic( + "[INFO] '" + logAdapterClassName + + "' from classloader " + objectId(currentCL) + + " does not declare optional method " + + "setLogFactory(LogFactory)"); + } + + logDiagnostic( + "Log adapter '" + logAdapterClassName + + "' from classloader " + objectId(logAdapterClass.getClassLoader()) + + " has been selected for use."); + } + + return logAdapter; + } + + + /** + * Return the classloader from which we should try to load the logging + * adapter classes. + * <p> + * This method usually returns the context classloader. However if it + * is discovered that the classloader which loaded this class is a child + * of the context classloader <i>and</i> the allowFlawedContext option + * has been set then the classloader which loaded this class is returned + * instead. + * <p> + * The only time when the classloader which loaded this class is a + * descendant (rather than the same as or an ancestor of the context + * classloader) is when an app has created custom classloaders but + * failed to correctly set the context classloader. This is a bug in + * the calling application; however we provide the option for JCL to + * simply generate a warning rather than fail outright. + * + */ + private ClassLoader getBaseClassLoader() throws LogConfigurationException { + ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class); + + if (useTCCL == false) { + return thisClassLoader; + } + + ClassLoader contextClassLoader = getContextClassLoader(); + + ClassLoader baseClassLoader = getLowestClassLoader( + contextClassLoader, thisClassLoader); + + if (baseClassLoader == null) { + // The two classloaders are not part of a parent child relationship. + // In some classloading setups (e.g. JBoss with its + // UnifiedLoaderRepository) this can still work, so if user hasn't + // forbidden it, just return the contextClassLoader. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[WARNING] the context classloader is not part of a" + + " parent-child relationship with the classloader that" + + " loaded LogFactoryImpl."); + } + // If contextClassLoader were null, getLowestClassLoader() would + // have returned thisClassLoader. The fact we are here means + // contextClassLoader is not null, so we can just return it. + return contextClassLoader; + } + else { + throw new LogConfigurationException( + "Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + if (baseClassLoader != contextClassLoader) { + // We really should just use the contextClassLoader as the starting + // point for scanning for log adapter classes. However it is expected + // that there are a number of broken systems out there which create + // custom classloaders but fail to set the context classloader so + // we handle those flawed systems anyway. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Warning: the context classloader is an ancestor of the" + + " classloader that loaded LogFactoryImpl; it should be" + + " the same or a descendant. The application using" + + " commons-logging should ensure the context classloader" + + " is used correctly."); + } + } else { + throw new LogConfigurationException( + "Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + return baseClassLoader; + } + + /** + * Given two related classloaders, return the one which is a child of + * the other. + * <p> + * @param c1 is a classloader (including the null classloader) + * @param c2 is a classloader (including the null classloader) + * + * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor, + * and null if neither is an ancestor of the other. + */ + private ClassLoader getLowestClassLoader(ClassLoader c1, ClassLoader c2) { + // TODO: use AccessController when dealing with classloaders here + + if (c1 == null) + return c2; + + if (c2 == null) + return c1; + + ClassLoader current; + + // scan c1's ancestors to find c2 + current = c1; + while (current != null) { + if (current == c2) + return c1; + current = current.getParent(); + } + + // scan c2's ancestors to find c1 + current = c2; + while (current != null) { + if (current == c1) + return c2; + current = current.getParent(); + } + + return null; + } + + /** + * Generates an internal diagnostic logging of the discovery failure and + * then throws a <code>LogConfigurationException</code> that wraps + * the passed <code>Throwable</code>. + * + * @param logAdapterClassName is the class name of the Log implementation + * that could not be instantiated. Cannot be <code>null</code>. + * + * @param classLoader is the classloader that we were trying to load the + * logAdapterClassName from when the exception occurred. + * + * @param discoveryFlaw is the Throwable created by the classloader + * + * @throws LogConfigurationException ALWAYS + */ + private void handleFlawedDiscovery(String logAdapterClassName, + ClassLoader classLoader, + Throwable discoveryFlaw) { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Could not instantiate Log '" + + logAdapterClassName + "' -- " + + discoveryFlaw.getClass().getName() + ": " + + discoveryFlaw.getLocalizedMessage()); + } + + if (!allowFlawedDiscovery) { + throw new LogConfigurationException(discoveryFlaw); + } + } + + + /** + * Report a problem loading the log adapter, then either return + * (if the situation is considered recoverable) or throw a + * LogConfigurationException. + * <p> + * There are two possible reasons why we successfully loaded the + * specified log adapter class then failed to cast it to a Log object: + * <ol> + * <li>the specific class just doesn't implement the Log interface + * (user screwed up), or + * <li> the specified class has bound to a Log class loaded by some other + * classloader; Log@classloaderX cannot be cast to Log@classloaderY. + * </ol> + * <p> + * Here we try to figure out which case has occurred so we can give the + * user some reasonable feedback. + * + * @param badClassLoader is the classloader we loaded the problem class from, + * ie it is equivalent to badClass.getClassLoader(). + * + * @param badClass is a Class object with the desired name, but which + * does not implement Log correctly. + * + * @throws LogConfigurationException when the situation + * should not be recovered from. + */ + private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass) + throws LogConfigurationException { + + boolean implementsLog = false; + String logInterfaceName = Log.class.getName(); + Class interfaces[] = badClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (logInterfaceName.equals(interfaces[i].getName())) { + implementsLog = true; + break; + } + } + + if (implementsLog) { + // the class does implement an interface called Log, but + // it is in the wrong classloader + if (isDiagnosticsEnabled()) { + try { + ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); + logDiagnostic( + "Class '" + badClass.getName() + + "' was found in classloader " + + objectId(badClassLoader) + + ". It is bound to a Log interface which is not" + + " the one loaded from classloader " + + objectId(logInterfaceClassLoader)); + } catch (Throwable t) { + logDiagnostic( + "Error while trying to output diagnostics about" + + " bad class '" + badClass + "'"); + } + } + + if (!allowFlawedHierarchy) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context "); + msg.append("due to bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("Warning: bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + logDiagnostic(msg.toString()); + } + } else { + // this is just a bad adapter class + if (!allowFlawedDiscovery) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context. "); + msg.append("Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("[WARNING] Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + logDiagnostic(msg.toString()); + } + } + } +} diff --git a/src/org/apache/commons/logging/impl/NoOpLog.java b/src/org/apache/commons/logging/impl/NoOpLog.java new file mode 100644 index 0000000..b698813 --- /dev/null +++ b/src/org/apache/commons/logging/impl/NoOpLog.java @@ -0,0 +1,106 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + + +import java.io.Serializable; +import org.apache.commons.logging.Log; + + +/** + * <p>Trivial implementation of Log that throws away all messages. No + * configurable system properties are supported.</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author Rod Waldhoff + * @version $Id: NoOpLog.java 155426 2005-02-26 13:10:49Z dirkv $ + */ +public class NoOpLog implements Log, Serializable { + + /** Convenience constructor */ + public NoOpLog() { } + /** Base constructor */ + public NoOpLog(String name) { } + /** Do nothing */ + public void trace(Object message) { } + /** Do nothing */ + public void trace(Object message, Throwable t) { } + /** Do nothing */ + public void debug(Object message) { } + /** Do nothing */ + public void debug(Object message, Throwable t) { } + /** Do nothing */ + public void info(Object message) { } + /** Do nothing */ + public void info(Object message, Throwable t) { } + /** Do nothing */ + public void warn(Object message) { } + /** Do nothing */ + public void warn(Object message, Throwable t) { } + /** Do nothing */ + public void error(Object message) { } + /** Do nothing */ + public void error(Object message, Throwable t) { } + /** Do nothing */ + public void fatal(Object message) { } + /** Do nothing */ + public void fatal(Object message, Throwable t) { } + + /** + * Debug is never enabled. + * + * @return false + */ + public final boolean isDebugEnabled() { return false; } + + /** + * Error is never enabled. + * + * @return false + */ + public final boolean isErrorEnabled() { return false; } + + /** + * Fatal is never enabled. + * + * @return false + */ + public final boolean isFatalEnabled() { return false; } + + /** + * Info is never enabled. + * + * @return false + */ + public final boolean isInfoEnabled() { return false; } + + /** + * Trace is never enabled. + * + * @return false + */ + public final boolean isTraceEnabled() { return false; } + + /** + * Warn is never enabled. + * + * @return false + */ + public final boolean isWarnEnabled() { return false; } + +} diff --git a/src/org/apache/commons/logging/impl/SimpleLog.java b/src/org/apache/commons/logging/impl/SimpleLog.java new file mode 100644 index 0000000..6b643d3 --- /dev/null +++ b/src/org/apache/commons/logging/impl/SimpleLog.java @@ -0,0 +1,709 @@ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; + +/** + * <p>Simple implementation of Log that sends all enabled log messages, + * for all defined loggers, to System.err. The following system properties + * are supported to configure the behavior of this logger:</p> + * <ul> + * <li><code>org.apache.commons.logging.simplelog.defaultlog</code> - + * Default logging detail level for all instances of SimpleLog. + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, defaults to "info". </li> + * <li><code>org.apache.commons.logging.simplelog.log.xxxxx</code> - + * Logging detail level for a SimpleLog instance named "xxxxx". + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, the default logging detail level is used.</li> + * <li><code>org.apache.commons.logging.simplelog.showlogname</code> - + * Set to <code>true</code> if you want the Log instance name to be + * included in output messages. Defaults to <code>false</code>.</li> + * <li><code>org.apache.commons.logging.simplelog.showShortLogname</code> - + * Set to <code>true</code> if you want the last component of the name to be + * included in output messages. Defaults to <code>true</code>.</li> + * <li><code>org.apache.commons.logging.simplelog.showdatetime</code> - + * Set to <code>true</code> if you want the current date and time + * to be included in output messages. Default is <code>false</code>.</li> + * <li><code>org.apache.commons.logging.simplelog.dateTimeFormat</code> - + * The date and time format to be used in the output messages. + * The pattern describing the date and time format is the same that is + * used in <code>java.text.SimpleDateFormat</code>. If the format is not + * specified or is invalid, the default format is used. + * The default format is <code>yyyy/MM/dd HH:mm:ss:SSS zzz</code>.</li> + * </ul> + * + * <p>In addition to looking for system properties with the names specified + * above, this implementation also checks for a class loader resource named + * <code>"simplelog.properties"</code>, and includes any matching definitions + * from this resource (if it exists).</p> + * + * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * + * @version $Id: SimpleLog.java 399221 2006-05-03 09:20:24Z dennisl $ + */ +public class SimpleLog implements Log, Serializable { + + + // ------------------------------------------------------- Class Attributes + + /** All system properties used by <code>SimpleLog</code> start with this */ + static protected final String systemPrefix = + "org.apache.commons.logging.simplelog."; + + /** Properties loaded from simplelog.properties */ + static protected final Properties simpleLogProps = new Properties(); + + /** The default format to use when formating dates */ + static protected final String DEFAULT_DATE_TIME_FORMAT = + "yyyy/MM/dd HH:mm:ss:SSS zzz"; + + /** Include the instance name in the log message? */ + static protected boolean showLogName = false; + /** Include the short name ( last component ) of the logger in the log + * message. Defaults to true - otherwise we'll be lost in a flood of + * messages without knowing who sends them. + */ + static protected boolean showShortName = true; + /** Include the current time in the log message */ + static protected boolean showDateTime = false; + /** The date and time format to use in the log message */ + static protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + /** Used to format times */ + static protected DateFormat dateFormatter = null; + + // ---------------------------------------------------- Log Level Constants + + + /** "Trace" level logging. */ + public static final int LOG_LEVEL_TRACE = 1; + /** "Debug" level logging. */ + public static final int LOG_LEVEL_DEBUG = 2; + /** "Info" level logging. */ + public static final int LOG_LEVEL_INFO = 3; + /** "Warn" level logging. */ + public static final int LOG_LEVEL_WARN = 4; + /** "Error" level logging. */ + public static final int LOG_LEVEL_ERROR = 5; + /** "Fatal" level logging. */ + public static final int LOG_LEVEL_FATAL = 6; + + /** Enable all logging levels */ + public static final int LOG_LEVEL_ALL = (LOG_LEVEL_TRACE - 1); + + /** Enable no logging levels */ + public static final int LOG_LEVEL_OFF = (LOG_LEVEL_FATAL + 1); + + // ------------------------------------------------------------ Initializer + + private static String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + ; // Ignore + } + return (prop == null) ? simpleLogProps.getProperty(name) : prop; + } + + private static String getStringProperty(String name, String dephault) { + String prop = getStringProperty(name); + return (prop == null) ? dephault : prop; + } + + private static boolean getBooleanProperty(String name, boolean dephault) { + String prop = getStringProperty(name); + return (prop == null) ? dephault : "true".equalsIgnoreCase(prop); + } + + // Initialize class attributes. + // Load properties file, if found. + // Override with system properties. + static { + // Add props from the resource simplelog.properties + InputStream in = getResourceAsStream("simplelog.properties"); + if(null != in) { + try { + simpleLogProps.load(in); + in.close(); + } catch(java.io.IOException e) { + // ignored + } + } + + showLogName = getBooleanProperty( systemPrefix + "showlogname", showLogName); + showShortName = getBooleanProperty( systemPrefix + "showShortLogname", showShortName); + showDateTime = getBooleanProperty( systemPrefix + "showdatetime", showDateTime); + + if(showDateTime) { + dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", + dateTimeFormat); + try { + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } catch(IllegalArgumentException e) { + // If the format pattern is invalid - use the default format + dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } + } + } + + + // ------------------------------------------------------------- Attributes + + /** The name of this simple log instance */ + protected String logName = null; + /** The current log level */ + protected int currentLogLevel; + /** The short name of this simple log instance */ + private String shortLogName = null; + + + // ------------------------------------------------------------ Constructor + + /** + * Construct a simple log with given name. + * + * @param name log name + */ + public SimpleLog(String name) { + + logName = name; + + // Set initial log level + // Used to be: set default log level to ERROR + // IMHO it should be lower, but at least info ( costin ). + setLevel(SimpleLog.LOG_LEVEL_INFO); + + // Set log level from properties + String lvl = getStringProperty(systemPrefix + "log." + logName); + int i = String.valueOf(name).lastIndexOf("."); + while(null == lvl && i > -1) { + name = name.substring(0,i); + lvl = getStringProperty(systemPrefix + "log." + name); + i = String.valueOf(name).lastIndexOf("."); + } + + if(null == lvl) { + lvl = getStringProperty(systemPrefix + "defaultlog"); + } + + if("all".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ALL); + } else if("trace".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_TRACE); + } else if("debug".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_DEBUG); + } else if("info".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_INFO); + } else if("warn".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_WARN); + } else if("error".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ERROR); + } else if("fatal".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_FATAL); + } else if("off".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_OFF); + } + + } + + + // -------------------------------------------------------- Properties + + /** + * <p> Set logging level. </p> + * + * @param currentLogLevel new logging level + */ + public void setLevel(int currentLogLevel) { + + this.currentLogLevel = currentLogLevel; + + } + + + /** + * <p> Get logging level. </p> + */ + public int getLevel() { + + return currentLogLevel; + } + + + // -------------------------------------------------------- Logging Methods + + + /** + * <p> Do the actual logging. + * This method assembles the message + * and then calls <code>write()</code> to cause it to be written.</p> + * + * @param type One of the LOG_LEVEL_XXX constants defining the log level + * @param message The message itself (typically a String) + * @param t The exception whose stack trace should be logged + */ + protected void log(int type, Object message, Throwable t) { + // Use a string buffer for better performance + StringBuffer buf = new StringBuffer(); + + // Append date-time if so configured + if(showDateTime) { + buf.append(dateFormatter.format(new Date())); + buf.append(" "); + } + + // Append a readable representation of the log level + switch(type) { + case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break; + case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break; + case SimpleLog.LOG_LEVEL_INFO: buf.append("[INFO] "); break; + case SimpleLog.LOG_LEVEL_WARN: buf.append("[WARN] "); break; + case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break; + case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break; + } + + // Append the name of the log instance if so configured + if( showShortName) { + if( shortLogName==null ) { + // Cut all but the last component of the name for both styles + shortLogName = logName.substring(logName.lastIndexOf(".") + 1); + shortLogName = + shortLogName.substring(shortLogName.lastIndexOf("/") + 1); + } + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if(showLogName) { + buf.append(String.valueOf(logName)).append(" - "); + } + + // Append the message + buf.append(String.valueOf(message)); + + // Append stack trace if not null + if(t != null) { + buf.append(" <"); + buf.append(t.toString()); + buf.append(">"); + + java.io.StringWriter sw= new java.io.StringWriter(1024); + java.io.PrintWriter pw= new java.io.PrintWriter(sw); + t.printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } + + // Print to the appropriate destination + write(buf); + + } + + + /** + * <p>Write the content of the message accumulated in the specified + * <code>StringBuffer</code> to the appropriate output destination. The + * default implementation writes to <code>System.err</code>.</p> + * + * @param buffer A <code>StringBuffer</code> containing the accumulated + * text to be logged + */ + protected void write(StringBuffer buffer) { + + System.err.println(buffer.toString()); + + } + + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return (logLevel >= currentLogLevel); + } + + + // -------------------------------------------------------- Log Implementation + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public final void debug(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public final void debug(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public final void trace(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public final void trace(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public final void info(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO,message,null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public final void info(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public final void warn(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public final void warn(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, t); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public final void error(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public final void error(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, t); + } + } + + + /** + * Log a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL</code>. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public final void fatal(Object message) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, null); + } + } + + + /** + * Logs a message with + * <code>org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL</code>. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public final void fatal(Object message, Throwable t) { + + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, t); + } + } + + + /** + * <p> Are debug messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isDebugEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG); + } + + + /** + * <p> Are error messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isErrorEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR); + } + + + /** + * <p> Are fatal messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isFatalEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL); + } + + + /** + * <p> Are info messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isInfoEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO); + } + + + /** + * <p> Are trace messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isTraceEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE); + } + + + /** + * <p> Are warn messages currently enabled? </p> + * + * <p> This allows expensive operations such as <code>String</code> + * concatenation to be avoided when the message will be ignored by the + * logger. </p> + */ + public final boolean isWarnEnabled() { + + return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN); + } + + + /** + * Return the thread context class loader if available. + * Otherwise return null. + * + * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified. + */ + private static ClassLoader getContextClassLoader() + { + ClassLoader classLoader = null; + + if (classLoader == null) { + try { + // Are we running on a JDK 1.2 or later system? + Method method = Thread.class.getMethod("getContextClassLoader", + (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), + (Object[]) null); + + } catch (IllegalAccessException e) { + ; // ignore + } catch (InvocationTargetException e) { + /** + * InvocationTargetException is thrown by 'invoke' when + * the method being invoked (getContextClassLoader) throws + * an exception. + * + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * In the first case (not related), we want to ignore and + * keep going. We cannot help but also ignore the second + * with the logic below, but other calls elsewhere (to + * obtain a class loader) will trigger this exception where + * we can make a distinction. + */ + if (e.getTargetException() instanceof SecurityException) { + ; // ignore + } else { + // Capture 'e.getTargetException()' exception for details + // alternate: log 'e.getTargetException()', and pass back 'e'. + throw new LogConfigurationException + ("Unexpected InvocationTargetException", e.getTargetException()); + } + } + } catch (NoSuchMethodException e) { + // Assume we are running on JDK 1.1 + ; // ignore + } + } + + if (classLoader == null) { + classLoader = SimpleLog.class.getClassLoader(); + } + + // Return the selected class loader + return classLoader; + } + + private static InputStream getResourceAsStream(final String name) + { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + ClassLoader threadCL = getContextClassLoader(); + + if (threadCL != null) { + return threadCL.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } +} + diff --git a/src/org/apache/commons/logging/impl/WeakHashtable.java b/src/org/apache/commons/logging/impl/WeakHashtable.java new file mode 100644 index 0000000..e4749b6 --- /dev/null +++ b/src/org/apache/commons/logging/impl/WeakHashtable.java @@ -0,0 +1,478 @@ +/* + * Copyright 2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.commons.logging.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.*; + +/** + * <p>Implementation of <code>Hashtable</code> that uses <code>WeakReference</code>'s + * to hold its keys thus allowing them to be reclaimed by the garbage collector. + * The associated values are retained using strong references.</p> + * + * <p>This class follows the symantics of <code>Hashtable</code> as closely as + * possible. It therefore does not accept null values or keys.</p> + * + * <p><strong>Note:</strong> + * This is <em>not</em> intended to be a general purpose hash table replacement. + * This implementation is also tuned towards a particular purpose: for use as a replacement + * for <code>Hashtable</code> in <code>LogFactory</code>. This application requires + * good liveliness for <code>get</code> and <code>put</code>. Various tradeoffs + * have been made with this in mind. + * </p> + * <p> + * <strong>Usage:</strong> typical use case is as a drop-in replacement + * for the <code>Hashtable</code> used in <code>LogFactory</code> for J2EE enviroments + * running 1.3+ JVMs. Use of this class <i>in most cases</i> (see below) will + * allow classloaders to be collected by the garbage collector without the need + * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}. + * </p> + * + * <p><code>org.apache.commons.logging.LogFactory</code> checks whether this class + * can be supported by the current JVM, and if so then uses it to store + * references to the <code>LogFactory</code> implementationd it loads + * (rather than using a standard Hashtable instance). + * Having this class used instead of <code>Hashtable</code> solves + * certain issues related to dynamic reloading of applications in J2EE-style + * environments. However this class requires java 1.3 or later (due to its use + * of <code>java.lang.ref.WeakReference</code> and associates). + * And by the way, this extends <code>Hashtable</code> rather than <code>HashMap</code> + * for backwards compatibility reasons. See the documentation + * for method <code>LogFactory.createFactoryStore</code> for more details.</p> + * + * <p>The reason all this is necessary is due to a issue which + * arises during hot deploy in a J2EE-like containers. + * Each component running in the container owns one or more classloaders; when + * the component loads a LogFactory instance via the component classloader + * a reference to it gets stored in the static LogFactory.factories member, + * keyed by the component's classloader so different components don't + * stomp on each other. When the component is later unloaded, the container + * sets the component's classloader to null with the intent that all the + * component's classes get garbage-collected. However there's still a + * reference to the component's classloader from a key in the "global" + * <code>LogFactory</code>'s factories member! If <code>LogFactory.release()</code> + * is called whenever component is unloaded, the classloaders will be correctly + * garbage collected; this <i>should</i> be done by any container that + * bundles commons-logging by default. However, holding the classloader + * references weakly ensures that the classloader will be garbage collected + * without the container performing this step. </p> + * + * <p> + * <strong>Limitations:</strong> + * There is still one (unusual) scenario in which a component will not + * be correctly unloaded without an explicit release. Though weak references + * are used for its keys, it is necessary to use strong references for its values. + * </p> + * + * <p> If the abstract class <code>LogFactory</code> is + * loaded by the container classloader but a subclass of + * <code>LogFactory</code> [LogFactory1] is loaded by the component's + * classloader and an instance stored in the static map associated with the + * base LogFactory class, then there is a strong reference from the LogFactory + * class to the LogFactory1 instance (as normal) and a strong reference from + * the LogFactory1 instance to the component classloader via + * <code>getClass().getClassLoader()</code>. This chain of references will prevent + * collection of the child classloader.</p> + * + * <p> + * Such a situation occurs when the commons-logging.jar is + * loaded by a parent classloader (e.g. a server level classloader in a + * servlet container) and a custom <code>LogFactory</code> implementation is + * loaded by a child classloader (e.g. a web app classloader).</p> + * + * <p>To avoid this scenario, ensure + * that any custom LogFactory subclass is loaded by the same classloader as + * the base <code>LogFactory</code>. Creating custom LogFactory subclasses is, + * however, rare. The standard LogFactoryImpl class should be sufficient + * for most or all users.</p> + * + * + * @author Brian Stansberry + * + * @since 1.1 + */ +public final class WeakHashtable extends Hashtable { + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of all cleared entries. + */ + private static final int MAX_CHANGES_BEFORE_PURGE = 100; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of one cleared entry. + */ + private static final int PARTIAL_PURGE_COUNT = 10; + + /* ReferenceQueue we check for gc'd keys */ + private ReferenceQueue queue = new ReferenceQueue(); + /* Counter used to control how often we purge gc'd entries */ + private int changeCount = 0; + + /** + * Constructs a WeakHashtable with the Hashtable default + * capacity and load factor. + */ + public WeakHashtable() {} + + + /** + *@see Hashtable + */ + public boolean containsKey(Object key) { + // purge should not be required + Referenced referenced = new Referenced(key); + return super.containsKey(referenced); + } + + /** + *@see Hashtable + */ + public Enumeration elements() { + purge(); + return super.elements(); + } + + /** + *@see Hashtable + */ + public Set entrySet() { + purge(); + Set referencedEntries = super.entrySet(); + Set unreferencedEntries = new HashSet(); + for (Iterator it=referencedEntries.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Referenced referencedKey = (Referenced) entry.getKey(); + Object key = referencedKey.getValue(); + Object value = entry.getValue(); + if (key != null) { + Entry dereferencedEntry = new Entry(key, value); + unreferencedEntries.add(dereferencedEntry); + } + } + return unreferencedEntries; + } + + /** + *@see Hashtable + */ + public Object get(Object key) { + // for performance reasons, no purge + Referenced referenceKey = new Referenced(key); + return super.get(referenceKey); + } + + /** + *@see Hashtable + */ + public Enumeration keys() { + purge(); + final Enumeration enumer = super.keys(); + return new Enumeration() { + public boolean hasMoreElements() { + return enumer.hasMoreElements(); + } + public Object nextElement() { + Referenced nextReference = (Referenced) enumer.nextElement(); + return nextReference.getValue(); + } + }; + } + + + /** + *@see Hashtable + */ + public Set keySet() { + purge(); + Set referencedKeys = super.keySet(); + Set unreferencedKeys = new HashSet(); + for (Iterator it=referencedKeys.iterator(); it.hasNext();) { + Referenced referenceKey = (Referenced) it.next(); + Object keyValue = referenceKey.getValue(); + if (keyValue != null) { + unreferencedKeys.add(keyValue); + } + } + return unreferencedKeys; + } + + /** + *@see Hashtable + */ + public Object put(Object key, Object value) { + // check for nulls, ensuring symantics match superclass + if (key == null) { + throw new NullPointerException("Null keys are not allowed"); + } + if (value == null) { + throw new NullPointerException("Null values are not allowed"); + } + + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) { + purgeOne(); + } + + Object result = null; + Referenced keyRef = new Referenced(key, queue); + return super.put(keyRef, value); + } + + /** + *@see Hashtable + */ + public void putAll(Map t) { + if (t != null) { + Set entrySet = t.entrySet(); + for (Iterator it=entrySet.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + *@see Hashtable + */ + public Collection values() { + purge(); + return super.values(); + } + + /** + *@see Hashtable + */ + public Object remove(Object key) { + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) { + purgeOne(); + } + return super.remove(new Referenced(key)); + } + + /** + *@see Hashtable + */ + public boolean isEmpty() { + purge(); + return super.isEmpty(); + } + + /** + *@see Hashtable + */ + public int size() { + purge(); + return super.size(); + } + + /** + *@see Hashtable + */ + public String toString() { + purge(); + return super.toString(); + } + + /** + * @see Hashtable + */ + protected void rehash() { + // purge here to save the effort of rehashing dead entries + purge(); + super.rehash(); + } + + /** + * Purges all entries whose wrapped keys + * have been garbage collected. + */ + private void purge() { + synchronized (queue) { + WeakKey key; + while ((key = (WeakKey) queue.poll()) != null) { + super.remove(key.getReferenced()); + } + } + } + + /** + * Purges one entry whose wrapped key + * has been garbage collected. + */ + private void purgeOne() { + + synchronized (queue) { + WeakKey key = (WeakKey) queue.poll(); + if (key != null) { + super.remove(key.getReferenced()); + } + } + } + + /** Entry implementation */ + private final static class Entry implements Map.Entry { + + private final Object key; + private final Object value; + + private Entry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + boolean result = false; + if (o != null && o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + result = (getKey()==null ? + entry.getKey() == null : + getKey().equals(entry.getKey())) + && + (getValue()==null ? + entry.getValue() == null : + getValue().equals(entry.getValue())); + } + return result; + } + + public int hashCode() { + + return (getKey()==null ? 0 : getKey().hashCode()) ^ + (getValue()==null ? 0 : getValue().hashCode()); + } + + public Object setValue(Object value) { + throw new UnsupportedOperationException("Entry.setValue is not supported."); + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + } + + + /** Wrapper giving correct symantics for equals and hashcode */ + private final static class Referenced { + + private final WeakReference reference; + private final int hashCode; + + /** + * + * @throws NullPointerException if referant is <code>null</code> + */ + private Referenced(Object referant) { + reference = new WeakReference(referant); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = referant.hashCode(); + } + + /** + * + * @throws NullPointerException if key is <code>null</code> + */ + private Referenced(Object key, ReferenceQueue queue) { + reference = new WeakKey(key, queue, this); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = key.hashCode(); + + } + + public int hashCode() { + return hashCode; + } + + private Object getValue() { + return reference.get(); + } + + public boolean equals(Object o) { + boolean result = false; + if (o instanceof Referenced) { + Referenced otherKey = (Referenced) o; + Object thisKeyValue = getValue(); + Object otherKeyValue = otherKey.getValue(); + if (thisKeyValue == null) { + result = (otherKeyValue == null); + + // Since our hashcode was calculated from the original + // non-null referant, the above check breaks the + // hashcode/equals contract, as two cleared Referenced + // objects could test equal but have different hashcodes. + // We can reduce (not eliminate) the chance of this + // happening by comparing hashcodes. + if (result == true) { + result = (this.hashCode() == otherKey.hashCode()); + } + // In any case, as our c'tor does not allow null referants + // and Hashtable does not do equality checks between + // existing keys, normal hashtable operations should never + // result in an equals comparison between null referants + } + else + { + result = thisKeyValue.equals(otherKeyValue); + } + } + return result; + } + } + + /** + * WeakReference subclass that holds a hard reference to an + * associated <code>value</code> and also makes accessible + * the Referenced object holding it. + */ + private final static class WeakKey extends WeakReference { + + private final Referenced referenced; + + private WeakKey(Object key, + ReferenceQueue queue, + Referenced referenced) { + super(key, queue); + this.referenced = referenced; + } + + private Referenced getReferenced() { + return referenced; + } + } +} diff --git a/src/org/apache/commons/logging/impl/package.html b/src/org/apache/commons/logging/impl/package.html new file mode 100644 index 0000000..eb26b76 --- /dev/null +++ b/src/org/apache/commons/logging/impl/package.html @@ -0,0 +1,21 @@ +<!-- + + Copyright 2001-2004 The Apache Software Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + +<body> +<p>Concrete implementations of commons-logging wrapper APIs.</p> +</body> |