commit e8a71d016ca4ddfce35879572016de7fb706c1ad
Author: zhouganqing There is a great need for debugging and logging information inside of
+Commons components such as HTTPClient and dbcp. However, there are many
+logging APIs out there and it is difficult to choose among them.
+ The Logging package will be an ultra-thin bridge between different logging
+libraries. Commons components may use the Logging JAR to remove
+compile-time/runtime dependencies on any particular logging package,
+and contributors may write Log implementations for the library of their choice.
+ The package shall create and maintain a package that provides extremely
+basic logging functionality and bridges to other, more sophisticated logging
+implementations.
+
+The package should :
+Proposal for Logging Package
+(0) Rationale
+
+(1) Scope of the Package
+
+
+
+
+Non-goals: +
Logging relies on: +
+ +logging
in the
+jakarta-commons
CVS repository.The initial committers on the Logging component shall be:
+ +
+ * The six logging levels used by Log
are (in order):
+ *
+ * Performance is often a logging concern. + * By examining the appropriate property, + * a component can avoid expensive operations (producing information + * to be logged). + *
+ * For example, + *
+ * if (log.isDebugEnabled()) { + * ... do something expensive ... + * log.debug(theResult); + * } + *+ *
+ * Configuration of the underlying logging system will generally be done + * external to the Logging APIs, through whatever mechanism is supported by + * that system. + * + * @version $Id: Log.java 1606045 2014-06-27 12:11:56Z tn $ + */ +public interface Log { + + /** + * Logs a message with debug log level. + * + * @param message log this message + */ + void debug(Object message); + + /** + * Logs an error with debug log level. + * + * @param message log this message + * @param t log this cause + */ + void debug(Object message, Throwable t); + + /** + * Logs a message with error log level. + * + * @param message log this message + */ + void error(Object message); + + /** + * Logs an error with error log level. + * + * @param message log this message + * @param t log this cause + */ + void error(Object message, Throwable t); + + /** + * Logs a message with fatal log level. + * + * @param message log this message + */ + void fatal(Object message); + + /** + * Logs an error with fatal log level. + * + * @param message log this message + * @param t log this cause + */ + void fatal(Object message, Throwable t); + + /** + * Logs a message with info log level. + * + * @param message log this message + */ + void info(Object message); + + /** + * Logs an error with info log level. + * + * @param message log this message + * @param t log this cause + */ + void info(Object message, Throwable t); + + /** + * Is debug logging currently enabled? + *
+ * Call this method to prevent having to perform expensive operations
+ * (for example, String
concatenation)
+ * when the log level is more than debug.
+ *
+ * @return true if debug is enabled in the underlying logger.
+ */
+ boolean isDebugEnabled();
+
+ /**
+ * Is error logging currently enabled?
+ *
+ * Call this method to prevent having to perform expensive operations
+ * (for example, String
concatenation)
+ * when the log level is more than error.
+ *
+ * @return true if error is enabled in the underlying logger.
+ */
+ boolean isErrorEnabled();
+
+ /**
+ * Is fatal logging currently enabled?
+ *
+ * Call this method to prevent having to perform expensive operations
+ * (for example, String
concatenation)
+ * when the log level is more than fatal.
+ *
+ * @return true if fatal is enabled in the underlying logger.
+ */
+ boolean isFatalEnabled();
+
+ /**
+ * Is info logging currently enabled?
+ *
+ * Call this method to prevent having to perform expensive operations
+ * (for example, String
concatenation)
+ * when the log level is more than info.
+ *
+ * @return true if info is enabled in the underlying logger.
+ */
+ boolean isInfoEnabled();
+
+ /**
+ * Is trace logging currently enabled?
+ *
+ * Call this method to prevent having to perform expensive operations
+ * (for example, String
concatenation)
+ * when the log level is more than trace.
+ *
+ * @return true if trace is enabled in the underlying logger.
+ */
+ boolean isTraceEnabled();
+
+ /**
+ * Is warn logging currently enabled?
+ *
+ * Call this method to prevent having to perform expensive operations
+ * (for example, String
concatenation)
+ * when the log level is more than warn.
+ *
+ * @return true if warn is enabled in the underlying logger.
+ */
+ boolean isWarnEnabled();
+
+ /**
+ * Logs a message with trace log level.
+ *
+ * @param message log this message
+ */
+ void trace(Object message);
+
+ /**
+ * Logs an error with trace log level.
+ *
+ * @param message log this message
+ * @param t log this cause
+ */
+ void trace(Object message, Throwable t);
+
+ /**
+ * Logs a message with warn log level.
+ *
+ * @param message log this message
+ */
+ void warn(Object message);
+
+ /**
+ * Logs an error with warn log level.
+ *
+ * @param message log this message
+ * @param t log this cause
+ */
+ void warn(Object message, Throwable t);
+}
diff --git a/src/main/java/org/apache/commons/logging/LogConfigurationException.java b/src/main/java/org/apache/commons/logging/LogConfigurationException.java
new file mode 100644
index 0000000..e612e9b
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/LogConfigurationException.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+/**
+ * An exception that is thrown only if a suitable LogFactory
+ * or Log
instance cannot be created by the corresponding
+ * factory methods.
+ *
+ * @version $Id: LogConfigurationException.java 1432663 2013-01-13 17:24:18Z tn $
+ */
+public class LogConfigurationException extends RuntimeException {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = 8486587136871052495L;
+
+ /**
+ * Construct a new exception with null
as its detail message.
+ */
+ public LogConfigurationException() {
+ super();
+ }
+
+ /**
+ * Construct a new exception with the specified detail message.
+ *
+ * @param message The detail message
+ */
+ public LogConfigurationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Construct a new exception with the specified cause and a derived
+ * detail message.
+ *
+ * @param cause The underlying cause
+ */
+ public LogConfigurationException(Throwable cause) {
+ this(cause == null ? null : cause.toString(), cause);
+ }
+
+ /**
+ * Construct a new exception with the specified detail message and cause.
+ *
+ * @param message The detail message
+ * @param cause The underlying cause
+ */
+ public LogConfigurationException(String message, Throwable cause) {
+ super(message + " (Caused by " + cause + ")");
+ this.cause = cause; // Two-argument version requires JDK 1.4 or later
+ }
+
+ /**
+ * The underlying cause of this exception.
+ */
+ protected Throwable cause = null;
+
+ /**
+ * Return the underlying cause of this exception (if any).
+ */
+ public Throwable getCause() {
+ return this.cause;
+ }
+}
diff --git a/src/main/java/org/apache/commons/logging/LogFactory.java b/src/main/java/org/apache/commons/logging/LogFactory.java
new file mode 100644
index 0000000..705cb96
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/LogFactory.java
@@ -0,0 +1,1703 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+import java.io.BufferedReader;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+
+/**
+ * Factory for creating {@link Log} instances, with discovery and
+ * configuration features similar to that employed by standard Java APIs
+ * such as JAXP.
+ *
+ * IMPLEMENTATION NOTE - This implementation is heavily
+ * based on the SAXParserFactory and DocumentBuilderFactory implementations
+ * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.
+ *
+ * @version $Id: LogFactory.java 1606041 2014-06-27 11:56:59Z tn $
+ */
+public abstract class LogFactory {
+ // Implementation note re AccessController usage
+ //
+ // It is important to keep code invoked via an AccessController to small
+ // auditable blocks. Such code must carefully evaluate all user input
+ // (parameters, system properties, config file contents, etc). As an
+ // example, a Log implementation should not write to its logfile
+ // with an AccessController anywhere in the call stack, otherwise an
+ // insecure application could configure the log implementation to write
+ // to a protected file using the privileges granted to JCL rather than
+ // to the calling application.
+ //
+ // Under no circumstance should a non-private method return data that is
+ // retrieved via an AccessController. That would allow an insecure app
+ // to invoke that method and obtain data that it is not permitted to have.
+ //
+ // Invoking user-supplied code with an AccessController set is not a major
+ // issue (eg invoking the constructor of the class specified by
+ // HASHTABLE_IMPLEMENTATION_PROPERTY). That class will be in a different
+ // trust domain, and therefore must have permissions to do whatever it
+ // is trying to do regardless of the permissions granted to JCL. There is
+ // a slight issue in that untrusted code may point that environment var
+ // to another trusted library, in which case the code runs if both that
+ // lib and JCL have the necessary permissions even when the untrusted
+ // caller does not. That's a pretty hard route to exploit though.
+
+ // ----------------------------------------------------- Manifest Constants
+
+ /**
+ * The name (priority
) of the key in the config file used to
+ * specify the priority of that particular config file. The associated value
+ * is a floating-point number; higher values take priority over lower values.
+ */
+ public static final String PRIORITY_KEY = "priority";
+
+ /**
+ * The name (use_tccl
) of the key in the config file used
+ * to specify whether logging classes should be loaded via the thread
+ * context class loader (TCCL), or not. By default, the TCCL is used.
+ */
+ public static final String TCCL_KEY = "use_tccl";
+
+ /**
+ * The name (org.apache.commons.logging.LogFactory
) of the property
+ * used to identify the LogFactory implementation
+ * class name. This can be used as a system property, or as an entry in a
+ * configuration properties file.
+ */
+ public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
+
+ /**
+ * The fully qualified class name of the fallback LogFactory
+ * implementation class to use, if no other can be found.
+ */
+ public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl";
+
+ /**
+ * The name (commons-logging.properties
) of the properties file to search for.
+ */
+ public static final String FACTORY_PROPERTIES = "commons-logging.properties";
+
+ /**
+ * JDK1.3+
+ * 'Service Provider' specification.
+ */
+ protected static final String SERVICE_ID =
+ "META-INF/services/org.apache.commons.logging.LogFactory";
+
+ /**
+ * The name (org.apache.commons.logging.diagnostics.dest
)
+ * of the property used to enable internal commons-logging
+ * diagnostic output, in order to get information on what logging
+ * implementations are being discovered, what classloaders they
+ * are loaded through, etc.
+ *
+ * If a system property of this name is set then the value is + * assumed to be the name of a file. The special strings + * STDOUT or STDERR (case-sensitive) indicate output to + * System.out and System.err respectively. + *
+ * Diagnostic logging should be used only to debug problematic
+ * configurations and should not be set in normal production use.
+ */
+ public static final String DIAGNOSTICS_DEST_PROPERTY =
+ "org.apache.commons.logging.diagnostics.dest";
+
+ /**
+ * When null (the usual case), no diagnostic output will be
+ * generated by LogFactory or LogFactoryImpl. When non-null,
+ * interesting events will be written to the specified object.
+ */
+ private static PrintStream diagnosticsStream = null;
+
+ /**
+ * A string that gets prefixed to every message output by the
+ * logDiagnostic method, so that users can clearly see which
+ * LogFactory class is generating the output.
+ */
+ private static final String diagnosticPrefix;
+
+ /**
+ * Setting this system property
+ * (org.apache.commons.logging.LogFactory.HashtableImpl
)
+ * value allows the Hashtable
used to store
+ * classloaders to be substituted by an alternative implementation.
+ *
+ * Note: LogFactory
will print:
+ *
+ * [ERROR] LogFactory: Load of custom hashtable failed + *+ * to system error and then continue using a standard Hashtable. + *
+ * Usage: Set this property when Java is invoked
+ * and LogFactory
will attempt to load a new instance
+ * of the given implementation class.
+ * For example, running the following ant scriplet:
+ *
+ * <java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}"> + * ... + * <sysproperty + * key="org.apache.commons.logging.LogFactory.HashtableImpl" + * value="org.apache.commons.logging.AltHashtable"/> + * </java> + *+ * will mean that
LogFactory
will load an instance of
+ * org.apache.commons.logging.AltHashtable
.
+ *
+ * A typical use case is to allow a custom
+ * Hashtable implementation using weak references to be substituted.
+ * This will allow classloaders to be garbage collected without
+ * the need to release them (on 1.3+ JVMs only, of course ;).
+ */
+ public static final String HASHTABLE_IMPLEMENTATION_PROPERTY =
+ "org.apache.commons.logging.LogFactory.HashtableImpl";
+
+ /** Name used to load the weak hashtable implementation by names. */
+ private static final String WEAK_HASHTABLE_CLASSNAME =
+ "org.apache.commons.logging.impl.WeakHashtable";
+
+ /**
+ * A reference to the classloader that loaded this class. This is the
+ * same as LogFactory.class.getClassLoader(). However computing this
+ * value isn't quite as simple as that, as we potentially need to use
+ * AccessControllers etc. It's more efficient to compute it once and
+ * cache it here.
+ */
+ private static final ClassLoader thisClassLoader;
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * Protected constructor that is not available for public use.
+ */
+ protected LogFactory() {
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Return the configuration attribute with the specified name (if any),
+ * or null
if there is no such attribute.
+ *
+ * @param name Name of the attribute to return
+ */
+ public abstract Object getAttribute(String name);
+
+ /**
+ * Return an array containing the names of all currently defined
+ * configuration attributes. If there are no such attributes, a zero
+ * length array is returned.
+ */
+ public abstract String[] getAttributeNames();
+
+ /**
+ * Convenience method to derive a name from the specified class and
+ * call getInstance(String)
with it.
+ *
+ * @param clazz Class for which a suitable Log name will be derived
+ * @throws LogConfigurationException if a suitable Log
+ * instance cannot be returned
+ */
+ public abstract Log getInstance(Class clazz)
+ throws LogConfigurationException;
+
+ /**
+ * Construct (if necessary) and return a Log
instance,
+ * using the factory's current set of configuration attributes.
+ *
+ * NOTE - Depending upon the implementation of
+ * the LogFactory
you are using, the Log
+ * 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.
+ *
+ * @param name Logical name of the Log
instance to be
+ * returned (the meaning of this name is only known to the underlying
+ * logging implementation that is being wrapped)
+ * @throws LogConfigurationException if a suitable Log
+ * instance cannot be returned
+ */
+ public abstract Log getInstance(String name)
+ throws LogConfigurationException;
+
+ /**
+ * Release any internal references to previously created {@link 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 abstract void release();
+
+ /**
+ * Remove any configuration attribute associated with the specified name.
+ * If there is no such attribute, no action is taken.
+ *
+ * @param name Name of the attribute to remove
+ */
+ public abstract void removeAttribute(String name);
+
+ /**
+ * Set the configuration attribute with the specified name. Calling
+ * this with a null
value is equivalent to calling
+ * removeAttribute(name)
.
+ *
+ * @param name Name of the attribute to set
+ * @param value Value of the attribute to set, or null
+ * to remove any setting for this attribute
+ */
+ public abstract void setAttribute(String name, Object value);
+
+ // ------------------------------------------------------- Static Variables
+
+ /**
+ * The previously constructed LogFactory
instances, keyed by
+ * the ClassLoader
with which it was created.
+ */
+ protected static Hashtable factories = null;
+
+ /**
+ * Previously constructed LogFactory
instance as in the
+ * factories
map, but for the case where
+ * getClassLoader
returns null
.
+ * This can happen when:
+ *
factories
is a Hashtable (not a HashMap),
+ * and hashtables don't allow null as a key.
+ * @deprecated since 1.1.2
+ */
+ protected static volatile LogFactory nullClassLoaderFactory = null;
+
+ /**
+ * Create the hashtable which will be used to store a map of
+ * (context-classloader -> logfactory-object). Version 1.2+ of Java
+ * supports "weak references", allowing a custom Hashtable class
+ * to be used which uses only weak references to its keys. Using weak
+ * references can fix memory leaks on webapp unload in some cases (though
+ * not all). Version 1.1 of Java does not support weak references, so we
+ * must dynamically determine which we are using. And just for fun, this
+ * code also supports the ability for a system property to specify an
+ * arbitrary Hashtable implementation name.
+ * + * Note that the correct way to ensure no memory leaks occur is to ensure + * that LogFactory.release(contextClassLoader) is called whenever a + * webapp is undeployed. + */ + private static final Hashtable createFactoryStore() { + Hashtable result = null; + String storeImplementationClass; + try { + storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null); + } catch (SecurityException ex) { + // Permissions don't allow this to be accessed. Default to the "modern" + // weak hashtable implementation if it is available. + storeImplementationClass = null; + } + + if (storeImplementationClass == null) { + storeImplementationClass = WEAK_HASHTABLE_CLASSNAME; + } + try { + Class implementationClass = Class.forName(storeImplementationClass); + result = (Hashtable) implementationClass.newInstance(); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + + // ignore + if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) { + // if the user's trying to set up a custom implementation, give a clue + if (isDiagnosticsEnabled()) { + // use internal logging to issue the warning + logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed"); + } else { + // we *really* want this output, even if diagnostics weren't + // explicitly enabled by the user. + System.err.println("[ERROR] LogFactory: Load of custom hashtable failed"); + } + } + } + if (result == null) { + result = new Hashtable(); + } + return result; + } + + // --------------------------------------------------------- Static Methods + + /** Utility method to safely trim a string. */ + private static String trim(String src) { + if (src == null) { + return null; + } + return src.trim(); + } + + /** + * Checks whether the supplied Throwable is one that needs to be + * re-thrown and ignores all others. + * + * The following errors are re-thrown: + *
LogFactory
+ * instance, using the following ordered lookup procedure to determine
+ * the name of the implementation class to be loaded.
+ * + *
org.apache.commons.logging.LogFactory
system
+ * property.commons-logging.properties
+ * file, if found in the class path of this class. The configuration
+ * file is in standard java.util.Properties
format and
+ * contains the fully qualified name of the implementation class
+ * with the key being the system property defined above.org.apache.commons.logging.impl.LogFactoryImpl
).
+ * NOTE - If the properties file method of identifying the
+ * LogFactory
implementation class is utilized, all of the
+ * properties defined in this file will be set as configuration attributes
+ * on the corresponding LogFactory
instance.
+ *
+ * NOTE - In a multi-threaded environment it is possible
+ * that two different instances will be returned for the same
+ * classloader environment.
+ *
+ * @throws LogConfigurationException if the implementation class is not
+ * available or cannot be instantiated.
+ */
+ public static LogFactory getFactory() throws LogConfigurationException {
+ // Identify the class loader we will be using
+ ClassLoader contextClassLoader = getContextClassLoaderInternal();
+
+ if (contextClassLoader == null) {
+ // This is an odd enough situation to report about. This
+ // output will be a nuisance on JDK1.1, as the system
+ // classloader is null in that environment.
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Context classloader is null.");
+ }
+ }
+
+ // Return any previously registered factory for this class loader
+ LogFactory factory = getCachedFactory(contextClassLoader);
+ if (factory != null) {
+ return factory;
+ }
+
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic(
+ "[LOOKUP] LogFactory implementation requested for the first time for context classloader " +
+ objectId(contextClassLoader));
+ logHierarchy("[LOOKUP] ", contextClassLoader);
+ }
+
+ // Load properties file.
+ //
+ // If the properties file exists, then its contents are used as
+ // "attributes" on the LogFactory implementation class. One particular
+ // property may also control which LogFactory concrete subclass is
+ // used, but only if other discovery mechanisms fail..
+ //
+ // As the properties file (if it exists) will be used one way or
+ // another in the end we may as well look for it first.
+
+ Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
+
+ // Determine whether we will be using the thread context class loader to
+ // load logging classes or not by checking the loaded properties file (if any).
+ ClassLoader baseClassLoader = contextClassLoader;
+ if (props != null) {
+ String useTCCLStr = props.getProperty(TCCL_KEY);
+ if (useTCCLStr != null) {
+ // The Boolean.valueOf(useTCCLStr).booleanValue() formulation
+ // is required for Java 1.2 compatibility.
+ if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
+ // Don't use current context classloader when locating any
+ // LogFactory or Log classes, just use the class that loaded
+ // this abstract class. When this class is deployed in a shared
+ // classpath of a container, it means webapps cannot deploy their
+ // own logging implementations. It also means that it is up to the
+ // implementation whether to load library-specific config files
+ // from the TCCL or not.
+ baseClassLoader = thisClassLoader;
+ }
+ }
+ }
+
+ // Determine which concrete LogFactory subclass to use.
+ // First, try a global system property
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +
+ "] to define the LogFactory subclass to use...");
+ }
+
+ try {
+ String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
+ if (factoryClass != null) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
+ "' as specified by system property " + FACTORY_PROPERTY);
+ }
+ factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
+ } else {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
+ }
+ }
+ } catch (SecurityException e) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" +
+ " instance of the custom factory class" + ": [" + trim(e.getMessage()) +
+ "]. Trying alternative implementations...");
+ }
+ // ignore
+ } catch (RuntimeException e) {
+ // This is not consistent with the behaviour when a bad LogFactory class is
+ // specified in a services file.
+ //
+ // One possible exception that can occur here is a ClassCastException when
+ // the specified class wasn't castable to this LogFactory type.
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] An exception occurred while trying to create an" +
+ " instance of the custom factory class" + ": [" +
+ trim(e.getMessage()) +
+ "] as specified by a system property.");
+ }
+ throw e;
+ }
+
+ // Second, try to find a service by using the JDK1.3 class
+ // discovery mechanism, which involves putting a file with the name
+ // of an interface class in the META-INF/services directory, where the
+ // contents of the file is a single line specifying a concrete class
+ // that implements the desired interface.
+
+ if (factory == null) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
+ "] to define the LogFactory subclass to use...");
+ }
+ try {
+ final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
+
+ if( is != null ) {
+ // This code is needed by EBCDIC and other strange systems.
+ // It's a fix for bugs reported in xerces
+ BufferedReader rd;
+ try {
+ rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+ } catch (java.io.UnsupportedEncodingException e) {
+ rd = new BufferedReader(new InputStreamReader(is));
+ }
+
+ String factoryClassName = rd.readLine();
+ rd.close();
+
+ if (factoryClassName != null && ! "".equals(factoryClassName)) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " +
+ factoryClassName +
+ " as specified by file '" + SERVICE_ID +
+ "' which was present in the path of the context classloader.");
+ }
+ factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
+ }
+ } else {
+ // is == null
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");
+ }
+ }
+ } catch (Exception ex) {
+ // note: if the specified LogFactory class wasn't compatible with LogFactory
+ // for some reason, a ClassCastException will be caught here, and attempts will
+ // continue to find a compatible class.
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic(
+ "[LOOKUP] A security exception occurred while trying to create an" +
+ " instance of the custom factory class" +
+ ": [" + trim(ex.getMessage()) +
+ "]. Trying alternative implementations...");
+ }
+ // ignore
+ }
+ }
+
+ // Third try looking into the properties file read earlier (if found)
+
+ if (factory == null) {
+ if (props != null) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic(
+ "[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY +
+ "' to define the LogFactory subclass to use...");
+ }
+ String factoryClass = props.getProperty(FACTORY_PROPERTY);
+ if (factoryClass != null) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic(
+ "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
+ }
+ factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
+
+ // TODO: think about whether we need to handle exceptions from newFactory
+ } else {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");
+ }
+ }
+ } else {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from..");
+ }
+ }
+ }
+
+ // Fourth, try the fallback implementation class
+
+ if (factory == null) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic(
+ "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT +
+ "' via the same classloader that loaded this LogFactory" +
+ " class (ie not looking in the context classloader).");
+ }
+
+ // Note: unlike the above code which can try to load custom LogFactory
+ // implementations via the TCCL, we don't try to load the default LogFactory
+ // implementation via the context classloader because:
+ // * that can cause problems (see comments in newFactory method)
+ // * no-one should be customising the code of the default class
+ // Yes, we do give up the ability for the child to ship a newer
+ // version of the LogFactoryImpl class and have it used dynamically
+ // by an old LogFactory class in the parent, but that isn't
+ // necessarily a good idea anyway.
+ factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
+ }
+
+ if (factory != null) {
+ /**
+ * Always cache using context class loader.
+ */
+ cacheFactory(contextClassLoader, factory);
+
+ if (props != null) {
+ Enumeration names = props.propertyNames();
+ while (names.hasMoreElements()) {
+ String name = (String) names.nextElement();
+ String value = props.getProperty(name);
+ factory.setAttribute(name, value);
+ }
+ }
+ }
+
+ return factory;
+ }
+
+ /**
+ * Convenience method to return a named logger, without the application
+ * having to care about factories.
+ *
+ * @param clazz Class from which a log name will be derived
+ * @throws LogConfigurationException if a suitable Log
+ * instance cannot be returned
+ */
+ public static Log getLog(Class clazz) throws LogConfigurationException {
+ return getFactory().getInstance(clazz);
+ }
+
+ /**
+ * Convenience method to return a named logger, without the application
+ * having to care about factories.
+ *
+ * @param name Logical name of the Log
instance to be
+ * returned (the meaning of this name is only known to the underlying
+ * logging implementation that is being wrapped)
+ * @throws LogConfigurationException if a suitable Log
+ * instance cannot be returned
+ */
+ public static Log getLog(String name) throws LogConfigurationException {
+ return getFactory().getInstance(name);
+ }
+
+ /**
+ * Release any internal references to previously created {@link LogFactory}
+ * instances that have been associated with the specified class loader
+ * (if any), after calling the instance method release()
on
+ * each of them.
+ *
+ * @param classLoader ClassLoader for which to release the LogFactory
+ */
+ public static void release(ClassLoader classLoader) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Releasing factory for classloader " + objectId(classLoader));
+ }
+ // factories is not final and could be replaced in this block.
+ final Hashtable factories = LogFactory.factories;
+ synchronized (factories) {
+ if (classLoader == null) {
+ if (nullClassLoaderFactory != null) {
+ nullClassLoaderFactory.release();
+ nullClassLoaderFactory = null;
+ }
+ } else {
+ final LogFactory factory = (LogFactory) factories.get(classLoader);
+ if (factory != null) {
+ factory.release();
+ factories.remove(classLoader);
+ }
+ }
+ }
+ }
+
+ /**
+ * Release any internal references to previously created {@link LogFactory}
+ * instances, after calling the instance method release()
on
+ * each of them. 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 static void releaseAll() {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Releasing factory for all classloaders.");
+ }
+ // factories is not final and could be replaced in this block.
+ final Hashtable factories = LogFactory.factories;
+ synchronized (factories) {
+ final Enumeration elements = factories.elements();
+ while (elements.hasMoreElements()) {
+ LogFactory element = (LogFactory) elements.nextElement();
+ element.release();
+ }
+ factories.clear();
+
+ if (nullClassLoaderFactory != null) {
+ nullClassLoaderFactory.release();
+ nullClassLoaderFactory = null;
+ }
+ }
+ }
+
+ // ------------------------------------------------------ Protected Methods
+
+ /**
+ * Safely get access to the classloader for the specified class.
+ *
+ * Theoretically, calling getClassLoader can throw a security exception, + * and so should be done under an AccessController in order to provide + * maximum flexibility. However in practice people don't appear to use + * security policies that forbid getClassLoader calls. So for the moment + * all code is written to call this method rather than Class.getClassLoader, + * so that we could put AccessController stuff in this method without any + * disruption later if we need to. + *
+ * Even when using an AccessController, however, this method can still + * throw SecurityException. Commons-logging basically relies on the + * ability to access classloaders, ie a policy that forbids all + * classloader access will also prevent commons-logging from working: + * currently this method will throw an exception preventing the entire app + * from starting up. Maybe it would be good to detect this situation and + * just disable all commons-logging? Not high priority though - as stated + * above, security policies that prevent classloader access aren't common. + *
+ * Note that returning an object fetched via an AccessController would + * technically be a security flaw anyway; untrusted code that has access + * to a trusted JCL library could use it to fetch the classloader for + * a class even when forbidden to do so directly. + * + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + try { + return clazz.getClassLoader(); + } catch (SecurityException ex) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to get classloader for class '" + clazz + + "' due to security restrictions - " + ex.getMessage()); + } + throw ex; + } + } + + /** + * Returns the current context classloader. + *
+ * In versions prior to 1.1, this method did not use an AccessController. + * In version 1.1, an AccessController wrapper was incorrectly added to + * this method, causing a minor security flaw. + *
+ * In version 1.1.1 this change was reverted; this method no longer uses + * an AccessController. User code wishing to obtain the context classloader + * must invoke this method via AccessController.doPrivileged if it needs + * support for that. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return directGetContextClassLoader(); + } + + /** + * Calls LogFactory.directGetContextClassLoader under the control of an + * AccessController class. This means that java code running under a + * security manager that forbids access to ClassLoaders will still work + * if this class is given appropriate privileges, even when the caller + * doesn't have such privileges. Without using an AccessController, the + * the entire call stack must have the privilege before the call is + * allowed. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + private static ClassLoader getContextClassLoaderInternal() throws LogConfigurationException { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return directGetContextClassLoader(); + } + }); + } + + /** + * Return the thread context class loader if available; otherwise return null. + *
+ * Most/all code should call getContextClassLoaderInternal rather than + * calling this method directly. + *
+ * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + *
+ * Note that no internal logging is done within this method because
+ * this method is called every time LogFactory.getLogger() is called,
+ * and we don't want too much output generated here.
+ *
+ * @throws LogConfigurationException if a suitable class loader
+ * cannot be identified.
+ * @return the thread's context classloader or {@code null} if the java security
+ * policy forbids access to the context classloader from one of the classes
+ * in the current call stack.
+ * @since 1.1
+ */
+ protected static ClassLoader directGetContextClassLoader() throws LogConfigurationException {
+ ClassLoader classLoader = null;
+
+ try {
+ classLoader = Thread.currentThread().getContextClassLoader();
+ } catch (SecurityException ex) {
+ /**
+ * 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.
+ *
+ * We ignore this exception to be consistent with the previous
+ * behavior (e.g. 1.1.3 and earlier).
+ */
+ // ignore
+ }
+
+ // Return the selected class loader
+ return classLoader;
+ }
+
+ /**
+ * Check cached factories (keyed by contextClassLoader)
+ *
+ * @param contextClassLoader is the context classloader associated
+ * with the current thread. This allows separate LogFactory objects
+ * per component within a container, provided each component has
+ * a distinct context classloader set. This parameter may be null
+ * in JDK1.1, and in embedded systems where jcl-using code is
+ * placed in the bootclasspath.
+ *
+ * @return the factory associated with the specified classloader if
+ * one has previously been created, or null if this is the first time
+ * we have seen this particular classloader.
+ */
+ private static LogFactory getCachedFactory(ClassLoader contextClassLoader) {
+ if (contextClassLoader == null) {
+ // We have to handle this specially, as factories is a Hashtable
+ // and those don't accept null as a key value.
+ //
+ // nb: nullClassLoaderFactory might be null. That's ok.
+ return nullClassLoaderFactory;
+ } else {
+ return (LogFactory) factories.get(contextClassLoader);
+ }
+ }
+
+ /**
+ * Remember this factory, so later calls to LogFactory.getCachedFactory
+ * can return the previously created object (together with all its
+ * cached Log objects).
+ *
+ * @param classLoader should be the current context classloader. Note that
+ * this can be null under some circumstances; this is ok.
+ * @param factory should be the factory to cache. This should never be null.
+ */
+ private static void cacheFactory(ClassLoader classLoader, LogFactory factory) {
+ // Ideally we would assert(factory != null) here. However reporting
+ // errors from within a logging implementation is a little tricky!
+
+ if (factory != null) {
+ if (classLoader == null) {
+ nullClassLoaderFactory = factory;
+ } else {
+ factories.put(classLoader, factory);
+ }
+ }
+ }
+
+ /**
+ * Return a new instance of the specified LogFactory
+ * implementation class, loaded by the specified class loader.
+ * If that fails, try the class loader used to load this
+ * (abstract) LogFactory.
+ *
+ * Note that there can be problems if the specified ClassLoader is not the + * same as the classloader that loaded this class, ie when loading a + * concrete LogFactory subclass via a context classloader. + *
+ * The problem is the same one that can occur when loading a concrete Log + * subclass via a context classloader. + *
+ * The problem occurs when code running in the context classloader calls + * class X which was loaded via a parent classloader, and class X then calls + * LogFactory.getFactory (either directly or via LogFactory.getLog). Because + * class X was loaded via the parent, it binds to LogFactory loaded via + * the parent. When the code in this method finds some LogFactoryYYYY + * class in the child (context) classloader, and there also happens to be a + * LogFactory class defined in the child classloader, then LogFactoryYYYY + * will be bound to LogFactory@childloader. It cannot be cast to + * LogFactory@parentloader, ie this method cannot return the object as + * the desired type. Note that it doesn't matter if the LogFactory class + * in the child classloader is identical to the LogFactory class in the + * parent classloader, they are not compatible. + *
+ * The solution taken here is to simply print out an error message when
+ * this occurs then throw an exception. The deployer of the application
+ * must ensure they remove all occurrences of the LogFactory class from
+ * the child classloader in order to resolve the issue. Note that they
+ * do not have to move the custom LogFactory subclass; that is ok as
+ * long as the only LogFactory class it can find to bind to is in the
+ * parent classloader.
+ *
+ * @param factoryClass Fully qualified name of the LogFactory
+ * implementation class
+ * @param classLoader ClassLoader from which to load this class
+ * @param contextClassLoader is the context that this new factory will
+ * manage logging for.
+ * @throws LogConfigurationException if a suitable instance
+ * cannot be created
+ * @since 1.1
+ */
+ protected static LogFactory newFactory(final String factoryClass,
+ final ClassLoader classLoader,
+ final ClassLoader contextClassLoader)
+ throws LogConfigurationException {
+ // Note that any unchecked exceptions thrown by the createFactory
+ // method will propagate out of this method; in particular a
+ // ClassCastException can be thrown.
+ Object result = AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ return createFactory(factoryClass, classLoader);
+ }
+ });
+
+ if (result instanceof LogConfigurationException) {
+ LogConfigurationException ex = (LogConfigurationException) result;
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("An error occurred while loading the factory class:" + ex.getMessage());
+ }
+ throw ex;
+ }
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Created object " + objectId(result) + " to manage classloader " +
+ objectId(contextClassLoader));
+ }
+ return (LogFactory)result;
+ }
+
+ /**
+ * Method provided for backwards compatibility; see newFactory version that
+ * takes 3 parameters.
+ *
+ * This method would only ever be called in some rather odd situation.
+ * Note that this method is static, so overriding in a subclass doesn't
+ * have any effect unless this method is called from a method in that
+ * subclass. However this method only makes sense to use from the
+ * getFactory method, and as that is almost always invoked via
+ * LogFactory.getFactory, any custom definition in a subclass would be
+ * pointless. Only a class with a custom getFactory method, then invoked
+ * directly via CustomFactoryImpl.getFactory or similar would ever call
+ * this. Anyway, it's here just in case, though the "managed class loader"
+ * value output to the diagnostics will not report the correct value.
+ */
+ protected static LogFactory newFactory(final String factoryClass,
+ final ClassLoader classLoader) {
+ return newFactory(factoryClass, classLoader, null);
+ }
+
+ /**
+ * Implements the operations described in the javadoc for newFactory.
+ *
+ * @param factoryClass
+ * @param classLoader used to load the specified factory class. This is
+ * expected to be either the TCCL or the classloader which loaded this
+ * class. Note that the classloader which loaded this class might be
+ * "null" (ie the bootloader) for embedded systems.
+ * @return either a LogFactory object or a LogConfigurationException object.
+ * @since 1.1
+ */
+ protected static Object createFactory(String factoryClass, ClassLoader classLoader) {
+ // This will be used to diagnose bad configurations
+ // and allow a useful message to be sent to the user
+ Class logFactoryClass = null;
+ try {
+ if (classLoader != null) {
+ try {
+ // First the given class loader param (thread class loader)
+
+ // Warning: must typecast here & allow exception
+ // to be generated/caught & recast properly.
+ logFactoryClass = classLoader.loadClass(factoryClass);
+ if (LogFactory.class.isAssignableFrom(logFactoryClass)) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Loaded class " + logFactoryClass.getName() +
+ " from classloader " + objectId(classLoader));
+ }
+ } else {
+ //
+ // This indicates a problem with the ClassLoader tree.
+ // An incompatible ClassLoader was used to load the
+ // implementation.
+ // As the same classes
+ // must be available in multiple class loaders,
+ // it is very likely that multiple JCL jars are present.
+ // The most likely fix for this
+ // problem is to remove the extra JCL jars from the
+ // ClassLoader hierarchy.
+ //
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Factory class " + logFactoryClass.getName() +
+ " loaded from classloader " + objectId(logFactoryClass.getClassLoader()) +
+ " does not extend '" + LogFactory.class.getName() +
+ "' as loaded by this classloader.");
+ logHierarchy("[BAD CL TREE] ", classLoader);
+ }
+ }
+
+ return (LogFactory) logFactoryClass.newInstance();
+
+ } catch (ClassNotFoundException ex) {
+ if (classLoader == thisClassLoader) {
+ // Nothing more to try, onwards.
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Unable to locate any class called '" + factoryClass +
+ "' via classloader " + objectId(classLoader));
+ }
+ throw ex;
+ }
+ // ignore exception, continue
+ } catch (NoClassDefFoundError e) {
+ if (classLoader == thisClassLoader) {
+ // Nothing more to try, onwards.
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Class '" + factoryClass + "' cannot be loaded" +
+ " via classloader " + objectId(classLoader) +
+ " - it depends on some other class that cannot be found.");
+ }
+ throw e;
+ }
+ // ignore exception, continue
+ } catch (ClassCastException e) {
+ if (classLoader == thisClassLoader) {
+ // There's no point in falling through to the code below that
+ // tries again with thisClassLoader, because we've just tried
+ // loading with that loader (not the TCCL). Just throw an
+ // appropriate exception here.
+
+ final boolean implementsLogFactory = implementsLogFactory(logFactoryClass);
+
+ //
+ // Construct a good message: users may not actual expect that a custom implementation
+ // has been specified. Several well known containers use this mechanism to adapt JCL
+ // to their native logging system.
+ //
+ final StringBuffer msg = new StringBuffer();
+ msg.append("The application has specified that a custom LogFactory implementation ");
+ msg.append("should be used but Class '");
+ msg.append(factoryClass);
+ msg.append("' cannot be converted to '");
+ msg.append(LogFactory.class.getName());
+ msg.append("'. ");
+ if (implementsLogFactory) {
+ msg.append("The conflict is caused by the presence of multiple LogFactory classes ");
+ msg.append("in incompatible classloaders. ");
+ msg.append("Background can be found in http://commons.apache.org/logging/tech.html. ");
+ msg.append("If you have not explicitly specified a custom LogFactory then it is likely ");
+ msg.append("that the container has set one without your knowledge. ");
+ msg.append("In this case, consider using the commons-logging-adapters.jar file or ");
+ msg.append("specifying the standard LogFactory from the command line. ");
+ } else {
+ msg.append("Please check the custom implementation. ");
+ }
+ msg.append("Help can be found @http://commons.apache.org/logging/troubleshooting.html.");
+
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic(msg.toString());
+ }
+
+ throw new ClassCastException(msg.toString());
+ }
+
+ // Ignore exception, continue. Presumably the classloader was the
+ // TCCL; the code below will try to load the class via thisClassLoader.
+ // This will handle the case where the original calling class is in
+ // a shared classpath but the TCCL has a copy of LogFactory and the
+ // specified LogFactory implementation; we will fall back to using the
+ // LogFactory implementation from the same classloader as this class.
+ //
+ // Issue: this doesn't handle the reverse case, where this LogFactory
+ // is in the webapp, and the specified LogFactory implementation is
+ // in a shared classpath. In that case:
+ // (a) the class really does implement LogFactory (bad log msg above)
+ // (b) the fallback code will result in exactly the same problem.
+ }
+ }
+
+ /* At this point, either classLoader == null, OR
+ * classLoader was unable to load factoryClass.
+ *
+ * In either case, we call Class.forName, which is equivalent
+ * to LogFactory.class.getClassLoader().load(name), ie we ignore
+ * the classloader parameter the caller passed, and fall back
+ * to trying the classloader associated with this class. See the
+ * javadoc for the newFactory method for more info on the
+ * consequences of this.
+ *
+ * Notes:
+ * * LogFactory.class.getClassLoader() may return 'null'
+ * if LogFactory is loaded by the bootstrap classloader.
+ */
+ // Warning: must typecast here & allow exception
+ // to be generated/caught & recast properly.
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Unable to load factory class via classloader " + objectId(classLoader) +
+ " - trying the classloader associated with this LogFactory.");
+ }
+ logFactoryClass = Class.forName(factoryClass);
+ return (LogFactory) logFactoryClass.newInstance();
+ } catch (Exception e) {
+ // Check to see if we've got a bad configuration
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Unable to create LogFactory instance.");
+ }
+ if (logFactoryClass != null && !LogFactory.class.isAssignableFrom(logFactoryClass)) {
+ return new LogConfigurationException(
+ "The chosen LogFactory implementation does not extend LogFactory." +
+ " Please check your configuration.", e);
+ }
+ return new LogConfigurationException(e);
+ }
+ }
+
+ /**
+ * Determines whether the given class actually implements LogFactory
.
+ * Diagnostic information is also logged.
+ *
+ * Usage: to diagnose whether a classloader conflict is the cause
+ * of incompatibility. The test used is whether the class is assignable from
+ * the LogFactory
class loaded by the class's classloader.
+ * @param logFactoryClass Class
which may implement LogFactory
+ * @return true if the logFactoryClass
does extend
+ * LogFactory
when that class is loaded via the same
+ * classloader that loaded the logFactoryClass
.
+ */
+ private static boolean implementsLogFactory(Class logFactoryClass) {
+ boolean implementsLogFactory = false;
+ if (logFactoryClass != null) {
+ try {
+ ClassLoader logFactoryClassLoader = logFactoryClass.getClassLoader();
+ if (logFactoryClassLoader == null) {
+ logDiagnostic("[CUSTOM LOG FACTORY] was loaded by the boot classloader");
+ } else {
+ logHierarchy("[CUSTOM LOG FACTORY] ", logFactoryClassLoader);
+ Class factoryFromCustomLoader
+ = Class.forName("org.apache.commons.logging.LogFactory", false, logFactoryClassLoader);
+ implementsLogFactory = factoryFromCustomLoader.isAssignableFrom(logFactoryClass);
+ if (implementsLogFactory) {
+ logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() +
+ " implements LogFactory but was loaded by an incompatible classloader.");
+ } else {
+ logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() +
+ " does not implement LogFactory.");
+ }
+ }
+ } catch (SecurityException e) {
+ //
+ // The application is running within a hostile security environment.
+ // This will make it very hard to diagnose issues with JCL.
+ // Consider running less securely whilst debugging this issue.
+ //
+ logDiagnostic("[CUSTOM LOG FACTORY] SecurityException thrown whilst trying to determine whether " +
+ "the compatibility was caused by a classloader conflict: " + e.getMessage());
+ } catch (LinkageError e) {
+ //
+ // This should be an unusual circumstance.
+ // LinkageError's usually indicate that a dependent class has incompatibly changed.
+ // Another possibility may be an exception thrown by an initializer.
+ // Time for a clean rebuild?
+ //
+ logDiagnostic("[CUSTOM LOG FACTORY] LinkageError thrown whilst trying to determine whether " +
+ "the compatibility was caused by a classloader conflict: " + e.getMessage());
+ } catch (ClassNotFoundException e) {
+ //
+ // LogFactory cannot be loaded by the classloader which loaded the custom factory implementation.
+ // The custom implementation is not viable until this is corrected.
+ // Ensure that the JCL jar and the custom class are available from the same classloader.
+ // Running with diagnostics on should give information about the classloaders used
+ // to load the custom factory.
+ //
+ logDiagnostic("[CUSTOM LOG FACTORY] LogFactory class cannot be loaded by classloader which loaded " +
+ "the custom LogFactory implementation. Is the custom factory in the right classloader?");
+ }
+ }
+ return implementsLogFactory;
+ }
+
+ /**
+ * Applets may run in an environment where accessing resources of a loader is
+ * a secure operation, but where the commons-logging library has explicitly
+ * been granted permission for that operation. In this case, we need to
+ * run the operation using an AccessController.
+ */
+ private static InputStream getResourceAsStream(final ClassLoader loader, final String name) {
+ return (InputStream)AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ if (loader != null) {
+ return loader.getResourceAsStream(name);
+ } else {
+ return ClassLoader.getSystemResourceAsStream(name);
+ }
+ }
+ });
+ }
+
+ /**
+ * Given a filename, return an enumeration of URLs pointing to
+ * all the occurrences of that filename in the classpath.
+ *
+ * This is just like ClassLoader.getResources except that the + * operation is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *
+ * If no instances are found, an Enumeration is returned whose + * hasMoreElements method returns false (ie an "empty" enumeration). + * If resources could not be listed for some reason, null is returned. + */ + private static Enumeration getResources(final ClassLoader loader, final String name) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + try { + if (loader != null) { + return loader.getResources(name); + } else { + return ClassLoader.getSystemResources(name); + } + } catch (IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Exception while trying to find configuration file " + + name + ":" + e.getMessage()); + } + return null; + } catch (NoSuchMethodError e) { + // we must be running on a 1.1 JVM which doesn't support + // ClassLoader.getSystemResources; just return null in + // this case. + return null; + } + } + }; + Object result = AccessController.doPrivileged(action); + return (Enumeration) result; + } + + /** + * Given a URL that refers to a .properties file, load that file. + * This is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *
+ * {@code Null} is returned if the URL cannot be opened. + */ + private static Properties getProperties(final URL url) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + InputStream stream = null; + try { + // We must ensure that useCaches is set to false, as the + // default behaviour of java is to cache file handles, and + // this "locks" files, preventing hot-redeploy on windows. + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + stream = connection.getInputStream(); + if (stream != null) { + Properties props = new Properties(); + props.load(stream); + stream.close(); + stream = null; + return props; + } + } catch (IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read URL " + url); + } + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // ignore exception; this should not happen + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to close stream for URL " + url); + } + } + } + } + + return null; + } + }; + return (Properties) AccessController.doPrivileged(action); + } + + /** + * Locate a user-provided configuration file. + *
+ * The classpath of the specified classLoader (usually the context classloader) + * is searched for properties files of the specified name. If none is found, + * null is returned. If more than one is found, then the file with the greatest + * value for its PRIORITY property is returned. If multiple files have the + * same PRIORITY value then the first in the classpath is returned. + *
+ * This differs from the 1.0.x releases; those always use the first one found. + * However as the priority is a new field, this change is backwards compatible. + *
+ * The purpose of the priority field is to allow a webserver administrator to + * override logging settings in all webapps by placing a commons-logging.properties + * file in a shared classpath location with a priority > 0; this overrides any + * commons-logging.properties files without priorities which are in the + * webapps. Webapps can also use explicit priorities to override a configuration + * file in the shared classpath if needed. + */ + private static final Properties getConfigurationFile(ClassLoader classLoader, String fileName) { + Properties props = null; + double priority = 0.0; + URL propsUrl = null; + try { + Enumeration urls = getResources(classLoader, fileName); + + if (urls == null) { + return null; + } + + while (urls.hasMoreElements()) { + URL url = (URL) urls.nextElement(); + + Properties newProps = getProperties(url); + if (newProps != null) { + if (props == null) { + propsUrl = url; + props = newProps; + String priorityStr = props.getProperty(PRIORITY_KEY); + priority = 0.0; + if (priorityStr != null) { + priority = Double.parseDouble(priorityStr); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file found at '" + url + "'" + + " with priority " + priority); + } + } else { + String newPriorityStr = newProps.getProperty(PRIORITY_KEY); + double newPriority = 0.0; + if (newPriorityStr != null) { + newPriority = Double.parseDouble(newPriorityStr); + } + + if (newPriority > priority) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " overrides file at '" + propsUrl + "'" + + " with priority " + priority); + } + + propsUrl = url; + props = newProps; + priority = newPriority; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " does not override file at '" + propsUrl + "'" + + " with priority " + priority); + } + } + } + + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("SecurityException thrown while trying to find/read config files."); + } + } + + if (isDiagnosticsEnabled()) { + if (props == null) { + logDiagnostic("[LOOKUP] No properties file of name '" + fileName + "' found."); + } else { + logDiagnostic("[LOOKUP] Properties file of name '" + fileName + "' found at '" + propsUrl + '"'); + } + } + + return props; + } + + /** + * Read the specified system property, using an AccessController so that + * the property can be read if JCL has been granted the appropriate + * security rights even if the calling code has not. + *
+ * Take care not to expose the value returned by this method to the + * calling application in any way; otherwise the calling app can use that + * info to access data that should not be available to it. + */ + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return System.getProperty(key, def); + } + }); + } + + /** + * Determines whether the user wants internal diagnostic output. If so, + * returns an appropriate writer object. Users can enable diagnostic + * output by setting the system property named {@link #DIAGNOSTICS_DEST_PROPERTY} to + * a filename, or the special values STDOUT or STDERR. + */ + private static PrintStream initDiagnostics() { + String dest; + try { + dest = getSystemProperty(DIAGNOSTICS_DEST_PROPERTY, null); + if (dest == null) { + return null; + } + } catch (SecurityException ex) { + // We must be running in some very secure environment. + // We just have to assume output is not wanted.. + return null; + } + + if (dest.equals("STDOUT")) { + return System.out; + } else if (dest.equals("STDERR")) { + return System.err; + } else { + try { + // open the file in append mode + FileOutputStream fos = new FileOutputStream(dest, true); + return new PrintStream(fos); + } catch (IOException ex) { + // We should report this to the user - but how? + return null; + } + } + } + + /** + * Indicates true if the user has enabled internal logging. + *
+ * By the way, sorry for the incorrect grammar, but calling this method + * areDiagnosticsEnabled just isn't java beans style. + * + * @return true if calls to logDiagnostic will have any effect. + * @since 1.1 + */ + protected static boolean isDiagnosticsEnabled() { + return diagnosticsStream != null; + } + + /** + * Write the specified message to the internal logging destination. + *
+ * Note that this method is private; concrete subclasses of this class + * should not call it because the diagnosticPrefix string this + * method puts in front of all its messages is LogFactory@...., + * while subclasses should put SomeSubClass@... + *
+ * Subclasses should instead compute their own prefix, then call + * logRawDiagnostic. Note that calling isDiagnosticsEnabled is + * fine for subclasses. + *
+ * Note that it is safe to call this method before initDiagnostics + * is called; any output will just be ignored (as isDiagnosticsEnabled + * will return false). + * + * @param msg is the diagnostic message to be output. + */ + private static final void logDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.print(diagnosticPrefix); + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Write the specified message to the internal logging destination. + * + * @param msg is the diagnostic message to be output. + * @since 1.1 + */ + protected static final void logRawDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Generate useful diagnostics regarding the classloader tree for + * the specified class. + *
+ * As an example, if the specified class was loaded via a webapp's + * classloader, then you may get the following output: + *
+ * Class com.acme.Foo was loaded via classloader 11111 + * ClassLoader tree: 11111 -> 22222 (SYSTEM) -> 33333 -> BOOT + *+ *
+ * This method returns immediately if isDiagnosticsEnabled() + * returns false. + * + * @param clazz is the class whose classloader + tree are to be + * output. + */ + private static void logClassLoaderEnvironment(Class clazz) { + if (!isDiagnosticsEnabled()) { + return; + } + + try { + // Deliberately use System.getProperty here instead of getSystemProperty; if + // the overall security policy for the calling application forbids access to + // these variables then we do not want to output them to the diagnostic stream. + logDiagnostic("[ENV] Extension directories (java.ext.dir): " + System.getProperty("java.ext.dir")); + logDiagnostic("[ENV] Application classpath (java.class.path): " + System.getProperty("java.class.path")); + } catch (SecurityException ex) { + logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths."); + } + + String className = clazz.getName(); + ClassLoader classLoader; + + try { + classLoader = getClassLoader(clazz); + } catch (SecurityException ex) { + // not much useful diagnostics we can print here! + logDiagnostic("[ENV] Security forbids determining the classloader for " + className); + return; + } + + logDiagnostic("[ENV] Class " + className + " was loaded via classloader " + objectId(classLoader)); + logHierarchy("[ENV] Ancestry of classloader which loaded " + className + " is ", classLoader); + } + + /** + * Logs diagnostic messages about the given classloader + * and it's hierarchy. The prefix is prepended to the message + * and is intended to make it easier to understand the logs. + * @param prefix + * @param classLoader + */ + private static void logHierarchy(String prefix, ClassLoader classLoader) { + if (!isDiagnosticsEnabled()) { + return; + } + ClassLoader systemClassLoader; + if (classLoader != null) { + final String classLoaderString = classLoader.toString(); + logDiagnostic(prefix + objectId(classLoader) + " == '" + classLoaderString + "'"); + } + + try { + systemClassLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException ex) { + logDiagnostic(prefix + "Security forbids determining the system classloader."); + return; + } + if (classLoader != null) { + final StringBuffer buf = new StringBuffer(prefix + "ClassLoader tree:"); + for(;;) { + buf.append(objectId(classLoader)); + if (classLoader == systemClassLoader) { + buf.append(" (SYSTEM) "); + } + + try { + classLoader = classLoader.getParent(); + } catch (SecurityException ex) { + buf.append(" --> SECRET"); + break; + } + + buf.append(" --> "); + if (classLoader == null) { + buf.append("BOOT"); + break; + } + } + logDiagnostic(buf.toString()); + } + } + + /** + * Returns a string that uniquely identifies the specified object, including + * its class. + *
+ * The returned string is of form "classname@hashcode", ie is the same as
+ * the return value of the Object.toString() method, but works even when
+ * the specified object's class has overidden the toString method.
+ *
+ * @param o may be null.
+ * @return a string of form classname@hashcode, or "null" if param o is null.
+ * @since 1.1
+ */
+ public static String objectId(Object o) {
+ if (o == null) {
+ return "null";
+ } else {
+ return o.getClass().getName() + "@" + System.identityHashCode(o);
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Static initialiser block to perform initialisation at class load time.
+ //
+ // We can't do this in the class constructor, as there are many
+ // static methods on this class that can be called before any
+ // LogFactory instances are created, and they depend upon this
+ // stuff having been set up.
+ //
+ // Note that this block must come after any variable declarations used
+ // by any methods called from this block, as we want any static initialiser
+ // associated with the variable to run first. If static initialisers for
+ // variables run after this code, then (a) their value might be needed
+ // by methods called from here, and (b) they might *override* any value
+ // computed here!
+ //
+ // So the wisest thing to do is just to place this code at the very end
+ // of the class file.
+ // ----------------------------------------------------------------------
+
+ static {
+ // note: it's safe to call methods before initDiagnostics (though
+ // diagnostic output gets discarded).
+ thisClassLoader = getClassLoader(LogFactory.class);
+ // In order to avoid confusion where multiple instances of JCL are
+ // being used via different classloaders within the same app, we
+ // ensure each logged message has a prefix of form
+ // [LogFactory from classloader OID]
+ //
+ // Note that this prefix should be kept consistent with that
+ // in LogFactoryImpl. However here we don't need to output info
+ // about the actual *instance* of LogFactory, as all methods that
+ // output diagnostics from this class are static.
+ String classLoaderName;
+ try {
+ ClassLoader classLoader = thisClassLoader;
+ if (thisClassLoader == null) {
+ classLoaderName = "BOOTLOADER";
+ } else {
+ classLoaderName = objectId(classLoader);
+ }
+ } catch (SecurityException e) {
+ classLoaderName = "UNKNOWN";
+ }
+ diagnosticPrefix = "[LogFactory from " + classLoaderName + "] ";
+ diagnosticsStream = initDiagnostics();
+ logClassLoaderEnvironment(LogFactory.class);
+ factories = createFactoryStore();
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("BOOTSTRAP COMPLETED");
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/logging/LogSource.java b/src/main/java/org/apache/commons/logging/LogSource.java
new file mode 100644
index 0000000..95a12c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/LogSource.java
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+import java.lang.reflect.Constructor;
+import java.util.Hashtable;
+
+import org.apache.commons.logging.impl.NoOpLog;
+
+/**
+ * Factory for creating {@link Log} instances. Applications should call
+ * the makeNewLogInstance()
method to instantiate new instances
+ * of the configured {@link Log} implementation class.
+ *
+ * By default, calling getInstance()
will use the following
+ * algorithm:
+ *
org.apache.commons.logging.impl.Log4JLogger
.org.apache.commons.logging.impl.Jdk14Logger
.org.apache.commons.logging.impl.NoOpLog
.+ * You can change the default behavior in one of two ways: + *
org.apache.commons.logging.log
to the name of the
+ * org.apache.commons.logging.Log
implementation class
+ * you want to use.LogSource.setLogImplementation()
.Log
instance by class name. */
+ static public Log getInstance(String name) {
+ Log log = (Log) logs.get(name);
+ if (null == log) {
+ log = makeNewLogInstance(name);
+ logs.put(name, log);
+ }
+ return log;
+ }
+
+ /** Get a Log
instance by class. */
+ static public Log getInstance(Class clazz) {
+ return getInstance(clazz.getName());
+ }
+
+ /**
+ * Create a new {@link Log} implementation, based on the given name.
+ * + * The specific {@link Log} implementation returned is determined by the + * value of the org.apache.commons.logging.log property. The value + * of org.apache.commons.logging.log may be set to the fully specified + * name of a class that implements the {@link Log} interface. This class must + * also have a public constructor that takes a single {@link String} argument + * (containing the name of the {@link Log} to be constructed. + *
+ * When org.apache.commons.logging.log is not set, or when no corresponding + * class can be found, this method will return a Log4JLogger if the log4j Logger + * class is available in the {@link LogSource}'s classpath, or a Jdk14Logger if we + * are on a JDK 1.4 or later system, or NoOpLog if neither of the above conditions is true. + * + * @param name the log name (or category) + */ + static public Log makeNewLogInstance(String name) { + Log log; + try { + Object[] args = { name }; + log = (Log) logImplctor.newInstance(args); + } catch (Throwable t) { + log = null; + } + if (null == log) { + log = new NoOpLog(name); + } + return log; + } + + /** + * Returns a {@link String} array containing the names of + * all logs known to me. + */ + static public String[] getLogNames() { + return (String[]) logs.keySet().toArray(new String[logs.size()]); + } +} diff --git a/src/main/java/org/apache/commons/logging/impl/AvalonLogger.java b/src/main/java/org/apache/commons/logging/impl/AvalonLogger.java new file mode 100644 index 0000000..344462b --- /dev/null +++ b/src/main/java/org/apache/commons/logging/impl/AvalonLogger.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.avalon.framework.logger.Logger; +import org.apache.commons.logging.Log; + +/** + * Implementation of commons-logging Log interface that delegates all + * logging calls to the Avalon logging abstraction: the Logger interface. + *
+ * There are two ways in which this class can be used: + *
AvalonLogger
+ * instances created through the LogFactory
mechanisms will output
+ * to child loggers of this Logger
.
+ *
+ * Note: AvalonLogger
does not implement Serializable
+ * because the constructors available for it make this impossible to achieve in all
+ * circumstances; there is no way to "reconnect" to an underlying Logger object on
+ * deserialization if one was just passed in to the constructor of the original
+ * object. This class was marked Serializable in the 1.0.4 release of
+ * commons-logging, but this never actually worked (a NullPointerException would
+ * be thrown as soon as the deserialized object was used), so removing this marker
+ * is not considered to be an incompatible change.
+ *
+ * @version $Id: AvalonLogger.java 1435115 2013-01-18 12:40:19Z tn $
+ */
+public class AvalonLogger implements Log {
+
+ /** Ancestral Avalon logger. */
+ private static volatile Logger defaultLogger = null;
+ /** Avalon logger used to perform log. */
+ private final transient Logger logger;
+
+ /**
+ * Constructs an AvalonLogger
that outputs to the given
+ * Logger
instance.
+ *
+ * @param logger the Avalon logger implementation to delegate to
+ */
+ public AvalonLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ /**
+ * Constructs an AvalonLogger
that will log to a child
+ * of the Logger
set by calling {@link #setDefaultLogger}.
+ *
+ * @param name the name of the avalon logger implementation to delegate to
+ */
+ public AvalonLogger(String name) {
+ if (defaultLogger == null) {
+ throw new NullPointerException("default logger has to be specified if this constructor is used!");
+ }
+ this.logger = defaultLogger.getChildLogger(name);
+ }
+
+ /**
+ * Gets the Avalon logger implementation used to perform logging.
+ *
+ * @return avalon logger implementation
+ */
+ public Logger getLogger() {
+ return logger;
+ }
+
+ /**
+ * Sets the ancestral Avalon logger from which the delegating loggers will descend.
+ *
+ * @param logger the default avalon logger,
+ * in case there is no logger instance supplied in constructor
+ */
+ public static void setDefaultLogger(Logger logger) {
+ defaultLogger = logger;
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.debug
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#debug(Object, Throwable)
+ */
+ public void debug(Object message, Throwable t) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.debug
.
+ *
+ * @param message to log.
+ * @see org.apache.commons.logging.Log#debug(Object)
+ */
+ public void debug(Object message) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.error
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#error(Object, Throwable)
+ */
+ public void error(Object message, Throwable t) {
+ if (getLogger().isErrorEnabled()) {
+ getLogger().error(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.error
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#error(Object)
+ */
+ public void error(Object message) {
+ if (getLogger().isErrorEnabled()) {
+ getLogger().error(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.fatalError
.
+ *
+ * @param message to log.
+ * @param t log this cause.
+ * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
+ */
+ public void fatal(Object message, Throwable t) {
+ if (getLogger().isFatalErrorEnabled()) {
+ getLogger().fatalError(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.fatalError
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#fatal(Object)
+ */
+ public void fatal(Object message) {
+ if (getLogger().isFatalErrorEnabled()) {
+ getLogger().fatalError(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.info
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#info(Object, Throwable)
+ */
+ public void info(Object message, Throwable t) {
+ if (getLogger().isInfoEnabled()) {
+ getLogger().info(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.info
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#info(Object)
+ */
+ public void info(Object message) {
+ if (getLogger().isInfoEnabled()) {
+ getLogger().info(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Is logging to org.apache.avalon.framework.logger.Logger.debug
enabled?
+ * @see org.apache.commons.logging.Log#isDebugEnabled()
+ */
+ public boolean isDebugEnabled() {
+ return getLogger().isDebugEnabled();
+ }
+
+ /**
+ * Is logging to org.apache.avalon.framework.logger.Logger.error
enabled?
+ * @see org.apache.commons.logging.Log#isErrorEnabled()
+ */
+ public boolean isErrorEnabled() {
+ return getLogger().isErrorEnabled();
+ }
+
+ /**
+ * Is logging to org.apache.avalon.framework.logger.Logger.fatalError
enabled?
+ * @see org.apache.commons.logging.Log#isFatalEnabled()
+ */
+ public boolean isFatalEnabled() {
+ return getLogger().isFatalErrorEnabled();
+ }
+
+ /**
+ * Is logging to org.apache.avalon.framework.logger.Logger.info
enabled?
+ * @see org.apache.commons.logging.Log#isInfoEnabled()
+ */
+ public boolean isInfoEnabled() {
+ return getLogger().isInfoEnabled();
+ }
+
+ /**
+ * Is logging to org.apache.avalon.framework.logger.Logger.debug
enabled?
+ * @see org.apache.commons.logging.Log#isTraceEnabled()
+ */
+ public boolean isTraceEnabled() {
+ return getLogger().isDebugEnabled();
+ }
+
+ /**
+ * Is logging to org.apache.avalon.framework.logger.Logger.warn
enabled?
+ * @see org.apache.commons.logging.Log#isWarnEnabled()
+ */
+ public boolean isWarnEnabled() {
+ return getLogger().isWarnEnabled();
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.debug
.
+ *
+ * @param message to log.
+ * @param t log this cause.
+ * @see org.apache.commons.logging.Log#trace(Object, Throwable)
+ */
+ public void trace(Object message, Throwable t) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.debug
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#trace(Object)
+ */
+ public void trace(Object message) {
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.warn
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#warn(Object, Throwable)
+ */
+ public void warn(Object message, Throwable t) {
+ if (getLogger().isWarnEnabled()) {
+ getLogger().warn(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.avalon.framework.logger.Logger.warn
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#warn(Object)
+ */
+ public void warn(Object message) {
+ if (getLogger().isWarnEnabled()) {
+ getLogger().warn(String.valueOf(message));
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java b/src/main/java/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java
new file mode 100644
index 0000000..da3faa4
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 java.util.logging.LogRecord;
+import java.util.StringTokenizer;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Implementation of the org.apache.commons.logging.Log
+ * interface that wraps the standard JDK logging mechanisms that are
+ * available in SourceForge's Lumberjack for JDKs prior to 1.4.
+ *
+ * @version $Id: Jdk13LumberjackLogger.java 1432663 2013-01-13 17:24:18Z tn $
+ * @since 1.1
+ */
+public class Jdk13LumberjackLogger implements Log, Serializable {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = -8649807923527610591L;
+
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * The underlying Logger implementation we are using.
+ */
+ protected transient Logger logger = null;
+ protected String name = null;
+ private String sourceClassName = "unknown";
+ private String sourceMethodName = "unknown";
+ private boolean classAndMethodFound = false;
+
+ /**
+ * 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 Jdk13LumberjackLogger(String name) {
+ this.name = name;
+ logger = getLogger();
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ private void log( Level level, String msg, Throwable ex ) {
+ if( getLogger().isLoggable(level) ) {
+ LogRecord record = new LogRecord(level, msg);
+ if( !classAndMethodFound ) {
+ getClassAndMethod();
+ }
+ record.setSourceClassName(sourceClassName);
+ record.setSourceMethodName(sourceMethodName);
+ if( ex != null ) {
+ record.setThrown(ex);
+ }
+ getLogger().log(record);
+ }
+ }
+
+ /**
+ * Gets the class and method by looking at the stack trace for the
+ * first entry that is not this class.
+ */
+ private void getClassAndMethod() {
+ try {
+ Throwable throwable = new Throwable();
+ throwable.fillInStackTrace();
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter( stringWriter );
+ throwable.printStackTrace( printWriter );
+ String traceString = stringWriter.getBuffer().toString();
+ StringTokenizer tokenizer =
+ new StringTokenizer( traceString, "\n" );
+ tokenizer.nextToken();
+ String line = tokenizer.nextToken();
+ while ( line.indexOf( this.getClass().getName() ) == -1 ) {
+ line = tokenizer.nextToken();
+ }
+ while ( line.indexOf( this.getClass().getName() ) >= 0 ) {
+ line = tokenizer.nextToken();
+ }
+ int start = line.indexOf( "at " ) + 3;
+ int end = line.indexOf( '(' );
+ String temp = line.substring( start, end );
+ int lastPeriod = temp.lastIndexOf( '.' );
+ sourceClassName = temp.substring( 0, lastPeriod );
+ sourceMethodName = temp.substring( lastPeriod + 1 );
+ } catch ( Exception ex ) {
+ // ignore - leave class and methodname unknown
+ }
+ classAndMethodFound = true;
+ }
+
+ /**
+ * Logs a message with java.util.logging.Level.FINE
.
+ *
+ * @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 java.util.logging.Level.FINE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.INFO
.
+ *
+ * @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 java.util.logging.Level.INFO
.
+ *
+ * @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 java.util.logging.Level.FINEST
.
+ *
+ * @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 java.util.logging.Level.FINEST
.
+ *
+ * @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 java.util.logging.Level.WARNING
.
+ *
+ * @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 java.util.logging.Level.WARNING
.
+ *
+ * @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/main/java/org/apache/commons/logging/impl/Jdk14Logger.java b/src/main/java/org/apache/commons/logging/impl/Jdk14Logger.java
new file mode 100644
index 0000000..7a19acb
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/Jdk14Logger.java
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+/**
+ * Implementation of the org.apache.commons.logging.Log
+ * interface that wraps the standard JDK logging mechanisms that were
+ * introduced in the Merlin release (JDK 1.4).
+ *
+ * @version $Id: Jdk14Logger.java 1448063 2013-02-20 10:01:41Z tn $
+ */
+public class Jdk14Logger implements Log, Serializable {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = 4784713551416303804L;
+
+ /**
+ * 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;
+
+ // --------------------------------------------------------- Protected Methods
+
+ protected 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();
+ // LOGGING-132: use the provided logger name instead of the class name
+ String cname = name;
+ String method = "unknown";
+ // Caller will be the third element
+ if( locations != null && locations.length > 2 ) {
+ StackTraceElement caller = locations[2];
+ method = caller.getMethodName();
+ }
+ if( ex == null ) {
+ logger.logp( level, cname, method, msg );
+ } else {
+ logger.logp( level, cname, method, msg, ex );
+ }
+ }
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Logs a message with java.util.logging.Level.FINE
.
+ *
+ * @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 java.util.logging.Level.FINE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.SEVERE
.
+ *
+ * @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 java.util.logging.Level.INFO
.
+ *
+ * @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 java.util.logging.Level.INFO
.
+ *
+ * @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 java.util.logging.Level.FINEST
.
+ *
+ * @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 java.util.logging.Level.FINEST
.
+ *
+ * @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 java.util.logging.Level.WARNING
.
+ *
+ * @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 java.util.logging.Level.WARNING
.
+ *
+ * @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/main/java/org/apache/commons/logging/impl/Log4JLogger.java b/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java
new file mode 100644
index 0000000..de3b140
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/Log4JLogger.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Priority;
+import org.apache.log4j.Level;
+
+/**
+ * Implementation of {@link Log} that maps directly to a
+ * Logger for log4J version 1.2.
+ *
+ * Initial configuration of the corresponding Logger instances should be done + * in the usual manner, as outlined in the Log4J documentation. + *
+ * The reason this logger is distinct from the 1.3 logger is that in version 1.2 + * of Log4J: + *
org.apache.log4j.Priority.TRACE
.
+ * When using a log4j version that does not support the TRACE
+ * level, the message will be logged at the DEBUG
level.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#trace(Object)
+ */
+ public void trace(Object message) {
+ getLogger().log(FQCN, traceLevel, message, null);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.TRACE
.
+ * When using a log4j version that does not support the TRACE
+ * level, the message will be logged at the DEBUG
level.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#trace(Object, Throwable)
+ */
+ public void trace(Object message, Throwable t) {
+ getLogger().log(FQCN, traceLevel, message, t);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.DEBUG
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#debug(Object)
+ */
+ public void debug(Object message) {
+ getLogger().log(FQCN, Level.DEBUG, message, null);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.DEBUG
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#debug(Object, Throwable)
+ */
+ public void debug(Object message, Throwable t) {
+ getLogger().log(FQCN, Level.DEBUG, message, t);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.INFO
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#info(Object)
+ */
+ public void info(Object message) {
+ getLogger().log(FQCN, Level.INFO, message, null);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.INFO
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#info(Object, Throwable)
+ */
+ public void info(Object message, Throwable t) {
+ getLogger().log(FQCN, Level.INFO, message, t);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.WARN
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#warn(Object)
+ */
+ public void warn(Object message) {
+ getLogger().log(FQCN, Level.WARN, message, null);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.WARN
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#warn(Object, Throwable)
+ */
+ public void warn(Object message, Throwable t) {
+ getLogger().log(FQCN, Level.WARN, message, t);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.ERROR
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#error(Object)
+ */
+ public void error(Object message) {
+ getLogger().log(FQCN, Level.ERROR, message, null);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.ERROR
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#error(Object, Throwable)
+ */
+ public void error(Object message, Throwable t) {
+ getLogger().log(FQCN, Level.ERROR, message, t);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.FATAL
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#fatal(Object)
+ */
+ public void fatal(Object message) {
+ getLogger().log(FQCN, Level.FATAL, message, null);
+ }
+
+ /**
+ * Logs a message with org.apache.log4j.Priority.FATAL
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
+ */
+ public void fatal(Object message, Throwable t) {
+ getLogger().log(FQCN, Level.FATAL, message, t);
+ }
+
+ /**
+ * Return the native Logger instance we are using.
+ */
+ public Logger getLogger() {
+ Logger result = logger;
+ if (result == null) {
+ synchronized(this) {
+ result = logger;
+ if (result == null) {
+ logger = result = Logger.getLogger(name);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check whether the Log4j Logger used is enabled for DEBUG
priority.
+ */
+ public boolean isDebugEnabled() {
+ return getLogger().isDebugEnabled();
+ }
+
+ /**
+ * Check whether the Log4j Logger used is enabled for ERROR
priority.
+ */
+ public boolean isErrorEnabled() {
+ return getLogger().isEnabledFor(Level.ERROR);
+ }
+
+ /**
+ * Check whether the Log4j Logger used is enabled for FATAL
priority.
+ */
+ public boolean isFatalEnabled() {
+ return getLogger().isEnabledFor(Level.FATAL);
+ }
+
+ /**
+ * Check whether the Log4j Logger used is enabled for INFO
priority.
+ */
+ public boolean isInfoEnabled() {
+ return getLogger().isInfoEnabled();
+ }
+
+ /**
+ * Check whether the Log4j Logger used is enabled for TRACE
priority.
+ * When using a log4j version that does not support the TRACE level, this call
+ * will report whether DEBUG
is enabled or not.
+ */
+ public boolean isTraceEnabled() {
+ return getLogger().isEnabledFor(traceLevel);
+ }
+
+ /**
+ * Check whether the Log4j Logger used is enabled for WARN
priority.
+ */
+ public boolean isWarnEnabled() {
+ return getLogger().isEnabledFor(Level.WARN);
+ }
+}
diff --git a/src/main/java/org/apache/commons/logging/impl/LogFactoryImpl.java b/src/main/java/org/apache/commons/logging/impl/LogFactoryImpl.java
new file mode 100644
index 0000000..7ec3c27
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/LogFactoryImpl.java
@@ -0,0 +1,1393 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Hashtable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogConfigurationException;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Concrete subclass of {@link LogFactory} that implements the
+ * following algorithm to dynamically select a logging implementation
+ * class to instantiate a wrapper for:
+ * org.apache.commons.logging.Log
to identify the
+ * requested implementation class.org.apache.commons.logging.Log
system property
+ * to identify the requested implementation class.org.apache.commons.logging.impl.Log4JLogger
.org.apache.commons.logging.impl.Jdk14Logger
.org.apache.commons.logging.impl.SimpleLog
.
+ * If the selected {@link Log} implementation class has a
+ * setLogFactory()
method that accepts a {@link LogFactory}
+ * parameter, this method will be called on each newly created instance
+ * to identify the associated factory. This makes factory configuration
+ * attributes available to the Log instance, if it so desires.
+ *
+ * This factory will remember previously created Log
instances
+ * for the same name, and will return them on repeated requests to the
+ * getInstance()
method.
+ *
+ * @version $Id: LogFactoryImpl.java 1449064 2013-02-22 14:49:22Z tn $
+ */
+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 (org.apache.commons.logging.Log
) 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 (org.apache.commons.logging.Log.allowFlawedContext
)
+ * 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 (org.apache.commons.logging.Log.allowFlawedDiscovery
)
+ * 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 (org.apache.commons.logging.Log.allowFlawedHierarchy
)
+ * 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 getLogConstructor()
,
+ * and then returned repeatedly.
+ */
+ protected Constructor logConstructor = null;
+
+ /**
+ * The signature of the Constructor to be used.
+ */
+ protected Class logConstructorSignature[] = { java.lang.String.class };
+
+ /**
+ * The one-argument setLogFactory
method of the selected
+ * {@link org.apache.commons.logging.Log} method, if it exists.
+ */
+ protected Method logMethod = null;
+
+ /**
+ * The signature of the setLogFactory
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 null
if there is no such attribute.
+ *
+ * @param name Name of the attribute to return
+ */
+ public Object getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+ /**
+ * Return an array containing the names of all currently defined
+ * configuration attributes. If there are no such attributes, a zero
+ * length array is returned.
+ */
+ public String[] getAttributeNames() {
+ return (String[]) attributes.keySet().toArray(new String[attributes.size()]);
+ }
+
+ /**
+ * Convenience method to derive a name from the specified class and
+ * call getInstance(String)
with it.
+ *
+ * @param clazz Class for which a suitable Log name will be derived
+ *
+ * @exception LogConfigurationException if a suitable Log
+ * instance cannot be returned
+ */
+ public Log getInstance(Class clazz) throws LogConfigurationException {
+ return getInstance(clazz.getName());
+ }
+
+ /**
+ *
Construct (if necessary) and return a Log
instance,
+ * using the factory's current set of configuration attributes.
NOTE - Depending upon the implementation of
+ * the LogFactory
you are using, the Log
+ * 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.
Log
instance to be
+ * returned (the meaning of this name is only known to the underlying
+ * logging implementation that is being wrapped)
+ *
+ * @exception LogConfigurationException if a suitable Log
+ * instance cannot be returned
+ */
+ public Log getInstance(String name) throws LogConfigurationException {
+ Log instance = (Log) instances.get(name);
+ if (instance == null) {
+ instance = newInstance(name);
+ instances.put(name, instance);
+ }
+ return instance;
+ }
+
+ /**
+ * 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 null
value is equivalent to calling
+ * removeAttribute(name)
.
+ * + * 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 + *
+ * LogFactory.getFactory().setAttribute(...) + *+ * This must be done before the first Log object is created; configuration + * changes after that point will be ignored. + *
+ * 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 null
+ * 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 = value != null && 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.
+ *
+ * This string will later be prefixed to each "internal logging" message + * emitted, so that users can clearly see any unexpected behaviour. + *
+ * 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; + } + + + /** + *
Return the Constructor
that can be called to instantiate
+ * new {@link org.apache.commons.logging.Log} instances.
IMPLEMENTATION NOTE - Race conditions caused by
+ * calling this method from more than one thread are ignored, because
+ * the same Constructor
instance will ultimately be derived
+ * in all circumstances.
true
if JDK 1.4 or later logging
+ * is available. Also checks that the Throwable
class
+ * supports getStackTrace()
, which is required by
+ * Jdk14Logger.
+ *
+ * @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 Log4J 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;
+ 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 lce;
+
+ } catch (InvocationTargetException e) {
+ // A problem occurred invoking the Constructor or Method
+ // previously discovered
+ Throwable c = e.getTargetException();
+ throw new LogConfigurationException(c == null ? e : c);
+ } catch (Throwable t) {
+ handleThrowable(t); // may re-throw t
+ // A problem occurred invoking the Constructor or Method
+ // previously discovered
+ throw new LogConfigurationException(t);
+ }
+ }
+
+ // ------------------------------------------------------ Private Methods
+
+ /**
+ * Calls LogFactory.directGetContextClassLoader under the control of an
+ * AccessController class. This means that java code running under a
+ * security manager that forbids access to ClassLoaders will still work
+ * if this class is given appropriate privileges, even when the caller
+ * doesn't have such privileges. Without using an AccessController, the
+ * the entire call stack must have the privilege before the call is
+ * allowed.
+ *
+ * @return the context classloader associated with the current thread,
+ * or null if security doesn't allow it.
+ *
+ * @throws LogConfigurationException if there was some weird error while
+ * attempting to get the context classloader.
+ *
+ * @throws SecurityException if the current java security policy doesn't
+ * allow this class to access the context classloader.
+ */
+ private static ClassLoader getContextClassLoaderInternal()
+ throws LogConfigurationException {
+ return (ClassLoader)AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ return LogFactory.directGetContextClassLoader();
+ }
+ });
+ }
+
+ /**
+ * Read the specified system property, using an AccessController so that
+ * the property can be read if JCL has been granted the appropriate
+ * security rights even if the calling code has not.
+ * + * Take care not to expose the value returned by this method to the + * calling application in any way; otherwise the calling app can use that + * info to access data that should not be available to it. + */ + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return System.getProperty(key, def); + } + }); + } + + /** + * Fetch the parent classloader of a specified classloader. + *
+ * If a SecurityException occurs, null is returned. + *
+ * Note that this method is non-static merely so logDiagnostic is available. + */ + private ClassLoader getParentClassLoader(final ClassLoader cl) { + try { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return cl.getParent(); + } + }); + } catch (SecurityException ex) { + logDiagnostic("[SECURITY] Unable to obtain parent classloader"); + return null; + } + + } + + /** + * Utility method to check whether a particular logging library is + * present and available for use. Note that this does not + * 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. + *
+ * 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 {
+ // warning: minor security hole here, in that we potentially read a system
+ // property that the caller cannot, then output it in readable form as a
+ // diagnostic message. However it's only ever JCL-specific properties
+ // involved here, so the harm is truly trivial.
+ String value = getSystemProperty(property, null);
+ 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
+ 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
+ * 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 and the allowFlawedContext option
+ * has been set then the classloader which loaded this class is returned
+ * instead.
+ *
+ * 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) {
+ return thisClassLoader;
+ }
+
+ ClassLoader contextClassLoader = getContextClassLoaderInternal();
+
+ 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.
+ *
+ * @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();
+ current = getParentClassLoader(current);
+ }
+
+ // scan c2's ancestors to find c1
+ current = c2;
+ while (current != null) {
+ if (current == c1) {
+ return c2;
+ }
+ // current = current.getParent();
+ current = getParentClassLoader(current);
+ }
+
+ return null;
+ }
+
+ /**
+ * Generates an internal diagnostic logging of the discovery failure and
+ * then throws a
+ * There are two possible reasons why we successfully loaded the
+ * specified log adapter class then failed to cast it to a Log object:
+ *
+ * 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) {
+ handleThrowable(t); // may re-throw 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/main/java/org/apache/commons/logging/impl/LogKitLogger.java b/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java
new file mode 100644
index 0000000..6feaa31
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log.Logger;
+import org.apache.log.Hierarchy;
+import org.apache.commons.logging.Log;
+
+/**
+ * Implementation of
+ *
+ * In general, the WeakHashtable support added in commons-logging release 1.1
+ * ensures that logging classes do not hold references that prevent an
+ * undeployed webapp's memory from being garbage-collected even when multiple
+ * copies of commons-logging are deployed via multiple classloaders (a
+ * situation that earlier versions had problems with). However there are
+ * some rare cases where the WeakHashtable approach does not work; in these
+ * situations specifying this class as a listener for the web application will
+ * ensure that all references held by commons-logging are fully released.
+ *
+ * To use this class, configure the webapp deployment descriptor to call
+ * this class on webapp undeploy; the contextDestroyed method will tell
+ * every accessible LogFactory class that the entry in its map for the
+ * current webapp's context classloader should be cleared.
+ *
+ * @version $Id: ServletContextCleaner.java 1432580 2013-01-13 10:41:05Z tn $
+ * @since 1.1
+ */
+public class ServletContextCleaner implements ServletContextListener {
+
+ private static final Class[] RELEASE_SIGNATURE = {ClassLoader.class};
+
+ /**
+ * Invoked when a webapp is undeployed, this tells the LogFactory
+ * class to release any logging information related to the current
+ * contextClassloader.
+ */
+ public void contextDestroyed(ServletContextEvent sce) {
+ ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+
+ Object[] params = new Object[1];
+ params[0] = tccl;
+
+ // Walk up the tree of classloaders, finding all the available
+ // LogFactory classes and releasing any objects associated with
+ // the tccl (ie the webapp).
+ //
+ // When there is only one LogFactory in the classpath, and it
+ // is within the webapp being undeployed then there is no problem;
+ // garbage collection works fine.
+ //
+ // When there are multiple LogFactory classes in the classpath but
+ // parent-first classloading is used everywhere, this loop is really
+ // short. The first instance of LogFactory found will
+ // be the highest in the classpath, and then no more will be found.
+ // This is ok, as with this setup this will be the only LogFactory
+ // holding any data associated with the tccl being released.
+ //
+ // When there are multiple LogFactory classes in the classpath and
+ // child-first classloading is used in any classloader, then multiple
+ // LogFactory instances may hold info about this TCCL; whenever the
+ // webapp makes a call into a class loaded via an ancestor classloader
+ // and that class calls LogFactory the tccl gets registered in
+ // the LogFactory instance that is visible from the ancestor
+ // classloader. However the concrete logging library it points
+ // to is expected to have been loaded via the TCCL, so the
+ // underlying logging lib is only initialised/configured once.
+ // These references from ancestor LogFactory classes down to
+ // TCCL classloaders are held via weak references and so should
+ // be released but there are circumstances where they may not.
+ // Walking up the classloader ancestry ladder releasing
+ // the current tccl at each level tree, though, will definitely
+ // clear any problem references.
+ ClassLoader loader = tccl;
+ while (loader != null) {
+ // Load via the current loader. Note that if the class is not accessible
+ // via this loader, but is accessible via some ancestor then that class
+ // will be returned.
+ try {
+ Class logFactoryClass = loader.loadClass("org.apache.commons.logging.LogFactory");
+ Method releaseMethod = logFactoryClass.getMethod("release", RELEASE_SIGNATURE);
+ releaseMethod.invoke(null, params);
+ loader = logFactoryClass.getClassLoader().getParent();
+ } catch(ClassNotFoundException ex) {
+ // Neither the current classloader nor any of its ancestors could find
+ // the LogFactory class, so we can stop now.
+ loader = null;
+ } catch(NoSuchMethodException ex) {
+ // This is not expected; every version of JCL has this method
+ System.err.println("LogFactory instance found which does not support release method!");
+ loader = null;
+ } catch(IllegalAccessException ex) {
+ // This is not expected; every ancestor class should be accessible
+ System.err.println("LogFactory instance found which is not accessable!");
+ loader = null;
+ } catch(InvocationTargetException ex) {
+ // This is not expected
+ System.err.println("LogFactory instance release method failed!");
+ loader = null;
+ }
+ }
+
+ // Just to be sure, invoke release on the LogFactory that is visible from
+ // this ServletContextCleaner class too. This should already have been caught
+ // by the above loop but just in case...
+ LogFactory.release(tccl);
+ }
+
+ /**
+ * Invoked when a webapp is deployed. Nothing needs to be done here.
+ */
+ public void contextInitialized(ServletContextEvent sce) {
+ // do nothing
+ }
+}
diff --git a/src/main/java/org/apache/commons/logging/impl/SimpleLog.java b/src/main/java/org/apache/commons/logging/impl/SimpleLog.java
new file mode 100644
index 0000000..d4756af
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/SimpleLog.java
@@ -0,0 +1,649 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+/**
+ * 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:
+ *
+ * In addition to looking for system properties with the names specified
+ * above, this implementation also checks for a class loader resource named
+ *
+ * Any code that accesses this object should first obtain a lock on it,
+ * ie use synchronized(dateFormatter); this requirement was introduced
+ * in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format
+ * is not thread-safe).
+ */
+ 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 volatile String logName = null;
+ /** The current log level */
+ protected volatile int currentLogLevel;
+ /** The short name of this simple log instance */
+ private volatile 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
+
+ /**
+ * Set logging level.
+ *
+ * @param currentLogLevel new logging level
+ */
+ public void setLevel(int currentLogLevel) {
+ this.currentLogLevel = currentLogLevel;
+ }
+
+ /**
+ * Get logging level.
+ */
+ public int getLevel() {
+ return currentLogLevel;
+ }
+
+ // -------------------------------------------------------- Logging Methods
+
+ /**
+ * Do the actual logging.
+ *
+ * This method assembles the message and then calls
+ * This allows expensive operations such as
+ * This allows expensive operations such as
+ * This allows expensive operations such as
+ * This allows expensive operations such as
+ * This allows expensive operations such as
+ * This allows expensive operations such as
+ * This class follows the semantics of
+ * Note:
+ * This is not intended to be a general purpose hash table replacement.
+ * This implementation is also tuned towards a particular purpose: for use as a replacement
+ * for
+ * Usage: typical use case is as a drop-in replacement
+ * for the
+ *
+ * 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"
+ *
+ * Limitations:
+ * 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.
+ *
+ * If the abstract class
+ * 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
+ * To avoid this scenario, ensure
+ * that any custom LogFactory subclass is loaded by the same classloader as
+ * the base Concrete implementations of commons-logging wrapper APIs. Simple wrapper API around multiple logging APIs. This package provides an API for logging in server-based applications that
+can be used around a variety of different logging implementations, including
+prebuilt support for the following: For those impatient to just get on with it, the following example
+illustrates the typical declaration and use of a logger that is named (by
+convention) after the calling class:
+
+ Unless you configure things differently, all log output will be written
+to System.err. Therefore, you really will want to review the remainder of
+this page in order to understand how to configure logging for your
+application. From an application perspective, the first requirement is to retrieve an
+object reference to the If a Once an implementation class name is selected, the corresponding class is
+loaded from the current Thread context class loader (if there is one), or
+from the class loader that loaded the The Logging Package APIs include a default See the SimpleLog JavaDocs for detailed
+configuration information for this default implementation. The basic principle is that the user is totally responsible for the
+configuration of the underlying logging system.
+Commons-logging should not change the existing configuration. Each individual Log implementation may
+support its own configuration properties. These will be documented in the
+class descriptions for the corresponding implementation class. Finally, some Use of the Logging Package APIs, from the perspective of an application
+component, consists of the following steps: For convenience, into a single method call: For example, you might use the following technique to initialize and
+use a Log instance in an application component: The Logging Wrapper Library component of the Apache Commons
+subproject offers wrappers around an extensible set of concrete logging
+implementations, so that application code based on it does not need to be
+modified in order to select a different logging implementation. See the
+
+Package Description for the
+ Commons Logging uses Maven 2.0.x as its
+ primary build system. Ant can also be used.
+
+ To build the full website, run
+
+ The result will be in the
+ To build the jar files, run
+
+ The resulting 4 jar files will be in the
+ To create a full distribution, run
+
+ The resulting .zip and .tar.gz files will be in the
+ Further details can be found in the
+ commons build instructions.
+
+ We still use Ant to test the artifacts built my Maven.
+ Please follow the instructions in the file
+ Note: A 1.2 JDK is needed to run the tests.
+
+ We recommend you use a mirror to download our release
+ builds, but you must verify the integrity of
+ the downloaded files using signatures downloaded from our main
+ distribution directories. Recent releases (48 hours) may not yet
+ be available from the mirrors.
+
+ You are currently using [preferred]. If you
+ encounter a problem with this mirror, please select another
+ mirror. If all mirrors are failing, there are backup
+ mirrors (at the end of the mirrors list) that should be
+ available.
+
+ The KEYS
+ link links to the code signing keys used to sign the product.
+ The
+ Older releases can be obtained from the archives.
+
+ null
+ */
+ 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 = getSystemProperty(LOG_PROPERTY, null);
+ } 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 = getSystemProperty(LOG_PROPERTY_OLD, null);
+ } 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 true
if this object's state should
+ * be affected by this method call, false
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;
+ 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) {
+ handleThrowable(t); // may re-throw 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();
+ currentCL = getParentClassLoader(currentCL);
+ }
+
+ if (logAdapterClass != null && affectState) {
+ // We've succeeded, so set instance fields
+ this.logClassName = logAdapterClassName;
+ this.logConstructor = constructor;
+
+ // Identify the setLogFactory
method (if there is one)
+ try {
+ this.logMethod = logAdapterClass.getMethod("setLogFactory", logMethodSignature);
+ logDiagnostic("Found method setLogFactory(LogFactory) in '" + logAdapterClassName + "'");
+ } catch (Throwable t) {
+ handleThrowable(t); // may re-throw 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.
+ * LogConfigurationException
that wraps
+ * the passed Throwable
.
+ *
+ * @param logAdapterClassName is the class name of the Log implementation
+ * that could not be instantiated. Cannot be null
.
+ *
+ * @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, // USED?
+ Throwable discoveryFlaw) {
+
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Could not instantiate Log '" +
+ logAdapterClassName + "' -- " +
+ discoveryFlaw.getClass().getName() + ": " +
+ discoveryFlaw.getLocalizedMessage());
+
+ if (discoveryFlaw instanceof InvocationTargetException ) {
+ // Ok, the lib is there but while trying to create a real underlying
+ // logger something failed in the underlying lib; display info about
+ // that if possible.
+ InvocationTargetException ite = (InvocationTargetException)discoveryFlaw;
+ Throwable cause = ite.getTargetException();
+ if (cause != null) {
+ logDiagnostic("... InvocationTargetException: " +
+ cause.getClass().getName() + ": " +
+ cause.getLocalizedMessage());
+
+ if (cause instanceof ExceptionInInitializerError) {
+ ExceptionInInitializerError eiie = (ExceptionInInitializerError)cause;
+ Throwable cause2 = eiie.getException();
+ if (cause2 != null) {
+ final StringWriter sw = new StringWriter();
+ cause2.printStackTrace(new PrintWriter(sw, true));
+ logDiagnostic("... ExceptionInInitializerError: " + sw.toString());
+ }
+ }
+ }
+ }
+ }
+
+ 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.
+ *
+ *
+ * org.apache.commons.logging.Log
+ * that wraps the avalon-logkit
+ * logging system. Configuration of LogKit
is left to the user.
+ * LogKit
accepts only String
messages.
+ * Therefore, this implementation converts object messages into strings
+ * by called their toString()
method before logging them.
+ *
+ * @version $Id: LogKitLogger.java 1448119 2013-02-20 12:28:04Z tn $
+ */
+public class LogKitLogger implements Log, Serializable {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = 3768538055836059519L;
+
+ // ------------------------------------------------------------- Attributes
+
+ /** Logging goes to this LogKit
logger */
+ protected transient volatile Logger logger = null;
+
+ /** Name of this logger */
+ protected String name = null;
+
+ // ------------------------------------------------------------ Constructor
+
+ /**
+ * Construct LogKitLogger
which wraps the LogKit
+ * logger with given name.
+ *
+ * @param name log name
+ */
+ public LogKitLogger(String name) {
+ this.name = name;
+ this.logger = getLogger();
+ }
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * Return the underlying Logger we are using.
+ */
+ public Logger getLogger() {
+ Logger result = logger;
+ if (result == null) {
+ synchronized(this) {
+ result = logger;
+ if (result == null) {
+ logger = result = Hierarchy.getDefaultHierarchy().getLoggerFor(name);
+ }
+ }
+ }
+ return result;
+ }
+
+ // ----------------------------------------------------- Log Implementation
+
+ /**
+ * Logs a message with org.apache.log.Priority.DEBUG
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#trace(Object)
+ */
+ public void trace(Object message) {
+ debug(message);
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.DEBUG
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#trace(Object, Throwable)
+ */
+ public void trace(Object message, Throwable t) {
+ debug(message, t);
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.DEBUG
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#debug(Object)
+ */
+ public void debug(Object message) {
+ if (message != null) {
+ getLogger().debug(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.DEBUG
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#debug(Object, Throwable)
+ */
+ public void debug(Object message, Throwable t) {
+ if (message != null) {
+ getLogger().debug(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.INFO
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#info(Object)
+ */
+ public void info(Object message) {
+ if (message != null) {
+ getLogger().info(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.INFO
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#info(Object, Throwable)
+ */
+ public void info(Object message, Throwable t) {
+ if (message != null) {
+ getLogger().info(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.WARN
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#warn(Object)
+ */
+ public void warn(Object message) {
+ if (message != null) {
+ getLogger().warn(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.WARN
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#warn(Object, Throwable)
+ */
+ public void warn(Object message, Throwable t) {
+ if (message != null) {
+ getLogger().warn(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.ERROR
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#error(Object)
+ */
+ public void error(Object message) {
+ if (message != null) {
+ getLogger().error(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.ERROR
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#error(Object, Throwable)
+ */
+ public void error(Object message, Throwable t) {
+ if (message != null) {
+ getLogger().error(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.FATAL_ERROR
.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#fatal(Object)
+ */
+ public void fatal(Object message) {
+ if (message != null) {
+ getLogger().fatalError(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with org.apache.log.Priority.FATAL_ERROR
.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
+ */
+ public void fatal(Object message, Throwable t) {
+ if (message != null) {
+ getLogger().fatalError(String.valueOf(message), t);
+ }
+ }
+
+ /**
+ * Checks whether the LogKit
logger will log messages of priority DEBUG
.
+ */
+ public boolean isDebugEnabled() {
+ return getLogger().isDebugEnabled();
+ }
+
+ /**
+ * Checks whether the LogKit
logger will log messages of priority ERROR
.
+ */
+ public boolean isErrorEnabled() {
+ return getLogger().isErrorEnabled();
+ }
+
+ /**
+ * Checks whether the LogKit
logger will log messages of priority FATAL_ERROR
.
+ */
+ public boolean isFatalEnabled() {
+ return getLogger().isFatalErrorEnabled();
+ }
+
+ /**
+ * Checks whether the LogKit
logger will log messages of priority INFO
.
+ */
+ public boolean isInfoEnabled() {
+ return getLogger().isInfoEnabled();
+ }
+
+ /**
+ * Checks whether the LogKit
logger will log messages of priority DEBUG
.
+ */
+ public boolean isTraceEnabled() {
+ return getLogger().isDebugEnabled();
+ }
+
+ /**
+ * Checks whether the LogKit
logger will log messages of priority WARN
.
+ */
+ public boolean isWarnEnabled() {
+ return getLogger().isWarnEnabled();
+ }
+}
diff --git a/src/main/java/org/apache/commons/logging/impl/NoOpLog.java b/src/main/java/org/apache/commons/logging/impl/NoOpLog.java
new file mode 100644
index 0000000..ef0de24
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/NoOpLog.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+/**
+ * Trivial implementation of Log that throws away all messages. No
+ * configurable system properties are supported.
+ *
+ * @version $Id: NoOpLog.java 1432663 2013-01-13 17:24:18Z tn $
+ */
+public class NoOpLog implements Log, Serializable {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = 561423906191706148L;
+
+ /** 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/main/java/org/apache/commons/logging/impl/ServletContextCleaner.java b/src/main/java/org/apache/commons/logging/impl/ServletContextCleaner.java
new file mode 100644
index 0000000..c7c2162
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/ServletContextCleaner.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class is capable of receiving notifications about the undeployment of
+ * a webapp, and responds by ensuring that commons-logging releases all
+ * memory associated with the undeployed webapp.
+ *
+ *
+ * org.apache.commons.logging.simplelog.defaultlog
-
+ * 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". org.apache.commons.logging.simplelog.log.xxxxx
-
+ * 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.org.apache.commons.logging.simplelog.showlogname
-
+ * Set to true
if you want the Log instance name to be
+ * included in output messages. Defaults to false
.org.apache.commons.logging.simplelog.showShortLogname
-
+ * Set to true
if you want the last component of the name to be
+ * included in output messages. Defaults to true
.org.apache.commons.logging.simplelog.showdatetime
-
+ * Set to true
if you want the current date and time
+ * to be included in output messages. Default is false
.org.apache.commons.logging.simplelog.dateTimeFormat
-
+ * 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 java.text.SimpleDateFormat
. If the format is not
+ * specified or is invalid, the default format is used.
+ * The default format is yyyy/MM/dd HH:mm:ss:SSS zzz
."simplelog.properties"
, and includes any matching definitions
+ * from this resource (if it exists).
+ *
+ * @version $Id: SimpleLog.java 1435115 2013-01-18 12:40:19Z tn $
+ */
+public class SimpleLog implements Log, Serializable {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = 136942970684951178L;
+
+ // ------------------------------------------------------- Class Attributes
+
+ /** All system properties used by SimpleLog
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 volatile 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 volatile protected boolean showShortName = true;
+
+ /** Include the current time in the log message */
+ static volatile protected boolean showDateTime = false;
+
+ /** The date and time format to use in the log message */
+ static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
+
+ /**
+ * Used to format times.
+ * write()
+ * to cause it to be written.
+ *
+ * @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
+ final StringBuffer buf = new StringBuffer();
+
+ // Append date-time if so configured
+ if(showDateTime) {
+ final Date now = new Date();
+ String dateText;
+ synchronized(dateFormatter) {
+ dateText = dateFormatter.format(now);
+ }
+ buf.append(dateText);
+ 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
+ final String slName = logName.substring(logName.lastIndexOf(".") + 1);
+ shortLogName = slName.substring(slName.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(">");
+
+ final java.io.StringWriter sw = new java.io.StringWriter(1024);
+ final 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);
+ }
+
+ /**
+ * Write the content of the message accumulated in the specified
+ * StringBuffer
to the appropriate output destination. The
+ * default implementation writes to System.err
.
+ *
+ * @param buffer A StringBuffer
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
+ * org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG
.
+ *
+ * @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
+ * org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL
.
+ *
+ * @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 org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL
.
+ *
+ * @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);
+ }
+ }
+
+ /**
+ * Are debug messages currently enabled?
+ * String
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ public final boolean isDebugEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG);
+ }
+
+ /**
+ * Are error messages currently enabled?
+ * String
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ public final boolean isErrorEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR);
+ }
+
+ /**
+ * Are fatal messages currently enabled?
+ * String
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ public final boolean isFatalEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL);
+ }
+
+ /**
+ * Are info messages currently enabled?
+ * String
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ public final boolean isInfoEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO);
+ }
+
+ /**
+ * Are trace messages currently enabled?
+ * String
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ public final boolean isTraceEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE);
+ }
+
+ /**
+ * Are warn messages currently enabled?
+ * String
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ 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;
+
+ try {
+ // Are we running on a JDK 1.2 or later system?
+ final 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(), (Class[]) 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/main/java/org/apache/commons/logging/impl/WeakHashtable.java b/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java
new file mode 100644
index 0000000..c5773f3
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java
@@ -0,0 +1,482 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of Hashtable
that uses WeakReference
's
+ * to hold its keys thus allowing them to be reclaimed by the garbage collector.
+ * The associated values are retained using strong references.
+ * Hashtable
as closely as
+ * possible. It therefore does not accept null values or keys.
+ * Hashtable
in LogFactory
. This application requires
+ * good liveliness for get
and put
. Various tradeoffs
+ * have been made with this in mind.
+ * Hashtable
used in LogFactory
for J2EE environments
+ * running 1.3+ JVMs. Use of this class in most cases (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)}.
+ * org.apache.commons.logging.LogFactory
checks whether this class
+ * can be supported by the current JVM, and if so then uses it to store
+ * references to the LogFactory
implementation it loads
+ * (rather than using a standard Hashtable instance).
+ * Having this class used instead of Hashtable
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 java.lang.ref.WeakReference
and associates).
+ * And by the way, this extends Hashtable
rather than HashMap
+ * for backwards compatibility reasons. See the documentation
+ * for method LogFactory.createFactoryStore
for more details.
+ * LogFactory
's factories member! If LogFactory.release()
+ * is called whenever component is unloaded, the classloaders will be correctly
+ * garbage collected; this should 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.
+ * LogFactory
is
+ * loaded by the container classloader but a subclass of
+ * LogFactory
[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
+ * getClass().getClassLoader()
. This chain of references will prevent
+ * collection of the child classloader.
+ * LogFactory
implementation is
+ * loaded by a child classloader (e.g. a web app classloader).
+ * LogFactory
. Creating custom LogFactory subclasses is,
+ * however, rare. The standard LogFactoryImpl class should be sufficient
+ * for most or all users.
+ *
+ * @version $Id: WeakHashtable.java 1435077 2013-01-18 10:51:35Z tn $
+ * @since 1.1
+ */
+public final class WeakHashtable extends Hashtable {
+
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = -1546036869799732453L;
+
+ /**
+ * 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 final 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 synchronized Object put(Object key, Object value) {
+ // check for nulls, ensuring semantics 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();
+ }
+
+ 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 synchronized 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() {
+ final List toRemove = new ArrayList();
+ synchronized (queue) {
+ WeakKey key;
+ while ((key = (WeakKey) queue.poll()) != null) {
+ toRemove.add(key.getReferenced());
+ }
+ }
+
+ // LOGGING-119: do the actual removal of the keys outside the sync block
+ // to prevent deadlock scenarios as purge() may be called from
+ // non-synchronized methods too
+ final int size = toRemove.size();
+ for (int i = 0; i < size; i++) {
+ super.remove(toRemove.get(i));
+ }
+ }
+
+ /**
+ * 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 null
+ */
+ 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 null
+ */
+ 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.
+ result = 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 value
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/main/java/org/apache/commons/logging/impl/package.html b/src/main/java/org/apache/commons/logging/impl/package.html
new file mode 100644
index 0000000..5344784
--- /dev/null
+++ b/src/main/java/org/apache/commons/logging/impl/package.html
@@ -0,0 +1,22 @@
+
+
+Overview
+
+
+
+
+
+java.util.logging.Logger
instance.Logger
.Quick Start Guide
+
+
+ import org.apache.commons.logging.Log;
+ import org.apache.commons.logging.LogFactory;
+
+ public class Foo {
+
+ private Log log = LogFactory.getLog(Foo.class);
+
+ public void foo() {
+ ...
+ try {
+ if (log.isDebugEnabled()) {
+ log.debug("About to do something to object " + name);
+ }
+ name.bar();
+ } catch (IllegalStateException e) {
+ log.error("Something bad happened to " + name, e);
+ }
+ ...
+ }
+
+
+Configuring the Commons Logging Package
+
+
+Choosing a
+
+LogFactory
ImplementationLogFactory
instance that will be used
+to create Log
instances for this
+application. This is normally accomplished by calling the static
+getFactory()
method. This method implements the following
+discovery algorithm to select the name of the LogFactory
+implementation class this application wants to use:
+
+
+org.apache.commons.logging.LogFactory
.META-INF/services/org.apache.commons.logging.LogFactory
+ whose first line is assumed to contain the desired class name.commons-logging.properties
+ visible in the application class path, with a property named
+ org.apache.commons.logging.LogFactory
defining the
+ desired implementation class name.commons-logging.properties
file is found, all of the
+properties defined there are also used to set configuration attributes on
+the instantiated LogFactory
instance.LogFactory
class itself
+otherwise. This allows a copy of commons-logging.jar
to be
+shared in a multiple class loader environment (such as a servlet container),
+but still allow each web application to provide its own LogFactory
+implementation, if it so desires. An instance of this class will then be
+created, and cached per class loader.
+
+
+The Default
+
+LogFactory
ImplementationLogFactory
+implementation class (
+org.apache.commons.logging.impl.LogFactoryImpl) that is selected if no
+other implementation class name can be discovered. Its primary purpose is
+to create (as necessary) and return Log instances
+in response to calls to the getInstance()
method. The default
+implementation uses the following rules:
+
+
+Log
instance of the same name will be created.
+ Subsequent getInstance()
calls to the same
+ LogFactory
instance, with the same name or Class
+ parameter, will return the same Log
instance.Log
instance must be created, the default
+ LogFactory
implementation uses the following discovery
+ process:
+
+
org.apache.commons.logging.Log
(for backwards
+ compatibility to pre-1.0 versions of this API, an attribute
+ org.apache.commons.logging.log
is also consulted).org.apache.commons.logging.Log
(for backwards
+ compatibility to pre-1.0 versions of this API, a system property
+ org.apache.commons.logging.log
is also consulted).LogFactory
class otherwise.Log
+ implementation class, passing the specified name as the single
+ argument to its constructor.Configuring the Underlying Logging System
+
+Log
implementations (such as the one for Log4J)
+require an external configuration file for the entire logging environment.
+This file should be prepared in a manner that is specific to the actual logging
+technology being used.Using the Logging Package APIs
+
+
+
+
+trace()
, debug()
,
+ info()
, warn()
, error
, and
+ fatal()
).LogFactory
also offers a static method
+getLog()
that combines the typical two-step pattern:
+ Log log = LogFactory.getFactory().getInstance(Foo.class);
+
+
+ Log log = LogFactory.getLog(Foo.class);
+
+
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class MyComponent {
+
+ protected Log log =
+ LogFactory.getLog(MyComponent.class);
+
+ // Called once at startup time
+ public void start() {
+ ...
+ log.info("MyComponent started");
+ ...
+ }
+
+ // Called once at shutdown time
+ public void stop() {
+ ...
+ log.info("MyComponent stopped");
+ ...
+ }
+
+ // Called repeatedly to process a particular argument value
+ // which you want logged if debugging is enabled
+ public void process(String value) {
+ ...
+ // Do the string concatenation only if logging is enabled
+ if (log.isDebugEnabled())
+ log.debug("MyComponent processing " + value);
+ ...
+ }
+
+}
+
+
+
diff --git a/src/main/java/overview.html b/src/main/java/overview.html
new file mode 100644
index 0000000..4fc581b
--- /dev/null
+++ b/src/main/java/overview.html
@@ -0,0 +1,35 @@
+
+
+
+
+org.apache.commons.logging
+package for more information.target/site
folder.
+ You must be online and using JDK 1.4 or higher to successfully complete this target.
+ target
folder.
+ You must use JDK 1.4 or higher to successfully complete this target.
+ target
folder.
+ You must use JDK 1.4 or higher to successfully complete this target.
+ build-testing.xml
.
+
+ [if-any logo][end]
+ PGP
link downloads the OpenPGP compatible signature from our main site.
+ The MD5
link downloads the checksum from the main site.
+
+
+
+
+ commons-logging-1.2-bin.tar.gz
+ md5
+ pgp
+
+
+ commons-logging-1.2-bin.zip
+ md5
+ pgp
+
+
+
+
+ commons-logging-1.2-src.tar.gz
+ md5
+ pgp
+
+
+ commons-logging-1.2-src.zip
+ md5
+ pgp
+
+
+
+The Apache Commons Logging (JCL) provides a Log
interface that
+is intended to be both light-weight and an independent abstraction of other logging toolkits.
+It provides the middleware/tooling developer with a simple
+logging abstraction, that allows the user (application developer) to plug in
+a specific logging implementation.
+
JCL provides thin-wrapper Log
implementations for
+other logging tools, including
+Log4J,
+Avalon LogKit
+(the Avalon Framework's logging infrastructure),
+JDK 1.4, and an implementation of JDK 1.4 logging APIs (JSR-47) for pre-1.4
+systems.
+The interface maps closely to Log4J and LogKit.
+
+Familiarity with high-level details of the relevant Logging implementations is presumed. +
+
+As far as possible, JCL tries to be as unobtrusive as possible.
+In most cases, including the (full) commons-logging.jar
in the classpath
+should result in JCL configuring itself in a reasonable manner.
+There's a good chance that it'll guess (discover) your preferred logging system and you won't
+need to do any configuration of JCL at all!
+
+Note, however, that if you have a particular preference then providing a simple
+commons-logging.properties
file which specifies the concrete logging library to be
+used is recommended, since (in this case) JCL will log only to that system
+and will report any configuration problems that prevent that system being used.
+
+When no particular logging library is specified then JCL will silently ignore any logging library +that it finds but cannot initialise and continue to look for other alternatives. This is a deliberate +design decision; no application should fail to run because a "guessed" logging library cannot be +used. To ensure an exception is reported when a particular logging library cannot be used, use one +of the available JCL configuration mechanisms to force that library to be selected (ie disable +JCL's discovery process). +
+
+There are two base abstractions used by JCL: Log
+(the basic logger) and LogFactory
(which knows how to create Log
+instances). Specifying a particular Log implementation is very useful (whether that is
+one provided by commons-logging or a user-defined one). Specifying a
+LogFactory
implementation other than the default is a subject for
+advanced users only, so will not be addressed here.
+
+The default LogFactory
implementation uses the following discovery process
+to determine what type of Log
implementation it should use
+(the process terminates when the first positive match - in order - is found):
+
org.apache.commons.logging.Log
(for backwards compatibility to
+pre-1.0 versions of this API, an attribute
+org.apache.commons.logging.log
is also consulted).
+
+Configuration attributes can be set explicitly by java code, but they are more
+commonly set by placing a file named commons-logging.properties in the classpath.
+When such a file exists, every entry in the properties file becomes an "attribute"
+of the LogFactory. When there is more than one such file in the classpath, releases
+of commons-logging prior to 1.1 simply use the first one found. From release 1.1,
+each file may define a priority
key, and the file with
+the highest priority is used (no priority definition implies priority of zero).
+When multiple files have the same priority, the first one found is used.
+
+Defining this property in a commons-logging.properties file is the recommended +way of explicitly selecting a Log implementation. +
+org.apache.commons.logging.Log
(for backwards
+compatibility to pre-1.0 versions of this API, a system property
+org.apache.commons.logging.log
is also consulted).
+
+Consult the JCL javadocs for details of the various Log
+implementations that ship with the component. (The discovery process is also covered in more
+detail there.)
+
+The JCL SPI +can be configured to use different logging toolkits (see above). +JCL provides only a bridge for writing log messages. It does not (and will not) support any +sort of configuration API for the underlying logging system. +
++Configuration of the behavior of the JCL ultimately depends upon the +logging toolkit being used. Please consult the documentation for the chosen logging system. +
++JCL is NOT responsible for initialisation, configuration or shutdown of the underlying logging library. +In many cases logging libraries will automatically initialise/configure themselves when first used, and +need no explicit shutdown process. In these situations an application can simply use JCL and not depend +directly on the API of the underlying logging system in any way. However if the logging library being used +requires special initialisation, configuration or shutdown then some logging-library-specific code will +be required in the application. JCL simply forwards logging method calls to the correct underlying +implementation. When writing library code this issue is of course not relevant as the calling application +is responsible for handling such issues. +
++Log4J is a very commonly used logging implementation (as well as being the JCL primary default), +so a few details are presented herein to get the developer/integrator going. +Please see the Log4J Home for more details +on Log4J and it's configuration. +
++Configure Log4J using system properties and/or a properties file: +
+LogFactory.getLog(logger.name)
,
+used to create the logger instance. Priorities are:
+DEBUG
,
+INFO
,
+WARN
,
+ERROR
,
+or FATAL
.
+log4j.logger.org.apache.component=DEBUG
+will enable debug messages for all classes in both
+org.apache.component
+and
+org.apache.component.sub
.
+Likewise, setting
+log4j.logger.org.apache.component=DEBUG
+will enable debug message for all 'component' classes,
+but not for other Apache projects.
+ +To use the JCL SPI from a Java class, +include the following import statements: +
+
+import org.apache.commons.logging.Log;
+
+import org.apache.commons.logging.LogFactory;
+
+
+ +Note that some components using JCL may +either extend Log, +or provide a component-specific LogFactory implementation. +Review the component documentation for guidelines +on how commons-logging should be used in such components. +
+
+For each class definition, declare and initialize a
+log
attribute as follows:
+
+Note that for application code, declaring the log member as "static" is more +efficient as one Log object is created per class, and is recommended. +However this is not safe to do for a class which may be deployed via a "shared" +classloader in a servlet or j2ee container or similar environment. If the class +may end up invoked with different thread-context-classloader values set then the +member must not be declared static. The use of "static" should therefore +be avoided in code within any "library" type project. +
+
+Messages are logged to a logger, such as log
+by invoking a method corresponding to priority.
+The org.apache.commons.logging.Log
interface defines the
+following methods for use
+in writing log/trace messages to the log:
+
+Semantics for these methods are such that it is expected +that the severity, from highest to lowest, of messages is ordered as above. +
++In addition to the logging methods, the following are provided for code guards: +
+Prior to release 1.0.4, none of the standard Log implementations were + Serializable. If you are using such a release and have a Serializable class + with a member that is of type Log then it is necessary to declare + that member to be transient and to ensure that the value is restored on + deserialization. The recommended approach is to define a custom + readObject method on the class which reinitializes that member.
+In release 1.0.4, all standard Log implementations are Serializable. This + means that class members of type Log do not need to be declared transient; + on deserialization the Log object will "rebind" to the same category for the + same logging library. Note that the same underlying logging library will be + used on deserialization as was used in the original object, even if the + application the object was deserialized into is using a different logging + library. There is one exception; LogKitLogger (adapter for the Avalon LogKit + library) is not Serializable for technical reasons.
+Custom Log implementations not distributed with commons-logging may + or may not be Serializable. If you wish your code to be compatible with + any arbitrary log adapter then you should follow the advice given above + for pre-1.0.4 releases.
+
+The commons-logging.jar
file includes the JCL API, the default
+LogFactory
implementation and thin-wrapper Log
+implementations for
+Log4J,
+Avalon LogKit,
+the Avalon Framework's logging infrastructure,
+JDK 1.4, as well as an implementation of JDK 1.4 logging APIs (JSR-47) for
+pre-1.4 systems.
+
+In most cases, including commons-logging.jar
and your preferred
+logging implementation in the classpath should be all that is required to
+use JCL.
+
+The commons-logging-api.jar
file includes the JCL API and the
+default LogFactory
implementation as well as the built-in
+Log
implementations SimpleLog and NoOpLog. However it does not
+include the wrapper Log
implementations that require additional
+libraries such as Log4j
, Avalon
and
+Lumberjack
.
+
+This jar is intended for use by projects that recompile the commons-logging +source using alternate java environments, and cannot compile against all of +the optional libraries that the Apache release of commons-logging supports. +Because of the reduced dependencies of this jarfile, such projects should be +able to create an equivalent of this library with fewer difficulties. +
++This jar is also useful for build environments that automatically track +dependencies, and thus have difficulty with the concept that the main +commons-logging.jar has "optional" dependencies on various logging +implementations that can safely go unsatisfied at runtime. +
+
+The commons-logging-adapters.jar
file includes only adapters
+to third-party logging implementations, and none of the core commons-logging
+framework. As such, it cannot be used alone; either commons-logging.jar or
+commons-logging-api.jar must also be present in the classpath.
+
+This library will not often be used; it is only intended for situations where +a container has deployed commons-logging-api.jar in a shared classpath but a +webapp wants to bind logging to one of the external logging implementations +that the api jar does not include. In this situation, deploying the +commons-logging.jar file within the webapp can cause problems as this leads to +duplicates of the core commons-logging classes (Log, LogFactory, etc) in +the classpath which in turn can cause unpleasant ClassCastException exceptions +to occur. Deploying only the adapters avoids this problem. +
++Best practices for JCL are presented in two categories: +General and Enterprise. +The general principles are fairly clear.Enterprise practices are a bit more involved +and it is not always as clear as to why they are important. +
++Enterprise best-practice principles apply to middleware components +and tooling that is expected to execute in an "Enterprise" level +environment. +These issues relate to Logging as Internationalization, +and fault detection. +Enterprise requires more effort and planning, but are strongly encouraged (if not required) +in production level systems. Different corporate enterprises/environments have different +requirements, so being flexible always helps. +
+
+Code guards are typically used to guard code that
+only needs to execute in support of logging,
+that otherwise introduces undesirable runtime overhead
+in the general case (logging disabled).
+Examples are multiple parameters, or expressions (e.g. string + " more") for parameters.
+Use the guard methods of the form log.is<Priority>()
to verify
+that logging should be performed, before incurring the overhead of the logging method call.
+Yes, the logging methods will perform the same check, but only after resolving parameters.
+
+It is important to ensure that log message are +appropriate in content and severity. +The following guidelines are suggested: +
++By default the message priority should be no lower than info. +That is, by default debug message should not be seen in the logs. +
++The general rule in dealing with exceptions is to assume that +the user (developer using a tooling/middleware API) isn't going +to follow the rules. +Since any problems that result are going to be assigned to you, +it's in your best interest to be prepared with the proactive +tools necessary to demonstrate that your component works correctly, +or at worst that the problem can be analyzed from your logs. +For this discussion, we must make a distinction between different types of exceptions +based on what kind of boundaries they cross: +
+FileNotFoundException
+that cross API/SPI boundaries, and are exposed to the user of a component/toolkit.
+These are listed in the 'throws' clause of a method signature.
+NullPointerException
+that cross API/SPI boundaries, and are exposed to the user of a component/toolkit.
+These are runtime exceptions/error that are NOT
+listed in the 'throws' clause of a method signature.
+ComponentInternalError
.
+This ensures that the log contains a record of the root cause for
+future analysis in the event that the exception is not caught and
+logged/reported as expected by the user's code.
+ +You want to have exception/problem information available for +first-pass problem determination in a production level +enterprise application without turning on debug +as a default log level. There is simply too much information +in debug to be appropriate for day-to-day operations. +
++If more control is desired for the level of detail of these +'enterprise' exceptions, then consider creating a special +logger just for these exceptions: +
++This allows the 'enterprise' level information to be turned on/off explicitly +by most logger implementations. +
++NLS internationalization involves looking up messages from +a message file by a message key, and using that message for logging. +There are various tools in Java, and provided by other components, +for working with NLS messages. +
++NLS enabled components are particularly appreciated +(that's an open-source-correct term for 'required by corporate end-users' :-) +for tooling and middleware components. +
++NLS internationalization SHOULD be strongly considered for used for +fatal, error, warn, and info messages. +It is generally considered optional for debug and trace messages. +
+
+Perhaps more direct support for internationalizing log messages
+can be introduced in a future or alternate version of the Log
interface.
+
+The LogFactory
discovery process (see
+Configuration above) is a fairly expensive
+operation, so JCL certainly should not perform it each time user code
+invokes:
+
+Instead JCL caches the
+LogFactory
implementation created as a result of the discovery
+process and uses the cached factory to return Log
objects.
+Since in J2EE and similar multi-classloader environments, the result of the
+discovery process can vary depending on the thread context classloader
+(e.g. one webapp in a web container may be configured to use Log4j and
+another to use JDK 1.4 logging), JCL internally caches the
+LogFactory
instances in a static hashtable, keyed by classloader.
+
+While this approach is efficient, it can lead to memory leaks if container +implementors are not careful to call +
+ +
+whenever a classloader that has utilized JCL is undeployed. If
+release()
is not called, a reference to the undeployed
+classloader (and thus to all the classes loaded by it) will be
+held in LogFactory
's static hashtable.
+
+Beginning with JCL 1.1, LogFactory
caches factory implementations in a
+"WeakHashtable". This class is similar to java.util.WeakHashMap
in
+that it holds a WeakReference
to each key (but a strong reference
+to each value), thus allowing classloaders to be GC'd even if
+LogFactory.release()
is never invoked.
+
+Because WeakHashtable
depends on JDK 1.3+ features, it is dynamically
+loaded depending on the JVM version; when commons-logging is run on java versions
+prior to 1.3 the code defaults to a standard Hashtable instead.
+
+If a custom LogFactory implementation is used, however, then a
+WeakHashtable
alone can be insufficient to allow garbage collection
+of a classloader without a call to release
. If the abstract class
+LogFactory
is loaded by a parent classloader and a concrete
+subclass implementation of LogFactory
is loaded by a child
+classloader, the WeakHashtable's key is a weak reference to the TCCL (child
+classloader), but the value is a strong reference to the LogFactory instance,
+which in turn contains a strong reference to its class and thus loading
+classloader - the child classloader. This chain of strong references prevents
+the child loader from being garbage collected.
+
+If use of a custom LogFactory
subclass is desired, ensuring that
+the custom subclass is loaded by the same classloader as LogFactory
+will prevent problems. In normal deployments, the standard implementations
+of LogFactory
found in package org.apache.commons.logging.impl
+will be loaded by the same classloader that loads LogFactory
+itself, so use of the standard LogFactory
implementation
+should not pose problems. Alternatively, use the provided ServletContextCleaner
+to ensure this reference is explicitly released on webapp unload.
+
+JCL is designed to encourage extensions to be created that add functionality. +Typically, extensions to JCL fall into two categories: +
+Log
implementations that provide new bridges to logging systemsLogFactory
implementations that provide alternative discovery strategies
+
+When creating new implementations for Log
and LogFactory
,
+it is important to understand the implied contract between the factory
+and the log implementations:
+
+The JCL LogFactory implementation must assume responsibility for +either connecting/disconnecting to a logging toolkit, +or instantiating/initializing/destroying a logging toolkit. ++
+The JCL Log interface doesn't specify any exceptions to be handled, +the implementation must catch any exceptions. ++
+The JCL Log and LogFactory implementations must ensure +that any synchronization required by the logging toolkit +is met. ++
+The minimum requirement to integrate with another logger
+is to provide an implementation of the
+org.apache.commons.logging.Log
interface.
+In addition, an implementation of the
+org.apache.commons.logging.LogFactory
interface
+can be provided to meet
+specific requirements for connecting to, or instantiating, a logger.
+
+The default LogFactory
provided by JCL
+can be configured to instantiate a specific implementation of the
+org.apache.commons.logging.Log
interface
+by setting the property of the same name (org.apache.commons.logging.Log
).
+This property can be specified as a system property,
+or in the commons-logging.properties
file,
+which must exist in the CLASSPATH.
+
+If desired, the default implementation of the
+org.apache.commons.logging.LogFactory
+interface can be overridden,
+allowing the JDK 1.3 Service Provider discovery process
+to locate and create a LogFactory specific to the needs of the application.
+Review the Javadoc for the LogFactoryImpl.java
+for details.
+
+JCL is distributed with a very simple Log
implementation named
+org.apache.commons.logging.impl.SimpleLog
. This is intended to be a minimal
+implementation and those requiring a fully functional open source logging system are
+directed to Log4J.
+
+ SimpleLog
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:
trace
debug
info
warn
error
fatal
info
. trace
debug
info
warn
error
fatal
true
if you want the Log
instance name to be
+ included in output messages. Defaults to false
.true
if you want the last component of the name to be
+ included in output messages. Defaults to true
.true
if you want the current date and time
+ to be included in output messages. Default is false
.java.text.SimpleDateFormat
. If the format is not
+ specified or is invalid, the default format is used.
+ The default format is yyyy/MM/dd HH:mm:ss:SSS zzz
.
+In addition to looking for system properties with the names specified
+above, this implementation also checks for a class loader resource named
+"simplelog.properties"
, and includes any matching definitions
+from this resource (if it exists).
+
+See the FAQ document +on the commons-logging wiki site +
+When writing a library it is very useful to log information. However there +are many logging implementations out there, and a library cannot impose the use +of a particular one on the overall application that the library is a part of.
+ +The Logging package is an ultra-thin bridge between different logging +implementations. A library that uses the commons-logging API can be used with +any logging implementation at runtime. Commons-logging comes with support for a +number of popular logging implementations, and writing adapters for others is a +reasonably simple task.
+ +Applications (rather than libraries) may also choose to use commons-logging. +While logging-implementation independence is not as important for applications +as it is for libraries, using commons-logging does allow the application to +change to a different logging implementation without recompiling code. +
+Note that commons-logging does not attempt to initialise or terminate the underlying +logging implementation that is used at runtime; that is the responsibility of +the application. However many popular logging implementations do automatically +initialise themselves; in this case an application may be able to avoid +containing any code that is specific to the logging implementation used.
+ +The +Release Notes document the new features and bug fixes that have been +included in the latest release.
+ +The
+JavaDoc API documents for the latest release are available online.
+In particular, you should read the package overview of the org.apache.commons.logging
+package. In addition, there is a (short)
+User Guide.
The Wiki site has +the latest updates, an FAQ and much other useful information.
++Users needing to become experts or wanting to help develop JCL should +(in addition) consult the Tech Guide. +This gives short introductions to topics such as advanced class loading. +
++Binary and source distributions are available + here. +
+The main purpose of the 1.2 release is to drop support for Java 1.1.
+For a full list of changes since the 1.1.3 release, please refer to the + change-report.
+The 1.1.3 release only updates the Bundle-SymbolicName in the manifest + to "org.apache.commons.logging".
+For a full list of changes since the 1.1.1 release, please refer to the + change-report.
+The 1.1.2 release is a packaging of bug fixes since release 1.1.1.
+For the full details, see the release notes for this version.
++ This release is a minor update to the 1.1 release that fixes a number of bugs, and + resolves packaging issues for maven 1.x and maven 2.x users. +
+For the full details, see the release notes for this version.
+This release makes several changes that are intended to resolve issues that + have been encountered when using commons-logging in servlet containers or j2ee + containers where complex classpaths are present and multiple copies of + commons-logging libraries are present at different levels.
+This release also adds support for the TRACE level added to log4j in the + 1.2.12 release. In former commons-logging versions, the log.trace method + caused log4j to output the message at the DEBUG level (the lowest level + supported by log4j at that time).
+For the full details, see the release notes for this version.
++ Note: the 1.0.5 release was abandoned at alpha status. +
++ The next JCL release will be designated 1.1 since we feel this more + accurately reflects the improvements made to the codebase.
+The 1.0.4 release of commons-logging is a service release containing support + for both the 1.2.x and 1.3.x series of Log4J releases.
+The 1.0.3 release is primarily a maintenance and code cleanup release with minimal new features.
+The 1.0.2 release is a packaging of bug fixes since release 1.0.1.
+The 1.0.1 release is a packaging of bug fixes and minor enhancements since release 1.0.
+Regular builds of the current SVN HEAD code are made available. See the + wiki for details.
++ Commons Logging uses ASF JIRA for tracking issues. + See the Commons Logging JIRA project page. +
+ ++ To use JIRA you may need to create an account + (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically + created and you can use the Forgot Password + page to get a new password). +
+ ++ If you would like to report a bug, or raise an enhancement request with + Commons Logging please do the following: +
+ Please also remember these points: +
+ For more information on subversion and creating patches see the + Apache Contributors Guide. +
+ ++ You may also find these links useful: +
+ ++ The Apache Commons Logging test cases make extensive use of + sophisticated classloader configurations in order to simulate the + behaviour of various containers. It is difficult to run these tests + under Maven in the default "test" phase. As a consequence the tests + are executed via the failsafe-plugin as part of the "integration-test" + phase. The reports are available here. +
++ Commons Logging shares mailing lists with all the other + Commons Components. + To make it easier for people to only read messages related to components they are interested in, + the convention in Commons is to prefix the subject line of messages with the component's name, + for example: +
+ Questions related to the usage of Commons Logging should be posted to the
+ User List.
+
+ The Developer List
+ is for questions and discussion related to the development of Commons Logging.
+
+ Please do not cross-post; developers are also subscribed to the user list.
+
+ Note: please don't send patches or attachments to any of the mailing lists. + Patches are best handled via the Issue Tracking system. + Otherwise, please upload the file to a public server and include the URL in the mail. +
+
+ Please prefix the subject line of any messages for Commons Logging
+ with [logging] - thanks!
+
+
+
Name | +Subscribe | +Unsubscribe | +Post | +Archive | +Other Archives | +
---|---|---|---|---|---|
+ Commons User List
+ + Questions on using Commons Logging. + + |
+ Subscribe | +Unsubscribe | +Post | +mail-archives.apache.org | +markmail.org + www.mail-archive.com + news.gmane.org + |
+
+ Commons Developer List
+ + Discussion of development of Commons Logging. + + |
+ Subscribe | +Unsubscribe | +Post | +mail-archives.apache.org | +markmail.org + www.mail-archive.com + news.gmane.org + |
+
+ Commons Issues List
+ + Only for e-mails automatically generated by the issue tracking system. + + |
+ Subscribe | +Unsubscribe | +read only | +mail-archives.apache.org | +markmail.org + www.mail-archive.com + |
+
+ Commons Commits List
+ + Only for e-mails automatically generated by the source control sytem. + + |
+ Subscribe | +Unsubscribe | +read only | +mail-archives.apache.org | +markmail.org + www.mail-archive.com + |
+
+ Other mailing lists which you may find useful include: +
+ +Name | +Subscribe | +Unsubscribe | +Post | +Archive | +Other Archives | +
---|---|---|---|---|---|
+ Apache Announce List
+ + General announcements of Apache project releases. + + |
+ Subscribe | +Unsubscribe | +read only | +mail-archives.apache.org | +markmail.org + old.nabble.com + www.mail-archive.com + news.gmane.org + |
+
There is a great need for debugging and logging information inside of +Commons components such as HTTPClient and dbcp. However, there are many +logging APIs out there and it is difficult to choose among them. +
+ +The Logging package will be an ultra-thin bridge between different logging +libraries. Commons components may use the Logging JAR to remove +compile-time/runtime dependencies on any particular logging package, +and contributors may write Log implementations for the library of their choice. +
+ +The package shall create and maintain a package that provides extremely +basic logging functionality and bridges to other, more sophisticated logging +implementations. +
+ ++The package should : +
+Non-goals: +
Logging relies on: +
+ +logging
in the
+jakarta-commons
CVS repository.The initial committers on the Logging component shall be:
+ ++ This guide is aimed at describing the technologies that JCL developers and expert users + (and users who need to become experts) + should be familiar with. The aim is to give an understanding whilst being precise but brief. + Details which are not relevant for JCL have been suppressed. + References have been included. +
++ These topics are a little difficult and it's easy for even experienced developers to make + mistakes. We need you to help us get it right! Please submit corrections, comments, additional references + and requests for clarification + by either: +
++ TIA +
++ This is intended to present a guide to the process by which Java bytecode uses bytecode in other classes + from the perspective of the language and virtual machine specifications. The focus will be on deciding + which bytecode will be used (rather than the mechanics of the usage). It focuses on facts and terminology. +
++ The process is recursive: it is therefore difficult to pick a starting point. + Sun's documentation starts from the perspective of the startup of a new application. + This guide starts from the perspective of an executing application. +
++ During this discussion, please assume that each time that class is mentioned, + the comments applied equally well to interfaces. +
++ This document is targeted at Java 1.2 and above. +
++ (LangSpec 12.3.3) + The bytecode representation of a class contains symbolic names for other classes referenced. +
++ + In practical development terms: If a class is imported (either explicitly in the list of imports at the top of + the source file or implicitly through a fully qualified name in the source code) it is referenced symbolically. + +
++ (VMSpec 5.4.3) + Resolution of a symbolic reference occurs dynamically at runtime and is carried out by + the Java Virtual Machine. Resolution of a symbolic reference requires loading and linking of the new class. +
++ + Note: references are not statically resolved at compile time. + +
++ (VMSpec 2.17.2) + Loading is the name given to the process by which a binary form of a class is obtained + by the Java Virtual Machine. + Java classes are always loaded and linked dynamically by the Java Virtual Machine + (rather than statically by the compiler). +
++ + In practical development terms: + This means that the developer has no certain knowledge about the actual + bytecode that will be used to execute any external call (one made outside the class). This is determined only + at execution time and is affected by the way that the code is deployed. + +
++ (VMSpec 2.17.3) + Linking is the name used for combining the + binary form of a class into the Java Virtual Machine. This must happen before the class can be used. +
++ (VMSpec 2.17.3) + Linking is composed of verification, preparation and resolution (of symbolic references). + Flexibility is allowed over the timing of resolution. (Within limit) this may happen at any time after + preparation and before that reference is used. +
++ + In practical development terms: This means that different JVMs may realize that a reference cannot be + resolved at different times during execution. Consequently, the actual behaviour cannot be precisely predicted + without intimate knowledge of the JVM (on which the bytecode will be executed). + This makes it hard to give universal guidance to users. + +
+
+ (VMSpec 2.17.2)
+ The loading process is performed by a ClassLoader
.
+
+ (VMSpec 5.3) + A classloader may create a class either by delegation or by defining it directly. + The classloader that initiates loading of a class is known as the initiating loader. + The classloader that defines the class is known as the defining loader. +
++ + In practical terms: understanding and appreciating this distinction is crucial when debugging issues + concerning classloaders. + +
+
+ (VMSPEC 5.3)
+ The bootstrap is the base ClassLoader
supplied by the Java Virtual Machine.
+ All others are user (also known as application) ClassLoader
instances.
+
+
+ In practical development terms: The System classloader returned by Classloader.getSystemClassLoader()
+ will be either the bootstrap classloader or a direct descendant of the bootstrap classloader.
+ Only when debugging issues concerning the system classloader should there be any need to consider the detailed
+ differences between the bootstrap classloader and the system classloader.
+
+
+ (VMSpec 5.3) + At runtime, a class (or interface) is determined by its fully qualified name + and by the classloader that defines it. This is known as the class's runtime package. +
++ (VMSpec 5.4.4) + Only classes in the same runtime package are mutually accessible. +
++ + In practical development terms: two classes with the same symbolic name can only be used interchangeably + if they are defined by the same classloader. A classic symptom indicative of a classloader issue is that + two classes with the same fully qualified name are found to be incompatible during a method call. + This may happen when a member is expecting an interface which is (seemingly) implemented by a class + but the class is in a different runtime package after being defined by a different classloader. This is a + fundamental java language security feature. + +
++ (VMSpec 5.3) + The classloader which defines the class (whose reference is being resolved) is the one + used to initiate loading of the class referred to. +
++ + In practical development terms: This is very important to bear in mind when trying to solve classloader issues. + A classic misunderstanding is this: suppose class A defined by classloader C has a symbolic reference to + class B and further that when C initiates loading of B, this is delegated to classloader D which defines B. + Class B can now only resolve symbols that can be loaded by D, rather than all those which can be loaded by C. + This is a classic recipe for classloader problems. + +
+
+ When asked to load a class, a class loader may either define the class itself or delegate.
+ The base ClassLoader
class insists that every implementation has a parent class loader.
+ This delegation model therefore naturally forms a tree structure rooted in the bootstrap classloader.
+
+ Containers (i.e. applications such as servlet engines or application servers + that manage and provide support services for a number of "contained" applications + that run inside of them) often use complex trees to allow isolation of different applications + running within the container. This is particularly true of J2EE containers. +
++ When a classloader is asked to load a class, a question presents itself: should it immediately + delegate the loading to its parent (and thus only define those classes not defined by its parent) + or should it try to define it first itself (and only delegate to its parent those classes it does + not itself define). Classloaders which universally adopt the first approach are termed parent-first + and the second child-first. +
++ Note: the term child-first (though commonly used) is misleading. + A better term (and one which may be encountered on the mailing list) is parent-last. + This more accurately describes the actual process of classloading performed + by such a classloader. +
++ Parent-first loading has been the standard mechanism in the JDK + class loader, at least since Java 1.2 introduced hierarchical classloaders. +
++ Child-first classloading has the advantage of helping to improve isolation + between containers and the applications inside them. If an application + uses a library jar that is also used by the container, but the version of + the jar used by the two is different, child-first classloading allows the + contained application to load its version of the jar without affecting the + container. +
++ The ability for a servlet container to offer child-first classloading + is made available, as an option, by language in the servlet spec (Section + 9.7.2) that allows a container to offer child-first loading with + certain restrictions, such as not allowing replacement of java.* or + javax.* classes, or the container's implementation classes. +
++ Though child-first and parent-first are not the only strategies possible, + they are by far the most common. + All other strategies are rare. + However, it is not uncommon to be faced with a mixture of parent-first and child-first + classloaders within the same hierarchy. +
+
+ The class loader used to define a class is available programmatically by calling
+ the getClassLoader
method
+ on the class in question. This is often known as the class classloader.
+
+ Java 1.2 introduces a mechanism which allows code to access classloaders
+ which are not the class classloader or one of its parents.
+ A thread may have a class loader associated with it by its creator for use
+ by code running in the thread when loading resources and classes.
+ This classloader is accessed by the getContextClassLoader
+ method on Thread
. It is therefore often known as the context classloader.
+
+ Note that the quality and appropriateness of the context classloader depends on the + care with which the thread's owner manages it. +
+
+ The Javadoc for
+
+ Thread.setContextClassLoader
emphasizes the setting of the
+ context classloader as an aspect of thread creation. However, in many
+ applications the context classloader is not fixed at thread creation but
+ rather is changed throughout the life of a thread as thread execution moves
+ from one context to another. This usage of the context classloader is
+ particularly important in container applications.
+
+ For example, in a hypothetical servlet container, a pool of threads + is created to handle HTTP requests. When created these threads have their + context classloader set to a classloader that loads container classes. + After the thread is assigned to handle a request, container code parses + the request and then determines which of the deployed web applications + should handle it. Only when the container is about to call code associated + with a particular web application (i.e. is about to cross an "application + boundary") is the context classloader set to the classloader used to load + the web app's classes. When the web application finishes handling the + request and the call returns, the context classloader is set back to the + container classloader. +
+
+ In a properly managed container, changes in the context classloader are
+ made when code execution crosses an application boundary. When contained
+ application A
is handling a request, the context classloader
+ should be the one used to load A
's resources. When application
+ B
is handling a request, the context classloader should be
+ B
's.
+
+ While a contained application is handling a request, it is not + unusual for it to call system or library code loaded by the container. + For example, a contained application may wish to call a utility function + provided by a shared library. This kind of call is considered to be + within the "application boundary", so the context classloader remains + the contained application's classloader. If the system or library code + needs to load classes or other resources only visible to the contained + application's classloader, it can use the context classloader to access + these resources. +
++ If the context classloader is properly managed, system and library code + that can be accessed by multiple applications can not only use it to load + application-specific resources, but also can use it to detect which + application is making a call and thereby provided services tailored to the + caller. +
++ In practice, context classloaders vary in quality and issues sometimes arise + when using them. + The owner of the thread is responsible for setting the classloader. + If the context classloader is not set then it will default to the system + classloader. + Any container doing so will cause difficulties for any code using the context classloader. +
++ The owner is also at liberty to set the classloader as they wish. + Containers may set the context classloader so that it is neither a child nor a parent + of the classloader that defines the class using that loader. + Again, this will cause difficulties. +
++ Introduced in Java J2EE 1.3 + is a requirement for vendors to appropriately set the context classloader. + Section 6.2.4.8 (1.4 text): +
+ ++ This specification leaves quite a lot of freedom for vendors. + (As well as using unconventional terminology and containing the odd typo.) + It is a difficult passage (to say the least). +
+
+ Reflection cannot bypass restrictions imposed by the java language security model, but, by avoiding symbolic
+ references, reflection can be used to load classes which could not otherwise be loaded. Another ClassLoader
+ can be used to load a class and then reflection used to create an instance.
+
+ Recall that the runtime packaging is used to determine accessibility. + Reflection cannot be used to avoid basic java security. + Therefore, the runtime packaging becomes an issue when attempting to cast classes + created by reflection using other class loaders. + When using this strategy, various modes of failure are possible + when common class references are defined by the different class loaders. +
++ Reflection is often used with the context classloader. In theory, this allows a class defined in + a parent classloader to load any class that is loadable by the application. + In practice, this only works well when the context classloader is set carefully. +
++ JCL takes the view that different context class loader indicate boundaries between applications + running in a container environment. Isolation requires that JCL honours these boundaries + and therefore allows different isolated applications to configure their logging systems + independently. +
++ Performance dictates that symbolic references to these classes are present in the calling application code + (reflection would simply be too slow). Therefore, these classes must be loadable by the classloader + that loads the application code. +
++ Performance dictates that symbolic references to the logging systems are present in the implementation + classes (again, reflection would simply be too slow). So, for an implementation to be able to function, + it is necessary for the logging system to be loadable by the classloader that defines the implementing class. +
+
+ However, there is actually no reason why LogFactory
requires symbolic references to particular Log
+ implementations. Reflection can be used to load these from an appropriate classloader
+ without unacceptable performance degradation.
+ This is the strategy adopted by JCL.
+
+ JCL uses the context classloader to load the Log
implementation.
+
+Diagnostics is a feature introduced in JCL 1.1 as an aid to debugging problems
+with JCL configurations. When diagnostics are switched on, messages are logged
+to a stream (specified by the user) by the two main classes involved in discovery
+in JCL (LogFactory
and LogFactoryImpl
).
+
+Diagnostics are intended to be used in conjunction with the source. The source +contains numerous and lengthy comments. Often these are intended to help explain +the meaning of the messages. +
++Diagnostic logging is intended only to be used when debugging a problematic +configuration. It should be switched off for production. +
+
+Diagnostic logging is controlled through the system property
+org.apache.commons.logging.diagnostics.dest
. Setting the property value
+to the special strings STDOUT
or STDERR
(case-sensitive)
+will output messages to System.out
and System.err
respectively.
+Setting the property value to a valid file name will result in the messages being logged
+to that file.
+
+Diagnostics uses the concept of an Object ID (OID). This allows the identity of objects
+to be tracked without relying on useful toString
implementations.
+These are of the form:
+
+classname@system identity hash code
+
+
+The system identity hash code is found by calling System.identityHashCode()
+which should uniquely identify a particular instance. The classname is usually the fully qualified
+class name though in a few cases, org.apache.commons.logging.impl.LogFactoryImpl
may be
+shortened to LogFactoryImpl
to increase ease of reading. For example:
+
+sun.misc.Launcher$AppClassLoader@20120943
+LogFactoryImpl@1671711
+
+ +OIDs are intended to be used to cross-reference. They allow particular instances of classloaders +and JCL classes to be tracked in different contexts. This plays a vital role in building +up the understanding of the classloader environment required to diagnose JCL problems. +
++Each diagnostic message is prefixed with details of the relevant class in a standard format. +This takes the form: +
+
+[class-identifier from ClassLoader OID]
+
+ +ClassLoader OID is the OID of a classloader which loaded +the class issuing the message. +class-identifier identifies the object issuing the message. +
+
+In the case of
+LogFactory
, this is just LogFactory
. For example (line split):
+
+[LogFactory
+ from sun.misc.Launcher$AppClassLoader@20120943] BOOTSTRAP COMPLETED
+
+
+In the case of
+LogFactoryImpl
, the prefix is the instance OID. This can be cross referenced
+to discover the details of the TCCL used to manage this instance. For example (line split):
+
+[LogFactoryImpl@1671711
+ from sun.misc.Launcher$AppClassLoader@20120943] Instance created.
+
+
+Understanding the relationships between classloaders is vital when debugging JCL.
+At various points, JCL will print to the diagnostic log the hierarchy for important
+classloaders. This is obtained by walking the tree using getParent
.
+Each classloader is represented (visually) by an OID (to allow cross referencing)
+and the relationship indicated in child --> parent
fashion.
+For example (line split for easy reading):
+
+ClassLoader tree:java.net.URLClassLoader@3526198
+ --> sun.misc.Launcher$AppClassLoader@20120943 (SYSTEM)
+ --> sun.misc.Launcher$ExtClassLoader@11126876
+ --> BOOT
+
+ +Represents a hierarchy with four elements ending in the boot classloader. +
+
+Whenever the LogFactory
class is initialized, diagnostic messages about
+the classloader environment are logged. The content of each of these messages is prefixed by
+[ENV]
to help distinguish them. The extension directories, application classpath,
+details of the classloader (including the OID and toString
+value) used to load LogFactory
and the
+classloader tree for that classloader
+are logged.
+
+Many Sun classloaders have confusing toString
values. For example, the OID may be
+
+sun.misc.Launcher$AppClassLoader@20120943
+
+
+with a toString
value of
+
+sun.misc.Launcher$AppClassLoader@133056f
+
+ +Other classloader implementations may give very useful information (such as the local classpath). +
+
+Finally, once initialization is complete a BOOTSTRAP COMPLETED
message is issued.
+
+LogFactoryImpl
is the standard and default LogFactory
implementation.
+This section obviously only applies to configurations using this implementation.
+
+Before assigning a Log
instance, LogFactory
loads a
+LogFactory
implementation. The content is prefixed by [LOOKUP]
+for each diagnostic message logged by this process.
+
+The implementation used can vary per Thread context classloader (TCCL). If this is the first time +that a Log has been requested for a particular TCCL a new instance will be created. +
+
+Information of particular interest is logged at this stage. Details of the TCCL are logged
+allowing the OID later to be cross-referenced to the toString
value
+and the classloader tree. For example, the
+following log snippet details the TCCL (lines split):
+
+[LogFactory from sun.misc.Launcher$AppClassLoader@20120943]
+ [LOOKUP] LogFactory implementation requested for the first time for context
+ classloader java.net.URLClassLoader@3526198
+[LogFactory from sun.misc.Launcher$AppClassLoader@20120943]
+ [LOOKUP] java.net.URLClassLoader@3526198 == 'java.net.URLClassLoader@35ce36'
+[LogFactory from sun.misc.Launcher$AppClassLoader@20120943]
+ [LOOKUP] ClassLoader tree:java.net.URLClassLoader@3526198
+ --> sun.misc.Launcher$AppClassLoader@20120943 (SYSTEM)
+ --> sun.misc.Launcher$ExtClassLoader@11126876
+ --> BOOT
+
+
+The standard LogFactoryImpl
issues many diagnostic messages when discovering
+the Log
implementation to be used.
+
+During discovery, environment variables are loaded and values set. This content is prefixed by
+[ENV]
to make it easier to distinguish this material.
+
+The possible messages issued during discovery are numerous. To understand them, the source
+should be consulted. Attention should be paid to the classloader hierarchy trees for the
+classloader used to load LogFactory
and to the TCCL.
+
+ Some containers use a custom LogFactory
implementation to adapt JCL to their particular
+ logging system. This has some important consequences for the deployment of applications using JCL within
+ these containers.
+
+ Containers known to use this mechanism: +
++ Containers suspected to use this mechanism: +
++The Apache Commons team would be grateful if reports were posted to the development list +of other containers using a custom implementation. +
++ An exception is thrown by JCL with a message similar to: +
+
+ The chosen LogFactory implementation does not extend LogFactory. Please check your configuration.
+ (Caused by java.lang.ClassCastException: The application has specified that a custom LogFactory
+ implementation should be used but Class 'com.ibm.ws.commons.logging.TrLogFactory' cannot be converted
+ to 'org.apache.commons.logging.LogFactory'. The conflict is caused by the presence of multiple
+ LogFactory classes in incompatible classloaders. Background can be found in
+ http://commons.apache.org/logging/tech.html. If you have not explicitly specified a custom
+ LogFactory then it is likely that the container has set one without your knowledge.
+ In this case, consider using the commons-logging-adapters.jar file or specifying the standard
+ LogFactory from the command line. Help can be found @http://commons.apache.org/logging.
+
+
+ This is a WebSphere example so the name of the custom LogFactory is
+ com.ibm.ws.commons.logging.TrLogFactory
. For other containers, this class name will
+ differ.
+
+ A custom LogFactory
implementation can only be used if the implementation class loaded
+ dynamically at runtime can be cast to the LogFactory
class that loaded it. There are
+ several ways in which this cast can fail. The most obvious is that the source code may not actually
+ extend LogFactory
. The source may be compatible but if the LogFactory
class
+ against which the source is compiled is not binary compatible then the cast will also fail.
+
+ There is also another more unusual way in which this cast can fail: even when the binary is compatible,
+ the implementation class loaded at runtime may be linked to a different instance of the
+ LogFactory
class. For more information, see the tech guide.
+
+ This situation may be encountered in containers which use a custom LogFactory
implementation.
+ The implementation will typically be provided in a shared, high level classloader together with JCL.
+ When an application classloader contains LogFactory
, the implementation will be loaded
+ from that higher level classloader. The implementation class will be linked to the LogFactory
+ class loaded by the higher level classloader. Even if the
+ LogFactory
implementations are binary compatible, since they are loaded by different classloaders
+ the two LogFactory
Class instances are not equal and so the cast must fail.
+
+The policy adopted by JCL in this situation is to re-throw this exception. Additional information
+is included in the message to help diagnosis. The reasoning behind this choice is that a
+particular LogFactory
implementation has been actively specified and this
+choice should not be ignored. This policy has unfortunate consequences when running in
+containers which have custom implementations: the above runtime exception may be thrown
+under certain classloading policies without the user knowingly specifying a custom
+implementation.
+
+ There are various ways to fix this problem. Which fix is right depends on the circumstances. +
+
+ If you are happy using another classloading policy for the application, select a
+ classloading policy which ensures that LogFactory
will be loaded from the
+ shared classloader containing the custom implementation.
+
+ If you want to bypass the container adaption mechanism then set the appropriate system property + to the default value when the container is started: +
+
+ -Dorg.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
+
+ + If you want to continue to use the default container mechanism then: +
+LogFactory
.
+ + If you encounter difficulties when applying the fixes recommended, please turn on + diagnostics and consult the logs. +
++ Because commons-logging is such a fundamental library, some containers modify the way + in which classloading behaves for commons-logging classes. +
++ At the current date, Tomcat 5.5.16 is the current release. All releases from version + 4.1.x through 5.5.16 have a startup process that places jarfile + ${tomcat.home}/bin/commons-logging-api.jar in the system classpath and then + prevents any webapp from overriding the classes in that jarfile. Effectively, all + webapps behave as if "parent-first" classloading were enabled for those classes. +
++ This has some benefits; in particular it means that there are no problems in + these Tomcat versions with having multiple copies of the commons-logging Log + interface in the classpath (which avoids the "Log does not implement Log" + problem described elsewhere). +
++ However it also means that no webapp can override the core commons-logging + classes by including an updated commons-logging jarfile in WEB-INF/lib; any + class already loaded via the container takes priority. In particular, as + Tomcat bundles logging 1.0.4 only, the new diagnostics and memory-leak-prevention + features of the 1.1 release will not be available unless the container's + library version is updated. +
++ Because the commons-logging-api.jar in the container does not contain any + log-library-adapter classes, updated behaviour for these will be + seen when logging 1.1 is bundled in WEB-INF/lib. In particular, the + support for log4j's TRACE level will take effect without having to update + the container. +
++ If you do wish to update Tomcat's version of commons-logging, then you + must use the commons-logging-1.1-api jar only, not the full jar. + Classes in the webapp cannot override classes loaded from the system + classpath set up during Tomcat's startup process, and logging adapters + can only see their matching concrete logging library if that library is + available in the same classpath. Bundling the full commons-logging jarfile + (with adapters) into the system classpath therefore means that logging + libraries (eg log4j) within WEB-INF/lib are not accessible. +
++ Note that the behaviour described here only applies if the standard Tomcat + startup process is run. When Tomcat is embedded in a larger + framework (eg run embedded within an IDE) this may not apply. +
++ The JBoss Application Server can be configured to prevent deployed + code from overriding classes higher in the hierarchy, effectively + forcing "parent-first" behaviour for selected classes. By default, + commons-logging is in this list (at least for some JBoss versions + starting with 4.0.2), and therefore including an updated version + of commons-logging in WEB-INF/lib or similar will have no effect. + See the JBoss classloading documentation for more details. +
++ As more information becomes available on this topic, it may be added + to the commons-logging wiki site. +
++ * This method ensures that the appropriate system property is defined + * to force the LogFactory class to use the AltHashtable class as its + * Hashtable implementation for storing factories in. + *
+ * This does make the assumption that whatever JVM we are running in + * doesn't initialise classes until they are actually referenced (ie the + * LogFactory class hasn't been initialised before this method is called). + * This is true of all JVMs I know of; and if it isn't then this test will + * fail and someone will tell us. + */ + public void setUp() { + System.setProperty( + "org.apache.commons.logging.LogFactory.HashtableImpl", + AltHashtable.class.getName()); + } + + /** + * Verify that initialising the LogFactory class will cause it + * to instantiate an object of type specified in system property + * "org.apache.commons.logging.LogFactory.HashtableImpl". + */ + public void testType() { + // Here, the reference to the LogFactory class should cause the + // class to be loaded and initialised. It will see the property + // set and use the AltHashtable class. If other tests in this + // class have already been run within the same classloader then + // LogFactory will already have been initialised, but that + // doesn't change the effectiveness of this test. + assertTrue(LogFactory.factories instanceof AltHashtable); + } + + /** + * Verify that when LogFactory sees a context-classloader for the + * first time that it creates a new entry in the LogFactory.factories + * hashmap. In particular, this checks that this process works ok when + * a system property has been used to specify an alternative Hashtable + * implementation for LogFactory to use. + */ + public void testPutCalled() throws Exception { + AltHashtable.lastKey = null; + AltHashtable.lastValue = null; + + LogFactory.getLog(AltHashtableTestCase.class); + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + assertEquals(contextLoader, AltHashtable.lastKey); + assertNotNull(AltHashtable.lastValue); + } +} diff --git a/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java b/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java new file mode 100644 index 0000000..eda6e3f --- /dev/null +++ b/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; +import java.util.Hashtable; + +/** + * Tests behaviour when the property is misconfigured. + */ +public class BadHashtablePropertyTestCase extends TestCase { + + public void testType() { + assertTrue(LogFactory.factories instanceof Hashtable); + } + + public void testPutCalled() throws Exception { + LogFactory.getLog(BadHashtablePropertyTestCase.class); + } +} diff --git a/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java b/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java new file mode 100644 index 0000000..dfadab2 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + +/** + * Tests the basic logging operations to ensure that they all function + * without exception failure. In other words, that they do no fail by + * throwing exceptions. + * This is the minimum requirement for any well behaved logger + * and so this test should be run for each kind. + */ +public class BasicOperationsTestCase extends TestCase +{ + public void testIsEnabledClassLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class); + executeIsEnabledTest(log); + } + + public void testIsEnabledNamedLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName()); + executeIsEnabledTest(log); + } + + public void executeIsEnabledTest(Log log) + { + try + { + log.isTraceEnabled(); + log.isDebugEnabled(); + log.isInfoEnabled(); + log.isWarnEnabled(); + log.isErrorEnabled(); + log.isFatalEnabled(); + } + catch (Throwable t) + { + t.printStackTrace(); + fail("Exception thrown: " + t); + } + } + + public void testMessageWithoutExceptionClassLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class); + executeMessageWithoutExceptionTest(log); + } + + public void testMessageWithoutExceptionNamedLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName()); + executeMessageWithoutExceptionTest(log); + } + + public void executeMessageWithoutExceptionTest(Log log) + { + try + { + log.trace("Hello, Mum"); + log.debug("Hello, Mum"); + log.info("Hello, Mum"); + log.warn("Hello, Mum"); + log.error("Hello, Mum"); + log.fatal("Hello, Mum"); + } + catch (Throwable t) + { + t.printStackTrace(); + fail("Exception thrown: " + t); + } + } + + public void testMessageWithExceptionClassLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class); + executeMessageWithExceptionTest(log); + } + + public void testMessageWithExceptionNamedLog() + { + Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName()); + executeMessageWithExceptionTest(log); + } + + public void executeMessageWithExceptionTest(Log log) + { + try + { + log.trace("Hello, Mum", new ArithmeticException()); + log.debug("Hello, Mum", new ArithmeticException()); + log.info("Hello, Mum", new ArithmeticException()); + log.warn("Hello, Mum", new ArithmeticException()); + log.error("Hello, Mum", new ArithmeticException()); + log.fatal("Hello, Mum", new ArithmeticException()); + } + catch (Throwable t) + { + t.printStackTrace(); + fail("Exception thrown: " + t); + } + } +} diff --git a/src/test/java/org/apache/commons/logging/DummyException.java b/src/test/java/org/apache/commons/logging/DummyException.java new file mode 100644 index 0000000..2f5fdd4 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/DummyException.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +/** + * Dummy exception that unit tests create instances of when they want to test + * logging of an Exception object. + */ +public class DummyException extends Exception { + private static final long serialVersionUID = 1L; + public DummyException() { + // super("Dummy Exception for unit testing"); + } +} diff --git a/src/test/java/org/apache/commons/logging/LoadTestCase.java b/src/test/java/org/apache/commons/logging/LoadTestCase.java new file mode 100644 index 0000000..ac3365b --- /dev/null +++ b/src/test/java/org/apache/commons/logging/LoadTestCase.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + +/** + * testcase to emulate container and application isolated from container + * @author baliuka + * @version $Id: LoadTestCase.java 1432587 2013-01-13 11:11:32Z tn $ + */ +public class LoadTestCase extends TestCase{ + //TODO: need some way to add service provider packages + static private String LOG_PCKG[] = {"org.apache.commons.logging", + "org.apache.commons.logging.impl"}; + + /** + * A custom classloader which "duplicates" logging classes available + * in the parent classloader into itself. + *
+ * When asked to load a class that is in one of the LOG_PCKG packages, + * it loads the class itself (child-first). This class doesn't need + * to be set up with a classpath, as it simply uses the same classpath + * as the classloader that loaded it. + */ + static class AppClassLoader extends ClassLoader{ + + java.util.Map classes = new java.util.HashMap(); + + AppClassLoader(ClassLoader parent){ + super(parent); + } + + private Class def(String name)throws ClassNotFoundException{ + + Class result = (Class)classes.get(name); + if(result != null){ + return result; + } + + try{ + + ClassLoader cl = this.getClass().getClassLoader(); + String classFileName = name.replace('.','/') + ".class"; + java.io.InputStream is = cl.getResourceAsStream(classFileName); + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + + while(is.available() > 0){ + out.write(is.read()); + } + + byte data [] = out.toByteArray(); + + result = super.defineClass(name, data, 0, data.length ); + classes.put(name,result); + + return result; + + }catch(java.io.IOException ioe){ + + throw new ClassNotFoundException( name + " caused by " + + ioe.getMessage() ); + } + + + } + + // not very trivial to emulate we must implement "findClass", + // but it will delegete to junit class loder first + public Class loadClass(String name)throws ClassNotFoundException{ + + //isolates all logging classes, application in the same classloader too. + //filters exeptions to simlify handling in test + for(int i = 0; i < LOG_PCKG.length; i++ ){ + if( name.startsWith( LOG_PCKG[i] ) && + name.indexOf("Exception") == -1 ){ + return def(name); + } + } + return super.loadClass(name); + } + + } + + + /** + * Call the static setAllowFlawedContext method on the specified class + * (expected to be a UserClass loaded via a custom classloader), passing + * it the specified state parameter. + */ + private void setAllowFlawedContext(Class c, String state) throws Exception { + Class[] params = {String.class}; + java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params); + m.invoke(null, new Object[] {state}); + } + + /** + * Test what happens when we play various classloader tricks like those + * that happen in web and j2ee containers. + *
+ * Note that this test assumes that commons-logging.jar and log4j.jar + * are available via the system classpath. + */ + public void testInContainer()throws Exception{ + + //problem can be in this step (broken app container or missconfiguration) + //1. Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + //2. Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + // we expect this : + // 1. Thread.currentThread().setContextClassLoader(appLoader); + // 2. Thread.currentThread().setContextClassLoader(null); + + // Context classloader is same as class calling into log + Class cls = reload(); + Thread.currentThread().setContextClassLoader(cls.getClassLoader()); + execute(cls); + + // Context classloader is the "bootclassloader". This is technically + // bad, but LogFactoryImpl.ALLOW_FLAWED_CONTEXT defaults to true so + // this test should pass. + cls = reload(); + Thread.currentThread().setContextClassLoader(null); + execute(cls); + + // Context classloader is the "bootclassloader". This is same as above + // except that ALLOW_FLAWED_CONTEXT is set to false; an error should + // now be reported. + cls = reload(); + Thread.currentThread().setContextClassLoader(null); + try { + setAllowFlawedContext(cls, "false"); + execute(cls); + fail("Logging config succeeded when context classloader was null!"); + } catch(LogConfigurationException ex) { + // expected; the boot classloader doesn't *have* JCL available + } + + // Context classloader is the system classloader. + // + // This is expected to cause problems, as LogFactoryImpl will attempt + // to use the system classloader to load the Log4JLogger class, which + // will then be unable to cast that object to the Log interface loaded + // via the child classloader. However as ALLOW_FLAWED_CONTEXT defaults + // to true this test should pass. + cls = reload(); + Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + execute(cls); + + // Context classloader is the system classloader. This is the same + // as above except that ALLOW_FLAWED_CONTEXT is set to false; an error + // should now be reported. + cls = reload(); + Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + try { + setAllowFlawedContext(cls, "false"); + execute(cls); + fail("Error: somehow downcast a Logger loaded via system classloader" + + " to the Log interface loaded via a custom classloader"); + } catch(LogConfigurationException ex) { + // expected + } + } + + /** + * Load class UserClass via a temporary classloader which is a child of + * the classloader used to load this test class. + */ + private Class reload()throws Exception{ + + Class testObjCls = null; + + AppClassLoader appLoader = new AppClassLoader( + this.getClass().getClassLoader()); + try{ + + testObjCls = appLoader.loadClass(UserClass.class.getName()); + + }catch(ClassNotFoundException cnfe){ + throw cnfe; + }catch(Throwable t){ + t.printStackTrace(); + fail("AppClassLoader failed "); + } + + assertTrue( "app isolated" ,testObjCls.getClassLoader() == appLoader ); + + + return testObjCls; + + + } + + + private void execute(Class cls)throws Exception{ + + cls.newInstance(); + + } + + public void setUp() { + // save state before test starts so we can restore it when test ends + origContextClassLoader = Thread.currentThread().getContextClassLoader(); + } + + public void tearDown() { + // restore original state so a test can't stuff up later tests. + Thread.currentThread().setContextClassLoader(origContextClassLoader); + } + + private ClassLoader origContextClassLoader; +} diff --git a/src/test/java/org/apache/commons/logging/LogTestCase.java b/src/test/java/org/apache/commons/logging/LogTestCase.java new file mode 100644 index 0000000..0ac2a6f --- /dev/null +++ b/src/test/java/org/apache/commons/logging/LogTestCase.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + + +public class LogTestCase extends AbstractLogTest +{ + + public Log getLogObject() + { + /** + * Pickup whatever is found/configured! + */ + return LogFactory.getLog(this.getClass().getName()); + } + +} diff --git a/src/test/java/org/apache/commons/logging/NullClassLoaderTestCase.java b/src/test/java/org/apache/commons/logging/NullClassLoaderTestCase.java new file mode 100644 index 0000000..0e84c1c --- /dev/null +++ b/src/test/java/org/apache/commons/logging/NullClassLoaderTestCase.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import junit.framework.TestCase; + +/** + * Test cases for situations where getClassLoader or getContextClassLoader + * return null. This can happen when using JDK 1.1. It can also happen when + * JCL is deployed via the bootclassloader - something that could be done when + * using java in embedded systems. + */ +public class NullClassLoaderTestCase extends TestCase { + + //---------------------- unit tests --------------------------------- + + /** + * This tests that when getContextClassLoader returns null, the + * LogFactory.getLog(name) method still correctly returns the same + * log object when called multiple times with the same name. + */ + public void testSameLogObject() throws Exception { + // unfortunately, there just isn't any way to emulate JCL being + // accessable via the null classloader in "standard" systems, so + // we can't include this test in our standard unit tests. + } +} diff --git a/src/test/java/org/apache/commons/logging/PathableClassLoader.java b/src/test/java/org/apache/commons/logging/PathableClassLoader.java new file mode 100644 index 0000000..830f652 --- /dev/null +++ b/src/test/java/org/apache/commons/logging/PathableClassLoader.java @@ -0,0 +1,436 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A ClassLoader which sees only specified classes, and which can be + * set to do parent-first or child-first path lookup. + *
+ * Note that this classloader is not "industrial strength"; users + * looking for such a class may wish to look at the Tomcat sourcecode + * instead. In particular, this class may not be threadsafe. + *
+ * Note that the ClassLoader.getResources method isn't overloaded here. + * It would be nice to ensure that when child-first lookup is set the + * resources from the child are returned earlier in the list than the + * resources from the parent. However overriding this method isn't possible + * as the java 1.4 version of ClassLoader declares this method final + * (though the java 1.5 version has removed the final qualifier). As the + * ClassLoader javadoc doesn't specify the order in which resources + * are returned, it's valid to return the resources in any order (just + * untidy) so the inherited implementation is technically ok. + */ + +public class PathableClassLoader extends URLClassLoader { + + private static final URL[] NO_URLS = new URL[0]; + + /** + * A map of package-prefix to ClassLoader. Any class which is in + * this map is looked up via the specified classloader instead of + * the classpath associated with this classloader or its parents. + *
+ * This is necessary in order for the rest of the world to communicate + * with classes loaded via a custom classloader. As an example, junit + * testcases which are loaded via a custom classloader needs to see + * the same junit classes as the code invoking the testcase, otherwise + * they can't pass result objects back. + *
+ * Normally, only a classloader created with a null parent needs to + * have any lookasides defined. + */ + private HashMap lookasides = null; + + /** + * See setParentFirst. + */ + private boolean parentFirst = true; + + /** + * Constructor. + *
+ * Often, null is passed as the parent, ie the parent of the new + * instance is the bootloader. This ensures that the classpath is + * totally clean; nothing but the standard java library will be + * present. + *
+ * When using a null parent classloader with a junit testcase, it *is* + * necessary for the junit library to also be visible. In this case, it + * is recommended that the following code be used: + *
+ * pathableLoader.useExplicitLoader( + * "junit.", + * junit.framework.Test.class.getClassLoader()); + *+ * Note that this works regardless of whether junit is on the system + * classpath, or whether it has been loaded by some test framework that + * creates its own classloader to run unit tests in (eg maven2's + * Surefire plugin). + */ + public PathableClassLoader(ClassLoader parent) { + super(NO_URLS, parent); + } + + /** + * Allow caller to explicitly add paths. Generally this not a good idea; + * use addLogicalLib instead, then define the location for that logical + * library in the build.xml file. + */ + public void addURL(URL url) { + super.addURL(url); + } + + /** + * Specify whether this classloader should ask the parent classloader + * to resolve a class first, before trying to resolve it via its own + * classpath. + *
+ * Checking with the parent first is the normal approach for java, but + * components within containers such as servlet engines can use + * child-first lookup instead, to allow the components to override libs + * which are visible in shared classloaders provided by the container. + *
+ * Note that the method getResources always behaves as if parentFirst=true, + * because of limitations in java 1.4; see the javadoc for method + * getResourcesInOrder for details. + *
+ * This value defaults to true. + */ + public void setParentFirst(boolean state) { + parentFirst = state; + } + + /** + * For classes with the specified prefix, get them from the system + * classpath which is active at the point this method is called. + *
+ * This method is just a shortcut for + *
+ * useExplicitLoader(prefix, ClassLoader.getSystemClassLoader()); + *+ *
+ * Of course, this assumes that the classes of interest are already + * in the classpath of the system classloader. + */ + public void useSystemLoader(String prefix) { + useExplicitLoader(prefix, ClassLoader.getSystemClassLoader()); + + } + + /** + * Specify a classloader to use for specific java packages. + *
+ * The specified classloader is normally a loader that is NOT + * an ancestor of this classloader. In particular, this loader + * may have the bootloader as its parent, but be configured to + * see specific other classes (eg the junit library loaded + * via the system classloader). + *
+ * The differences between using this method, and using + * addLogicalLib are: + *
+ * If there is no system property, but the classloader that loaded + * this class is a URLClassLoader then the set of URLs that the + * classloader uses for its classpath is scanned; any jar in the + * URL set whose name starts with the specified string is added to + * the classpath managed by this instance. + *
+ * Using logical library names allows the calling code to specify its + * desired classpath without knowing the exact location of the necessary + * classes. + */ + public void addLogicalLib(String logicalLib) { + // first, check the system properties + String filename = System.getProperty(logicalLib); + if (filename != null) { + try { + URL libUrl = new File(filename).toURL(); + addURL(libUrl); + return; + } catch(java.net.MalformedURLException e) { + throw new UnknownError( + "Invalid file [" + filename + "] for logical lib [" + logicalLib + "]"); + } + } + + // now check the classpath for a similar-named lib + URL libUrl = libFromClasspath(logicalLib); + if (libUrl != null) { + addURL(libUrl); + return; + } + + // lib not found + throw new UnknownError( + "Logical lib [" + logicalLib + "] is not defined" + + " as a System property."); + } + + /** + * If the classloader that loaded this class has this logical lib in its + * path, then return the matching URL otherwise return null. + *
+ * This only works when the classloader loading this class is an instance + * of URLClassLoader and thus has a getURLs method that returns the classpath + * it uses when loading classes. However in practice, the vast majority of the + * time this type is the classloader used. + *
+ * The classpath of the classloader for this instance is scanned, and any
+ * jarfile in the path whose name starts with the logicalLib string is
+ * considered a match. For example, passing "foo" will match a url
+ * of file:///some/where/foo-2.7.jar
.
+ *
+ * When multiple classpath entries match the specified logicalLib string,
+ * the one with the shortest filename component is returned. This means that
+ * if "foo-1.1.jar" and "foobar-1.1.jar" are in the path, then a logicalLib
+ * name of "foo" will match the first entry above.
+ */
+ private URL libFromClasspath(String logicalLib) {
+ ClassLoader cl = this.getClass().getClassLoader();
+ if (cl instanceof URLClassLoader == false) {
+ return null;
+ }
+
+ URLClassLoader ucl = (URLClassLoader) cl;
+ URL[] path = ucl.getURLs();
+ URL shortestMatch = null;
+ int shortestMatchLen = Integer.MAX_VALUE;
+ for(int i=0; i
+ * For each explicitly mapped package prefix, if the name matches the
+ * prefix associated with that entry then attempt to load the class via
+ * that entries' classloader.
+ */
+ protected Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+ // just for performance, check java and javax
+ if (name.startsWith("java.") || name.startsWith("javax.")) {
+ return super.loadClass(name, resolve);
+ }
+
+ if (lookasides != null) {
+ for(Iterator i = lookasides.entrySet().iterator(); i.hasNext(); ) {
+ Map.Entry entry = (Map.Entry) i.next();
+ String prefix = (String) entry.getKey();
+ if (name.startsWith(prefix) == true) {
+ ClassLoader loader = (ClassLoader) entry.getValue();
+ Class clazz = Class.forName(name, resolve, loader);
+ return clazz;
+ }
+ }
+ }
+
+ if (parentFirst) {
+ return super.loadClass(name, resolve);
+ } else {
+ // Implement child-first.
+ //
+ // It appears that the findClass method doesn't check whether the
+ // class has already been loaded. This seems odd to me, but without
+ // first checking via findLoadedClass we can get java.lang.LinkageError
+ // with message "duplicate class definition" which isn't good.
+
+ try {
+ Class clazz = findLoadedClass(name);
+ if (clazz == null) {
+ clazz = super.findClass(name);
+ }
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ } catch(ClassNotFoundException e) {
+ return super.loadClass(name, resolve);
+ }
+ }
+ }
+
+ /**
+ * Same as parent class method except that when parentFirst is false
+ * the resource is looked for in the local classpath before the parent
+ * loader is consulted.
+ */
+ public URL getResource(String name) {
+ if (parentFirst) {
+ return super.getResource(name);
+ } else {
+ URL local = super.findResource(name);
+ if (local != null) {
+ return local;
+ }
+ return super.getResource(name);
+ }
+ }
+
+ /**
+ * Emulate a proper implementation of getResources which respects the
+ * setting for parentFirst.
+ *
+ * Note that it's not possible to override the inherited getResources, as
+ * it's declared final in java1.4 (thought that's been removed for 1.5).
+ * The inherited implementation always behaves as if parentFirst=true.
+ */
+ public Enumeration getResourcesInOrder(String name) throws IOException {
+ if (parentFirst) {
+ return super.getResources(name);
+ } else {
+ Enumeration localUrls = super.findResources(name);
+
+ ClassLoader parent = getParent();
+ if (parent == null) {
+ // Alas, there is no method to get matching resources
+ // from a null (BOOT) parent classloader. Calling
+ // ClassLoader.getSystemClassLoader isn't right. Maybe
+ // calling Class.class.getResources(name) would do?
+ //
+ // However for the purposes of unit tests, we can
+ // simply assume that no relevant resources are
+ // loadable from the parent; unit tests will never be
+ // putting any of their resources in a "boot" classloader
+ // path!
+ return localUrls;
+ }
+ Enumeration parentUrls = parent.getResources(name);
+
+ ArrayList localItems = toList(localUrls);
+ ArrayList parentItems = toList(parentUrls);
+ localItems.addAll(parentItems);
+ return Collections.enumeration(localItems);
+ }
+ }
+
+ /**
+ *
+ * Clean implementation of list function of
+ * {@link java.utils.Collection} added in JDK 1.4
+ * @param en
+ * For tests that need to control exactly what the classloader hierarchy is
+ * like when the test is run, something like the following is recommended:
+ *
+ * The use of PathableClassLoader is not required to use this class, but it
+ * is expected that using the two classes together is common practice.
+ *
+ * This class will run each test methods within the specified TestCase using
+ * the specified context classloader and system classloader. If different
+ * tests within the same class require different context classloaders,
+ * then the context classloader passed to the constructor should be the
+ * "lowest" one available, and tests that need the context set to some parent
+ * of this "lowest" classloader can call
+ *
+ * This class does not provide facilities for manipulating system properties;
+ * tests that need specific system properties can simply set them in the
+ * fixture or at the start of a test method.
+ *
+ * Important! When the test case is run, "this.getClass()" refers of
+ * course to the Class object passed to the constructor of this class - which
+ * is different from the class whose suite() method was executed to determine
+ * the classpath. This means that the suite method cannot communicate with
+ * the test cases simply by setting static variables (for example to make the
+ * custom classloaders available to the test methods or setUp/tearDown fixtures).
+ * If this is really necessary then it is possible to use reflection to invoke
+ * static methods on the class object passed to the constructor of this class.
+ *
+ *
+ * This class cannot control the system classloader (ie what method
+ * ClassLoader.getSystemClassLoader returns) because Java provides no
+ * mechanism for setting the system classloader. In this case, the only
+ * option is to invoke the unit test in a separate JVM with the appropriate
+ * settings.
+ *
+ * The effect of using this approach in a system that uses junit's
+ * "reloading classloader" behaviour is unknown. This junit feature is
+ * intended for junit GUI apps where a test may be run multiple times
+ * within the same JVM - and in particular, when the .class file may
+ * be modified between runs of the test. How junit achieves this is
+ * actually rather weird (the whole junit code is rather weird in fact)
+ * and it is not clear whether this approach will work as expected in
+ * such situations.
+ */
+public class PathableTestSuite extends TestSuite {
+
+ /**
+ * The classloader that should be set as the context classloader
+ * before each test in the suite is run.
+ */
+ private final ClassLoader contextLoader;
+
+ /**
+ * Constructor.
+ *
+ * @param testClass is the TestCase that is to be run, as loaded by
+ * the appropriate ClassLoader.
+ *
+ * @param contextClassLoader is the loader that should be returned by
+ * calls to Thread.currentThread.getContextClassLoader from test methods
+ * (or any method called by test methods).
+ */
+ public PathableTestSuite(Class testClass, ClassLoader contextClassLoader) {
+ super(testClass);
+ contextLoader = contextClassLoader;
+ }
+
+ /**
+ * This method is invoked once for each Test in the current TestSuite.
+ * Note that a Test may itself be a TestSuite object (ie a collection
+ * of tests).
+ *
+ * The context classloader and system properties are saved before each
+ * test, and restored after the test completes to better isolate tests.
+ */
+ public void runTest(Test test, TestResult result) {
+ ClassLoader origContext = Thread.currentThread().getContextClassLoader();
+ Properties oldSysProps = (Properties) System.getProperties().clone();
+ try {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ test.run(result);
+ } finally {
+ System.setProperties(oldSysProps);
+ Thread.currentThread().setContextClassLoader(origContext);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/SimpleLogTestCase.java b/src/test/java/org/apache/commons/logging/SimpleLogTestCase.java
new file mode 100644
index 0000000..cc2fceb
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/SimpleLogTestCase.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+import org.apache.commons.logging.impl.SimpleLog;
+
+public class SimpleLogTestCase extends AbstractLogTest
+{
+ public Log getLogObject()
+ {
+ return new SimpleLog(this.getClass().getName());
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/UserClass.java b/src/test/java/org/apache/commons/logging/UserClass.java
new file mode 100644
index 0000000..9013133
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/UserClass.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.impl.LogFactoryImpl;
+
+public class UserClass {
+
+ /**
+ * Set the ALLOW_FLAWED_CONTEXT feature on the LogFactoryImpl object
+ * associated with this class' classloader.
+ *
+ * Don't forget to set the context classloader to whatever it will be
+ * when an instance of this class is actually created before calling
+ * this method!
+ */
+ public static void setAllowFlawedContext(String state) {
+ LogFactory f = LogFactory.getFactory();
+ f.setAttribute(LogFactoryImpl.ALLOW_FLAWED_CONTEXT_PROPERTY, state);
+ }
+
+ public UserClass() {
+ Log log = LogFactory.getLog(LoadTestCase.class);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java b/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java
new file mode 100644
index 0000000..182624d
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/avalon/AvalonLoggerTestCase.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.avalon;
+
+import org.apache.avalon.framework.logger.NullLogger;
+import org.apache.commons.logging.impl.AvalonLogger;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.AbstractLogTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * @author Neeme Praks
+ * @version $Revision: 1432587 $ $Date: 2013-01-13 12:11:32 +0100 (Sun, 13 Jan 2013) $
+ */
+public class AvalonLoggerTestCase extends AbstractLogTest {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(AvalonLoggerTestCase.class);
+ return suite;
+ }
+
+ public Log getLogObject() {
+ // Output does not seem to be used, so don't display it.
+ Log log = new AvalonLogger(new NullLogger());
+ return log;
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/config/FirstPriorityConfigTestCase.java b/src/test/java/org/apache/commons/logging/config/FirstPriorityConfigTestCase.java
new file mode 100644
index 0000000..4725dcd
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/config/FirstPriorityConfigTestCase.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.config;
+
+
+import java.net.URL;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Tests that verify that the process of configuring logging on startup
+ * works correctly by selecting the file with the highest priority.
+ *
+ * This test sets up a classpath where:
+ *
+ * This test sets up a classpath where:
+ *
+ * Note that parentFirst=true is used in this test because method
+ * TestCase for JDK 1.4 logging when running on a JDK 1.4 system with
+ * custom configuration, so that JDK 1.4 should be selected and an appropriate
+ * logger configured per the configuration properties. Construct a new instance of this test case. The customized The underlying The underlying The underlying The message levels that should have been logged. The message strings that should have been logged. TestCase for JDK 1.4 logging when running on a JDK 1.4 system with
+ * zero configuration, and with Log4J not present (so JDK 1.4 logging
+ * should be automatically configured. Construct a new instance of this test case. The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected. Test implementation of
+ * The tests verify that when running on a system with Log4J present,
+ * Log4J is selected and that the logger basically works.
+ */
+
+public abstract class StandardTests extends TestCase {
+
+ /**
+ * Simple structure to store information about messages that actually get
+ * logged by the underlying logging library.
+ */
+ public static class LogEvent {
+ public String msg;
+ public String level;
+ public Throwable throwable;
+ }
+
+ // -------------------------------------------------------------------
+ // JUnit Infrastructure Methods
+ // -------------------------------------------------------------------
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // -----------------------------------------------------------
+ // abstract methods
+ // -----------------------------------------------------------
+
+ /**
+ * Modify log4j's setup so that all messages actually logged get redirected
+ * into the specified list.
+ *
+ * This method also sets the logging level to INFO so that we
+ * can test whether messages are getting properly filtered.
+ */
+ public abstract void setUpTestAppender(List logEvents) throws Exception;
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Test that a LogFactory gets created as expected.
+ */
+ public void testCreateFactory() {
+ LogFactory factory = LogFactory.getFactory();
+ assertNotNull("LogFactory exists", factory);
+ assertEquals("LogFactory class",
+ "org.apache.commons.logging.impl.LogFactoryImpl",
+ factory.getClass().getName());
+
+ String names[] = factory.getAttributeNames();
+ assertNotNull("Names exists", names);
+ assertEquals("Names empty", 0, names.length);
+ }
+
+ /**
+ * Verify that we can log messages without exceptions.
+ */
+ public void testPlainMessages() throws Exception {
+ List logEvents = new ArrayList();
+ setUpTestAppender(logEvents);
+ Log log = LogFactory.getLog("test-category");
+ logPlainMessages(log);
+ checkLoggingEvents(logEvents, false);
+ }
+
+ /**
+ * Verify that we can log exception messages.
+ */
+ public void testExceptionMessages() throws Exception {
+ List logEvents = new ArrayList();
+ setUpTestAppender(logEvents);
+ Log log = LogFactory.getLog("test-category");
+ logExceptionMessages(log);
+ checkLoggingEvents(logEvents, true);
+ }
+
+ /**
+ * Test Serializability of Log instance
+ */
+ public void testSerializable() throws Exception {
+ List logEvents = new ArrayList();
+ setUpTestAppender(logEvents);
+ Log log = LogFactory.getLog("test-category");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(log);
+ oos.close();
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ Log newLog = (Log) ois.readObject();
+ ois.close();
+
+ // Check the characteristics of the resulting object
+ logExceptionMessages(newLog);
+ checkLoggingEvents(logEvents, true);
+ }
+
+ // -------------------------------------------------------- Support Methods
+
+ /**
+ * Verify that the TestAppender has received the expected
+ * number of messages. This assumes that:
+ * The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected.
+ * This simply applies the tests defined in AbstractLogTest to this class.
+ */
+public class NoOpLogTestCase extends AbstractLogTest
+{
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+
+ System.setProperty(
+ "org.apache.commons.logging.Log",
+ "org.apache.commons.logging.impl.NoOpLog");
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ System.getProperties().remove("org.apache.commons.logging.Log");
+ }
+
+ /**
+ * Override the abstract method from the parent class so that the
+ * inherited tests can access the right Log object type.
+ */
+ public Log getLogObject()
+ {
+ return new NoOpLog(this.getClass().getName());
+ }
+
+ // Test Serializability of standard instance
+ public void testSerializable() throws Exception {
+ Log log = LogFactory.getLog(this.getClass().getName());
+ checkLog(log);
+
+ // Serialize and deserialize the instance
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(log);
+ oos.close();
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ log = (Log) ois.readObject();
+ ois.close();
+
+ checkLog(log);
+ }
+
+
+ // -------------------------------------------------------- Support Methods
+
+ private void checkLog(Log log) {
+
+ assertNotNull("Log exists", log);
+ assertEquals("Log class",
+ "org.apache.commons.logging.impl.NoOpLog",
+ log.getClass().getName());
+
+ // Can we call level checkers with no exceptions?
+ // Note that *everything* is permanently disabled for NoOpLog
+ assertFalse(log.isTraceEnabled());
+ assertFalse(log.isDebugEnabled());
+ assertFalse(log.isInfoEnabled());
+ assertFalse(log.isWarnEnabled());
+ assertFalse(log.isErrorEnabled());
+ assertFalse(log.isFatalEnabled());
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/pathable/ChildFirstTestCase.java b/src/test/java/org/apache/commons/logging/pathable/ChildFirstTestCase.java
new file mode 100644
index 0000000..1aeb12d
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/pathable/ChildFirstTestCase.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.pathable;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+/**
+ * Tests for the PathableTestSuite and PathableClassLoader functionality,
+ * where lookup order for the PathableClassLoader is child-first.
+ *
+ * These tests assume:
+ *
+ * This method works in conjunction with testResetProps2. There is no
+ * way of knowing which test method junit will run first, but it doesn't
+ * matter; whichever one of them runs first will modify the system properties.
+ * If the PathableTestSuite isn't resetting the system properties then whichever
+ * of them runs second will fail. Of course if other methods are run in-between
+ * then those methods might also fail...
+ */
+ public void testResetProps1() {
+ checkAndSetProperties();
+ }
+
+ /**
+ * See testResetProps1.
+ */
+ public void testResetProps2() {
+ checkAndSetProperties();
+ }
+
+ /**
+ * Verify that the context classloader is a custom one, then reset it to
+ * a non-custom one.
+ */
+ private static void checkAndSetContext() {
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ assertEquals("ContextLoader is of unexpected type",
+ contextLoader.getClass().getName(),
+ PathableClassLoader.class.getName());
+
+ URL[] noUrls = new URL[0];
+ Thread.currentThread().setContextClassLoader(new URLClassLoader(noUrls));
+ }
+
+ /**
+ * Verify that when a test method modifies the context classloader it is
+ * reset before the next test is run.
+ *
+ * This method works in conjunction with testResetContext2. There is no
+ * way of knowing which test method junit will run first, but it doesn't
+ * matter; whichever one of them runs first will modify the contextClassloader.
+ * If the PathableTestSuite isn't resetting the contextClassLoader then whichever
+ * of them runs second will fail. Of course if other methods are run in-between
+ * then those methods might also fail...
+ */
+ public void testResetContext1() {
+ checkAndSetContext();
+ }
+
+ /**
+ * See testResetContext1.
+ */
+ public void testResetContext2() {
+ checkAndSetContext();
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java b/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java
new file mode 100644
index 0000000..f9bf452
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.pathable;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+/**
+ * Tests for the PathableTestSuite and PathableClassLoader functionality,
+ * where lookup order for the PathableClassLoader is parent-first.
+ *
+ * These tests assume:
+ *
+ * Performing tests with security permissions disabled is tricky, as building error
+ * messages on failure requires certain security permissions. If the security manager
+ * blocks these, then the test can fail without the error messages being output.
+ *
+ * This class has only one unit test, as we are (in part) checking behaviour in
+ * the static block of the LogFactory class. As that class cannot be unloaded after
+ * being loaded into a classloader, the only workaround is to use the
+ * PathableClassLoader approach to ensure each test is run in its own
+ * classloader, and use a separate testcase class for each test.
+ */
+public class SecurityForbiddenTestCase extends TestCase
+{
+ private SecurityManager oldSecMgr;
+ private ClassLoader otherClassLoader;
+
+ // Dummy special hashtable, so we can tell JCL to use this instead of
+ // the standard one.
+ public static class CustomHashtable extends Hashtable {
+
+ /**
+ * Generated serial version ID.
+ */
+ private static final long serialVersionUID = 7224652794746236024L;
+ }
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parent.addLogicalLib("commons-logging");
+ parent.addLogicalLib("testclasses");
+
+ Class testClass = parent.loadClass(
+ "org.apache.commons.logging.security.SecurityForbiddenTestCase");
+ return new PathableTestSuite(testClass, parent);
+ }
+
+ public void setUp() {
+ // save security manager so it can be restored in tearDown
+ oldSecMgr = System.getSecurityManager();
+
+ PathableClassLoader classLoader = new PathableClassLoader(null);
+ classLoader.addLogicalLib("commons-logging");
+ classLoader.addLogicalLib("testclasses");
+
+ otherClassLoader = classLoader;
+ }
+
+ public void tearDown() {
+ // Restore, so other tests don't get stuffed up if a test
+ // sets a custom security manager.
+ System.setSecurityManager(oldSecMgr);
+ }
+
+ /**
+ * Test what happens when JCL is run with absolutely no security
+ * privileges at all, including reading system properties. Everything
+ * should fall back to the built-in defaults.
+ */
+ public void testAllForbidden() {
+ System.setProperty(
+ LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY,
+ CustomHashtable.class.getName());
+ MockSecurityManager mySecurityManager = new MockSecurityManager();
+
+ System.setSecurityManager(mySecurityManager);
+
+ try {
+ // Use reflection so that we can control exactly when the static
+ // initialiser for the LogFactory class is executed.
+ Class c = this.getClass().getClassLoader().loadClass(
+ "org.apache.commons.logging.LogFactory");
+ Method m = c.getMethod("getLog", new Class[] {Class.class});
+ Log log = (Log) m.invoke(null, new Object[] {this.getClass()});
+ log.info("testing");
+
+ // check that the default map implementation was loaded, as JCL was
+ // forbidden from reading the HASHTABLE_IMPLEMENTATION_PROPERTY property.
+ //
+ // The default is either the java Hashtable class (java < 1.2) or the
+ // JCL WeakHashtable (java >= 1.3).
+ System.setSecurityManager(oldSecMgr);
+ Field factoryField = c.getDeclaredField("factories");
+ factoryField.setAccessible(true);
+ Object factoryTable = factoryField.get(null);
+ assertNotNull(factoryTable);
+ String ftClassName = factoryTable.getClass().getName();
+ assertTrue("Custom hashtable unexpectedly used",
+ !CustomHashtable.class.getName().equals(ftClassName));
+
+ assertEquals(0, mySecurityManager.getUntrustedCodeCount());
+ } catch(Throwable t) {
+ // Restore original security manager so output can be generated; the
+ // PrintWriter constructor tries to read the line.separator
+ // system property.
+ System.setSecurityManager(oldSecMgr);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString());
+ }
+ }
+
+ /**
+ * Test what happens when JCL is run with absolutely no security
+ * privileges at all and a class loaded with a different classloader
+ * than the context classloader of the current thread tries to log something.
+ */
+ public void testContextClassLoader() {
+ System.setProperty(
+ LogFactory.HASHTABLE_IMPLEMENTATION_PROPERTY,
+ CustomHashtable.class.getName());
+ MockSecurityManager mySecurityManager = new MockSecurityManager();
+
+ System.setSecurityManager(mySecurityManager);
+
+ try {
+ // load a dummy class with another classloader
+ // to force a SecurityException when the LogFactory calls
+ // Thread.getCurrentThread().getContextClassLoader()
+ loadClass("org.apache.commons.logging.security.DummyClass", otherClassLoader);
+
+ System.setSecurityManager(oldSecMgr);
+ assertEquals(0, mySecurityManager.getUntrustedCodeCount());
+ } catch(Throwable t) {
+ // Restore original security manager so output can be generated; the
+ // PrintWriter constructor tries to read the line.separator
+ // system property.
+ System.setSecurityManager(oldSecMgr);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString());
+ }
+ }
+
+ /**
+ * Loads a class with the given classloader.
+ */
+ private Object loadClass(String name, ClassLoader classLoader) {
+ try {
+ Class clazz = classLoader.loadClass(name);
+ Object obj = clazz.newInstance();
+ return obj;
+ } catch ( Exception e ) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ fail("Unexpected exception:" + e.getMessage() + ":" + sw.toString());
+ }
+ return null;
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/servlet/BasicServletTestCase.java b/src/test/java/org/apache/commons/logging/servlet/BasicServletTestCase.java
new file mode 100644
index 0000000..e1ecdbb
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/servlet/BasicServletTestCase.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.servlet;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+import org.apache.commons.logging.impl.ServletContextCleaner;
+
+
+/**
+ * Tests for ServletContextCleaner utility class.
+ */
+
+public class BasicServletTestCase extends TestCase {
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ // LogFactory in parent
+ // LogFactory in child (loads test)
+ // LogFactory in tccl
+ //
+ // Having the test loaded via a loader above the tccl emulates the situation
+ // where a web.xml file specifies ServletContextCleaner as a listener, and
+ // that class is deployed via a shared classloader.
+
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parent.addLogicalLib("commons-logging");
+ parent.addLogicalLib("servlet-api");
+
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.setParentFirst(false);
+ child.addLogicalLib("commons-logging");
+ child.addLogicalLib("testclasses");
+
+ PathableClassLoader tccl = new PathableClassLoader(child);
+ tccl.setParentFirst(false);
+ tccl.addLogicalLib("commons-logging");
+
+ Class testClass = child.loadClass(BasicServletTestCase.class.getName());
+ return new PathableTestSuite(testClass, tccl);
+ }
+
+ /**
+ * Test that calling ServletContextCleaner.contextDestroyed doesn't crash.
+ * Testing anything else is rather difficult...
+ */
+ public void testBasics() {
+ ServletContextCleaner scc = new ServletContextCleaner();
+ scc.contextDestroyed(null);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java
new file mode 100644
index 0000000..c3a89b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.simple;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import junit.framework.Test;
+
+import org.apache.commons.logging.DummyException;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+import org.apache.commons.logging.impl.SimpleLog;
+
+
+/**
+ * TestCase for simple logging when running with custom configuration
+ * properties. The expected log records. The message levels that should have been logged. The message strings that should have been logged.
+ * We need to use a PathableClassLoader here because the SimpleLog class
+ * is a pile of junk and chock-full of static variables. Any other test
+ * (like simple.CustomConfigTestCase) that has used the SimpleLog class
+ * will already have caused it to do once-only initialisation that we
+ * can't reset, even by calling LogFactory.releaseAll, because of those
+ * ugly statics. The only clean solution is to load a clean copy of
+ * commons-logging including SimpleLog via a nice clean classloader.
+ * Or we could fix SimpleLog to be sane...
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = CustomConfigTestCase.class;
+
+ PathableClassLoader loader = new PathableClassLoader(null);
+ loader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ loader.addLogicalLib("testclasses");
+ loader.addLogicalLib("commons-logging");
+
+ Class testClass = loader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, loader);
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ super.tearDown();
+ expected = null;
+ }
+
+
+ // ----------------------------------------------------------- Test Methods
+
+
+ // Test logging message strings with exceptions
+ public void testExceptionMessages() throws Exception {
+
+ ((DecoratedSimpleLog) log).clearCache();
+ logExceptionMessages();
+ checkExpected();
+
+ }
+
+
+ // Test logging plain message strings
+ public void testPlainMessages() throws Exception {
+
+ ((DecoratedSimpleLog) log).clearCache();
+ logPlainMessages();
+ checkExpected();
+
+ }
+
+
+ // Test Serializability of standard instance
+ public void testSerializable() throws Exception {
+
+ ((DecoratedSimpleLog) log).clearCache();
+ logPlainMessages();
+ super.testSerializable();
+ logExceptionMessages();
+ checkExpected();
+
+ }
+
+
+ // -------------------------------------------------------- Support Methods
+
+
+ // Check the decorated log instance
+ protected void checkDecorated() {
+
+ assertNotNull("Log exists", log);
+ assertEquals("Log class",
+ "org.apache.commons.logging.simple.DecoratedSimpleLog",
+ log.getClass().getName());
+
+ // Can we call level checkers with no exceptions?
+ assertTrue(log.isDebugEnabled());
+ assertTrue(log.isErrorEnabled());
+ assertTrue(log.isFatalEnabled());
+ assertTrue(log.isInfoEnabled());
+ assertTrue(!log.isTraceEnabled());
+ assertTrue(log.isWarnEnabled());
+
+ // Can we retrieve the current log level?
+ assertEquals(SimpleLog.LOG_LEVEL_DEBUG, ((SimpleLog) log).getLevel());
+
+ // Can we validate the extra exposed properties?
+ checkDecoratedDateTime();
+ assertEquals("DecoratedLogger",
+ ((DecoratedSimpleLog) log).getLogName());
+ checkShowDateTime();
+ assertTrue(((DecoratedSimpleLog) log).getShowShortName());
+
+ }
+
+ /** Hook for subclassses */
+ protected void checkShowDateTime() {
+ assertTrue(!((DecoratedSimpleLog) log).getShowDateTime());
+ }
+
+ /** Hook for subclasses */
+ protected void checkDecoratedDateTime() {
+ assertEquals("yyyy/MM/dd HH:mm:ss:SSS zzz",
+ ((DecoratedSimpleLog) log).getDateTimeFormat());
+ }
+
+
+
+ // Check the actual log records against the expected ones
+ protected void checkExpected() {
+
+ List acts = ((DecoratedSimpleLog) log).getCache();
+ Iterator exps = expected.iterator();
+ int n = 0;
+ while (exps.hasNext()) {
+ LogRecord exp = (LogRecord) exps.next();
+ LogRecord act = (LogRecord) acts.get(n++);
+ assertEquals("Row " + n + " type", exp.type, act.type);
+ assertEquals("Row " + n + " message", exp.message, act.message);
+ assertEquals("Row " + n + " throwable", exp.t, act.t);
+ }
+
+ }
+
+
+ // Check the standard log instance
+ protected void checkStandard() {
+
+ checkDecorated();
+
+ }
+
+
+ // Log the messages with exceptions
+ protected void logExceptionMessages() {
+
+ // Generate log records
+ Throwable t = new DummyException();
+ log.trace("trace", t); // Should not actually get logged
+ log.debug("debug", t);
+ log.info("info", t);
+ log.warn("warn", t);
+ log.error("error", t);
+ log.fatal("fatal", t);
+
+ // Record the log records we expect
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_DEBUG, "debug", t));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_INFO, "info", t));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_WARN, "warn", t));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_ERROR, "error", t));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_FATAL, "fatal", t));
+
+ }
+
+
+ // Log the plain messages
+ protected void logPlainMessages() {
+
+ // Generate log records
+ log.trace("trace"); // Should not actually get logged
+ log.debug("debug");
+ log.info("info");
+ log.warn("warn");
+ log.error("error");
+ log.fatal("fatal");
+
+ // Record the log records we expect
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_DEBUG, "debug", null));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_INFO, "info", null));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_WARN, "warn", null));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_ERROR, "error", null));
+ expected.add(new LogRecord(SimpleLog.LOG_LEVEL_FATAL, "fatal", null));
+
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java
new file mode 100644
index 0000000..5e72718
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/simple/DateTimeCustomConfigTestCase.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.simple;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import junit.framework.Test;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Tests custom date time format configuration
+ */
+public class DateTimeCustomConfigTestCase extends CustomConfigTestCase {
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * Return the tests included in this test suite.
+ *
+ * We need to use a PathableClassLoader here because the SimpleLog class
+ * is a pile of junk and chock-full of static variables. Any other test
+ * (like simple.CustomConfigTestCase) that has used the SimpleLog class
+ * will already have caused it to do once-only initialisation that we
+ * can't reset, even by calling LogFactory.releaseAll, because of those
+ * ugly statics. The only clean solution is to load a clean copy of
+ * commons-logging including SimpleLog via a nice clean classloader.
+ * Or we could fix SimpleLog to be sane...
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = DateTimeCustomConfigTestCase.class;
+
+ PathableClassLoader loader = new PathableClassLoader(null);
+ loader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ loader.addLogicalLib("testclasses");
+ loader.addLogicalLib("commons-logging");
+
+ Class testClass = loader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, loader);
+ }
+
+
+ /**
+ * Set up system properties required by this unit test. Here, we
+ * set up the props defined in the parent class setProperties method,
+ * and add a few to configure the SimpleLog class date/time output.
+ */
+ public void setProperties() {
+ super.setProperties();
+
+ System.setProperty(
+ "org.apache.commons.logging.simplelog.dateTimeFormat",
+ "dd.mm.yyyy");
+ System.setProperty(
+ "org.apache.commons.logging.simplelog.showdatetime",
+ "true");
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+
+ // ----------------------------------------------------------- Methods
+
+ /** Checks that the date time format has been successfully set */
+ protected void checkDecoratedDateTime() {
+ assertEquals("Expected date format to be set", "dd.mm.yyyy",
+ ((DecoratedSimpleLog) log).getDateTimeFormat());
+ // try the formatter
+ Date now = new Date();
+ DateFormat formatter = ((DecoratedSimpleLog) log).getDateTimeFormatter();
+ SimpleDateFormat sampleFormatter = new SimpleDateFormat("dd.mm.yyyy");
+ assertEquals("Date should be formatters to pattern dd.mm.yyyy",
+ sampleFormatter.format(now), formatter.format(now));
+ }
+
+ /** Hook for subclassses */
+ protected void checkShowDateTime() {
+ assertTrue(((DecoratedSimpleLog) log).getShowDateTime());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/logging/simple/DecoratedSimpleLog.java b/src/test/java/org/apache/commons/logging/simple/DecoratedSimpleLog.java
new file mode 100644
index 0000000..0129bcd
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/simple/DecoratedSimpleLog.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.simple;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.text.DateFormat;
+import org.apache.commons.logging.impl.SimpleLog;
+
+
+/**
+ * Decorated instance of SimpleLog to expose internal state and
+ * support buffered output. TestCase for simple logging when running with zero configuration
+ * other than selecting the SimpleLog implementation. The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected.
+ * We need to use a PathableClassLoader here because the SimpleLog class
+ * is a pile of junk and chock-full of static variables. Any other test
+ * (like simple.CustomConfigTestCase) that has used the SimpleLog class
+ * will already have caused it to do once-only initialisation that we
+ * can't reset, even by calling LogFactory.releaseAll, because of those
+ * ugly statics. The only clean solution is to load a clean copy of
+ * commons-logging including SimpleLog via a nice clean classloader.
+ * Or we could fix SimpleLog to be sane...
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = DefaultConfigTestCase.class;
+
+ PathableClassLoader loader = new PathableClassLoader(null);
+ loader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ loader.addLogicalLib("testclasses");
+ loader.addLogicalLib("commons-logging");
+
+ Class testClass = loader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, loader);
+ }
+
+ /**
+ * Set system properties that will control the LogFactory/Log objects
+ * when they are created. Subclasses can override this method to
+ * define properties that suit them.
+ */
+ public void setProperties() {
+ System.setProperty(
+ "org.apache.commons.logging.Log",
+ "org.apache.commons.logging.impl.SimpleLog");
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ setProperties();
+ setUpFactory();
+ setUpLog("TestLogger");
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ log = null;
+ factory = null;
+ LogFactory.releaseAll();
+ }
+
+
+ // ----------------------------------------------------------- Test Methods
+
+
+ // Test pristine DecoratedSimpleLog instance
+ public void testPristineDecorated() {
+
+ setUpDecorated("DecoratedLogger");
+ checkDecorated();
+
+ }
+
+
+ // Test pristine Log instance
+ public void testPristineLog() {
+
+ checkStandard();
+
+ }
+
+
+ // Test pristine LogFactory instance
+ public void testPristineFactory() {
+
+ assertNotNull("LogFactory exists", factory);
+ assertEquals("LogFactory class",
+ "org.apache.commons.logging.impl.LogFactoryImpl",
+ factory.getClass().getName());
+
+ String names[] = factory.getAttributeNames();
+ assertNotNull("Names exists", names);
+ assertEquals("Names empty", 0, names.length);
+
+ }
+
+
+ // Test Serializability of standard instance
+ public void testSerializable() throws Exception {
+
+ // Serialize and deserialize the instance
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(log);
+ oos.close();
+ ByteArrayInputStream bais =
+ new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ log = (Log) ois.readObject();
+ ois.close();
+
+ // Check the characteristics of the resulting object
+ checkStandard();
+
+ }
+
+
+ // -------------------------------------------------------- Support Methods
+
+
+
+ // Check the decorated log instance
+ protected void checkDecorated() {
+
+ assertNotNull("Log exists", log);
+ assertEquals("Log class",
+ "org.apache.commons.logging.simple.DecoratedSimpleLog",
+ log.getClass().getName());
+
+ // Can we call level checkers with no exceptions?
+ assertTrue(!log.isDebugEnabled());
+ assertTrue(log.isErrorEnabled());
+ assertTrue(log.isFatalEnabled());
+ assertTrue(log.isInfoEnabled());
+ assertTrue(!log.isTraceEnabled());
+ assertTrue(log.isWarnEnabled());
+
+ // Can we retrieve the current log level?
+ assertEquals(SimpleLog.LOG_LEVEL_INFO, ((SimpleLog) log).getLevel());
+
+ // Can we validate the extra exposed properties?
+ assertEquals("yyyy/MM/dd HH:mm:ss:SSS zzz",
+ ((DecoratedSimpleLog) log).getDateTimeFormat());
+ assertEquals("DecoratedLogger",
+ ((DecoratedSimpleLog) log).getLogName());
+ assertTrue(!((DecoratedSimpleLog) log).getShowDateTime());
+ assertTrue(((DecoratedSimpleLog) log).getShowShortName());
+
+ }
+
+
+ // Check the standard log instance
+ protected void checkStandard() {
+
+ assertNotNull("Log exists", log);
+ assertEquals("Log class",
+ "org.apache.commons.logging.impl.SimpleLog",
+ log.getClass().getName());
+
+ // Can we call level checkers with no exceptions?
+ assertTrue(!log.isDebugEnabled());
+ assertTrue(log.isErrorEnabled());
+ assertTrue(log.isFatalEnabled());
+ assertTrue(log.isInfoEnabled());
+ assertTrue(!log.isTraceEnabled());
+ assertTrue(log.isWarnEnabled());
+
+ // Can we retrieve the current log level?
+ assertEquals(SimpleLog.LOG_LEVEL_INFO, ((SimpleLog) log).getLevel());
+
+ }
+
+
+ // Set up decorated log instance
+ protected void setUpDecorated(String name) {
+ log = new DecoratedSimpleLog(name);
+ }
+
+
+ // Set up factory instance
+ protected void setUpFactory() throws Exception {
+ factory = LogFactory.getFactory();
+ }
+
+
+ // Set up log instance
+ protected void setUpLog(String name) throws Exception {
+ log = LogFactory.getLog(name);
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/logging/simple/LogRecord.java b/src/test/java/org/apache/commons/logging/simple/LogRecord.java
new file mode 100644
index 0000000..0c6d784
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/simple/LogRecord.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.simple;
+
+
+import java.io.Serializable;
+
+
+public class LogRecord implements Serializable {
+
+
+ /**
+ * Generated serial version ID.
+ */
+ private static final long serialVersionUID = -5254831759209770665L;
+
+ public LogRecord(int type, Object message, Throwable t) {
+ this.type = type;
+ this.message = message;
+ this.t = t;
+ }
+
+ public int type;
+ public Object message;
+ public Throwable t;
+
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/BadTCCLTestCase.java b/src/test/java/org/apache/commons/logging/tccl/BadTCCLTestCase.java
new file mode 100644
index 0000000..7211156
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/BadTCCLTestCase.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * Simulates the case when TCCL is badly set and cannot load JCL.
+ */
+public class BadTCCLTestCase extends TestCase {
+
+ public static Test suite() throws Exception {
+ PathableClassLoader contextClassLoader = new PathableClassLoader(null);
+ contextClassLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ PathableTestSuite suite = new PathableTestSuite(BadTCCLTestCase.class, contextClassLoader);
+ return suite;
+ }
+
+ // test methods
+
+ /**
+ * This test just tests that a log implementation can be found
+ * by the LogFactory.
+ */
+ public void testGetLog() {
+ Log log = LogFactory.getLog(BadTCCLTestCase.class);
+ log.debug("Hello, Mum");
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/NullTCCLTestCase.java b/src/test/java/org/apache/commons/logging/tccl/NullTCCLTestCase.java
new file mode 100644
index 0000000..ae5ab4a
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/NullTCCLTestCase.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+/**
+ * Simulates the case when TCCL is set to NULL.
+ */
+public class NullTCCLTestCase extends TestCase {
+
+ public static Test suite() throws Exception {
+ PathableTestSuite suite = new PathableTestSuite(NullTCCLTestCase.class, null);
+ return suite;
+ }
+
+ // test methods
+
+ /**
+ * This test just tests that a log implementation can be found
+ * by the LogFactory.
+ */
+ public void testGetLog() {
+ Log log = LogFactory.getLog(NullTCCLTestCase.class);
+ log.debug("Hello, Mum");
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/custom/MyLog.java b/src/test/java/org/apache/commons/logging/tccl/custom/MyLog.java
new file mode 100644
index 0000000..214f7b3
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/custom/MyLog.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl.custom;
+
+import org.apache.commons.logging.Log;
+
+public class MyLog implements Log {
+
+ public MyLog(String category) {}
+
+ public boolean isDebugEnabled() { return false; }
+ public boolean isErrorEnabled() { return false; }
+ public boolean isFatalEnabled() { return false; }
+ public boolean isInfoEnabled() { return false; }
+ public boolean isTraceEnabled() { return false; }
+ public boolean isWarnEnabled() { return false; }
+
+ public void trace(Object message) {}
+ public void trace(Object message, Throwable t) {}
+ public void debug(Object message) {}
+ public void debug(Object message, Throwable t) {}
+ public void info(Object message) {}
+ public void info(Object message, Throwable t) {}
+ public void warn(Object message) {}
+ public void warn(Object message, Throwable t) {}
+ public void error(Object message) {}
+ public void error(Object message, Throwable t) {}
+ public void fatal(Object message) {}
+ public void fatal(Object message, Throwable t) {}
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/custom/MyLogFactoryImpl.java b/src/test/java/org/apache/commons/logging/tccl/custom/MyLogFactoryImpl.java
new file mode 100644
index 0000000..63d5ac6
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/custom/MyLogFactoryImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl.custom;
+
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class MyLogFactoryImpl extends LogFactory {
+ public Object getAttribute(String name) { return null; }
+ public String[] getAttributeNames() { return null; }
+ public Log getInstance(Class clazz) { return null; }
+ public Log getInstance(String name) { return null; }
+ public void release() {}
+ public void removeAttribute(String name) {}
+ public void setAttribute(String name, Object value) {}
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/log/TcclDisabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/log/TcclDisabledTestCase.java
new file mode 100644
index 0000000..152af3c
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/log/TcclDisabledTestCase.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl.log;
+
+
+import java.net.URL;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogConfigurationException;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Verify that by default LogFactoryImpl is loaded from the tccl classloader.
+ */
+
+public class TcclDisabledTestCase extends TestCase {
+
+ public static final String MY_LOG_PKG =
+ "org.apache.commons.logging.tccl.custom";
+
+ public static final String MY_LOG_IMPL =
+ MY_LOG_PKG + ".MyLog";
+
+ // ------------------------------------------- JUnit Infrastructure Methods
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = TcclDisabledTestCase.class;
+
+ // Determine the URL to this .class file, so that we can then
+ // append the priority dirs to it. For tidiness, load this
+ // class through a dummy loader though this is not absolutely
+ // necessary...
+ PathableClassLoader dummy = new PathableClassLoader(null);
+ dummy.useExplicitLoader("junit.", Test.class.getClassLoader());
+ dummy.addLogicalLib("testclasses");
+ dummy.addLogicalLib("commons-logging");
+
+ String thisClassPath = thisClass.getName().replace('.', '/') + ".class";
+ URL baseUrl = dummy.findResource(thisClassPath);
+
+ // Now set up the desired classloader hierarchy. Everything goes into
+ // the parent classpath, but we exclude the custom Log class.
+ //
+ // We then create a tccl classloader that can see the custom
+ // Log class. Therefore if that class can be found, then the
+ // TCCL must have been used to load it.
+ PathableClassLoader emptyLoader = new PathableClassLoader(null);
+
+ PathableClassLoader parentLoader = new PathableClassLoader(null);
+ parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parentLoader.addLogicalLib("commons-logging");
+ parentLoader.addLogicalLib("testclasses");
+ // hack to ensure that the testcase classloader can't see
+ // the custom MyLog
+ parentLoader.useExplicitLoader(MY_LOG_PKG + ".", emptyLoader);
+
+ URL propsEnableUrl = new URL(baseUrl, "props_disable_tccl/");
+ parentLoader.addURL(propsEnableUrl);
+
+ PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader);
+ tcclLoader.addLogicalLib("testclasses");
+
+ Class testClass = parentLoader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, tcclLoader);
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Verify that MyLog is only loadable via the tccl.
+ */
+ public void testLoader() throws Exception {
+
+ ClassLoader thisClassLoader = this.getClass().getClassLoader();
+ ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader();
+
+ // the tccl loader should NOT be the same as the loader that loaded this test class.
+ assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader);
+
+ // MyLog should not be loadable via parent loader
+ try {
+ Class clazz = thisClassLoader.loadClass(MY_LOG_IMPL);
+ fail("Unexpectedly able to load MyLog via test class classloader");
+ assertNotNull(clazz); // silence warnings about unused var
+ } catch(ClassNotFoundException ex) {
+ // ok, expected
+ }
+
+ // MyLog should be loadable via tccl loader
+ try {
+ Class clazz = tcclLoader.loadClass(MY_LOG_IMPL);
+ assertNotNull(clazz);
+ } catch(ClassNotFoundException ex) {
+ fail("Unexpectedly unable to load MyLog via tccl classloader");
+ }
+ }
+
+ /**
+ * Verify that the custom Log implementation which is only accessable
+ * via the TCCL has NOT been loaded. Because this is only accessable via the
+ * TCCL, and we've use a commons-logging.properties that disables TCCL loading,
+ * we should see the default Log rather than the custom one.
+ */
+ public void testTcclLoading() throws Exception {
+ LogFactory instance = LogFactory.getFactory();
+ assertEquals(
+ "Correct LogFactory loaded",
+ "org.apache.commons.logging.impl.LogFactoryImpl",
+ instance.getClass().getName());
+
+ try {
+ Log log = instance.getInstance("test");
+ fail("Unexpectedly succeeded in loading a custom Log class"
+ + " that is only accessable via the tccl.");
+ assertNotNull(log); // silence compiler warning about unused var
+ } catch(LogConfigurationException ex) {
+ // ok, expected
+ int index = ex.getMessage().indexOf(MY_LOG_IMPL);
+ assertTrue("MyLog not found", index >= 0);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/log/TcclEnabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/log/TcclEnabledTestCase.java
new file mode 100644
index 0000000..692b53e
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/log/TcclEnabledTestCase.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl.log;
+
+
+import java.net.URL;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Verify that by default the standard LogFactoryImpl class loads a
+ * custom Log implementation via the TCCL.
+ */
+
+public class TcclEnabledTestCase extends TestCase {
+
+ public static final String MY_LOG_PKG =
+ "org.apache.commons.logging.tccl.custom";
+
+ public static final String MY_LOG_IMPL =
+ MY_LOG_PKG + ".MyLog";
+
+ // ------------------------------------------- JUnit Infrastructure Methods
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = TcclEnabledTestCase.class;
+
+ // Determine the URL to this .class file, so that we can then
+ // append the priority dirs to it. For tidiness, load this
+ // class through a dummy loader though this is not absolutely
+ // necessary...
+ PathableClassLoader dummy = new PathableClassLoader(null);
+ dummy.useExplicitLoader("junit.", Test.class.getClassLoader());
+ dummy.addLogicalLib("testclasses");
+ dummy.addLogicalLib("commons-logging");
+
+ String thisClassPath = thisClass.getName().replace('.', '/') + ".class";
+ URL baseUrl = dummy.findResource(thisClassPath);
+
+ // Now set up the desired classloader hierarchy. Everything goes into
+ // the parent classpath, but we exclude the custom Log class.
+ //
+ // We then create a tccl classloader that can see the custom
+ // Log class. Therefore if that class can be found, then the
+ // TCCL must have been used to load it.
+ PathableClassLoader emptyLoader = new PathableClassLoader(null);
+
+ PathableClassLoader parentLoader = new PathableClassLoader(null);
+ parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parentLoader.addLogicalLib("commons-logging");
+ parentLoader.addLogicalLib("testclasses");
+ // hack to ensure that the testcase classloader can't see
+ // the custom MyLogFactoryImpl
+ parentLoader.useExplicitLoader(MY_LOG_PKG + ".", emptyLoader);
+
+ URL propsEnableUrl = new URL(baseUrl, "props_enable_tccl/");
+ parentLoader.addURL(propsEnableUrl);
+
+ PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader);
+ tcclLoader.addLogicalLib("testclasses");
+
+ Class testClass = parentLoader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, tcclLoader);
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Verify that MyLogFactoryImpl is only loadable via the tccl.
+ */
+ public void testLoader() throws Exception {
+
+ ClassLoader thisClassLoader = this.getClass().getClassLoader();
+ ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader();
+
+ // the tccl loader should NOT be the same as the loader that loaded this test class.
+ assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader);
+
+ // MyLog should not be loadable via parent loader
+ try {
+ Class clazz = thisClassLoader.loadClass(MY_LOG_IMPL);
+ fail("Unexpectedly able to load MyLog via test class classloader");
+ assertNotNull(clazz); // silence warnings about unused var
+ } catch(ClassNotFoundException ex) {
+ // ok, expected
+ }
+
+ // MyLog should be loadable via tccl loader
+ try {
+ Class clazz = tcclLoader.loadClass(MY_LOG_IMPL);
+ assertNotNull(clazz);
+ } catch(ClassNotFoundException ex) {
+ fail("Unexpectedly unable to load MyLog via tccl classloader");
+ }
+ }
+
+ /**
+ * Verify that the custom Log implementation which is only accessable
+ * via the TCCL has successfully been loaded as specified in the config file.
+ * This proves that the TCCL was used to load that class.
+ */
+ public void testTcclLoading() throws Exception {
+ LogFactory instance = LogFactory.getFactory();
+
+ assertEquals(
+ "Correct LogFactory loaded",
+ "org.apache.commons.logging.impl.LogFactoryImpl",
+ instance.getClass().getName());
+
+ Log log = instance.getInstance("test");
+ assertEquals(
+ "Correct Log loaded",
+ MY_LOG_IMPL,
+ log.getClass().getName());
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclDisabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclDisabledTestCase.java
new file mode 100644
index 0000000..fc577e3
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclDisabledTestCase.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl.logfactory;
+
+
+import java.net.URL;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Verify that a commons-logging.properties file can prevent a custom
+ * LogFactoryImpl being loaded from the tccl classloader.
+ */
+
+public class TcclDisabledTestCase extends TestCase {
+
+ public static final String MY_LOG_FACTORY_PKG =
+ "org.apache.commons.logging.tccl.custom";
+
+ public static final String MY_LOG_FACTORY_IMPL =
+ MY_LOG_FACTORY_PKG + ".MyLogFactoryImpl";
+
+ // ------------------------------------------- JUnit Infrastructure Methods
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = TcclDisabledTestCase.class;
+
+ // Determine the URL to this .class file, so that we can then
+ // append the priority dirs to it. For tidiness, load this
+ // class through a dummy loader though this is not absolutely
+ // necessary...
+ PathableClassLoader dummy = new PathableClassLoader(null);
+ dummy.useExplicitLoader("junit.", Test.class.getClassLoader());
+ dummy.addLogicalLib("testclasses");
+ dummy.addLogicalLib("commons-logging");
+
+ String thisClassPath = thisClass.getName().replace('.', '/') + ".class";
+ URL baseUrl = dummy.findResource(thisClassPath);
+
+ // Now set up the desired classloader hierarchy. Everything goes into
+ // the parent classpath, but we exclude the custom LogFactoryImpl
+ // class.
+ //
+ // We then create a tccl classloader that can see the custom
+ // LogFactory class. Therefore if that class can be found, then the
+ // TCCL must have been used to load it.
+ PathableClassLoader emptyLoader = new PathableClassLoader(null);
+
+ PathableClassLoader parentLoader = new PathableClassLoader(null);
+ parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parentLoader.addLogicalLib("commons-logging");
+ parentLoader.addLogicalLib("testclasses");
+ // hack to ensure that the testcase classloader can't see
+ // the custom MyLogFactoryImpl
+ parentLoader.useExplicitLoader(
+ MY_LOG_FACTORY_PKG + ".", emptyLoader);
+
+ URL propsEnableUrl = new URL(baseUrl, "props_disable_tccl/");
+ parentLoader.addURL(propsEnableUrl);
+
+ PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader);
+ tcclLoader.addLogicalLib("testclasses");
+
+ Class testClass = parentLoader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, tcclLoader);
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Verify that MyLogFactoryImpl is only loadable via the tccl.
+ */
+ public void testLoader() throws Exception {
+
+ ClassLoader thisClassLoader = this.getClass().getClassLoader();
+ ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader();
+
+ // the tccl loader should NOT be the same as the loader that loaded this test class.
+ assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader);
+
+ // MyLogFactoryImpl should not be loadable via parent loader
+ try {
+ Class clazz = thisClassLoader.loadClass(MY_LOG_FACTORY_IMPL);
+ fail("Unexpectedly able to load MyLogFactoryImpl via test class classloader");
+ assertNotNull(clazz); // silence warning about unused var
+ } catch(ClassNotFoundException ex) {
+ // ok, expected
+ }
+
+ // MyLogFactoryImpl should be loadable via tccl loader
+ try {
+ Class clazz = tcclLoader.loadClass(MY_LOG_FACTORY_IMPL);
+ assertNotNull(clazz);
+ } catch(ClassNotFoundException ex) {
+ fail("Unexpectedly unable to load MyLogFactoryImpl via tccl classloader");
+ }
+ }
+
+ /**
+ * Verify that the custom LogFactory implementation which is only accessable
+ * via the TCCL has NOT been loaded. Because this is only accessable via the
+ * TCCL, and we've use a commons-logging.properties that disables TCCL loading,
+ * we should see the default LogFactoryImpl rather than the custom one.
+ */
+ public void testTcclLoading() throws Exception {
+ try {
+ LogFactory instance = LogFactory.getFactory();
+ fail("Unexpectedly succeeded in loading custom factory, though TCCL disabled.");
+ assertNotNull(instance); // silence warning about unused var
+ } catch(org.apache.commons.logging.LogConfigurationException ex) {
+ // ok, custom MyLogFactoryImpl as specified in props_disable_tccl
+ // could not be found.
+ int index = ex.getMessage().indexOf(MY_LOG_FACTORY_IMPL);
+ assertTrue("MylogFactoryImpl not found", index >= 0);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclEnabledTestCase.java b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclEnabledTestCase.java
new file mode 100644
index 0000000..58d746b
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/tccl/logfactory/TcclEnabledTestCase.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.tccl.logfactory;
+
+
+import java.net.URL;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Verify that by default a custom LogFactoryImpl is loaded from the
+ * tccl classloader.
+ */
+
+public class TcclEnabledTestCase extends TestCase {
+
+ // ------------------------------------------- JUnit Infrastructure Methods
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = TcclEnabledTestCase.class;
+
+ // Determine the URL to this .class file, so that we can then
+ // append the priority dirs to it. For tidiness, load this
+ // class through a dummy loader though this is not absolutely
+ // necessary...
+ PathableClassLoader dummy = new PathableClassLoader(null);
+ dummy.useExplicitLoader("junit.", Test.class.getClassLoader());
+ dummy.addLogicalLib("testclasses");
+ dummy.addLogicalLib("commons-logging");
+
+ String thisClassPath = thisClass.getName().replace('.', '/') + ".class";
+ URL baseUrl = dummy.findResource(thisClassPath);
+
+ // Now set up the desired classloader hierarchy. Everything goes into
+ // the parent classpath, but we exclude the custom LogFactoryImpl
+ // class.
+ //
+ // We then create a tccl classloader that can see the custom
+ // LogFactory class. Therefore if that class can be found, then the
+ // TCCL must have been used to load it.
+ PathableClassLoader emptyLoader = new PathableClassLoader(null);
+
+ PathableClassLoader parentLoader = new PathableClassLoader(null);
+ parentLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parentLoader.addLogicalLib("commons-logging");
+ parentLoader.addLogicalLib("testclasses");
+ // hack to ensure that the testcase classloader can't see
+ // the cust MyLogFactoryImpl
+ parentLoader.useExplicitLoader(
+ "org.apache.commons.logging.tccl.custom.", emptyLoader);
+
+ URL propsEnableUrl = new URL(baseUrl, "props_enable_tccl/");
+ parentLoader.addURL(propsEnableUrl);
+
+ PathableClassLoader tcclLoader = new PathableClassLoader(parentLoader);
+ tcclLoader.addLogicalLib("testclasses");
+
+ Class testClass = parentLoader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, tcclLoader);
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Verify that MyLogFactoryImpl is only loadable via the tccl.
+ */
+ public void testLoader() throws Exception {
+
+ ClassLoader thisClassLoader = this.getClass().getClassLoader();
+ ClassLoader tcclLoader = Thread.currentThread().getContextClassLoader();
+
+ // the tccl loader should NOT be the same as the loader that loaded this test class.
+ assertNotSame("tccl not same as test classloader", thisClassLoader, tcclLoader);
+
+ // MyLogFactoryImpl should not be loadable via parent loader
+ try {
+ Class clazz = thisClassLoader.loadClass(
+ "org.apache.commons.logging.tccl.custom.MyLogFactoryImpl");
+ fail("Unexpectedly able to load MyLogFactoryImpl via test class classloader");
+ assertNotNull(clazz); // silence warning about unused var
+ } catch(ClassNotFoundException ex) {
+ // ok, expected
+ }
+
+ // MyLogFactoryImpl should be loadable via tccl loader
+ try {
+ Class clazz = tcclLoader.loadClass(
+ "org.apache.commons.logging.tccl.custom.MyLogFactoryImpl");
+ assertNotNull(clazz);
+ } catch(ClassNotFoundException ex) {
+ fail("Unexpectedly unable to load MyLogFactoryImpl via tccl classloader");
+ }
+ }
+
+ /**
+ * Verify that the custom LogFactory implementation which is only accessable
+ * via the TCCL has successfully been loaded as specified in the config file.
+ * This proves that the TCCL was used to load that class.
+ */
+ public void testTcclLoading() throws Exception {
+ LogFactory instance = LogFactory.getFactory();
+
+ assertEquals(
+ "Correct LogFactory loaded",
+ "org.apache.commons.logging.tccl.custom.MyLogFactoryImpl",
+ instance.getClass().getName());
+ }
+}
diff --git a/src/test/resources/org/apache/commons/logging/config/nopriority/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/nopriority/commons-logging.properties
new file mode 100644
index 0000000..98d1177
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/config/nopriority/commons-logging.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+configId=nopriority
diff --git a/src/test/resources/org/apache/commons/logging/config/priority10/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/priority10/commons-logging.properties
new file mode 100644
index 0000000..fa10f1a
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/config/priority10/commons-logging.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+priority=10.5
+configId=priority10
diff --git a/src/test/resources/org/apache/commons/logging/config/priority20/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/priority20/commons-logging.properties
new file mode 100644
index 0000000..4ead91f
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/config/priority20/commons-logging.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+priority=20.6
+configId=priority20
diff --git a/src/test/resources/org/apache/commons/logging/config/priority20a/commons-logging.properties b/src/test/resources/org/apache/commons/logging/config/priority20a/commons-logging.properties
new file mode 100644
index 0000000..6a91d83
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/config/priority20a/commons-logging.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+priority=20.6
+configId=priority20a
diff --git a/src/test/resources/org/apache/commons/logging/jdk14/CustomConfig.properties b/src/test/resources/org/apache/commons/logging/jdk14/CustomConfig.properties
new file mode 100644
index 0000000..2a5e0e7
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/jdk14/CustomConfig.properties
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+
+# This is the custom configuration properties for the JDK 1.4 logger tests
+# in CustomConfigTestCase.
+
+# Configure the Handler so we can examine the logged messages
+handlers = org.apache.commons.logging.jdk14.TestHandler
+
+# Configre the default logging level to be FINE so we should get
+# everything except trace messages
+.level = FINE
diff --git a/src/test/resources/org/apache/commons/logging/tccl/log/props_disable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/log/props_disable_tccl/commons-logging.properties
new file mode 100644
index 0000000..8f55b92
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/tccl/log/props_disable_tccl/commons-logging.properties
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+use_tccl=false
+org.apache.commons.logging.Log=org.apache.commons.logging.tccl.custom.MyLog
+org.apache.commons.logging.diagnostics.dest=STDERR
\ No newline at end of file
diff --git a/src/test/resources/org/apache/commons/logging/tccl/log/props_enable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/log/props_enable_tccl/commons-logging.properties
new file mode 100644
index 0000000..440c764
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/tccl/log/props_enable_tccl/commons-logging.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+org.apache.commons.logging.Log=org.apache.commons.logging.tccl.custom.MyLog
+org.apache.commons.logging.diagnostics.dest=STDERR
\ No newline at end of file
diff --git a/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_disable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_disable_tccl/commons-logging.properties
new file mode 100644
index 0000000..9d21a8c
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_disable_tccl/commons-logging.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+use_tccl=false
+org.apache.commons.logging.LogFactory=org.apache.commons.logging.tccl.custom.MyLogFactoryImpl
diff --git a/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_enable_tccl/commons-logging.properties b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_enable_tccl/commons-logging.properties
new file mode 100644
index 0000000..29bc41a
--- /dev/null
+++ b/src/test/resources/org/apache/commons/logging/tccl/logfactory/props_enable_tccl/commons-logging.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+org.apache.commons.logging.LogFactory=org.apache.commons.logging.tccl.custom.MyLogFactoryImpl
Enumeration
, possibly null
+ * @return ArrayList
containing the enumerated
+ * elements in the enumerated order, not null
+ */
+ private ArrayList toList(Enumeration en) {
+ ArrayList results = new ArrayList();
+ if (en != null) {
+ while (en.hasMoreElements()){
+ Object element = en.nextElement();
+ results.add(element);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Same as parent class method except that when parentFirst is false
+ * the resource is looked for in the local classpath before the parent
+ * loader is consulted.
+ */
+ public InputStream getResourceAsStream(String name) {
+ if (parentFirst) {
+ return super.getResourceAsStream(name);
+ } else {
+ URL local = super.findResource(name);
+ if (local != null) {
+ try {
+ return local.openStream();
+ } catch(IOException e) {
+ // TODO: check if this is right or whether we should
+ // fall back to trying parent. The javadoc doesn't say...
+ return null;
+ }
+ }
+ return super.getResourceAsStream(name);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/PathableTestSuite.java b/src/test/java/org/apache/commons/logging/PathableTestSuite.java
new file mode 100644
index 0000000..1a2ca81
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/PathableTestSuite.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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;
+
+import java.util.Properties;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+/**
+ * Custom TestSuite class that can be used to control the context classloader
+ * in operation when a test runs.
+ *
+ * class SomeTestCase extends TestCase {
+ * public static Test suite() throws Exception {
+ * PathableClassLoader parent = new PathableClassLoader(null);
+ * parent.useSystemLoader("junit.");
+ *
+ * PathableClassLoader child = new PathableClassLoader(parent);
+ * child.addLogicalLib("testclasses");
+ * child.addLogicalLib("log4j12");
+ * child.addLogicalLib("commons-logging");
+ *
+ * Class testClass = child.loadClass(SomeTestCase.class.getName());
+ * ClassLoader contextClassLoader = child;
+ *
+ * PathableTestSuite suite = new PathableTestSuite(testClass, child);
+ * return suite;
+ * }
+ *
+ * // test methods go here
+ * }
+ *
+ * Note that if the suite method throws an exception then this will be handled
+ * reasonable gracefully by junit; it will report that the suite method for
+ * a test case failed with exception yyy.
+ *
+ * // NB: pseudo-code only
+ * setContextClassLoader(getContextClassLoader().getParent());
+ *
+ * This class ensures that any context classloader changes applied by a test
+ * is undone after the test is run, so tests don't need to worry about
+ * restoring the context classloader on exit. This class also ensures that
+ * the system properties are restored to their original settings after each
+ * test, so tests that manipulate those don't need to worry about resetting them.
+ * Limitations
+ *
+ *
+ * The result should be that the first file is used.
+ */
+public class FirstPriorityConfigTestCase extends TestCase {
+
+ // ------------------------------------------- JUnit Infrastructure Methods
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = FirstPriorityConfigTestCase.class;
+
+ // Determine the URL to this .class file, so that we can then
+ // append the priority dirs to it. For tidiness, load this
+ // class through a dummy loader though this is not absolutely
+ // necessary...
+ PathableClassLoader dummy = new PathableClassLoader(null);
+ dummy.useExplicitLoader("junit.", Test.class.getClassLoader());
+ dummy.addLogicalLib("testclasses");
+ dummy.addLogicalLib("commons-logging");
+
+ String thisClassPath = thisClass.getName().replace('.', '/') + ".class";
+ URL baseUrl = dummy.findResource(thisClassPath);
+
+ // Now set up the desired classloader hierarchy. We'll put JCL
+ // in the container path, the testcase in a webapp path, and
+ // both config files into the webapp path too.
+ PathableClassLoader containerLoader = new PathableClassLoader(null);
+ containerLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ containerLoader.addLogicalLib("commons-logging");
+
+ PathableClassLoader webappLoader = new PathableClassLoader(containerLoader);
+ webappLoader.addLogicalLib("testclasses");
+
+ URL pri20URL = new URL(baseUrl, "priority20/");
+ webappLoader.addURL(pri20URL);
+
+ URL pri10URL = new URL(baseUrl, "priority10/");
+ webappLoader.addURL(pri10URL);
+
+ // load the test class via webapp loader, and use the webapp loader
+ // as the tccl loader too.
+ Class testClass = webappLoader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, webappLoader);
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Verify that the config file being used is the one containing
+ * the desired configId value.
+ */
+ public void testPriority() throws Exception {
+ LogFactory instance = LogFactory.getFactory();
+
+ ClassLoader thisClassLoader = this.getClass().getClassLoader();
+ ClassLoader lfClassLoader = instance.getClass().getClassLoader();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+ // context classloader should be thisClassLoader
+ assertEquals(thisClassLoader, contextClassLoader);
+
+ // lfClassLoader should be parent of this classloader
+ assertEquals(lfClassLoader, thisClassLoader.getParent());
+ assertEquals(PathableClassLoader.class.getName(),
+ lfClassLoader.getClass().getName());
+
+ String id = (String) instance.getAttribute("configId");
+ assertEquals("Correct config file loaded", "priority20", id );
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/config/PriorityConfigTestCase.java b/src/test/java/org/apache/commons/logging/config/PriorityConfigTestCase.java
new file mode 100644
index 0000000..831e255
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/config/PriorityConfigTestCase.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.config;
+
+
+import java.net.URL;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Tests that verify that the process of configuring logging on startup
+ * works correctly by selecting the file with the highest priority.
+ *
+ *
+ * The result should be that the third file is used.
+ * PathableClassLoader.getResources
always behaves as if
+ * parentFirst=true; see the PathableClassLoader javadoc for details.
+ */
+
+public class PriorityConfigTestCase extends TestCase {
+
+ // ------------------------------------------- JUnit Infrastructure Methods
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = PriorityConfigTestCase.class;
+
+ // Determine the URL to this .class file, so that we can then
+ // append the priority dirs to it. For tidiness, load this
+ // class through a dummy loader though this is not absolutely
+ // necessary...
+ PathableClassLoader dummy = new PathableClassLoader(null);
+ dummy.useExplicitLoader("junit.", Test.class.getClassLoader());
+ dummy.addLogicalLib("testclasses");
+ dummy.addLogicalLib("commons-logging");
+
+ String thisClassPath = thisClass.getName().replace('.', '/') + ".class";
+ URL baseUrl = dummy.findResource(thisClassPath);
+
+ // Now set up the desired classloader hierarchy. We'll put a config
+ // file of priority=10 in the container path, and ones of both
+ // "no priority" and priority=20 in the webapp path.
+ //
+ // A second properties file with priority=20 is also added,
+ // so we can check that the first one in the classpath is
+ // used.
+ PathableClassLoader containerLoader = new PathableClassLoader(null);
+ containerLoader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ containerLoader.addLogicalLib("commons-logging");
+
+ URL pri10URL = new URL(baseUrl, "priority10/");
+ containerLoader.addURL(pri10URL);
+
+ PathableClassLoader webappLoader = new PathableClassLoader(containerLoader);
+ webappLoader.setParentFirst(true);
+ webappLoader.addLogicalLib("testclasses");
+
+ URL noPriorityURL = new URL(baseUrl, "nopriority/");
+ webappLoader.addURL(noPriorityURL);
+
+ URL pri20URL = new URL(baseUrl, "priority20/");
+ webappLoader.addURL(pri20URL);
+
+ URL pri20aURL = new URL(baseUrl, "priority20a/");
+ webappLoader.addURL(pri20aURL);
+
+ // load the test class via webapp loader, and use the webapp loader
+ // as the tccl loader too.
+ Class testClass = webappLoader.loadClass(thisClass.getName());
+ return new PathableTestSuite(testClass, webappLoader);
+ }
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ // ----------------------------------------------------------- Test Methods
+
+ /**
+ * Verify that the config file being used is the one containing
+ * the desired configId value.
+ */
+ public void testPriority() throws Exception {
+ LogFactory instance = LogFactory.getFactory();
+ String id = (String) instance.getAttribute("configId");
+ assertEquals("Correct config file loaded", "priority20", id );
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java b/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java
new file mode 100644
index 0000000..8a37068
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java
@@ -0,0 +1,313 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+public class WeakHashtableTestCase extends TestCase {
+
+ private static final int WAIT_FOR_THREAD_COMPLETION = 5000; // 5 seconds
+ private static final int RUN_LOOPS = 3000;
+ private static final int OUTER_LOOP = 400;
+ private static final int THREAD_COUNT = 10;
+
+ private static WeakHashtable hashtable;
+
+ /** Maximum number of iterations before our test fails */
+ private static final int MAX_GC_ITERATIONS = 50;
+
+ private WeakHashtable weakHashtable;
+ private Long keyOne;
+ private Long keyTwo;
+ private Long keyThree;
+ private Long valueOne;
+ private Long valueTwo;
+ private Long valueThree;
+
+ public WeakHashtableTestCase(String testName) {
+ super(testName);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ weakHashtable = new WeakHashtable();
+
+ keyOne = new Long(1);
+ keyTwo = new Long(2);
+ keyThree = new Long(3);
+ valueOne = new Long(100);
+ valueTwo = new Long(200);
+ valueThree = new Long(300);
+
+ weakHashtable.put(keyOne, valueOne);
+ weakHashtable.put(keyTwo, valueTwo);
+ weakHashtable.put(keyThree, valueThree);
+ }
+
+ /** Tests public boolean contains(Object value) */
+ public void testContains() throws Exception {
+ assertFalse(weakHashtable.contains(new Long(1)));
+ assertFalse(weakHashtable.contains(new Long(2)));
+ assertFalse(weakHashtable.contains(new Long(3)));
+ assertTrue(weakHashtable.contains(new Long(100)));
+ assertTrue(weakHashtable.contains(new Long(200)));
+ assertTrue(weakHashtable.contains(new Long(300)));
+ assertFalse(weakHashtable.contains(new Long(400)));
+ }
+
+ /** Tests public boolean containsKey(Object key) */
+ public void testContainsKey() throws Exception {
+ assertTrue(weakHashtable.containsKey(new Long(1)));
+ assertTrue(weakHashtable.containsKey(new Long(2)));
+ assertTrue(weakHashtable.containsKey(new Long(3)));
+ assertFalse(weakHashtable.containsKey(new Long(100)));
+ assertFalse(weakHashtable.containsKey(new Long(200)));
+ assertFalse(weakHashtable.containsKey(new Long(300)));
+ assertFalse(weakHashtable.containsKey(new Long(400)));
+ }
+
+ /** Tests public boolean containsValue(Object value) */
+ public void testContainsValue() throws Exception {
+ assertFalse(weakHashtable.containsValue(new Long(1)));
+ assertFalse(weakHashtable.containsValue(new Long(2)));
+ assertFalse(weakHashtable.containsValue(new Long(3)));
+ assertTrue(weakHashtable.containsValue(new Long(100)));
+ assertTrue(weakHashtable.containsValue(new Long(200)));
+ assertTrue(weakHashtable.containsValue(new Long(300)));
+ assertFalse(weakHashtable.containsValue(new Long(400)));
+ }
+
+ /** Tests public Enumeration elements() */
+ public void testElements() throws Exception {
+ ArrayList elements = new ArrayList();
+ for (Enumeration e = weakHashtable.elements(); e.hasMoreElements();) {
+ elements.add(e.nextElement());
+ }
+ assertEquals(3, elements.size());
+ assertTrue(elements.contains(valueOne));
+ assertTrue(elements.contains(valueTwo));
+ assertTrue(elements.contains(valueThree));
+ }
+
+ /** Tests public Set entrySet() */
+ public void testEntrySet() throws Exception {
+ Set entrySet = weakHashtable.entrySet();
+ for (Iterator it = entrySet.iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Object key = entry.getKey();
+ if (keyOne.equals(key)) {
+ assertEquals(valueOne, entry.getValue());
+ } else if (keyTwo.equals(key)) {
+ assertEquals(valueTwo, entry.getValue());
+ } else if (keyThree.equals(key)) {
+ assertEquals(valueThree, entry.getValue());
+ } else {
+ fail("Unexpected key");
+ }
+ }
+ }
+
+ /** Tests public Object get(Object key) */
+ public void testGet() throws Exception {
+ assertEquals(valueOne, weakHashtable.get(keyOne));
+ assertEquals(valueTwo, weakHashtable.get(keyTwo));
+ assertEquals(valueThree, weakHashtable.get(keyThree));
+ assertNull(weakHashtable.get(new Long(50)));
+ }
+
+ /** Tests public Enumeration keys() */
+ public void testKeys() throws Exception {
+ ArrayList keys = new ArrayList();
+ for (Enumeration e = weakHashtable.keys(); e.hasMoreElements();) {
+ keys.add(e.nextElement());
+ }
+ assertEquals(3, keys.size());
+ assertTrue(keys.contains(keyOne));
+ assertTrue(keys.contains(keyTwo));
+ assertTrue(keys.contains(keyThree));
+ }
+
+ /** Tests public Set keySet() */
+ public void testKeySet() throws Exception {
+ Set keySet = weakHashtable.keySet();
+ assertEquals(3, keySet.size());
+ assertTrue(keySet.contains(keyOne));
+ assertTrue(keySet.contains(keyTwo));
+ assertTrue(keySet.contains(keyThree));
+ }
+
+ /** Tests public Object put(Object key, Object value) */
+ public void testPut() throws Exception {
+ Long anotherKey = new Long(2004);
+ weakHashtable.put(anotherKey, new Long(1066));
+
+ assertEquals(new Long(1066), weakHashtable.get(anotherKey));
+
+ // Test compliance with the hashtable API re nulls
+ Exception caught = null;
+ try {
+ weakHashtable.put(null, new Object());
+ }
+ catch (Exception e) {
+ caught = e;
+ }
+ assertNotNull("did not throw an exception adding a null key", caught);
+ caught = null;
+ try {
+ weakHashtable.put(new Object(), null);
+ }
+ catch (Exception e) {
+ caught = e;
+ }
+ assertNotNull("did not throw an exception adding a null value", caught);
+ }
+
+ /** Tests public void putAll(Map t) */
+ public void testPutAll() throws Exception {
+ Map newValues = new HashMap();
+ Long newKey = new Long(1066);
+ Long newValue = new Long(1415);
+ newValues.put(newKey, newValue);
+ Long anotherNewKey = new Long(1645);
+ Long anotherNewValue = new Long(1815);
+ newValues.put(anotherNewKey, anotherNewValue);
+ weakHashtable.putAll(newValues);
+
+ assertEquals(5, weakHashtable.size());
+ assertEquals(newValue, weakHashtable.get(newKey));
+ assertEquals(anotherNewValue, weakHashtable.get(anotherNewKey));
+ }
+
+ /** Tests public Object remove(Object key) */
+ public void testRemove() throws Exception {
+ weakHashtable.remove(keyOne);
+ assertEquals(2, weakHashtable.size());
+ assertNull(weakHashtable.get(keyOne));
+ }
+
+ /** Tests public Collection values() */
+ public void testValues() throws Exception {
+ Collection values = weakHashtable.values();
+ assertEquals(3, values.size());
+ assertTrue(values.contains(valueOne));
+ assertTrue(values.contains(valueTwo));
+ assertTrue(values.contains(valueThree));
+ }
+
+ /**
+ * Disabled this test as it makes wrong assumptions wrt the GC.
+ * This test especially fails with:
+ *
+ * Java(TM) SE Runtime Environment (build pxi3260sr12-20121025_01(SR12))
+ * IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4 Linux x86-32 jvmxi3260sr12-20121024_1
+ */
+ public void xxxIgnoretestRelease() throws Exception {
+ assertNotNull(weakHashtable.get(new Long(1)));
+ ReferenceQueue testQueue = new ReferenceQueue();
+ WeakReference weakKeyOne = new WeakReference(keyOne, testQueue);
+
+ // lose our references
+ keyOne = null;
+ keyTwo = null;
+ keyThree = null;
+ valueOne = null;
+ valueTwo = null;
+ valueThree = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+
+ if(weakHashtable.get(new Long(1)) == null) {
+ break;
+
+ } else {
+ // create garbage:
+ byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ // some JVMs seem to take a little time to put references on
+ // the reference queue once the reference has been collected
+ // need to think about whether this is enough to justify
+ // stepping through the collection each time...
+ while(testQueue.poll() == null) {}
+
+ // Test that the released objects are not taking space in the table
+ assertEquals("underlying table not emptied", 0, weakHashtable.size());
+ }
+
+ public static class StupidThread extends Thread {
+
+ public StupidThread(String name) {
+ super(name);
+ }
+
+ public void run() {
+ for (int i = 0; i < RUN_LOOPS; i++) {
+ hashtable.put("key" + ":" + i%10, Boolean.TRUE);
+ if(i%50 == 0) {
+ yield();
+ }
+ }
+ }
+ }
+
+ public void testLOGGING_119() throws Exception {
+ Thread [] t = new Thread[THREAD_COUNT];
+ for (int j=1; j <= OUTER_LOOP; j++) {
+ hashtable = new WeakHashtable();
+ for (int i = 0; i < t.length; i++) {
+ t[i] = new StupidThread("Thread:" + i);
+ t[i].setDaemon(true); // Otherwise we cannot exit
+ t[i].start();
+ }
+ for (int i = 0; i < t.length; i++) {
+ t[i].join(WAIT_FOR_THREAD_COMPLETION);
+ if (t[i].isAlive()) {
+ break; // at least one thread is stuck
+ }
+ }
+ int active=0;
+ for (int i = 0; i < t.length; i++) {
+ if (t[i].isAlive()) {
+ active++;
+ }
+ }
+ if (active > 0) {
+ fail("Attempt: " + j + " Stuck threads: " + active);
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java
new file mode 100644
index 0000000..afa04d6
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jdk14;
+
+import junit.framework.Test;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * TestCase for Jdk14 logging when the commons-logging-api jar file is in
+ * the parent classpath and commons-logging.jar is in the child.
+ */
+
+public class CustomConfigAPITestCase extends CustomConfigTestCase {
+
+ public CustomConfigAPITestCase(String name) {
+ super(name);
+ }
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+
+ // the TestHandler class must be accessable from the System classloader
+ // in order for java.util.logging.LogManager.readConfiguration to
+ // be able to instantiate it. And this test case must see the same
+ // class in order to be able to access its data. Yes this is ugly
+ // but the whole jdk14 API is a ******* mess anyway.
+ ClassLoader scl = ClassLoader.getSystemClassLoader();
+ loadTestHandler(HANDLER_NAME, scl);
+ parent.useExplicitLoader(HANDLER_NAME, scl);
+ parent.addLogicalLib("commons-logging-api");
+
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.addLogicalLib("testclasses");
+ child.addLogicalLib("commons-logging");
+
+ Class testClass = child.loadClass(CustomConfigAPITestCase.class.getName());
+ return new PathableTestSuite(testClass, child);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java
new file mode 100644
index 0000000..86a6232
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jdk14;
+
+
+import junit.framework.Test;
+
+import org.apache.commons.logging.PathableTestSuite;
+import org.apache.commons.logging.PathableClassLoader;
+
+
+/**
+ * TestCase for Jdk14 logging when the commons-logging jar file is in
+ * the parent classpath.
+ */
+
+public class CustomConfigFullTestCase extends CustomConfigTestCase {
+
+
+ public CustomConfigFullTestCase(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+
+ // the TestHandler class must be accessable from the System classloader
+ // in order for java.util.logging.LogManager.readConfiguration to
+ // be able to instantiate it. And this test case must see the same
+ // class in order to be able to access its data. Yes this is ugly
+ // but the whole jdk14 API is a ******* mess anyway.
+ ClassLoader scl = ClassLoader.getSystemClassLoader();
+ loadTestHandler(HANDLER_NAME, scl);
+ parent.useExplicitLoader(HANDLER_NAME, scl);
+ parent.addLogicalLib("commons-logging");
+
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.addLogicalLib("testclasses");
+
+ Class testClass = child.loadClass(CustomConfigFullTestCase.class.getName());
+ return new PathableTestSuite(testClass, child);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java
new file mode 100644
index 0000000..d970d76
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jdk14;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import junit.framework.Test;
+
+import org.apache.commons.logging.DummyException;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Handler
we will be using.Handler
s we will be using.Logger
we will be using.LogManager
we will be using.java.util.logging.Handler
.
+ *
+ *
+ * @param logEvents is the list of log events received.
+ *
+ * @param thrown False if logPlainMessages was called
+ * (ie the TestAppender is expected to have received
+ * logevents with no associated exception info). True if
+ * logExceptionMessages was called.
+ */
+ private void checkLoggingEvents(List logEvents, boolean thrown) {
+ LogEvent ev;
+
+ assertEquals("Unexpected number of log events", 4, logEvents.size());
+
+ ev = (LogEvent) logEvents.get(0);
+ assertEquals("Info message expected", "info", ev.msg);
+ assertEquals("Info level expected", "INFO", ev.level);
+ assertEquals("Exception data incorrect", ev.throwable!=null, thrown);
+
+ ev = (LogEvent) logEvents.get(1);
+ assertEquals("Warn message expected", "warn", ev.msg);
+ assertEquals("Warn level expected", "WARN", ev.level);
+ assertEquals("Exception data incorrect", ev.throwable!=null, thrown);
+
+ ev = (LogEvent) logEvents.get(2);
+ assertEquals("Error message expected", "error", ev.msg);
+ assertEquals("Error level expected", "ERROR", ev.level);
+ assertEquals("Exception data incorrect", ev.throwable!=null, thrown);
+
+ ev = (LogEvent) logEvents.get(3);
+ assertEquals("Fatal message expected", "fatal", ev.msg);
+ assertEquals("Fatal level expected", "FATAL", ev.level);
+ assertEquals("Exception data incorrect", ev.throwable!=null, thrown);
+ }
+
+
+ /**
+ * Log plain messages.
+ */
+ private void logPlainMessages(Log log) {
+ log.trace("trace"); // Should not actually get logged
+ log.debug("debug"); // Should not actually get logged
+ log.info("info");
+ log.warn("warn");
+ log.error("error");
+ log.fatal("fatal");
+ }
+
+ /**
+ * Log messages with exceptions
+ */
+ private void logExceptionMessages(Log log) {
+ Throwable t = new DummyException();
+ log.trace("trace", t); // Should not actually get logged
+ log.debug("debug", t); // Should not actually get logged
+ log.info("info", t);
+ log.warn("warn", t);
+ log.error("error", t);
+ log.fatal("fatal", t);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/ApiClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/ApiClasspathStandardTestCase.java
new file mode 100644
index 0000000..1b7bace
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/ApiClasspathStandardTestCase.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log4j.log4j12;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Tests for Log4J logging that emulate a webapp running within
+ * a container where the commons-logging-api jar file is in
+ * the parent classpath and commons-logging.jar is in the child.
+ */
+
+public class ApiClasspathStandardTestCase extends TestCase {
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parent.addLogicalLib("commons-logging-api");
+
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.addLogicalLib("log4j12");
+ child.addLogicalLib("commons-logging");
+ child.addLogicalLib("testclasses");
+
+ Class testClass = child.loadClass(
+ "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests");
+ return new PathableTestSuite(testClass, child);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/AppClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/AppClasspathStandardTestCase.java
new file mode 100644
index 0000000..71da059
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/AppClasspathStandardTestCase.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log4j.log4j12;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+/**
+ * Tests for Log4J logging when there is only one classloader and everything
+ * is in it, as would be the situation for a standalone application.
+ */
+
+public class AppClasspathStandardTestCase extends TestCase {
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader loader = new PathableClassLoader(null);
+ loader.useExplicitLoader("junit.", Test.class.getClassLoader());
+ loader.addLogicalLib("testclasses");
+ loader.addLogicalLib("log4j12");
+ loader.addLogicalLib("commons-logging");
+
+ Class testClass = loader.loadClass(
+ "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests");
+ return new PathableTestSuite(testClass, loader);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/ChildClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/ChildClasspathStandardTestCase.java
new file mode 100644
index 0000000..2620dbd
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/ChildClasspathStandardTestCase.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log4j.log4j12;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+
+/**
+ * Tests for Log4J logging that emulate a webapp running within
+ * a container where all the necessary libs are in the child.
+ */
+
+public class ChildClasspathStandardTestCase extends TestCase {
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.addLogicalLib("testclasses");
+ child.addLogicalLib("log4j12");
+ child.addLogicalLib("commons-logging");
+
+ Class testClass = child.loadClass(
+ "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests");
+ return new PathableTestSuite(testClass, child);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/Log4j12StandardTests.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/Log4j12StandardTests.java
new file mode 100644
index 0000000..a03e74a
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/Log4j12StandardTests.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log4j.log4j12;
+
+import java.util.List;
+
+import org.apache.commons.logging.log4j.StandardTests;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+/**
+ * A concrete class that runs the standard tests, and is compiled
+ * specifically against log4j12. The parent class can't call any
+ * log4j methods at all as that would mean it has to be compiled
+ * against a particular version of log4j.
+ */
+
+public class Log4j12StandardTests extends StandardTests {
+
+ public void setUpTestAppender(List logEvents) {
+ TestAppender appender = new TestAppender(logEvents);
+ Logger rootLogger = Logger.getRootLogger();
+ rootLogger.removeAllAppenders();
+ rootLogger.addAppender(appender);
+ rootLogger.setLevel(Level.INFO);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/ParentClasspathStandardTestCase.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/ParentClasspathStandardTestCase.java
new file mode 100644
index 0000000..3654d92
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/ParentClasspathStandardTestCase.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log4j.log4j12;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+/**
+ * Tests for Log4J logging that emulate a webapp running within
+ * a container where all the necessary libs are in the parent.
+ */
+
+public class ParentClasspathStandardTestCase extends TestCase {
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() throws Exception {
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.useExplicitLoader("junit.", Test.class.getClassLoader());
+ parent.addLogicalLib("commons-logging");
+ parent.addLogicalLib("log4j12");
+
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.addLogicalLib("testclasses");
+
+ Class testClass = child.loadClass(
+ "org.apache.commons.logging.log4j.log4j12.Log4j12StandardTests");
+ return new PathableTestSuite(testClass, child);
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java
new file mode 100644
index 0000000..5ad71c8
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.log4j.log4j12;
+
+
+import java.util.List;
+
+import org.apache.commons.logging.log4j.StandardTests;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * A custom implementation of org.apache.log4j.Appender
which
+ * converts the log4j-specific log event record into a representation that
+ * doesn't have a dependency on log4j and stores that new representation into
+ * an external list.
+ */
+
+public class TestAppender extends AppenderSkeleton {
+
+ /**
+ * Constructor.
+ */
+ public TestAppender(List logEvents) {
+ events = logEvents;
+ }
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ // The set of logged events for this appender
+ private final List events;
+
+
+ // ------------------------------------------------------- Appender Methods
+
+ protected void append(LoggingEvent event) {
+ StandardTests.LogEvent lev = new StandardTests.LogEvent();
+
+ lev.level = event.getLevel().toString();
+
+ if (event.getMessage() == null) {
+ lev.msg = null;
+ } else {
+ lev.msg = event.getMessage().toString();
+ }
+
+ if (event.getThrowableInformation() == null) {
+ lev.throwable = null;
+ } else {
+ lev.throwable = event.getThrowableInformation().getThrowable();
+ }
+
+ events.add(lev);
+ }
+
+
+ public void close() {
+ }
+
+
+ public boolean requiresLayout() {
+ return false;
+ }
+
+
+}
diff --git a/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java b/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java
new file mode 100644
index 0000000..4c1fbd8
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.logkit;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import junit.framework.Test;
+
+import org.apache.commons.logging.AbstractLogTest;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+import org.apache.commons.logging.impl.LogKitLogger;
+
+/**
+ * Basic tests for Avalon LogKit logger adapter.
+ */
+
+public class StandardTestCase extends AbstractLogTest {
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ *
+ *
+ */
+
+public class ChildFirstTestCase extends TestCase {
+
+ /**
+ * Set up a custom classloader hierarchy for this test case.
+ * The hierarchy is:
+ *
+ *
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = ChildFirstTestCase.class;
+ ClassLoader thisClassLoader = thisClass.getClassLoader();
+
+ // Make the parent a direct child of the bootloader to hide all
+ // other classes in the system classpath
+ PathableClassLoader parent = new PathableClassLoader(null);
+ parent.setParentFirst(false);
+
+ // Make the junit classes visible as a special case, as junit
+ // won't be able to call this class at all without this. The
+ // junit classes must be visible from the classloader that loaded
+ // this class, so use that as the source for future access to classes
+ // from the junit package.
+ parent.useExplicitLoader("junit.", thisClassLoader);
+
+ // Make the commons-logging.jar classes visible via the parent
+ parent.addLogicalLib("commons-logging");
+
+ // Create a child classloader to load the test case through
+ PathableClassLoader child = new PathableClassLoader(parent);
+ child.setParentFirst(false);
+
+ // Obviously, the child classloader needs to have the test classes
+ // in its path!
+ child.addLogicalLib("testclasses");
+ child.addLogicalLib("commons-logging-adapters");
+
+ // Create a third classloader to be the context classloader.
+ PathableClassLoader context = new PathableClassLoader(child);
+ context.setParentFirst(false);
+
+ // reload this class via the child classloader
+ Class testClass = child.loadClass(thisClass.getName());
+
+ // and return our custom TestSuite class
+ return new PathableTestSuite(testClass, context);
+ }
+
+ /**
+ * Utility method to return the set of all classloaders in the
+ * parent chain starting from the one that loaded the class for
+ * this object instance.
+ */
+ private Set getAncestorCLs() {
+ Set s = new HashSet();
+ ClassLoader cl = this.getClass().getClassLoader();
+ while (cl != null) {
+ s.add(cl);
+ cl = cl.getParent();
+ }
+ return s;
+ }
+
+ /**
+ * Test that the classloader hierarchy is as expected, and that
+ * calling loadClass() on various classloaders works as expected.
+ * Note that for this test case, parent-first classloading is
+ * in effect.
+ */
+ public void testPaths() throws Exception {
+ // the context classloader is not expected to be null
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ assertNotNull("Context classloader is null", contextLoader);
+ assertEquals("Context classloader has unexpected type",
+ PathableClassLoader.class.getName(),
+ contextLoader.getClass().getName());
+
+ // the classloader that loaded this class is obviously not null
+ ClassLoader thisLoader = this.getClass().getClassLoader();
+ assertNotNull("thisLoader is null", thisLoader);
+ assertEquals("thisLoader has unexpected type",
+ PathableClassLoader.class.getName(),
+ thisLoader.getClass().getName());
+
+ // the suite method specified that the context classloader's parent
+ // is the loader that loaded this test case.
+ assertSame("Context classloader is not child of thisLoader",
+ thisLoader, contextLoader.getParent());
+
+ // thisLoader's parent should be available
+ ClassLoader parentLoader = thisLoader.getParent();
+ assertNotNull("Parent classloader is null", parentLoader);
+ assertEquals("Parent classloader has unexpected type",
+ PathableClassLoader.class.getName(),
+ parentLoader.getClass().getName());
+
+ // parent should have a parent of null
+ assertNull("Parent classloader has non-null parent", parentLoader.getParent());
+
+ // getSystemClassloader is not a PathableClassLoader; it's of a
+ // built-in type. This also verifies that system classloader is none of
+ // (context, child, parent).
+ ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
+ assertNotNull("System classloader is null", systemLoader);
+ assertFalse("System classloader has unexpected type",
+ PathableClassLoader.class.getName().equals(
+ systemLoader.getClass().getName()));
+
+ // junit classes should be visible; their classloader is not
+ // in the hierarchy of parent classloaders for this class,
+ // though it is accessable due to trickery in the PathableClassLoader.
+ Class junitTest = contextLoader.loadClass("junit.framework.Test");
+ Set ancestorCLs = getAncestorCLs();
+ assertFalse("Junit not loaded by ancestor classloader",
+ ancestorCLs.contains(junitTest.getClassLoader()));
+
+ // jcl api classes should be visible only via the parent
+ Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
+ assertSame("Log class not loaded via parent",
+ logClass.getClassLoader(), parentLoader);
+
+ // jcl adapter classes should be visible via both parent and child. However
+ // as the classloaders are child-first we should see the child one.
+ Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger");
+ assertSame("Log4JLogger not loaded via child",
+ log4jClass.getClassLoader(), thisLoader);
+
+ // test classes should be visible via the child only
+ Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
+ assertSame("PathableTestSuite not loaded via child",
+ testClass.getClassLoader(), thisLoader);
+
+ // test loading of class that is not available
+ try {
+ Class noSuchClass = contextLoader.loadClass("no.such.class");
+ fail("Class no.such.class is unexpectedly available");
+ assertNotNull(noSuchClass); // silence warning about unused var
+ } catch(ClassNotFoundException ex) {
+ // ok
+ }
+
+ // String class classloader is null
+ Class stringClass = contextLoader.loadClass("java.lang.String");
+ assertNull("String class classloader is not null!",
+ stringClass.getClassLoader());
+ }
+
+ /**
+ * Test that the various flavours of ClassLoader.getResource work as expected.
+ */
+ public void testResource() {
+ URL resource;
+
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+
+ // getResource where it doesn't exist
+ resource = childLoader.getResource("nosuchfile");
+ assertNull("Non-null URL returned for invalid resource name", resource);
+
+ // getResource where it is accessable only to parent classloader
+ resource = childLoader.getResource("org/apache/commons/logging/Log.class");
+ assertNotNull("Unable to locate Log.class resource", resource);
+
+ // getResource where it is accessable only to child classloader
+ resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
+ assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
+
+ // getResource where it is accessable to both classloaders. The one visible
+ // to the child should be returned. The URL returned will be of form
+ // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
+ // of form commons-logging-adapters-nnnn.jar, not commons-logging-nnnn.jar
+ resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class");
+ assertNotNull("Unable to locate Log4JLogger.class resource", resource);
+ assertTrue("Incorrect source for Log4JLogger class",
+ resource.toString().indexOf("/commons-logging-adapters-1.") > 0);
+ }
+
+ /**
+ * Test that the various flavours of ClassLoader.getResources work as expected.
+ */
+ public void testResources() throws Exception {
+ Enumeration resources;
+ URL[] urls;
+
+ // verify the classloader hierarchy
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+ ClassLoader parentLoader = childLoader.getParent();
+ ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResources where no instances exist
+ resources = childLoader.getResources("nosuchfile");
+ urls = toURLArray(resources);
+ assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
+
+ // getResources where the resource only exists in the parent
+ resources = childLoader.getResources("org/apache/commons/logging/Log.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
+
+ // getResources where the resource only exists in the child
+ resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
+
+ // getResources where the resource exists in both.
+ // resources should be returned in order (child-resource, parent-resource).
+ //
+ // IMPORTANT: due to the fact that in java 1.4 and earlier method
+ // ClassLoader.getResources is final it isn't possible for PathableClassLoader
+ // to override this. So even when child-first is enabled the resource order
+ // is still (parent-resources, child-resources). This test verifies the expected
+ // behaviour - even though it's not the desired behaviour.
+
+ resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length);
+
+ // There is no guarantee about the ordering of results returned from getResources
+ // To make this test portable across JVMs, sort the string to give them a known order
+ String[] urlsToStrings = new String[2];
+ urlsToStrings[0] = urls[0].toString();
+ urlsToStrings[1] = urls[1].toString();
+ Arrays.sort(urlsToStrings);
+ assertTrue("Incorrect source for Log4JLogger class",
+ urlsToStrings[0].indexOf("/commons-logging-1.") > 0);
+ assertTrue("Incorrect source for Log4JLogger class",
+ urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
+ }
+
+ /**
+ * Utility method to convert an enumeration-of-URLs into an array of URLs.
+ */
+ private static URL[] toURLArray(Enumeration e) {
+ ArrayList l = new ArrayList();
+ while (e.hasMoreElements()) {
+ URL u = (URL) e.nextElement();
+ l.add(u);
+ }
+ URL[] tmp = new URL[l.size()];
+ return (URL[]) l.toArray(tmp);
+ }
+
+ /**
+ * Test that getResourceAsStream works.
+ */
+ public void testResourceAsStream() throws Exception {
+ java.io.InputStream is;
+
+ // verify the classloader hierarchy
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+ ClassLoader parentLoader = childLoader.getParent();
+ ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResourceAsStream where no instances exist
+ is = childLoader.getResourceAsStream("nosuchfile");
+ assertNull("Invalid resource returned non-null stream", is);
+
+ // getResourceAsStream where resource does exist
+ is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
+ assertNotNull("Null returned for valid resource", is);
+ is.close();
+
+ // It would be nice to test parent-first ordering here, but that would require
+ // having a resource with the same name in both the parent and child loaders,
+ // but with different contents. That's a little tricky to set up so we'll
+ // skip that for now.
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/pathable/GeneralTestCase.java b/src/test/java/org/apache/commons/logging/pathable/GeneralTestCase.java
new file mode 100644
index 0000000..86fd475
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/pathable/GeneralTestCase.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.pathable;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.PathableClassLoader;
+import org.apache.commons.logging.PathableTestSuite;
+
+/**
+ * Tests for the PathableTestSuite class.
+ */
+
+public class GeneralTestCase extends TestCase {
+
+ /**
+ * Set up a custom classloader hierarchy for this test case.
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = GeneralTestCase.class;
+ ClassLoader thisClassLoader = thisClass.getClassLoader();
+
+ PathableClassLoader loader = new PathableClassLoader(null);
+ loader.useExplicitLoader("junit.", thisClassLoader);
+ loader.addLogicalLib("testclasses");
+
+ // reload this class via the child classloader
+ Class testClass = loader.loadClass(thisClass.getName());
+
+ // and return our custom TestSuite class
+ return new PathableTestSuite(testClass, loader);
+ }
+
+ /**
+ * Verify that a certain system property is not set, then set it.
+ */
+ private static void checkAndSetProperties() {
+ String prop = System.getProperty("no.such.property");
+ assertNull("no.such.property is unexpectedly defined", prop);
+ System.setProperty("no.such.property", "dummy value");
+ prop = System.getProperty("no.such.property");
+ assertNotNull("no.such.property is unexpectedly undefined", prop);
+ }
+
+ /**
+ * Verify that when a test method modifies the system properties they are
+ * reset before the next test is run.
+ *
+ *
+ */
+
+public class ParentFirstTestCase extends TestCase {
+
+ /**
+ * Set up a custom classloader hierarchy for this test case.
+ * The hierarchy is:
+ *
+ *
+ */
+ public static Test suite() throws Exception {
+ Class thisClass = ParentFirstTestCase.class;
+ ClassLoader thisClassLoader = thisClass.getClassLoader();
+
+ // Make the parent a direct child of the bootloader to hide all
+ // other classes in the system classpath
+ PathableClassLoader parent = new PathableClassLoader(null);
+
+ // Make the junit classes visible as a special case, as junit
+ // won't be able to call this class at all without this. The
+ // junit classes must be visible from the classloader that loaded
+ // this class, so use that as the source for future access to classes
+ // from the junit package.
+ parent.useExplicitLoader("junit.", thisClassLoader);
+
+ // make the commons-logging.jar classes visible via the parent
+ parent.addLogicalLib("commons-logging");
+
+ // create a child classloader to load the test case through
+ PathableClassLoader child = new PathableClassLoader(parent);
+
+ // obviously, the child classloader needs to have the test classes
+ // in its path!
+ child.addLogicalLib("testclasses");
+ child.addLogicalLib("commons-logging-adapters");
+
+ // create a third classloader to be the context classloader.
+ PathableClassLoader context = new PathableClassLoader(child);
+
+ // reload this class via the child classloader
+ Class testClass = child.loadClass(thisClass.getName());
+
+ // and return our custom TestSuite class
+ return new PathableTestSuite(testClass, context);
+ }
+
+ /**
+ * Utility method to return the set of all classloaders in the
+ * parent chain starting from the one that loaded the class for
+ * this object instance.
+ */
+ private Set getAncestorCLs() {
+ Set s = new HashSet();
+ ClassLoader cl = this.getClass().getClassLoader();
+ while (cl != null) {
+ s.add(cl);
+ cl = cl.getParent();
+ }
+ return s;
+ }
+
+ /**
+ * Test that the classloader hierarchy is as expected, and that
+ * calling loadClass() on various classloaders works as expected.
+ * Note that for this test case, parent-first classloading is
+ * in effect.
+ */
+ public void testPaths() throws Exception {
+ // the context classloader is not expected to be null
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ assertNotNull("Context classloader is null", contextLoader);
+ assertEquals("Context classloader has unexpected type",
+ PathableClassLoader.class.getName(),
+ contextLoader.getClass().getName());
+
+ // the classloader that loaded this class is obviously not null
+ ClassLoader thisLoader = this.getClass().getClassLoader();
+ assertNotNull("thisLoader is null", thisLoader);
+ assertEquals("thisLoader has unexpected type",
+ PathableClassLoader.class.getName(),
+ thisLoader.getClass().getName());
+
+ // the suite method specified that the context classloader's parent
+ // is the loader that loaded this test case.
+ assertSame("Context classloader is not child of thisLoader",
+ thisLoader, contextLoader.getParent());
+
+ // thisLoader's parent should be available
+ ClassLoader parentLoader = thisLoader.getParent();
+ assertNotNull("Parent classloader is null", parentLoader);
+ assertEquals("Parent classloader has unexpected type",
+ PathableClassLoader.class.getName(),
+ parentLoader.getClass().getName());
+
+ // parent should have a parent of null
+ assertNull("Parent classloader has non-null parent", parentLoader.getParent());
+
+ // getSystemClassloader is not a PathableClassLoader; it's of a
+ // built-in type. This also verifies that system classloader is none of
+ // (context, child, parent).
+ ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
+ assertNotNull("System classloader is null", systemLoader);
+ assertFalse("System classloader has unexpected type",
+ PathableClassLoader.class.getName().equals(
+ systemLoader.getClass().getName()));
+
+ // junit classes should be visible; their classloader is not
+ // in the hierarchy of parent classloaders for this class,
+ // though it is accessable due to trickery in the PathableClassLoader.
+ Class junitTest = contextLoader.loadClass("junit.framework.Test");
+ Set ancestorCLs = getAncestorCLs();
+ assertFalse("Junit not loaded by ancestor classloader",
+ ancestorCLs.contains(junitTest.getClassLoader()));
+
+ // jcl api classes should be visible only via the parent
+ Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
+ assertSame("Log class not loaded via parent",
+ logClass.getClassLoader(), parentLoader);
+
+ // jcl adapter classes should be visible via both parent and child. However
+ // as the classloaders are parent-first we should see the parent one.
+ Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger");
+ assertSame("Log4JLogger not loaded via parent",
+ log4jClass.getClassLoader(), parentLoader);
+
+ // test classes should be visible via the child only
+ Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
+ assertSame("PathableTestSuite not loaded via child",
+ testClass.getClassLoader(), thisLoader);
+
+ // test loading of class that is not available
+ try {
+ Class noSuchClass = contextLoader.loadClass("no.such.class");
+ fail("Class no.such.class is unexpectedly available");
+ assertNotNull(noSuchClass); // silence warning about unused var
+ } catch(ClassNotFoundException ex) {
+ // ok
+ }
+
+ // String class classloader is null
+ Class stringClass = contextLoader.loadClass("java.lang.String");
+ assertNull("String class classloader is not null!",
+ stringClass.getClassLoader());
+ }
+
+ /**
+ * Test that the various flavours of ClassLoader.getResource work as expected.
+ */
+ public void testResource() {
+ URL resource;
+
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+
+ // getResource where it doesn't exist
+ resource = childLoader.getResource("nosuchfile");
+ assertNull("Non-null URL returned for invalid resource name", resource);
+
+ // getResource where it is accessable only to parent classloader
+ resource = childLoader.getResource("org/apache/commons/logging/Log.class");
+ assertNotNull("Unable to locate Log.class resource", resource);
+
+ // getResource where it is accessable only to child classloader
+ resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
+ assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
+
+ // getResource where it is accessable to both classloaders. The one visible
+ // to the parent should be returned. The URL returned will be of form
+ // jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
+ // of form commons-logging-nnnn.jar, not commons-logging-adapters-nnnn.jar
+ resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class");
+ assertNotNull("Unable to locate Log4JLogger.class resource", resource);
+ assertTrue("Incorrect source for Log4JLogger class",
+ resource.toString().indexOf("/commons-logging-1.") > 0);
+ }
+
+ /**
+ * Test that the various flavours of ClassLoader.getResources work as expected.
+ */
+ public void testResources() throws Exception {
+ Enumeration resources;
+ URL[] urls;
+
+ // verify the classloader hierarchy
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+ ClassLoader parentLoader = childLoader.getParent();
+ ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResources where no instances exist
+ resources = childLoader.getResources("nosuchfile");
+ urls = toURLArray(resources);
+ assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
+
+ // getResources where the resource only exists in the parent
+ resources = childLoader.getResources("org/apache/commons/logging/Log.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
+
+ // getResources where the resource only exists in the child
+ resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
+
+ // getResources where the resource exists in both.
+ // resources should be returned in order (parent-resource, child-resource)
+ resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class");
+ urls = toURLArray(resources);
+ assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length);
+
+ // There is no gaurantee about the ordering of results returned from getResources
+ // To make this test portable across JVMs, sort the string to give them a known order
+ String[] urlsToStrings = new String[2];
+ urlsToStrings[0] = urls[0].toString();
+ urlsToStrings[1] = urls[1].toString();
+ Arrays.sort(urlsToStrings);
+ assertTrue("Incorrect source for Log4JLogger class",
+ urlsToStrings[0].indexOf("/commons-logging-1.") > 0);
+ assertTrue("Incorrect source for Log4JLogger class",
+ urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
+
+ }
+
+ /**
+ * Utility method to convert an enumeration-of-URLs into an array of URLs.
+ */
+ private static URL[] toURLArray(Enumeration e) {
+ ArrayList l = new ArrayList();
+ while (e.hasMoreElements()) {
+ URL u = (URL) e.nextElement();
+ l.add(u);
+ }
+ URL[] tmp = new URL[l.size()];
+ return (URL[]) l.toArray(tmp);
+ }
+
+ /**
+ * Test that getResourceAsStream works.
+ */
+ public void testResourceAsStream() throws Exception {
+ java.io.InputStream is;
+
+ // verify the classloader hierarchy
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader childLoader = contextLoader.getParent();
+ ClassLoader parentLoader = childLoader.getParent();
+ ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResourceAsStream where no instances exist
+ is = childLoader.getResourceAsStream("nosuchfile");
+ assertNull("Invalid resource returned non-null stream", is);
+
+ // getResourceAsStream where resource does exist
+ is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
+ assertNotNull("Null returned for valid resource", is);
+ is.close();
+
+ // It would be nice to test parent-first ordering here, but that would require
+ // having a resource with the same name in both the parent and child loaders,
+ // but with different contents. That's a little tricky to set up so we'll
+ // skip that for now.
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/security/DummyClass.java b/src/test/java/org/apache/commons/logging/security/DummyClass.java
new file mode 100644
index 0000000..ece0e7c
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/security/DummyClass.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You 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.security;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class DummyClass {
+
+ public DummyClass() {
+ Log log = LogFactory.getLog(DummyClass.class);
+ log.info("Some log message");
+ }
+}
diff --git a/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java b/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java
new file mode 100644
index 0000000..0d9b4de
--- /dev/null
+++ b/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.security;
+
+import java.io.FilePermission;
+import java.security.Permission;
+import java.security.Permissions;
+
+
+/**
+ * Custom implementation of a security manager, so we can control the
+ * security environment for tests in this package.
+ */
+public class MockSecurityManager extends SecurityManager {
+
+ private final Permissions permissions = new Permissions();
+ private static final Permission setSecurityManagerPerm =
+ new RuntimePermission("setSecurityManager");
+
+ private int untrustedCodeCount = 0;
+
+ public MockSecurityManager() {
+ permissions.add(setSecurityManagerPerm);
+ }
+
+ /**
+ * Define the set of permissions to be granted to classes in the o.a.c.l package,
+ * but NOT to unit-test classes in o.a.c.l.security package.
+ */
+ public void addPermission(Permission p) {
+ permissions.add(p);
+ }
+
+ /**
+ * This returns the number of times that a check of a permission failed
+ * due to stack-walking tracing up into untrusted code. Any non-zero
+ * value indicates a bug in JCL, ie a situation where code was not
+ * correctly wrapped in an AccessController block. The result of such a
+ * bug is that signing JCL is not sufficient to allow JCL to perform
+ * the operation; the caller would need to be signed too.
+ */
+ public int getUntrustedCodeCount() {
+ return untrustedCodeCount;
+ }
+
+ public void checkPermission(Permission p) throws SecurityException {
+ if (setSecurityManagerPerm.implies(p)) {
+ // ok, allow this; we don't want to block any calls to setSecurityManager
+ // otherwise this custom security manager cannot be reset to the original.
+ // System.out.println("setSecurityManager: granted");
+ return;
+ }
+
+ // Allow read-only access to files, as this is needed to load classes!
+ // Ideally, we would limit this to just .class and .jar files.
+ if (p instanceof FilePermission) {
+ FilePermission fp = (FilePermission) p;
+ if (fp.getActions().equals("read")) {
+ // System.out.println("Permit read of files");
+ return;
+ }
+ }
+
+ System.out.println("\n\ntesting permission:" + p.getClass() + ":"+ p);
+
+ Exception e = new Exception();
+ e.fillInStackTrace();
+ StackTraceElement[] stack = e.getStackTrace();
+
+ // scan the call stack from most recent to oldest.
+ // start at 1 to skip the entry in the stack for this method
+ for(int i=1; i