diff --git a/java/src/org/apache/commons/logging/Log.java b/java/src/org/apache/commons/logging/Log.java new file mode 100644 index 0000000..bf80bad --- /dev/null +++ b/java/src/org/apache/commons/logging/Log.java @@ -0,0 +1,246 @@ +/* + * 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; + +/** + *

A simple logging interface abstracting logging APIs. In order to be + * instantiated successfully by {@link LogFactory}, classes that implement + * this interface must have a constructor that takes a single String + * parameter representing the "name" of this Log.

+ * + *

The six logging levels used by Log are (in order): + *

    + *
  1. trace (the least serious)
  2. + *
  3. debug
  4. + *
  5. info
  6. + *
  7. warn
  8. + *
  9. error
  10. + *
  11. fatal (the most serious)
  12. + *
+ * The mapping of these log levels to the concepts used by the underlying + * logging system is implementation dependent. + * The implemention should ensure, though, that this ordering behaves + * as expected.

+ * + *

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.

+ * + * @author Scott Sanders + * @author Rod Waldhoff + * @version $Id: Log.java 424107 2006-07-20 23:15:42Z skitching $ + */ +public interface Log { + + + // ----------------------------------------------------- Logging Properties + + + /** + *

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. + */ + public 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. + */ + public 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. + */ + public 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. + */ + public 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. + */ + public 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. + */ + public boolean isWarnEnabled(); + + + // -------------------------------------------------------- Logging Methods + + + /** + *

Log a message with trace log level.

+ * + * @param message log this message + */ + public void trace(Object message); + + + /** + *

Log an error with trace log level.

+ * + * @param message log this message + * @param t log this cause + */ + public void trace(Object message, Throwable t); + + + /** + *

Log a message with debug log level.

+ * + * @param message log this message + */ + public void debug(Object message); + + + /** + *

Log an error with debug log level.

+ * + * @param message log this message + * @param t log this cause + */ + public void debug(Object message, Throwable t); + + + /** + *

Log a message with info log level.

+ * + * @param message log this message + */ + public void info(Object message); + + + /** + *

Log an error with info log level.

+ * + * @param message log this message + * @param t log this cause + */ + public void info(Object message, Throwable t); + + + /** + *

Log a message with warn log level.

+ * + * @param message log this message + */ + public void warn(Object message); + + + /** + *

Log an error with warn log level.

+ * + * @param message log this message + * @param t log this cause + */ + public void warn(Object message, Throwable t); + + + /** + *

Log a message with error log level.

+ * + * @param message log this message + */ + public void error(Object message); + + + /** + *

Log an error with error log level.

+ * + * @param message log this message + * @param t log this cause + */ + public void error(Object message, Throwable t); + + + /** + *

Log a message with fatal log level.

+ * + * @param message log this message + */ + public void fatal(Object message); + + + /** + *

Log an error with fatal log level.

+ * + * @param message log this message + * @param t log this cause + */ + public void fatal(Object message, Throwable t); + + +} diff --git a/java/src/org/apache/commons/logging/LogConfigurationException.java b/java/src/org/apache/commons/logging/LogConfigurationException.java new file mode 100644 index 0000000..aae0708 --- /dev/null +++ b/java/src/org/apache/commons/logging/LogConfigurationException.java @@ -0,0 +1,98 @@ +/* + * 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.

+ * + * @author Craig R. McClanahan + * @version $Revision: 424107 $ $Date: 2006-07-21 01:15:42 +0200 (fr, 21 jul 2006) $ + */ + +public class LogConfigurationException extends RuntimeException { + + + /** + * 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/java/src/org/apache/commons/logging/LogFactory.java b/java/src/org/apache/commons/logging/LogFactory.java new file mode 100644 index 0000000..526137e --- /dev/null +++ b/java/src/org/apache/commons/logging/LogFactory.java @@ -0,0 +1,1824 @@ +/* + * 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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +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.

+ * + * @author Craig R. McClanahan + * @author Costin Manolache + * @author Richard A. Sitze + * @version $Revision: 593798 $ $Date: 2007-11-10 18:40:43 +0100 $ + */ + +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 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 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 + * + * @exception 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) + * + * @exception 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; + + /** + * Prevously constructed LogFactory instance as in the + * factories map, but for the case where + * getClassLoader returns null. + * This can happen when: + * + * Note that factories is a Hashtable (not a HashMap), + * and hashtables don't allow null as a key. + */ + protected static 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) { + // 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(); + } + + /** + *

Construct (if necessary) and return a LogFactory + * instance, using the following ordered lookup procedure to determine + * the name of the implementation class to be loaded.

+ * + * + *

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

+ * + *

NOTE - In a multithreaded environment it is possible + * that two different instances will be returned for the same + * classloader environment. + *

+ * + * @exception 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 compatability. + 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 { + 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 + * + * @exception 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) + * + * @exception 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)); + } + synchronized (factories) { + if (classLoader == null) { + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } else { + 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."); + } + synchronized (factories) { + 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. + * + * @throws SecurityException if the current java security policy doesn't + * allow this class to access 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. + * + * @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 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. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified. + * + * @exception SecurityException 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 { + // Are we running on a JDK 1.2 or later system? + Method method = Thread.class.getMethod("getContextClassLoader", + (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), + (Object[]) null); + } catch (IllegalAccessException e) { + throw new LogConfigurationException + ("Unexpected IllegalAccessException", e); + } 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 + classLoader = getClassLoader(LogFactory.class); + + // We deliberately don't log a message here to outputStream; + // this message would be output for every call to LogFactory.getLog() + // when running on JDK1.1 + // + // if (outputStream != null) { + // outputStream.println( + // "Method Thread.getContextClassLoader does not exist;" + // + " assuming this is JDK 1.1, and that the context" + // + " classloader is the same as the class that loaded" + // + " the concrete LogFactory class."); + // } + + } + + // 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) + { + LogFactory factory = null; + + 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. + factory = nullClassLoaderFactory; + } else { + factory = (LogFactory) factories.get(contextClassLoader); + } + + return factory; + } + + /** + * 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. + *

+ *

ClassLoader conflicts

+ * 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. + * + * @exception 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. + // + String msg = + "The application has specified that a custom LogFactory implementation should be used but " + + "Class '" + factoryClass + "' cannot be converted to '" + + LogFactory.class.getName() + "'. "; + if (implementsLogFactory) { + msg = msg + "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. "; + } else { + msg = msg + "Please check the custom implementation. "; + } + msg = msg + "Help can be found @http://commons.apache.org/logging/troubleshooting.html."; + + if (isDiagnosticsEnabled()) { + logDiagnostic(msg); + } + + ClassCastException ex = new ClassCastException(msg); + throw ex; + } + + // 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. + *

+ * Null is returned if the URL cannot be opened. + */ + private static Properties getProperties(final URL url) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + try { + InputStream stream = url.openStream(); + if (stream != null) { + Properties props = new Properties(); + props.load(stream); + stream.close(); + return props; + } + } catch(IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read 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 void initDiagnostics() { + String dest; + try { + dest = getSystemProperty(DIAGNOSTICS_DEST_PROPERTY, null); + if (dest == null) { + return; + } + } catch(SecurityException ex) { + // We must be running in some very secure environment. + // We just have to assume output is not wanted.. + return; + } + + if (dest.equals("STDOUT")) { + diagnosticsStream = System.out; + } else if (dest.equals("STDERR")) { + diagnosticsStream = System.err; + } else { + try { + // open the file in append mode + FileOutputStream fos = new FileOutputStream(dest, true); + diagnosticsStream = new PrintStream(fos); + } catch(IOException ex) { + // We should report this to the user - but how? + return; + } + } + + // 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 + "] "; + } + + /** + * 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) { + 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); + initDiagnostics(); + logClassLoaderEnvironment(LogFactory.class); + factories = createFactoryStore(); + if (isDiagnosticsEnabled()) { + logDiagnostic("BOOTSTRAP COMPLETED"); + } + } +} diff --git a/java/src/org/apache/commons/logging/LogSource.java b/java/src/org/apache/commons/logging/LogSource.java new file mode 100644 index 0000000..9cb5f11 --- /dev/null +++ b/java/src/org/apache/commons/logging/LogSource.java @@ -0,0 +1,262 @@ +/* + * 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:

+ * + * + *

You can change the default behavior in one of two ways:

+ * + * + * @deprecated Use {@link LogFactory} instead - The default factory + * implementation performs exactly the same algorithm as this class did + * + * @author Rod Waldhoff + * @version $Id: LogSource.java 424107 2006-07-20 23:15:42Z skitching $ + */ +public class LogSource { + + // ------------------------------------------------------- Class Attributes + + static protected Hashtable logs = new Hashtable(); + + /** Is log4j available (in the current classpath) */ + static protected boolean log4jIsAvailable = false; + + /** Is JDK 1.4 logging available */ + static protected boolean jdk14IsAvailable = false; + + /** Constructor for current log class */ + static protected Constructor logImplctor = null; + + + // ----------------------------------------------------- Class Initializers + + static { + + // Is Log4J Available? + try { + if (null != Class.forName("org.apache.log4j.Logger")) { + log4jIsAvailable = true; + } else { + log4jIsAvailable = false; + } + } catch (Throwable t) { + log4jIsAvailable = false; + } + + // Is JDK 1.4 Logging Available? + try { + if ((null != Class.forName("java.util.logging.Logger")) && + (null != Class.forName("org.apache.commons.logging.impl.Jdk14Logger"))) { + jdk14IsAvailable = true; + } else { + jdk14IsAvailable = false; + } + } catch (Throwable t) { + jdk14IsAvailable = false; + } + + // Set the default Log implementation + String name = null; + try { + name = System.getProperty("org.apache.commons.logging.log"); + if (name == null) { + name = System.getProperty("org.apache.commons.logging.Log"); + } + } catch (Throwable t) { + } + if (name != null) { + try { + setLogImplementation(name); + } catch (Throwable t) { + try { + setLogImplementation + ("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + ; + } + } + } else { + try { + if (log4jIsAvailable) { + setLogImplementation + ("org.apache.commons.logging.impl.Log4JLogger"); + } else if (jdk14IsAvailable) { + setLogImplementation + ("org.apache.commons.logging.impl.Jdk14Logger"); + } else { + setLogImplementation + ("org.apache.commons.logging.impl.NoOpLog"); + } + } catch (Throwable t) { + try { + setLogImplementation + ("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + ; + } + } + } + + } + + + // ------------------------------------------------------------ Constructor + + + /** Don't allow others to create instances */ + private LogSource() { + } + + + // ---------------------------------------------------------- Class Methods + + + /** + * Set the log implementation/log implementation factory + * by the name of the class. The given class + * must implement {@link Log}, and provide a constructor that + * takes a single {@link String} argument (containing the name + * of the log). + */ + static public void setLogImplementation(String classname) throws + LinkageError, ExceptionInInitializerError, + NoSuchMethodException, SecurityException, + ClassNotFoundException { + try { + Class logclass = Class.forName(classname); + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } catch (Throwable t) { + logImplctor = null; + } + } + + + /** + * Set the log implementation/log implementation factory + * by class. The given class must implement {@link Log}, + * and provide a constructor that takes a single {@link String} + * argument (containing the name of the log). + */ + static public void setLogImplementation(Class logclass) throws + LinkageError, ExceptionInInitializerError, + NoSuchMethodException, SecurityException { + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } + + + /** Get a 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 = null; + try { + Object[] args = new Object[1]; + args[0] = 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/java/src/org/apache/commons/logging/impl/AvalonLogger.java b/java/src/org/apache/commons/logging/impl/AvalonLogger.java new file mode 100644 index 0000000..df8398c --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/AvalonLogger.java @@ -0,0 +1,292 @@ +/* + * 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: + *

+ * + *

+ * 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. + *

+ * @author Neeme Praks + * @version $Revision: 424107 $ $Date: 2006-07-21 01:15:42 +0200 (fr, 21 jul 2006) $ + */ +public class AvalonLogger implements Log { + + /** Ancesteral avalon logger */ + private static Logger defaultLogger = null; + /** Avalon logger used to perform log */ + private transient Logger logger = null; + + /** + * 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 ancesteral 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/java/src/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java b/java/src/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java new file mode 100644 index 0000000..2188bdc --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/Jdk13LumberjackLogger.java @@ -0,0 +1,335 @@ +/* + * 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.

+ * + * @author Scott Sanders + * @author Berin Loritsch + * @author Peter Donald + * @author Vince Eagen + * @version $Revision: 424107 $ $Date: 2006-07-21 01:15:42 +0200 (fr, 21 jul 2006) $ + * @since 1.1 + */ + +public class Jdk13LumberjackLogger implements Log, Serializable { + + + // ----------------------------------------------------- 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/java/src/org/apache/commons/logging/impl/Jdk14Logger.java b/java/src/org/apache/commons/logging/impl/Jdk14Logger.java new file mode 100644 index 0000000..b8cb510 --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/Jdk14Logger.java @@ -0,0 +1,304 @@ +/* + * 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).

+ * + * @author Scott Sanders + * @author Berin Loritsch + * @author Peter Donald + * @version $Revision: 424107 $ $Date: 2006-07-21 01:15:42 +0200 (fr, 21 jul 2006) $ + */ + +public class Jdk14Logger implements Log, Serializable { + + /** + * This member variable simply ensures that any attempt to initialise + * this class in a pre-1.4 JVM will result in an ExceptionInInitializerError. + * It must not be private, as an optimising compiler could detect that it + * is not used and optimise it away. + */ + protected static final Level dummyLevel = Level.FINE; + + // ----------------------------------------------------------- Constructors + + + /** + * Construct a named instance of this Logger. + * + * @param name Name of the logger to be constructed + */ + public Jdk14Logger(String name) { + + this.name = name; + logger = getLogger(); + + } + + + // ----------------------------------------------------- Instance Variables + + + /** + * The underlying Logger implementation we are using. + */ + protected transient Logger logger = null; + + + /** + * The name of the logger we are wrapping. + */ + protected String name = null; + + + // --------------------------------------------------------- Public Methods + + private void log( Level level, String msg, Throwable ex ) { + + Logger logger = getLogger(); + if (logger.isLoggable(level)) { + // Hack (?) to get the stack trace. + Throwable dummyException=new Throwable(); + StackTraceElement locations[]=dummyException.getStackTrace(); + // Caller will be the third element + String cname="unknown"; + String method="unknown"; + if( locations!=null && locations.length >2 ) { + StackTraceElement caller=locations[2]; + cname=caller.getClassName(); + method=caller.getMethodName(); + } + if( ex==null ) { + logger.logp( level, cname, method, msg ); + } else { + logger.logp( level, cname, method, msg, ex ); + } + } + + } + + /** + * Logs a message with 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/java/src/org/apache/commons/logging/impl/Log4JLogger.java b/java/src/org/apache/commons/logging/impl/Log4JLogger.java new file mode 100644 index 0000000..d609c0c --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/Log4JLogger.java @@ -0,0 +1,342 @@ +/* + * 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: + *

+ * Log4J1.3 is expected to change Level so it no longer extends Priority, which is + * a non-binary-compatible change. The class generated by compiling this code against + * log4j 1.2 will therefore not run against log4j 1.3. + * + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @version $Id: Log4JLogger.java 479747 2006-11-27 20:15:01Z dennisl $ + */ + +public class Log4JLogger implements Log, Serializable { + + // ------------------------------------------------------------- Attributes + + /** The fully qualified name of the Log4JLogger class. */ + private static final String FQCN = Log4JLogger.class.getName(); + + /** Log to this logger */ + private transient Logger logger = null; + + /** Logger name */ + private String name = null; + + private static Priority traceLevel; + + // ------------------------------------------------------------ + // Static Initializer. + // + // Note that this must come after the static variable declarations + // otherwise initialiser expressions associated with those variables + // will override any settings done here. + // + // Verify that log4j is available, and that it is version 1.2. + // If an ExceptionInInitializerError is generated, then LogFactoryImpl + // will treat that as meaning that the appropriate underlying logging + // library is just not present - if discovery is in progress then + // discovery will continue. + // ------------------------------------------------------------ + + static { + if (!Priority.class.isAssignableFrom(Level.class)) { + // nope, this is log4j 1.3, so force an ExceptionInInitializerError + throw new InstantiationError("Log4J 1.2 not available"); + } + + // Releases of log4j1.2 >= 1.2.12 have Priority.TRACE available, earlier + // versions do not. If TRACE is not available, then we have to map + // calls to Log.trace(...) onto the DEBUG level. + + try { + traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null); + } catch(Exception ex) { + // ok, trace not available + traceLevel = Priority.DEBUG; + } + } + + + // ------------------------------------------------------------ Constructor + + public Log4JLogger() { + } + + + /** + * Base constructor. + */ + public Log4JLogger(String name) { + this.name = name; + this.logger = getLogger(); + } + + /** + * For use with a log4j factory. + */ + public Log4JLogger(Logger logger ) { + if (logger == null) { + throw new IllegalArgumentException( + "Warning - null logger in constructor; possible log4j misconfiguration."); + } + this.name = logger.getName(); + this.logger=logger; + } + + + // --------------------------------------------------------- + // Implementation + // + // Note that in the methods below the Priority class is used to define + // levels even though the Level class is supported in 1.2. This is done + // so that at compile time the call definitely resolves to a call to + // a method that takes a Priority rather than one that takes a Level. + // + // The Category class (and hence its subclass Logger) in version 1.2 only + // has methods that take Priority objects. The Category class (and hence + // Logger class) in version 1.3 has methods that take both Priority and + // Level objects. This means that if we use Level here, and compile + // against log4j 1.3 then calls would be bound to the versions of + // methods taking Level objects and then would fail to run against + // version 1.2 of log4j. + // --------------------------------------------------------- + + + /** + * 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 + * @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, Priority.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, Priority.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, Priority.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, Priority.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, Priority.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, Priority.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, Priority.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, Priority.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, Priority.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, Priority.FATAL, message, t ); + } + + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(name); + } + return (this.logger); + } + + + /** + * 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(Priority.ERROR); + } + + + /** + * Check whether the Log4j Logger used is enabled for FATAL priority. + */ + public boolean isFatalEnabled() { + return getLogger().isEnabledFor(Priority.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(Priority.WARN); + } +} diff --git a/java/src/org/apache/commons/logging/impl/LogFactoryImpl.java b/java/src/org/apache/commons/logging/impl/LogFactoryImpl.java new file mode 100644 index 0000000..20c8a36 --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/LogFactoryImpl.java @@ -0,0 +1,1500 @@ +/* + * 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.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.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; + + +/** + *

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

+ * + * + *

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.

+ * + * @author Rod Waldhoff + * @author Craig R. McClanahan + * @author Richard A. Sitze + * @author Brian Stansberry + * @version $Revision: 581090 $ $Date: 2007-10-02 00:01:06 +0200 (ti, 02 okt 2007) $ + */ + +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() { + + Vector names = new Vector(); + Enumeration keys = attributes.keys(); + while (keys.hasMoreElements()) { + names.addElement((String) keys.nextElement()); + } + String results[] = new String[names.size()]; + for (int i = 0; i < results.length; i++) { + results[i] = (String) names.elementAt(i); + } + return (results); + + } + + + /** + * Convenience method to derive a name from the specified class and + * call 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.

+ * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public Log getInstance(String name) throws LogConfigurationException { + + Log instance = (Log) instances.get(name); + if (instance == null) { + instance = newInstance(name); + instances.put(name, instance); + } + return (instance); + + } + + + /** + * 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 = 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.

+ * + * @exception LogConfigurationException if a suitable constructor + * cannot be returned + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected Constructor getLogConstructor() + throws LogConfigurationException { + + // Return the previously identified Constructor (if any) + if (logConstructor == null) { + discoverLogImplementation(getClass().getName()); + } + + return logConstructor; + } + + + /** + * Is JDK 1.3 with Lumberjack logging available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk13LumberjackAvailable() { + return isLogLibraryAvailable( + "Jdk13Lumberjack", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); + } + + + /** + *

Return 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 = null; + try { + if (logConstructor == null) { + instance = discoverLogImplementation(name); + } + else { + Object params[] = { name }; + instance = (Log) logConstructor.newInstance(params); + } + + if (logMethod != null) { + Object params[] = { this }; + logMethod.invoke(instance, params); + } + + return (instance); + + } catch (LogConfigurationException lce) { + + // this type of exception means there was a problem in discovery + // and we've already output diagnostics about the issue, etc.; + // just pass it on + throw (LogConfigurationException) lce; + + } catch (InvocationTargetException e) { + // A problem occurred invoking the Constructor or Method + // previously discovered + Throwable c = e.getTargetException(); + if (c != null) { + throw new LogConfigurationException(c); + } else { + throw new LogConfigurationException(e); + } + } catch (Throwable t) { + // A problem occurred invoking the Constructor or Method + // previously discovered + throw new LogConfigurationException(t); + } + } + + + // ------------------------------------------------------ Private Methods + + /** + * 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 + if (specifiedLogClassName != null) { + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); + } + throw new LogConfigurationException(messageBuffer.toString()); + } + + return result; + } + + // No user specified log; try to discover what's on the classpath + // + // Note that we deliberately loop here over classesToDiscover and + // expect method createLogFromClass to loop over the possible source + // classloaders. The effect is: + // for each discoverable log adapter + // for each possible classloader + // see if it works + // + // It appears reasonable at first glance to do the opposite: + // for each possible classloader + // for each discoverable log adapter + // see if it works + // + // The latter certainly has advantages for user-installable logging + // libraries such as log4j; in a webapp for example this code should + // first check whether the user has provided any of the possible + // logging libraries before looking in the parent classloader. + // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, + // and SimpleLog will always work in any JVM. So the loop would never + // ever look for logging libraries in the parent classpath. Yet many + // users would expect that putting log4j there would cause it to be + // detected (and this is the historical JCL behaviour). So we go with + // the first approach. A user that has bundled a specific logging lib + // in a webapp should use a commons-logging.properties file or a + // service file in META-INF to force use of that logging lib anyway, + // rather than relying on discovery. + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "No user-specified Log implementation; performing discovery" + + " using the standard supported logging implementations..."); + } + for(int i=0; (iStringBuffer the message should be appended to, + * not null + * @param name the (trimmed) name to be test against the candidate, not null + * @param candidate the candidate name (not null) + */ + private void informUponSimilarName(final StringBuffer messageBuffer, final String name, + final String candidate) { + if (name.equals(candidate)) { + // Don't suggest a name that is exactly the same as the one the + // user tried... + return; + } + + // If the user provides a name that is in the right package, and gets + // the first 5 characters of the adapter class right (ignoring case), + // then suggest the candidate adapter class name. + if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { + messageBuffer.append(" Did you mean '"); + messageBuffer.append(candidate); + messageBuffer.append("'?"); + } + } + + + /** + * Checks system properties and the attribute map for + * a Log implementation specified by the user under the + * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. + * + * @return classname specified by the user, or 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 = null; + try { + c = Class.forName(logAdapterClassName, true, currentCL); + } catch (ClassNotFoundException originalClassNotFoundException) { + // The current classloader was unable to find the log adapter + // in this or any ancestor classloader. There's no point in + // trying higher up in the hierarchy in this case.. + String msg = "" + originalClassNotFoundException.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is not available via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + try { + // Try the class classloader. + // This may work in cases where the TCCL + // does not contain the code executed or JCL. + // This behaviour indicates that the application + // classloading strategy is not consistent with the + // Java 1.2 classloading guidelines but JCL can + // and so should handle this case. + c = Class.forName(logAdapterClassName); + } catch (ClassNotFoundException secondaryClassNotFoundException) { + // no point continuing: this adapter isn't available + msg = "" + secondaryClassNotFoundException.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is not available via the LogFactoryImpl class classloader: " + + msg.trim()); + break; + } + } + + constructor = c.getConstructor(logConstructorSignature); + Object o = constructor.newInstance(params); + + // Note that we do this test after trying to create an instance + // [rather than testing Log.class.isAssignableFrom(c)] so that + // we don't complain about Log hierarchy problems when the + // adapter couldn't be instantiated anyway. + if (o instanceof Log) { + logAdapterClass = c; + logAdapter = (Log) o; + break; + } + + // Oops, we have a potential problem here. An adapter class + // has been found and its underlying lib is present too, but + // there are multiple Log interface classes available making it + // impossible to cast to the type the caller wanted. We + // certainly can't use this logger, but we need to know whether + // to keep on discovering or terminate now. + // + // The handleFlawedHierarchy method will throw + // LogConfigurationException if it regards this problem as + // fatal, and just return if not. + handleFlawedHierarchy(currentCL, c); + } catch (NoClassDefFoundError e) { + // We were able to load the adapter but it had references to + // other classes that could not be found. This simply means that + // the underlying logger library is not present in this or any + // ancestor classloader. There's no point in trying higher up + // in the hierarchy in this case.. + String msg = "" + e.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is missing dependencies when loaded via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + break; + } catch (ExceptionInInitializerError e) { + // A static initializer block or the initializer code associated + // with a static variable on the log adapter class has thrown + // an exception. + // + // We treat this as meaning the adapter's underlying logging + // library could not be found. + String msg = "" + e.getMessage(); + logDiagnostic( + "The log adapter '" + + logAdapterClassName + + "' is unable to initialize itself when loaded via classloader " + + objectId(currentCL) + + ": " + + msg.trim()); + break; + } catch(LogConfigurationException e) { + // call to handleFlawedHierarchy above must have thrown + // a LogConfigurationException, so just throw it on + throw e; + } catch(Throwable t) { + // handleFlawedDiscovery will determine whether this is a fatal + // problem or not. If it is fatal, then a LogConfigurationException + // will be thrown. + handleFlawedDiscovery(logAdapterClassName, currentCL, t); + } + + if (currentCL == null) { + break; + } + + // try the parent classloader + // currentCL = currentCL.getParent(); + currentCL = getParentClassLoader(currentCL); + } + + if ((logAdapter != 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) { + 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. + *

+ * 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 == false) { + 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(); + } + + // scan c2's ancestors to find c1 + current = c2; + while (current != null) { + if (current == c1) + return c2; + current = current.getParent(); + } + + return null; + } + + /** + * Generates an internal diagnostic logging of the discovery failure and + * then throws a 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, + 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) { + logDiagnostic("... ExceptionInInitializerError: " + + cause2.getClass().getName() + ": " + + cause2.getLocalizedMessage()); + } + } + } + } + } + + if (!allowFlawedDiscovery) { + throw new LogConfigurationException(discoveryFlaw); + } + } + + + /** + * Report a problem loading the log adapter, then either return + * (if the situation is considered recoverable) or throw a + * LogConfigurationException. + *

+ * There are two possible reasons why we successfully loaded the + * specified log adapter class then failed to cast it to a Log object: + *

    + *
  1. the specific class just doesn't implement the Log interface + * (user screwed up), or + *
  2. the specified class has bound to a Log class loaded by some other + * classloader; Log@classloaderX cannot be cast to Log@classloaderY. + *
+ *

+ * Here we try to figure out which case has occurred so we can give the + * user some reasonable feedback. + * + * @param badClassLoader is the classloader we loaded the problem class from, + * ie it is equivalent to badClass.getClassLoader(). + * + * @param badClass is a Class object with the desired name, but which + * does not implement Log correctly. + * + * @throws LogConfigurationException when the situation + * should not be recovered from. + */ + private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass) + throws LogConfigurationException { + + boolean implementsLog = false; + String logInterfaceName = Log.class.getName(); + Class interfaces[] = badClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (logInterfaceName.equals(interfaces[i].getName())) { + implementsLog = true; + break; + } + } + + if (implementsLog) { + // the class does implement an interface called Log, but + // it is in the wrong classloader + if (isDiagnosticsEnabled()) { + try { + ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); + logDiagnostic( + "Class '" + badClass.getName() + + "' was found in classloader " + + objectId(badClassLoader) + + ". It is bound to a Log interface which is not" + + " the one loaded from classloader " + + objectId(logInterfaceClassLoader)); + } catch (Throwable t) { + logDiagnostic( + "Error while trying to output diagnostics about" + + " bad class '" + badClass + "'"); + } + } + + if (!allowFlawedHierarchy) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context "); + msg.append("due to bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("Warning: bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + logDiagnostic(msg.toString()); + } + } else { + // this is just a bad adapter class + if (!allowFlawedDiscovery) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context. "); + msg.append("Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("[WARNING] Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + logDiagnostic(msg.toString()); + } + } + } +} diff --git a/java/src/org/apache/commons/logging/impl/LogKitLogger.java b/java/src/org/apache/commons/logging/impl/LogKitLogger.java new file mode 100644 index 0000000..5e6cf4b --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/LogKitLogger.java @@ -0,0 +1,294 @@ +/* + * 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 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.

+ * + * @author Scott Sanders + * @author Robert Burrell Donkin + * @version $Id: LogKitLogger.java 424107 2006-07-20 23:15:42Z skitching $ + */ + +public class LogKitLogger implements Log, Serializable { + + + // ------------------------------------------------------------- Attributes + + + /** Logging goes to this LogKit logger */ + protected transient 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() { + + if (logger == null) { + logger = Hierarchy.getDefaultHierarchy().getLoggerFor(name); + } + return (logger); + + } + + + // ----------------------------------------------------- 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/java/src/org/apache/commons/logging/impl/NoOpLog.java b/java/src/org/apache/commons/logging/impl/NoOpLog.java new file mode 100644 index 0000000..a66bd10 --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/NoOpLog.java @@ -0,0 +1,107 @@ +/* + * 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.

+ * + * @author Scott Sanders + * @author Rod Waldhoff + * @version $Id: NoOpLog.java 424107 2006-07-20 23:15:42Z skitching $ + */ +public class NoOpLog implements Log, Serializable { + + /** Convenience constructor */ + public NoOpLog() { } + /** Base constructor */ + public NoOpLog(String name) { } + /** Do nothing */ + public void trace(Object message) { } + /** Do nothing */ + public void trace(Object message, Throwable t) { } + /** Do nothing */ + public void debug(Object message) { } + /** Do nothing */ + public void debug(Object message, Throwable t) { } + /** Do nothing */ + public void info(Object message) { } + /** Do nothing */ + public void info(Object message, Throwable t) { } + /** Do nothing */ + public void warn(Object message) { } + /** Do nothing */ + public void warn(Object message, Throwable t) { } + /** Do nothing */ + public void error(Object message) { } + /** Do nothing */ + public void error(Object message, Throwable t) { } + /** Do nothing */ + public void fatal(Object message) { } + /** Do nothing */ + public void fatal(Object message, Throwable t) { } + + /** + * Debug is never enabled. + * + * @return false + */ + public final boolean isDebugEnabled() { return false; } + + /** + * Error is never enabled. + * + * @return false + */ + public final boolean isErrorEnabled() { return false; } + + /** + * Fatal is never enabled. + * + * @return false + */ + public final boolean isFatalEnabled() { return false; } + + /** + * Info is never enabled. + * + * @return false + */ + public final boolean isInfoEnabled() { return false; } + + /** + * Trace is never enabled. + * + * @return false + */ + public final boolean isTraceEnabled() { return false; } + + /** + * Warn is never enabled. + * + * @return false + */ + public final boolean isWarnEnabled() { return false; } + +} diff --git a/java/src/org/apache/commons/logging/impl/ServletContextCleaner.java b/java/src/org/apache/commons/logging/impl/ServletContextCleaner.java new file mode 100644 index 0000000..046bf7e --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/ServletContextCleaner.java @@ -0,0 +1,138 @@ +/* + * 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. + *

+ * 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 accessable LogFactory class that the entry in its map for the + * current webapp's context classloader should be cleared. + * + * @since 1.1 + */ + +public class ServletContextCleaner implements ServletContextListener { + + private 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 accessable + // via this loader, but is accessable 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 accessable + 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/java/src/org/apache/commons/logging/impl/SimpleLog.java b/java/src/org/apache/commons/logging/impl/SimpleLog.java new file mode 100644 index 0000000..fe1a7c5 --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/SimpleLog.java @@ -0,0 +1,721 @@ +/* + * 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 + * "simplelog.properties", and includes any matching definitions + * from this resource (if it exists).

+ * + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * + * @version $Id: SimpleLog.java 581090 2007-10-01 22:01:06Z dennisl $ + */ +public class SimpleLog implements Log, Serializable { + + + // ------------------------------------------------------- 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 protected boolean showLogName = false; + /** Include the short name ( last component ) of the logger in the log + * message. Defaults to true - otherwise we'll be lost in a flood of + * messages without knowing who sends them. + */ + static protected boolean showShortName = true; + /** Include the current time in the log message */ + static protected boolean showDateTime = false; + /** The date and time format to use in the log message */ + static protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + + /** + * Used to format times. + *

+ * 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 String logName = null; + /** The current log level */ + protected int currentLogLevel; + /** The short name of this simple log instance */ + private String shortLogName = null; + + + // ------------------------------------------------------------ Constructor + + /** + * Construct a simple log with given name. + * + * @param name log name + */ + public SimpleLog(String name) { + + logName = name; + + // Set initial log level + // Used to be: set default log level to ERROR + // IMHO it should be lower, but at least info ( costin ). + setLevel(SimpleLog.LOG_LEVEL_INFO); + + // Set log level from properties + String lvl = getStringProperty(systemPrefix + "log." + logName); + int i = String.valueOf(name).lastIndexOf("."); + while(null == lvl && i > -1) { + name = name.substring(0,i); + lvl = getStringProperty(systemPrefix + "log." + name); + i = String.valueOf(name).lastIndexOf("."); + } + + if(null == lvl) { + lvl = getStringProperty(systemPrefix + "defaultlog"); + } + + if("all".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ALL); + } else if("trace".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_TRACE); + } else if("debug".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_DEBUG); + } else if("info".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_INFO); + } else if("warn".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_WARN); + } else if("error".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ERROR); + } else if("fatal".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_FATAL); + } else if("off".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_OFF); + } + + } + + + // -------------------------------------------------------- Properties + + /** + *

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 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 + StringBuffer buf = new StringBuffer(); + + // Append date-time if so configured + if(showDateTime) { + 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 + shortLogName = logName.substring(logName.lastIndexOf(".") + 1); + shortLogName = + shortLogName.substring(shortLogName.lastIndexOf("/") + 1); + } + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if(showLogName) { + buf.append(String.valueOf(logName)).append(" - "); + } + + // Append the message + buf.append(String.valueOf(message)); + + // Append stack trace if not null + if(t != null) { + buf.append(" <"); + buf.append(t.toString()); + buf.append(">"); + + java.io.StringWriter sw= new java.io.StringWriter(1024); + java.io.PrintWriter pw= new java.io.PrintWriter(sw); + t.printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } + + // Print to the appropriate destination + write(buf); + + } + + + /** + *

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?

+ * + *

This allows expensive operations such as 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?

+ * + *

This allows expensive operations such as 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?

+ * + *

This allows expensive operations such as 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?

+ * + *

This allows expensive operations such as 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?

+ * + *

This allows expensive operations such as 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?

+ * + *

This allows expensive operations such as 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; + + if (classLoader == null) { + try { + // Are we running on a JDK 1.2 or later system? + Method method = Thread.class.getMethod("getContextClassLoader", + (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), + (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/java/src/org/apache/commons/logging/impl/WeakHashtable.java b/java/src/org/apache/commons/logging/impl/WeakHashtable.java new file mode 100644 index 0000000..3748ceb --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/WeakHashtable.java @@ -0,0 +1,478 @@ +/* + * 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.*; + +/** + *

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.

+ * + *

This class follows the symantics of Hashtable as closely as + * possible. It therefore does not accept null values or keys.

+ * + *

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 Hashtable in LogFactory. This application requires + * good liveliness for get and put. Various tradeoffs + * have been made with this in mind. + *

+ *

+ * Usage: typical use case is as a drop-in replacement + * for the Hashtable used in LogFactory for J2EE enviroments + * 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 implementationd 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.

+ * + *

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" + * 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.

+ * + *

+ * 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 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.

+ * + *

+ * 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 LogFactory implementation is + * loaded by a child classloader (e.g. a web app classloader).

+ * + *

To avoid this scenario, ensure + * that any custom LogFactory subclass is loaded by the same classloader as + * the base LogFactory. Creating custom LogFactory subclasses is, + * however, rare. The standard LogFactoryImpl class should be sufficient + * for most or all users.

+ * + * + * @author Brian Stansberry + * + * @since 1.1 + */ +public final class WeakHashtable extends Hashtable { + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of all cleared entries. + */ + private static final int MAX_CHANGES_BEFORE_PURGE = 100; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of one cleared entry. + */ + private static final int PARTIAL_PURGE_COUNT = 10; + + /* ReferenceQueue we check for gc'd keys */ + private ReferenceQueue queue = new ReferenceQueue(); + /* Counter used to control how often we purge gc'd entries */ + private int changeCount = 0; + + /** + * Constructs a WeakHashtable with the Hashtable default + * capacity and load factor. + */ + public WeakHashtable() {} + + + /** + *@see Hashtable + */ + public boolean containsKey(Object key) { + // purge should not be required + Referenced referenced = new Referenced(key); + return super.containsKey(referenced); + } + + /** + *@see Hashtable + */ + public Enumeration elements() { + purge(); + return super.elements(); + } + + /** + *@see Hashtable + */ + public Set entrySet() { + purge(); + Set referencedEntries = super.entrySet(); + Set unreferencedEntries = new HashSet(); + for (Iterator it=referencedEntries.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Referenced referencedKey = (Referenced) entry.getKey(); + Object key = referencedKey.getValue(); + Object value = entry.getValue(); + if (key != null) { + Entry dereferencedEntry = new Entry(key, value); + unreferencedEntries.add(dereferencedEntry); + } + } + return unreferencedEntries; + } + + /** + *@see Hashtable + */ + public Object get(Object key) { + // for performance reasons, no purge + Referenced referenceKey = new Referenced(key); + return super.get(referenceKey); + } + + /** + *@see Hashtable + */ + public Enumeration keys() { + purge(); + final Enumeration enumer = super.keys(); + return new Enumeration() { + public boolean hasMoreElements() { + return enumer.hasMoreElements(); + } + public Object nextElement() { + Referenced nextReference = (Referenced) enumer.nextElement(); + return nextReference.getValue(); + } + }; + } + + + /** + *@see Hashtable + */ + public Set keySet() { + purge(); + Set referencedKeys = super.keySet(); + Set unreferencedKeys = new HashSet(); + for (Iterator it=referencedKeys.iterator(); it.hasNext();) { + Referenced referenceKey = (Referenced) it.next(); + Object keyValue = referenceKey.getValue(); + if (keyValue != null) { + unreferencedKeys.add(keyValue); + } + } + return unreferencedKeys; + } + + /** + *@see Hashtable + */ + public Object put(Object key, Object value) { + // check for nulls, ensuring symantics match superclass + if (key == null) { + throw new NullPointerException("Null keys are not allowed"); + } + if (value == null) { + throw new NullPointerException("Null values are not allowed"); + } + + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) { + purgeOne(); + } + + Referenced keyRef = new Referenced(key, queue); + return super.put(keyRef, value); + } + + /** + *@see Hashtable + */ + public void putAll(Map t) { + if (t != null) { + Set entrySet = t.entrySet(); + for (Iterator it=entrySet.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + *@see Hashtable + */ + public Collection values() { + purge(); + return super.values(); + } + + /** + *@see Hashtable + */ + public Object remove(Object key) { + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if ((changeCount % PARTIAL_PURGE_COUNT) == 0) { + purgeOne(); + } + return super.remove(new Referenced(key)); + } + + /** + *@see Hashtable + */ + public boolean isEmpty() { + purge(); + return super.isEmpty(); + } + + /** + *@see Hashtable + */ + public int size() { + purge(); + return super.size(); + } + + /** + *@see Hashtable + */ + public String toString() { + purge(); + return super.toString(); + } + + /** + * @see Hashtable + */ + protected void rehash() { + // purge here to save the effort of rehashing dead entries + purge(); + super.rehash(); + } + + /** + * Purges all entries whose wrapped keys + * have been garbage collected. + */ + private void purge() { + synchronized (queue) { + WeakKey key; + while ((key = (WeakKey) queue.poll()) != null) { + super.remove(key.getReferenced()); + } + } + } + + /** + * Purges one entry whose wrapped key + * has been garbage collected. + */ + private void purgeOne() { + + synchronized (queue) { + WeakKey key = (WeakKey) queue.poll(); + if (key != null) { + super.remove(key.getReferenced()); + } + } + } + + /** Entry implementation */ + private final static class Entry implements Map.Entry { + + private final Object key; + private final Object value; + + private Entry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + boolean result = false; + if (o != null && o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + result = (getKey()==null ? + entry.getKey() == null : + getKey().equals(entry.getKey())) + && + (getValue()==null ? + entry.getValue() == null : + getValue().equals(entry.getValue())); + } + return result; + } + + public int hashCode() { + + return (getKey()==null ? 0 : getKey().hashCode()) ^ + (getValue()==null ? 0 : getValue().hashCode()); + } + + public Object setValue(Object value) { + throw new UnsupportedOperationException("Entry.setValue is not supported."); + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + } + + + /** Wrapper giving correct symantics for equals and hashcode */ + private final static class Referenced { + + private final WeakReference reference; + private final int hashCode; + + /** + * + * @throws NullPointerException if referant is 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. + if (result == true) { + result = (this.hashCode() == otherKey.hashCode()); + } + // In any case, as our c'tor does not allow null referants + // and Hashtable does not do equality checks between + // existing keys, normal hashtable operations should never + // result in an equals comparison between null referants + } + else + { + result = thisKeyValue.equals(otherKeyValue); + } + } + return result; + } + } + + /** + * WeakReference subclass that holds a hard reference to an + * associated 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/java/src/org/apache/commons/logging/impl/package.html b/java/src/org/apache/commons/logging/impl/package.html new file mode 100644 index 0000000..0d37071 --- /dev/null +++ b/java/src/org/apache/commons/logging/impl/package.html @@ -0,0 +1,22 @@ + + + +

Concrete implementations of commons-logging wrapper APIs.

+ diff --git a/java/src/org/apache/commons/logging/package.html b/java/src/org/apache/commons/logging/package.html new file mode 100644 index 0000000..6be8fd4 --- /dev/null +++ b/java/src/org/apache/commons/logging/package.html @@ -0,0 +1,255 @@ + + + +

Simple wrapper API around multiple logging APIs.

+ + +

Overview

+ +

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:

+ + + +

Quick Start Guide

+ +

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: + +

+    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);
+            }
+            ...
+        }
+
+ +

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.

+ + +

Configuring the Commons Logging Package

+ + +

Choosing a LogFactory Implementation

+ +

From an application perspective, the first requirement is to retrieve an +object reference to the LogFactory 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:

+ + +

If a commons-logging.properties file is found, all of the +properties defined there are also used to set configuration attributes on +the instantiated LogFactory instance.

+ +

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 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 Implementation

+ +

The Logging Package APIs include a default LogFactory +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:

+ + +

See the SimpleLog JavaDocs for detailed +configuration information for this default implementation.

+ + +

Configuring the Underlying Logging System

+ +

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 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

+ +

Use of the Logging Package APIs, from the perspective of an application +component, consists of the following steps:

+
    +
  1. Acquire a reference to an instance of + org.apache.commons.logging.Log, by calling the + factory method + + LogFactory.getInstance(String name). Your application can contain + references to multiple loggers that are used for different + purposes. A typical scenario for a server application is to have each + major component of the server use its own Log instance.
  2. +
  3. Cause messages to be logged (if the corresponding detail level is enabled) + by calling appropriate methods (trace(), debug(), + info(), warn(), error, and + fatal()).
  4. +
+ +

For convenience, LogFactory also offers a static method +getLog() that combines the typical two-step pattern:

+
+  Log log = LogFactory.getFactory().getInstance(Foo.class);
+
+

into a single method call:

+
+  Log log = LogFactory.getLog(Foo.class);
+
+ +

For example, you might use the following technique to initialize and +use a Log instance in an application component:

+
+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);
+    ...
+  }
+
+}
+
+ +