diff --git a/src/main/java/org/apache/commons/logging/LogConfigurationException.java b/src/main/java/org/apache/commons/logging/LogConfigurationException.java index 438f786..c2fa099 100644 --- a/src/main/java/org/apache/commons/logging/LogConfigurationException.java +++ b/src/main/java/org/apache/commons/logging/LogConfigurationException.java @@ -27,6 +27,11 @@ public class LogConfigurationException extends RuntimeException { /** Serializable version identifier. */ private static final long serialVersionUID = 8486587136871052495L; + /** + * The underlying cause of this exception. + */ + protected Throwable cause; + /** * Construct a new exception with {@code null} as its detail message. */ @@ -42,16 +47,6 @@ public class LogConfigurationException extends RuntimeException { super(message); } - /** - * Construct a new exception with the specified cause and a derived - * detail message. - * - * @param cause The underlying cause - */ - public LogConfigurationException(final Throwable cause) { - this(cause == null ? null : cause.toString(), cause); - } - /** * Construct a new exception with the specified detail message and cause. * @@ -64,9 +59,14 @@ public class LogConfigurationException extends RuntimeException { } /** - * The underlying cause of this exception. + * Construct a new exception with the specified cause and a derived + * detail message. + * + * @param cause The underlying cause */ - protected Throwable cause; + public LogConfigurationException(final Throwable cause) { + this(cause == null ? null : cause.toString(), cause); + } /** * Return the underlying cause of this exception (if any). diff --git a/src/main/java/org/apache/commons/logging/LogFactory.java b/src/main/java/org/apache/commons/logging/LogFactory.java index f0a71dd..9773a88 100644 --- a/src/main/java/org/apache/commons/logging/LogFactory.java +++ b/src/main/java/org/apache/commons/logging/LogFactory.java @@ -200,90 +200,14 @@ public abstract class LogFactory { // ----------------------------------------------------------- Constructors - /** - * Protected constructor that is not available for public use. - */ - protected LogFactory() { - } - - // --------------------------------------------------------- Public Methods - - /** - * Return the configuration attribute with the specified name (if any), - * or {@code null} if there is no such attribute. - * - * @param name Name of the attribute to return - * @return the configuration attribute with the specified name. - */ - public abstract Object getAttribute(String name); - - /** - * Gets an array containing the names of all currently defined configuration attributes. If there are no such attributes, a zero length array is returned. - * - * @return an array containing the names of all currently defined configuration attributes - */ - public abstract String[] getAttributeNames(); - - /** - * Convenience method to derive a name from the specified class and call {@code getInstance(String)} with it. - * - * @param clazz Class for which a suitable Log name will be derived - * @return a name from the specified class. - * @throws LogConfigurationException if a suitable {@code Log} instance cannot be returned - */ - public abstract Log getInstance(Class clazz) throws LogConfigurationException; - - /** - * Construct (if necessary) and return a {@code Log} instance, using the factory's current set of configuration attributes. - *
- * NOTE - Depending upon the implementation of the {@code LogFactory} you are using, the {@code 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 {@code Log} instance to be returned (the meaning of this name is only known to the underlying logging implementation that - * is being wrapped) - * @return a {@code Log} instance. - * @throws LogConfigurationException if a suitable {@code 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 {@code null} value is equivalent to calling - * {@code removeAttribute(name)}. - * - * @param name Name of the attribute to set - * @param value Value of the attribute to set, or {@code null} - * to remove any setting for this attribute - */ - public abstract void setAttribute(String name, Object value); - - // ------------------------------------------------------- Static Variables - /** * The previously constructed {@code LogFactory} instances, keyed by * the {@code ClassLoader} with which it was created. */ protected static Hashtable factories; + // --------------------------------------------------------- Public Methods + /** * Previously constructed {@code LogFactory} instance as in the * {@code factories} map, but for the case where @@ -302,6 +226,223 @@ public abstract class LogFactory { @Deprecated protected static volatile LogFactory nullClassLoaderFactory; + static { + // note: it's safe to call methods before initDiagnostics (though + // diagnostic output gets discarded). + ClassLoader thisClassLoader = getClassLoader(LogFactory.class); + thisClassLoaderRef = new WeakReference+ * Most/all code should call getContextClassLoaderInternal rather than + * calling this method directly. + *
+ * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + *
+ * Note that no internal logging is done within this method because + * this method is called every time LogFactory.getLogger() is called, + * and we don't want too much output generated here. + * + * @throws LogConfigurationException if a suitable class loader + * cannot be identified. + * @return the thread's context classloader or {@code null} if the java security + * policy forbids access to the context classloader from one of the classes + * in the current call stack. + * @since 1.1 + */ + protected static ClassLoader directGetContextClassLoader() throws LogConfigurationException { + ClassLoader classLoader = null; - /** Utility method to safely trim a string. */ - private static String trim(final String src) { - if (src == null) { - return null; + try { + classLoader = Thread.currentThread().getContextClassLoader(); + } catch (final SecurityException ex) { + /** + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * We ignore this exception to be consistent with the previous + * behavior (e.g. 1.1.3 and earlier). + */ + // ignore } - return src.trim(); + + // Return the selected class loader + return classLoader; } /** - * Checks whether the supplied Throwable is one that needs to be - * re-thrown and ignores all others. + * Check cached factories (keyed by contextClassLoader) * - * The following errors are re-thrown: - *
+ * 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. + *
+ * + * @param clazz Class. + * @return a ClassLoader. + * + * @since 1.1 + */ + protected static ClassLoader getClassLoader(final Class clazz) { + try { + return clazz.getClassLoader(); + } catch (final SecurityException ex) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to get classloader for class '" + clazz + + "' due to security restrictions - " + ex.getMessage()); + } + throw ex; } - // All other instances of Throwable will be silently ignored + } + + /** + * 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(final ClassLoader classLoader, final String fileName) { + Properties props = null; + double priority = 0.0; + URL propsUrl = null; + try { + final Enumeration urls = getResources(classLoader, fileName); + + if (urls == null) { + return null; + } + + while (urls.hasMoreElements()) { + final URL url = (URL) urls.nextElement(); + + final Properties newProps = getProperties(url); + if (newProps != null) { + if (props == null) { + propsUrl = url; + props = newProps; + final 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 { + final 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 (final 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; + } + + /** + * Returns the current context classloader. + *
+ * In versions prior to 1.1, this method did not use an AccessController. + * In version 1.1, an AccessController wrapper was incorrectly added to + * this method, causing a minor security flaw. + *
+ * In version 1.1.1 this change was reverted; this method no longer uses + * an AccessController. User code wishing to obtain the context classloader + * must invoke this method via AccessController.doPrivileged if it needs + * support for that. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return directGetContextClassLoader(); + } + + // --------------------------------------------------------- Static 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. + */ + private static ClassLoader getContextClassLoaderInternal() throws LogConfigurationException { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + return directGetContextClassLoader(); + } + }); } /** @@ -647,491 +1012,158 @@ public abstract class LogFactory { } /** - * 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 {@code release()} on - * each of them. - * - * @param classLoader ClassLoader for which to release the LogFactory + * Given a URL that refers to a .properties file, load that file. + * This is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *
+ * {@code Null} is returned if the URL cannot be opened. */ - public static void release(final ClassLoader classLoader) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Releasing factory for classloader " + objectId(classLoader)); - } - // factories is not final and could be replaced in this block. - final Hashtable factories = LogFactory.factories; - synchronized (factories) { - if (classLoader == null) { - if (nullClassLoaderFactory != null) { - nullClassLoaderFactory.release(); - nullClassLoaderFactory = null; + private static Properties getProperties(final URL url) { + final PrivilegedAction action = + new PrivilegedAction() { + @Override + public Object run() { + InputStream stream = null; + try { + // We must ensure that useCaches is set to false, as the + // default behavior of java is to cache file handles, and + // this "locks" files, preventing hot-redeploy on windows. + final URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + stream = connection.getInputStream(); + if (stream != null) { + final Properties props = new Properties(); + props.load(stream); + stream.close(); + stream = null; + return props; + } + } catch (final IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read URL " + url); + } + } finally { + if (stream != null) { + try { + stream.close(); + } catch (final IOException e) { + // ignore exception; this should not happen + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to close stream for URL " + url); + } + } + } + } + + return null; } - } else { - final LogFactory factory = (LogFactory) factories.get(classLoader); - if (factory != null) { - factory.release(); - factories.remove(classLoader); - } - } - } + }; + return (Properties) AccessController.doPrivileged(action); } /** - * Release any internal references to previously created {@link LogFactory} - * instances, after calling the instance method {@code 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. + * 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. */ - public static void releaseAll() { - if (isDiagnosticsEnabled()) { - logDiagnostic("Releasing factory for all classloaders."); - } - // factories is not final and could be replaced in this block. - final Hashtable factories = LogFactory.factories; - synchronized (factories) { - final Enumeration elements = factories.elements(); - while (elements.hasMoreElements()) { - final LogFactory element = (LogFactory) elements.nextElement(); - element.release(); - } - factories.clear(); + private static InputStream getResourceAsStream(final ClassLoader loader, final String name) { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + if (loader != null) { + return loader.getResourceAsStream(name); + } + return ClassLoader.getSystemResourceAsStream(name); + } + }); + } - if (nullClassLoaderFactory != null) { - nullClassLoaderFactory.release(); - nullClassLoaderFactory = null; - } - } + /** + * 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) { + final PrivilegedAction action = + new PrivilegedAction() { + @Override + public Object run() { + try { + if (loader != null) { + return loader.getResources(name); + } + return ClassLoader.getSystemResources(name); + } catch (final IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Exception while trying to find configuration file " + + name + ":" + e.getMessage()); + } + return null; + } catch (final 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; + } + } + }; + final Object result = AccessController.doPrivileged(action); + return (Enumeration) result; } // ------------------------------------------------------ Protected Methods /** - * Safely get access to the classloader for the specified class. + * 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. *
- * 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. - *
- * - * @param clazz Class. - * @return a ClassLoader. - * - * @since 1.1 + * 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. */ - protected static ClassLoader getClassLoader(final Class clazz) { - try { - return clazz.getClassLoader(); - } catch (final SecurityException ex) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Unable to get classloader for class '" + clazz + - "' due to security restrictions - " + ex.getMessage()); - } - throw ex; - } - } - - /** - * Returns the current context classloader. - *- * In versions prior to 1.1, this method did not use an AccessController. - * In version 1.1, an AccessController wrapper was incorrectly added to - * this method, causing a minor security flaw. - *
- * In version 1.1.1 this change was reverted; this method no longer uses - * an AccessController. User code wishing to obtain the context classloader - * must invoke this method via AccessController.doPrivileged if it needs - * support for that. - * - * @return the context classloader associated with the current thread, - * or null if security doesn't allow it. - * @throws LogConfigurationException if there was some weird error while - * attempting to get the context classloader. - */ - protected static ClassLoader getContextClassLoader() throws LogConfigurationException { - return directGetContextClassLoader(); - } - - /** - * Calls LogFactory.directGetContextClassLoader under the control of an - * AccessController class. This means that java code running under a - * security manager that forbids access to ClassLoaders will still work - * if this class is given appropriate privileges, even when the caller - * doesn't have such privileges. Without using an AccessController, the - * the entire call stack must have the privilege before the call is - * allowed. - * - * @return the context classloader associated with the current thread, - * or null if security doesn't allow it. - * @throws LogConfigurationException if there was some weird error while - * attempting to get the context classloader. - */ - private static ClassLoader getContextClassLoaderInternal() throws LogConfigurationException { - return (ClassLoader)AccessController.doPrivileged( - new PrivilegedAction() { - @Override - public Object run() { - return directGetContextClassLoader(); - } - }); - } - - /** - * Return the thread context class loader if available; otherwise return null. - *
- * Most/all code should call getContextClassLoaderInternal rather than - * calling this method directly. - *
- * The thread context class loader is available for JDK 1.2 - * or later, if certain security conditions are met. - *
- * Note that no internal logging is done within this method because - * this method is called every time LogFactory.getLogger() is called, - * and we don't want too much output generated here. - * - * @throws LogConfigurationException if a suitable class loader - * cannot be identified. - * @return the thread's context classloader or {@code null} if the java security - * policy forbids access to the context classloader from one of the classes - * in the current call stack. - * @since 1.1 - */ - protected static ClassLoader directGetContextClassLoader() throws LogConfigurationException { - ClassLoader classLoader = null; - - try { - classLoader = Thread.currentThread().getContextClassLoader(); - } catch (final SecurityException ex) { - /** - * getContextClassLoader() throws SecurityException when - * the context class loader isn't an ancestor of the - * calling class's class loader, or if security - * permissions are restricted. - * - * We ignore this exception to be consistent with the previous - * behavior (e.g. 1.1.3 and earlier). - */ - // ignore - } - - // Return the selected class loader - return classLoader; - } - - /** - * Check cached factories (keyed by contextClassLoader) - * - * @param contextClassLoader is the context classloader associated - * with the current thread. This allows separate LogFactory objects - * per component within a container, provided each component has - * a distinct context classloader set. This parameter may be null - * in JDK1.1, and in embedded systems where jcl-using code is - * placed in the bootclasspath. - * - * @return the factory associated with the specified classloader if - * one has previously been created, or null if this is the first time - * we have seen this particular classloader. - */ - private static LogFactory getCachedFactory(final ClassLoader contextClassLoader) { - if (contextClassLoader == null) { - // We have to handle this specially, as factories is a Hashtable - // and those don't accept null as a key value. - // - // nb: nullClassLoaderFactory might be null. That's ok. - return nullClassLoaderFactory; - } - return (LogFactory) factories.get(contextClassLoader); - } - - /** - * Remember this factory, so later calls to LogFactory.getCachedFactory - * can return the previously created object (together with all its - * cached Log objects). - * - * @param classLoader should be the current context classloader. Note that - * this can be null under some circumstances; this is ok. - * @param factory should be the factory to cache. This should never be null. - */ - private static void cacheFactory(final ClassLoader classLoader, final 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 {@code 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 {@code 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. - * @return a new instance of the specified {@code LogFactory}. - * @throws LogConfigurationException if a suitable instance cannot be created - * @since 1.1 - */ - protected static LogFactory newFactory(final String factoryClass, - final ClassLoader classLoader, - final ClassLoader contextClassLoader) - throws LogConfigurationException { - // Note that any unchecked exceptions thrown by the createFactory - // method will propagate out of this method; in particular a - // ClassCastException can be thrown. - final Object result = AccessController.doPrivileged( - new PrivilegedAction() { - @Override - public Object run() { - return createFactory(factoryClass, classLoader); - } - }); - - if (result instanceof LogConfigurationException) { - final 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. - *
- * - * @param factoryClass factory class. - * @param classLoader class loader. - * @return a LogFactory. - */ - 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 Factory class. - * @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(final String factoryClass, final 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); - } + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + return System.getProperty(key, def); } + }); + } - return (LogFactory) logFactoryClass.getConstructor().newInstance(); - - } catch (final ClassNotFoundException ex) { - if (classLoader == thisClassLoaderRef.get()) { - // 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 (final NoClassDefFoundError e) { - if (classLoader == thisClassLoaderRef.get()) { - // 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 (final ClassCastException e) { - if (classLoader == thisClassLoaderRef.get()) { - // There's no point in falling through to the code below that - // tries again with thisClassLoaderRef, because we've just tried - // loading with that loader (not the TCCL). Just throw an - // appropriate exception here. - - final boolean implementsLogFactory = implementsLogFactory(logFactoryClass); - - // - // Construct a good message: users may not actual expect that a custom implementation - // has been specified. Several well known containers use this mechanism to adapt JCL - // to their native logging system. - // - final StringBuilder msg = new StringBuilder(); - msg.append("The application has specified that a custom LogFactory implementation "); - msg.append("should be used but Class '"); - msg.append(factoryClass); - msg.append("' cannot be converted to '"); - msg.append(LogFactory.class.getName()); - msg.append("'. "); - if (implementsLogFactory) { - msg.append("The conflict is caused by the presence of multiple LogFactory classes "); - msg.append("in incompatible classloaders. "); - msg.append("Background can be found in http://commons.apache.org/logging/tech.html. "); - msg.append("If you have not explicitly specified a custom LogFactory then it is likely "); - msg.append("that the container has set one without your knowledge. "); - msg.append("In this case, consider using the commons-logging-adapters.jar file or "); - msg.append("specifying the standard LogFactory from the command line. "); - } else { - msg.append("Please check the custom implementation. "); - } - msg.append("Help can be found @http://commons.apache.org/logging/troubleshooting.html."); - - if (isDiagnosticsEnabled()) { - logDiagnostic(msg.toString()); - } - - throw new ClassCastException(msg.toString()); - } - - // Ignore exception, continue. Presumably the classloader was the - // TCCL; the code below will try to load the class via thisClassLoaderRef. - // 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 (final 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); + /** + * Checks whether the supplied Throwable is one that needs to be + * re-thrown and ignores all others. + * + * The following errors are re-thrown: + *- * 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) { - final PrivilegedAction action = - new PrivilegedAction() { - @Override - public Object run() { - try { - if (loader != null) { - return loader.getResources(name); - } - return ClassLoader.getSystemResources(name); - } catch (final IOException e) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Exception while trying to find configuration file " + - name + ":" + e.getMessage()); - } - return null; - } catch (final 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; - } - } - }; - final Object result = AccessController.doPrivileged(action); - return (Enumeration) result; - } - - /** - * Given a URL that refers to a .properties file, load that file. - * This is done under an AccessController so that this method will - * succeed when this jarfile is privileged but the caller is not. - * This method must therefore remain private to avoid security issues. - *
- * {@code Null} is returned if the URL cannot be opened. - */ - private static Properties getProperties(final URL url) { - final PrivilegedAction action = - new PrivilegedAction() { - @Override - public Object run() { - InputStream stream = null; - try { - // We must ensure that useCaches is set to false, as the - // default behavior of java is to cache file handles, and - // this "locks" files, preventing hot-redeploy on windows. - final URLConnection connection = url.openConnection(); - connection.setUseCaches(false); - stream = connection.getInputStream(); - if (stream != null) { - final Properties props = new Properties(); - props.load(stream); - stream.close(); - stream = null; - return props; - } - } catch (final IOException e) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Unable to read URL " + url); - } - } finally { - if (stream != null) { - try { - stream.close(); - } catch (final IOException e) { - // ignore exception; this should not happen - if (isDiagnosticsEnabled()) { - logDiagnostic("Unable to close stream for URL " + url); - } - } - } - } - - return null; - } - }; - return (Properties) AccessController.doPrivileged(action); - } - - /** - * Locate a user-provided configuration file. - *
- * The classpath of the specified classLoader (usually the context classloader) - * is searched for properties files of the specified name. If none is found, - * null is returned. If more than one is found, then the file with the greatest - * value for its PRIORITY property is returned. If multiple files have the - * same PRIORITY value then the first in the classpath is returned. - *
- * This differs from the 1.0.x releases; those always use the first one found. - * However as the priority is a new field, this change is backwards compatible. - *
- * The purpose of the priority field is to allow a webserver administrator to - * override logging settings in all webapps by placing a commons-logging.properties - * file in a shared classpath location with a priority > 0; this overrides any - * commons-logging.properties files without priorities which are in the - * webapps. Webapps can also use explicit priorities to override a configuration - * file in the shared classpath if needed. - */ - private static final Properties getConfigurationFile(final ClassLoader classLoader, final String fileName) { - Properties props = null; - double priority = 0.0; - URL propsUrl = null; - try { - final Enumeration urls = getResources(classLoader, fileName); - - if (urls == null) { - return null; - } - - while (urls.hasMoreElements()) { - final URL url = (URL) urls.nextElement(); - - final Properties newProps = getProperties(url); - if (newProps != null) { - if (props == null) { - propsUrl = url; - props = newProps; - final 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 { - final 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 (final 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() { - @Override - 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 @@ -1472,45 +1278,6 @@ public abstract class LogFactory { return DIAGNOSTICS_STREAM != 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(final String msg) { - if (DIAGNOSTICS_STREAM != null) { - DIAGNOSTICS_STREAM.print(diagnosticPrefix); - DIAGNOSTICS_STREAM.println(msg); - DIAGNOSTICS_STREAM.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(final String msg) { - if (DIAGNOSTICS_STREAM != null) { - DIAGNOSTICS_STREAM.println(msg); - DIAGNOSTICS_STREAM.flush(); - } - } - /** * Generate useful diagnostics regarding the classloader tree for * the specified class. @@ -1558,6 +1325,32 @@ public abstract class LogFactory { logHierarchy("[ENV] Ancestry of classloader which loaded " + className + " is ", classLoader); } + /** + * 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(final String msg) { + if (DIAGNOSTICS_STREAM != null) { + DIAGNOSTICS_STREAM.print(diagnosticPrefix); + DIAGNOSTICS_STREAM.println(msg); + DIAGNOSTICS_STREAM.flush(); + } + } + /** * Logs diagnostic messages about the given classloader * and it's hierarchy. The prefix is prepended to the message @@ -1606,6 +1399,105 @@ public abstract class LogFactory { } } + /** + * 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(final String msg) { + if (DIAGNOSTICS_STREAM != null) { + DIAGNOSTICS_STREAM.println(msg); + DIAGNOSTICS_STREAM.flush(); + } + } + + /** + * 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. + *
+ * + * @param factoryClass factory class. + * @param classLoader class loader. + * @return a LogFactory. + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader) { + return newFactory(factoryClass, classLoader, null); + } + + /** + * Return a new instance of the specified {@code 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 {@code 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. + * @return a new instance of the specified {@code LogFactory}. + * @throws LogConfigurationException if a suitable instance cannot be created + * @since 1.1 + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader, + final ClassLoader contextClassLoader) + throws LogConfigurationException { + // Note that any unchecked exceptions thrown by the createFactory + // method will propagate out of this method; in particular a + // ClassCastException can be thrown. + final Object result = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + return createFactory(factoryClass, classLoader); + } + }); + + if (result instanceof LogConfigurationException) { + final 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; + } + /** * Returns a string that uniquely identifies the specified object, including * its class. @@ -1625,6 +1517,136 @@ public abstract class LogFactory { return o.getClass().getName() + "@" + System.identityHashCode(o); } + /** + * 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 {@code release()} on + * each of them. + * + * @param classLoader ClassLoader for which to release the LogFactory + */ + public static void release(final ClassLoader classLoader) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for classloader " + objectId(classLoader)); + } + // factories is not final and could be replaced in this block. + final Hashtable factories = LogFactory.factories; + synchronized (factories) { + if (classLoader == null) { + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } else { + final LogFactory factory = (LogFactory) factories.get(classLoader); + if (factory != null) { + factory.release(); + factories.remove(classLoader); + } + } + } + } + + /** + * Release any internal references to previously created {@link LogFactory} + * instances, after calling the instance method {@code release()} on + * each of them. This is useful in environments like servlet containers, + * which implement application reloading by throwing away a ClassLoader. + * Dangling references to objects in that class loader would prevent + * garbage collection. + */ + public static void releaseAll() { + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for all classloaders."); + } + // factories is not final and could be replaced in this block. + final Hashtable factories = LogFactory.factories; + synchronized (factories) { + final Enumeration elements = factories.elements(); + while (elements.hasMoreElements()) { + final LogFactory element = (LogFactory) elements.nextElement(); + element.release(); + } + factories.clear(); + + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } + } + + /** Utility method to safely trim a string. */ + private static String trim(final String src) { + if (src == null) { + return null; + } + return src.trim(); + } + + /** + * Protected constructor that is not available for public use. + */ + protected LogFactory() { + } + + /** + * Return the configuration attribute with the specified name (if any), + * or {@code null} if there is no such attribute. + * + * @param name Name of the attribute to return + * @return the configuration attribute with the specified name. + */ + public abstract Object getAttribute(String name); + + /** + * Gets an array containing the names of all currently defined configuration attributes. If there are no such attributes, a zero length array is returned. + * + * @return an array containing the names of all currently defined configuration attributes + */ + public abstract String[] getAttributeNames(); + + /** + * Convenience method to derive a name from the specified class and call {@code getInstance(String)} with it. + * + * @param clazz Class for which a suitable Log name will be derived + * @return a name from the specified class. + * @throws LogConfigurationException if a suitable {@code Log} instance cannot be returned + */ + public abstract Log getInstance(Class clazz) throws LogConfigurationException; + + /** + * Construct (if necessary) and return a {@code Log} instance, using the factory's current set of configuration attributes. + *+ * NOTE - Depending upon the implementation of the {@code LogFactory} you are using, the {@code 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 {@code Log} instance to be returned (the meaning of this name is only known to the underlying logging implementation that + * is being wrapped) + * @return a {@code Log} instance. + * @throws LogConfigurationException if a suitable {@code 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); + // ---------------------------------------------------------------------- // Static initializer block to perform initialization at class load time. // @@ -1644,36 +1666,14 @@ public abstract class LogFactory { // of the class file. // ---------------------------------------------------------------------- - static { - // note: it's safe to call methods before initDiagnostics (though - // diagnostic output gets discarded). - ClassLoader thisClassLoader = getClassLoader(LogFactory.class); - thisClassLoaderRef = new WeakReference+ * 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( + (PrivilegedAction) () -> System.getProperty(key, def)); + } + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * + * @return Same as {@link LogFactory#isDiagnosticsEnabled()}. + * @see LogFactory#isDiagnosticsEnabled() + */ + protected static boolean isDiagnosticsEnabled() { + return LogFactory.isDiagnosticsEnabled(); + } + /** * Determines whether logging classes should be loaded using the thread-context * classloader, or via the classloader that loaded this LogFactoryImpl class. @@ -217,6 +278,8 @@ public class LogFactoryImpl extends LogFactory { */ protected Method logMethod; + // --------------------------------------------------------- Public Methods + /** * The signature of the {@code setLogFactory} method to be used. */ @@ -237,709 +300,16 @@ public class LogFactoryImpl extends LogFactory { */ private boolean allowFlawedHierarchy; - // --------------------------------------------------------- Public Methods - /** - * Return the configuration attribute with the specified name (if any), - * or {@code null} if there is no such attribute. - * - * @param name Name of the attribute to return + * Public no-arguments constructor required by the lookup mechanism. */ - @Override - public Object getAttribute(final 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. - */ - @Override - public String[] getAttributeNames() { - return (String[]) attributes.keySet().toArray(EMPTY_STRING_ARRAY); - } - - /** - * Convenience method to derive a name from the specified class and - * call {@code getInstance(String)} with it. - * - * @param clazz Class for which a suitable Log name will be derived - * - * @throws LogConfigurationException if a suitable {@code Log} - * instance cannot be returned - */ - @Override - public Log getInstance(final Class clazz) throws LogConfigurationException { - return getInstance(clazz.getName()); - } - - /** - *
Construct (if necessary) and return a {@code Log} instance, - * using the factory's current set of configuration attributes.
- * - *NOTE - Depending upon the implementation of - * the {@code LogFactory} you are using, the {@code 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 {@code Log} instance to be - * returned (the meaning of this name is only known to the underlying - * logging implementation that is being wrapped) - * - * @throws LogConfigurationException if a suitable {@code Log} - * instance cannot be returned - */ - @Override - public Log getInstance(final 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. - */ - @Override - 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 - */ - @Override - public void removeAttribute(final String name) { - attributes.remove(name); - } - - /** - * Set the configuration attribute with the specified name. Calling - * this with a {@code null} value is equivalent to calling - * {@code 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 behavior 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 {@code null} - * to remove any setting for this attribute - */ - @Override - public void setAttribute(final String name, final Object value) { - if (logConstructor != null) { - logDiagnostic("setAttribute: call too late; configuration already performed."); - } - - if (value == null) { - attributes.remove(name); - } else { - attributes.put(name, value); - } - - if (name.equals(TCCL_KEY)) { - useTCCL = value != null && Boolean.parseBoolean(value.toString()); - } - } - - // ------------------------------------------------------ - // 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. - * - * @return the context ClassLoader - * @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. - * - * @return Same as {@link LogFactory#isDiagnosticsEnabled()}. - * @see LogFactory#isDiagnosticsEnabled() - */ - protected static boolean isDiagnosticsEnabled() { - return LogFactory.isDiagnosticsEnabled(); - } - - /** - * Workaround for bug in Java1.2; in theory this method is not needed. {@link LogFactory#getClassLoader(Class)}. - * - * @param clazz See {@link LogFactory#getClassLoader(Class)}. - * @return See {@link LogFactory#getClassLoader(Class)}. - * @since 1.1 - */ - protected static ClassLoader getClassLoader(final 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 behavior. - *
- * 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. - final Class clazz = this.getClass(); - final ClassLoader classLoader = getClassLoader(clazz); - String classLoaderName; - try { - if (classLoader == null) { - classLoaderName = "BOOTLOADER"; - } else { - classLoaderName = objectId(classLoader); - } - } catch (final 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(final String msg) { + public LogFactoryImpl() { + initDiagnostics(); // method on this object if (isDiagnosticsEnabled()) { - logRawDiagnostic(diagnosticPrefix + msg); + logDiagnostic("Instance created."); } } - /** - * Return the fully qualified Java classname of the {@link Log} implementation we will be using. - * - * @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. - */ - @Deprecated - protected String getLogClassName() { - if (logClassName == null) { - discoverLogImplementation(getClass().getName()); - } - - return logClassName; - } - - - /** - *
- * Return the {@code 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 - * {@code Constructor} instance will ultimately be derived in all circumstances. - *
- * - * @return the {@code Constructor} that can be called to instantiate new {@link org.apache.commons.logging.Log} instances. - * - * @throws LogConfigurationException if a suitable constructor cannot be returned - * - * @deprecated Never invoked by this class; subclasses should not assume it will be. - */ - @Deprecated - protected Constructor getLogConstructor() - throws LogConfigurationException { - - // Return the previously identified Constructor (if any) - if (logConstructor == null) { - discoverLogImplementation(getClass().getName()); - } - - return logConstructor; - } - - /** - * Tests whether JDK 1.3 with Lumberjack logging available. - * - * @return whether JDK 1.3 with Lumberjack logging available. - * @deprecated Never invoked by this class; subclasses should not assume it will be. - */ - @Deprecated - protected boolean isJdk13LumberjackAvailable() { - return isLogLibraryAvailable( - "Jdk13Lumberjack", - "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); - } - - /** - * Tests {@code true} whether JDK 1.4 or later logging is available. Also checks that the {@code Throwable} class supports {@code getStackTrace()}, - * which is required by Jdk14Logger. - * - * @return Whether JDK 1.4 or later logging is available. - * - * @deprecated Never invoked by this class; subclasses should not assume it will be. - */ - @Deprecated - protected boolean isJdk14Available() { - return isLogLibraryAvailable("Jdk14", "org.apache.commons.logging.impl.Jdk14Logger"); - } - - /** - * Tests whether a Log4J implementation available. - * - * @return whether a Log4J implementation available. - * - * @deprecated Never invoked by this class; subclasses should not assume it will be. - */ - @Deprecated - 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 - * @return a new {@link org.apache.commons.logging.Log} - * - * @throws LogConfigurationException if a new instance cannot be created - */ - protected Log newInstance(final String name) throws LogConfigurationException { - Log instance; - try { - if (logConstructor == null) { - instance = discoverLogImplementation(name); - } - else { - final Object[] params = { name }; - instance = (Log) logConstructor.newInstance(params); - } - - if (logMethod != null) { - final Object[] params = { this }; - logMethod.invoke(instance, params); - } - - return instance; - - } catch (final LogConfigurationException lce) { - - // this type of exception means there was a problem in discovery - // and we've already output diagnostics about the issue, etc.; - // just pass it on - throw lce; - - } catch (final InvocationTargetException e) { - // A problem occurred invoking the Constructor or Method - // previously discovered - final Throwable c = e.getTargetException(); - throw new LogConfigurationException(c == null ? e : c); - } catch (final Throwable t) { - handleThrowable(t); // may re-throw t - // A problem occurred invoking the Constructor or Method - // previously discovered - throw new LogConfigurationException(t); - } - } - - // ------------------------------------------------------ Private Methods - - /** - * Calls LogFactory.directGetContextClassLoader under the control of an - * AccessController class. This means that java code running under a - * security manager that forbids access to ClassLoaders will still work - * if this class is given appropriate privileges, even when the caller - * doesn't have such privileges. Without using an AccessController, the - * the entire call stack must have the privilege before the call is - * allowed. - * - * @return the context classloader associated with the current thread, - * or null if security doesn't allow it. - * - * @throws LogConfigurationException if there was some weird error while - * attempting to get the context classloader. - * - * @throws SecurityException if the current java security policy doesn't - * allow this class to access the context classloader. - */ - private static ClassLoader getContextClassLoaderInternal() - throws LogConfigurationException { - return (ClassLoader)AccessController.doPrivileged( - (PrivilegedAction) 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( - (PrivilegedAction) () -> 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( - (PrivilegedAction) () -> cl.getParent()); - } catch (final 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 behavior of this class. - */ - private boolean isLogLibraryAvailable(final String name, final String classname) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Checking for '" + name + "'."); - } - try { - final Log log = createLogFromClass( - classname, - this.getClass().getName(), // dummy category - false); - - if (log == null) { - if (isDiagnosticsEnabled()) { - logDiagnostic("Did not find '" + name + "'."); - } - return false; - } - if (isDiagnosticsEnabled()) { - logDiagnostic("Found '" + name + "'."); - } - return true; - } catch (final 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(final String property) {
- if (isDiagnosticsEnabled()) {
- logDiagnostic("[ENV] Trying to get configuration for item " + property);
- }
-
- final 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.
- final 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 (final 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 behavior specified by key.
- * If nothing has explicitly been set, then return dflt.
- */
- private boolean getBooleanConfiguration(final String key, final boolean dflt) {
- final String val = getConfigurationValue(key);
- if (val == null) {
- return dflt;
- }
- return Boolean.parseBoolean(val);
- }
-
- /**
- * Initialize a number of variables that control the behavior 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(final 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
- final 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) {
- final StringBuilder messageBuffer = new StringBuilder("User-specified log class '");
- messageBuffer.append(specifiedLogClassName);
- messageBuffer.append("' cannot be found or is not useable.");
-
- // Mistyping or misspelling names is a common fault.
- // Construct a good error message, if we can
- informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
- informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
- informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
- informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
- throw new LogConfigurationException(messageBuffer.toString());
- }
-
- return result;
- }
-
- // No user specified log; try to discover what's on the classpath
- //
- // Note that we deliberately loop here over classesToDiscover and
- // expect method createLogFromClass to loop over the possible source
- // classloaders. The effect is:
- // for each discoverable log adapter
- // for each possible classloader
- // see if it works
- //
- // It appears reasonable at first glance to do the opposite:
- // for each possible classloader
- // for each discoverable log adapter
- // see if it works
- //
- // The latter certainly has advantages for user-installable logging
- // libraries such as log4j; in a webapp for example this code should
- // first check whether the user has provided any of the possible
- // logging libraries before looking in the parent classloader.
- // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4,
- // and SimpleLog will always work in any JVM. So the loop would never
- // ever look for logging libraries in the parent classpath. Yet many
- // users would expect that putting log4j there would cause it to be
- // detected (and this is the historical JCL behavior). So we go with
- // the first approach. A user that has bundled a specific logging lib
- // in a webapp should use a commons-logging.properties file or a
- // service file in META-INF to force use of that logging lib anyway,
- // rather than relying on discovery.
-
- if (isDiagnosticsEnabled()) {
- logDiagnostic(
- "No user-specified Log implementation; performing discovery" +
- " using the standard supported logging implementations...");
- }
- for(int i=0; i Construct (if necessary) and return a {@code Log} instance,
+ * using the factory's current set of configuration attributes. NOTE - Depending upon the implementation of
+ * the {@code LogFactory} you are using, the {@code 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.
+ * Return the {@code 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
+ * {@code Constructor} instance will ultimately be derived in all circumstances.
+ *
+ * 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(
+ (PrivilegedAction) () -> cl.getParent());
+ } catch (final SecurityException ex) {
+ logDiagnostic("[SECURITY] Unable to obtain parent classloader");
+ return null;
+ }
+
+ }
+
/**
* Generates an internal diagnostic logging of the discovery failure and
* then throws a {@code LogConfigurationException} that wraps
@@ -1389,4 +1117,276 @@ public class LogFactoryImpl extends LogFactory {
}
}
}
+
+ /**
+ * Appends message if the given name is similar to the candidate.
+ * @param messageBuffer {@code StringBuffer} 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 StringBuilder 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("'?");
+ }
+ }
+
+ /**
+ * Initialize a number of variables that control the behavior 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);
+ }
+
+ /**
+ * 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 behavior.
+ *
+ * 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.
+ final Class clazz = this.getClass();
+ final ClassLoader classLoader = getClassLoader(clazz);
+ String classLoaderName;
+ try {
+ if (classLoader == null) {
+ classLoaderName = "BOOTLOADER";
+ } else {
+ classLoaderName = objectId(classLoader);
+ }
+ } catch (final SecurityException e) {
+ classLoaderName = "UNKNOWN";
+ }
+ diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] ";
+ }
+
+ /**
+ * Tests whether JDK 1.3 with Lumberjack logging available.
+ *
+ * @return whether JDK 1.3 with Lumberjack logging available.
+ * @deprecated Never invoked by this class; subclasses should not assume it will be.
+ */
+ @Deprecated
+ protected boolean isJdk13LumberjackAvailable() {
+ return isLogLibraryAvailable(
+ "Jdk13Lumberjack",
+ "org.apache.commons.logging.impl.Jdk13LumberjackLogger");
+ }
+
+ /**
+ * Tests {@code true} whether JDK 1.4 or later logging is available. Also checks that the {@code Throwable} class supports {@code getStackTrace()},
+ * which is required by Jdk14Logger.
+ *
+ * @return Whether JDK 1.4 or later logging is available.
+ *
+ * @deprecated Never invoked by this class; subclasses should not assume it will be.
+ */
+ @Deprecated
+ protected boolean isJdk14Available() {
+ return isLogLibraryAvailable("Jdk14", "org.apache.commons.logging.impl.Jdk14Logger");
+ }
+
+ /**
+ * Tests whether a Log4J implementation available.
+ *
+ * @return whether a Log4J implementation available.
+ *
+ * @deprecated Never invoked by this class; subclasses should not assume it will be.
+ */
+ @Deprecated
+ protected boolean isLog4JAvailable() {
+ return isLogLibraryAvailable("Log4J", LOGGING_IMPL_LOG4J_LOGGER);
+ }
+
+ /**
+ * Utility method to check whether a particular logging library is
+ * present and available for use. Note that this does not
+ * affect the future behavior of this class.
+ */
+ private boolean isLogLibraryAvailable(final String name, final String classname) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Checking for '" + name + "'.");
+ }
+ try {
+ final Log log = createLogFromClass(
+ classname,
+ this.getClass().getName(), // dummy category
+ false);
+
+ if (log == null) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Did not find '" + name + "'.");
+ }
+ return false;
+ }
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Found '" + name + "'.");
+ }
+ return true;
+ } catch (final LogConfigurationException e) {
+ if (isDiagnosticsEnabled()) {
+ logDiagnostic("Logging system '" + name + "' is available but not useable.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * 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(final String msg) {
+ if (isDiagnosticsEnabled()) {
+ logRawDiagnostic(diagnosticPrefix + msg);
+ }
+ }
+
+ /**
+ * Create and return a new {@link org.apache.commons.logging.Log} instance for the specified name.
+ *
+ * @param name Name of the new logger
+ * @return a new {@link org.apache.commons.logging.Log}
+ *
+ * @throws LogConfigurationException if a new instance cannot be created
+ */
+ protected Log newInstance(final String name) throws LogConfigurationException {
+ Log instance;
+ try {
+ if (logConstructor == null) {
+ instance = discoverLogImplementation(name);
+ }
+ else {
+ final Object[] params = { name };
+ instance = (Log) logConstructor.newInstance(params);
+ }
+
+ if (logMethod != null) {
+ final Object[] params = { this };
+ logMethod.invoke(instance, params);
+ }
+
+ return instance;
+
+ } catch (final LogConfigurationException lce) {
+
+ // this type of exception means there was a problem in discovery
+ // and we've already output diagnostics about the issue, etc.;
+ // just pass it on
+ throw lce;
+
+ } catch (final InvocationTargetException e) {
+ // A problem occurred invoking the Constructor or Method
+ // previously discovered
+ final Throwable c = e.getTargetException();
+ throw new LogConfigurationException(c == null ? e : c);
+ } catch (final Throwable t) {
+ handleThrowable(t); // may re-throw t
+ // A problem occurred invoking the Constructor or Method
+ // previously discovered
+ throw new LogConfigurationException(t);
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ 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
+ */
+ @Override
+ public void removeAttribute(final String name) {
+ attributes.remove(name);
+ }
+
+ /**
+ * Set the configuration attribute with the specified name. Calling
+ * this with a {@code null} value is equivalent to calling
+ * {@code 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 behavior on a
+ * per-component level instead of globally as system properties would do.
+ * To use this method instead of a system property, call
+ *
+ * This method is also called automatically if LogFactory detects a
+ * commons-logging.properties file; every entry in that file is set
+ * automatically as an attribute here.
+ *
+ * @param name Name of the attribute to set
+ * @param value Value of the attribute to set, or {@code null}
+ * to remove any setting for this attribute
+ */
+ @Override
+ public void setAttribute(final String name, final Object value) {
+ if (logConstructor != null) {
+ logDiagnostic("setAttribute: call too late; configuration already performed.");
+ }
+
+ if (value == null) {
+ attributes.remove(name);
+ } else {
+ attributes.put(name, value);
+ }
+
+ if (name.equals(TCCL_KEY)) {
+ useTCCL = value != null && Boolean.parseBoolean(value.toString());
+ }
+ }
}
diff --git a/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java b/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java
index 11add6a..a05e8f0 100644
--- a/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java
+++ b/src/main/java/org/apache/commons/logging/impl/LogKitLogger.java
@@ -62,49 +62,6 @@ public class LogKitLogger implements Log, Serializable {
// --------------------------------------------------------- Public Methods
- /**
- * Gets the underlying Logger we are using.
- *
- * @return the underlying Logger we are using.
- */
- public Logger getLogger() {
- Logger result = logger;
- if (result == null) {
- synchronized(this) {
- result = logger;
- if (result == null) {
- logger = result = Hierarchy.getDefaultHierarchy().getLoggerFor(name);
- }
- }
- }
- return result;
- }
-
- // ----------------------------------------------------- Log Implementation
-
- /**
- * Logs a message with {@code org.apache.log.Priority.DEBUG}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#trace(Object)
- */
- @Override
- public void trace(final Object message) {
- debug(message);
- }
-
- /**
- * Logs a message with {@code org.apache.log.Priority.DEBUG}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#trace(Object, Throwable)
- */
- @Override
- public void trace(final Object message, final Throwable t) {
- debug(message, t);
- }
-
/**
* Logs a message with {@code org.apache.log.Priority.DEBUG}.
*
@@ -118,6 +75,8 @@ public class LogKitLogger implements Log, Serializable {
}
}
+ // ----------------------------------------------------- Log Implementation
+
/**
* Logs a message with {@code org.apache.log.Priority.DEBUG}.
*
@@ -132,60 +91,6 @@ public class LogKitLogger implements Log, Serializable {
}
}
- /**
- * Logs a message with {@code org.apache.log.Priority.INFO}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#info(Object)
- */
- @Override
- public void info(final Object message) {
- if (message != null) {
- getLogger().info(String.valueOf(message));
- }
- }
-
- /**
- * Logs a message with {@code org.apache.log.Priority.INFO}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#info(Object, Throwable)
- */
- @Override
- public void info(final Object message, final Throwable t) {
- if (message != null) {
- getLogger().info(String.valueOf(message), t);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.log.Priority.WARN}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#warn(Object)
- */
- @Override
- public void warn(final Object message) {
- if (message != null) {
- getLogger().warn(String.valueOf(message));
- }
- }
-
- /**
- * Logs a message with {@code org.apache.log.Priority.WARN}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#warn(Object, Throwable)
- */
- @Override
- public void warn(final Object message, final Throwable t) {
- if (message != null) {
- getLogger().warn(String.valueOf(message), t);
- }
- }
-
/**
* Logs a message with {@code org.apache.log.Priority.ERROR}.
*
@@ -240,6 +145,51 @@ public class LogKitLogger implements Log, Serializable {
}
}
+ /**
+ * Gets the underlying Logger we are using.
+ *
+ * @return the underlying Logger we are using.
+ */
+ public Logger getLogger() {
+ Logger result = logger;
+ if (result == null) {
+ synchronized(this) {
+ result = logger;
+ if (result == null) {
+ logger = result = Hierarchy.getDefaultHierarchy().getLoggerFor(name);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Logs a message with {@code org.apache.log.Priority.INFO}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#info(Object)
+ */
+ @Override
+ public void info(final Object message) {
+ if (message != null) {
+ getLogger().info(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.log.Priority.INFO}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#info(Object, Throwable)
+ */
+ @Override
+ public void info(final Object message, final Throwable t) {
+ if (message != null) {
+ getLogger().info(String.valueOf(message), t);
+ }
+ }
+
/**
* Checks whether the {@code LogKit} logger will log messages of priority {@code DEBUG}.
*/
@@ -287,4 +237,54 @@ public class LogKitLogger implements Log, Serializable {
public boolean isWarnEnabled() {
return getLogger().isWarnEnabled();
}
+
+ /**
+ * Logs a message with {@code org.apache.log.Priority.DEBUG}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#trace(Object)
+ */
+ @Override
+ public void trace(final Object message) {
+ debug(message);
+ }
+
+ /**
+ * Logs a message with {@code org.apache.log.Priority.DEBUG}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#trace(Object, Throwable)
+ */
+ @Override
+ public void trace(final Object message, final Throwable t) {
+ debug(message, t);
+ }
+
+ /**
+ * Logs a message with {@code org.apache.log.Priority.WARN}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#warn(Object)
+ */
+ @Override
+ public void warn(final Object message) {
+ if (message != null) {
+ getLogger().warn(String.valueOf(message));
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.log.Priority.WARN}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#warn(Object, Throwable)
+ */
+ @Override
+ public void warn(final Object message, final Throwable t) {
+ if (message != null) {
+ getLogger().warn(String.valueOf(message), t);
+ }
+ }
}
diff --git a/src/main/java/org/apache/commons/logging/impl/NoOpLog.java b/src/main/java/org/apache/commons/logging/impl/NoOpLog.java
index cf6b190..c5aed46 100644
--- a/src/main/java/org/apache/commons/logging/impl/NoOpLog.java
+++ b/src/main/java/org/apache/commons/logging/impl/NoOpLog.java
@@ -40,16 +40,6 @@ public class NoOpLog implements Log, Serializable {
public NoOpLog(final String name) {
}
- /** Do nothing */
- @Override
- public void trace(final Object message) {
- }
-
- /** Do nothing */
- @Override
- public void trace(final Object message, final Throwable t) {
- }
-
/** Do nothing */
@Override
public void debug(final Object message) {
@@ -60,26 +50,6 @@ public class NoOpLog implements Log, Serializable {
public void debug(final Object message, final Throwable t) {
}
- /** Do nothing */
- @Override
- public void info(final Object message) {
- }
-
- /** Do nothing */
- @Override
- public void info(final Object message, final Throwable t) {
- }
-
- /** Do nothing */
- @Override
- public void warn(final Object message) {
- }
-
- /** Do nothing */
- @Override
- public void warn(final Object message, final Throwable t) {
- }
-
/** Do nothing */
@Override
public void error(final Object message) {
@@ -100,6 +70,16 @@ public class NoOpLog implements Log, Serializable {
public void fatal(final Object message, final Throwable t) {
}
+ /** Do nothing */
+ @Override
+ public void info(final Object message) {
+ }
+
+ /** Do nothing */
+ @Override
+ public void info(final Object message, final Throwable t) {
+ }
+
/**
* Debug is never enabled.
*
@@ -159,4 +139,24 @@ public class NoOpLog implements Log, Serializable {
public final boolean isWarnEnabled() {
return false;
}
+
+ /** Do nothing */
+ @Override
+ public void trace(final Object message) {
+ }
+
+ /** Do nothing */
+ @Override
+ public void trace(final Object message, final Throwable t) {
+ }
+
+ /** Do nothing */
+ @Override
+ public void warn(final Object message) {
+ }
+
+ /** Do nothing */
+ @Override
+ public void warn(final Object message, final Throwable t) {
+ }
}
diff --git a/src/main/java/org/apache/commons/logging/impl/SimpleLog.java b/src/main/java/org/apache/commons/logging/impl/SimpleLog.java
index c78dd81..ad8cc4f 100644
--- a/src/main/java/org/apache/commons/logging/impl/SimpleLog.java
+++ b/src/main/java/org/apache/commons/logging/impl/SimpleLog.java
@@ -133,26 +133,6 @@ public class SimpleLog implements Log, Serializable {
// ------------------------------------------------------------ Initializer
- private static String getStringProperty(final String name) {
- String prop = null;
- try {
- prop = System.getProperty(name);
- } catch (final SecurityException e) {
- // Ignore
- }
- return prop == null ? simpleLogProps.getProperty(name) : prop;
- }
-
- private static String getStringProperty(final String name, final String dephault) {
- final String prop = getStringProperty(name);
- return prop == null ? dephault : prop;
- }
-
- private static boolean getBooleanProperty(final String name, final boolean dephault) {
- final String prop = getStringProperty(name);
- return prop == null ? dephault : "true".equalsIgnoreCase(prop);
- }
-
// Initialize class attributes.
// Load properties file, if found.
// Override with system properties.
@@ -190,423 +170,9 @@ public class SimpleLog implements Log, Serializable {
}
}
- // ------------------------------------------------------------- Attributes
-
- /** The name of this simple log instance */
- protected volatile String logName;
- /** The current log level */
- protected volatile int currentLogLevel;
- /** The short name of this simple log instance */
- private volatile String shortLogName;
-
- // ------------------------------------------------------------ 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(final int currentLogLevel) {
- this.currentLogLevel = currentLogLevel;
- }
-
- /**
- * Get logging level.
- *
- * @return logging level.
- */
- public int getLevel() {
- return currentLogLevel;
- }
-
- // -------------------------------------------------------- Logging Methods
-
- /**
- * Do the actual logging.
- *
- * This method assembles the message and then calls {@code 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(final int type, final Object message, final Throwable t) {
- // Use a string buffer for better performance
- final StringBuilder buf = new StringBuilder();
-
- // Append date-time if so configured
- if (showDateTime) {
- final Date now = new Date();
- String dateText;
- synchronized(dateFormatter) {
- dateText = dateFormatter.format(now);
- }
- buf.append(dateText);
- buf.append(" ");
- }
-
- // Append a readable representation of the log level
- switch(type) {
- case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break;
- case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break;
- case SimpleLog.LOG_LEVEL_INFO: buf.append("[INFO] "); break;
- case SimpleLog.LOG_LEVEL_WARN: buf.append("[WARN] "); break;
- case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break;
- case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break;
- }
-
- // Append the name of the log instance if so configured
- if (showShortName) {
- if (shortLogName == null) {
- // Cut all but the last component of the name for both styles
- final String slName = logName.substring(logName.lastIndexOf(".") + 1);
- shortLogName = slName.substring(slName.lastIndexOf("/") + 1);
- }
- buf.append(String.valueOf(shortLogName)).append(" - ");
- } else if (showLogName) {
- buf.append(String.valueOf(logName)).append(" - ");
- }
-
- // Append the message
- buf.append(String.valueOf(message));
-
- // Append stack trace if not null
- if (t != null) {
- buf.append(" <");
- buf.append(t.toString());
- buf.append(">");
-
- final StringWriter sw = new StringWriter(1024);
- final PrintWriter pw = new 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
- * {@code StringBuffer} to the appropriate output destination. The
- * default implementation writes to {@code System.err}.
- *
- * @param buffer A {@code StringBuffer} containing the accumulated
- * text to be logged
- */
- protected void write(final StringBuffer buffer) {
- System.err.println(buffer.toString());
- }
-
- /**
- * Write the content of the message accumulated in the specified
- * {@code StringBuffer} to the appropriate output destination. The
- * default implementation writes to {@code System.err}.
- *
- * @param buffer A {@code StringBuffer} containing the accumulated
- * text to be logged
- */
- private void write(final Object buffer) {
- System.err.println(buffer.toString());
- }
-
- /**
- * Tests whether the given log level currently enabled.
- *
- * @param logLevel is this level enabled?
- * @return whether the given log level currently enabled.
- */
- protected boolean isLevelEnabled(final int logLevel) {
- // log level are numerically ordered so can use simple numeric
- // comparison
- return logLevel >= currentLogLevel;
- }
-
- // -------------------------------------------------------- Log Implementation
-
- /**
- * Logs a message with
- * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#debug(Object)
- */
- @Override
- public final void debug(final Object message) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
- log(SimpleLog.LOG_LEVEL_DEBUG, message, null);
- }
- }
-
- /**
- * Logs a message with
- * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#debug(Object, Throwable)
- */
- @Override
- public final void debug(final Object message, final Throwable t) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
- log(SimpleLog.LOG_LEVEL_DEBUG, message, t);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#trace(Object)
- */
- @Override
- public final void trace(final Object message) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
- log(SimpleLog.LOG_LEVEL_TRACE, message, null);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#trace(Object, Throwable)
- */
- @Override
- public final void trace(final Object message, final Throwable t) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
- log(SimpleLog.LOG_LEVEL_TRACE, message, t);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#info(Object)
- */
- @Override
- public final void info(final Object message) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
- log(SimpleLog.LOG_LEVEL_INFO,message,null);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#info(Object, Throwable)
- */
- @Override
- public final void info(final Object message, final Throwable t) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
- log(SimpleLog.LOG_LEVEL_INFO, message, t);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#warn(Object)
- */
- @Override
- public final void warn(final Object message) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
- log(SimpleLog.LOG_LEVEL_WARN, message, null);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#warn(Object, Throwable)
- */
- @Override
- public final void warn(final Object message, final Throwable t) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
- log(SimpleLog.LOG_LEVEL_WARN, message, t);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#error(Object)
- */
- @Override
- public final void error(final Object message) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
- log(SimpleLog.LOG_LEVEL_ERROR, message, null);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#error(Object, Throwable)
- */
- @Override
- public final void error(final Object message, final Throwable t) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
- log(SimpleLog.LOG_LEVEL_ERROR, message, t);
- }
- }
-
- /**
- * Log a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
- *
- * @param message to log
- * @see org.apache.commons.logging.Log#fatal(Object)
- */
- @Override
- public final void fatal(final Object message) {
- if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
- log(SimpleLog.LOG_LEVEL_FATAL, message, null);
- }
- }
-
- /**
- * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
- *
- * @param message to log
- * @param t log this cause
- * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
- */
- @Override
- public final void fatal(final Object message, final 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 {@code String}
- * concatenation to be avoided when the message will be ignored by the
- * logger.
- */
- @Override
- public final boolean isDebugEnabled() {
- return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG);
- }
-
- /**
- * Are error messages currently enabled?
- *
- * This allows expensive operations such as {@code String}
- * concatenation to be avoided when the message will be ignored by the
- * logger.
- */
- @Override
- public final boolean isErrorEnabled() {
- return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR);
- }
-
- /**
- * Are fatal messages currently enabled?
- *
- * This allows expensive operations such as {@code String}
- * concatenation to be avoided when the message will be ignored by the
- * logger.
- */
- @Override
- public final boolean isFatalEnabled() {
- return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL);
- }
-
- /**
- * Are info messages currently enabled?
- *
- * This allows expensive operations such as {@code String}
- * concatenation to be avoided when the message will be ignored by the
- * logger.
- */
- @Override
- public final boolean isInfoEnabled() {
- return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO);
- }
-
- /**
- * Are trace messages currently enabled?
- *
- * This allows expensive operations such as {@code String}
- * concatenation to be avoided when the message will be ignored by the
- * logger.
- */
- @Override
- public final boolean isTraceEnabled() {
- return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE);
- }
-
- /**
- * Are warn messages currently enabled?
- *
- * This allows expensive operations such as {@code String}
- * concatenation to be avoided when the message will be ignored by the
- * logger.
- */
- @Override
- public final boolean isWarnEnabled() {
- return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN);
+ private static boolean getBooleanProperty(final String name, final boolean dephault) {
+ final String prop = getStringProperty(name);
+ return prop == null ? dephault : "true".equalsIgnoreCase(prop);
}
/**
@@ -682,5 +248,439 @@ public class SimpleLog implements Log, Serializable {
}
});
}
+
+ // ------------------------------------------------------------- Attributes
+
+ private static String getStringProperty(final String name) {
+ String prop = null;
+ try {
+ prop = System.getProperty(name);
+ } catch (final SecurityException e) {
+ // Ignore
+ }
+ return prop == null ? simpleLogProps.getProperty(name) : prop;
+ }
+ private static String getStringProperty(final String name, final String dephault) {
+ final String prop = getStringProperty(name);
+ return prop == null ? dephault : prop;
+ }
+ /** The name of this simple log instance */
+ protected volatile String logName;
+
+ // ------------------------------------------------------------ Constructor
+
+ /** The current log level */
+ protected volatile int currentLogLevel;
+
+ // -------------------------------------------------------- Properties
+
+ /** The short name of this simple log instance */
+ private volatile String shortLogName;
+
+ /**
+ * 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);
+ }
+ }
+
+ // -------------------------------------------------------- Logging Methods
+
+ /**
+ * Logs a message with
+ * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#debug(Object)
+ */
+ @Override
+ public final void debug(final Object message) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
+ log(SimpleLog.LOG_LEVEL_DEBUG, message, null);
+ }
+ }
+
+ /**
+ * Logs a message with
+ * {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#debug(Object, Throwable)
+ */
+ @Override
+ public final void debug(final Object message, final Throwable t) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) {
+ log(SimpleLog.LOG_LEVEL_DEBUG, message, t);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#error(Object)
+ */
+ @Override
+ public final void error(final Object message) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
+ log(SimpleLog.LOG_LEVEL_ERROR, message, null);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#error(Object, Throwable)
+ */
+ @Override
+ public final void error(final Object message, final Throwable t) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) {
+ log(SimpleLog.LOG_LEVEL_ERROR, message, t);
+ }
+ }
+
+ // -------------------------------------------------------- Log Implementation
+
+ /**
+ * Log a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#fatal(Object)
+ */
+ @Override
+ public final void fatal(final Object message) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
+ log(SimpleLog.LOG_LEVEL_FATAL, message, null);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#fatal(Object, Throwable)
+ */
+ @Override
+ public final void fatal(final Object message, final Throwable t) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) {
+ log(SimpleLog.LOG_LEVEL_FATAL, message, t);
+ }
+ }
+
+ /**
+ * Get logging level.
+ *
+ * @return logging level.
+ */
+ public int getLevel() {
+ return currentLogLevel;
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#info(Object)
+ */
+ @Override
+ public final void info(final Object message) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
+ log(SimpleLog.LOG_LEVEL_INFO,message,null);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#info(Object, Throwable)
+ */
+ @Override
+ public final void info(final Object message, final Throwable t) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) {
+ log(SimpleLog.LOG_LEVEL_INFO, message, t);
+ }
+ }
+
+ /**
+ * Are debug messages currently enabled?
+ *
+ * This allows expensive operations such as {@code String}
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ @Override
+ public final boolean isDebugEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG);
+ }
+
+ /**
+ * Are error messages currently enabled?
+ *
+ * This allows expensive operations such as {@code String}
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ @Override
+ public final boolean isErrorEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR);
+ }
+
+ /**
+ * Are fatal messages currently enabled?
+ *
+ * This allows expensive operations such as {@code String}
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ @Override
+ public final boolean isFatalEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL);
+ }
+
+ /**
+ * Are info messages currently enabled?
+ *
+ * This allows expensive operations such as {@code String}
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ @Override
+ public final boolean isInfoEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO);
+ }
+
+ /**
+ * Tests whether the given log level currently enabled.
+ *
+ * @param logLevel is this level enabled?
+ * @return whether the given log level currently enabled.
+ */
+ protected boolean isLevelEnabled(final int logLevel) {
+ // log level are numerically ordered so can use simple numeric
+ // comparison
+ return logLevel >= currentLogLevel;
+ }
+
+ /**
+ * Are trace messages currently enabled?
+ *
+ * This allows expensive operations such as {@code String}
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ @Override
+ public final boolean isTraceEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE);
+ }
+
+ /**
+ * Are warn messages currently enabled?
+ *
+ * This allows expensive operations such as {@code String}
+ * concatenation to be avoided when the message will be ignored by the
+ * logger.
+ */
+ @Override
+ public final boolean isWarnEnabled() {
+ return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN);
+ }
+
+ /**
+ * Do the actual logging.
+ *
+ * This method assembles the message and then calls {@code 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(final int type, final Object message, final Throwable t) {
+ // Use a string buffer for better performance
+ final StringBuilder buf = new StringBuilder();
+
+ // Append date-time if so configured
+ if (showDateTime) {
+ final Date now = new Date();
+ String dateText;
+ synchronized(dateFormatter) {
+ dateText = dateFormatter.format(now);
+ }
+ buf.append(dateText);
+ buf.append(" ");
+ }
+
+ // Append a readable representation of the log level
+ switch(type) {
+ case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break;
+ case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break;
+ case SimpleLog.LOG_LEVEL_INFO: buf.append("[INFO] "); break;
+ case SimpleLog.LOG_LEVEL_WARN: buf.append("[WARN] "); break;
+ case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break;
+ case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break;
+ }
+
+ // Append the name of the log instance if so configured
+ if (showShortName) {
+ if (shortLogName == null) {
+ // Cut all but the last component of the name for both styles
+ final String slName = logName.substring(logName.lastIndexOf(".") + 1);
+ shortLogName = slName.substring(slName.lastIndexOf("/") + 1);
+ }
+ buf.append(String.valueOf(shortLogName)).append(" - ");
+ } else if (showLogName) {
+ buf.append(String.valueOf(logName)).append(" - ");
+ }
+
+ // Append the message
+ buf.append(String.valueOf(message));
+
+ // Append stack trace if not null
+ if (t != null) {
+ buf.append(" <");
+ buf.append(t.toString());
+ buf.append(">");
+
+ final StringWriter sw = new StringWriter(1024);
+ final PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ pw.close();
+ buf.append(sw.toString());
+ }
+
+ // Print to the appropriate destination
+ write(buf);
+ }
+
+ /**
+ * Set logging level.
+ *
+ * @param currentLogLevel new logging level
+ */
+ public void setLevel(final int currentLogLevel) {
+ this.currentLogLevel = currentLogLevel;
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#trace(Object)
+ */
+ @Override
+ public final void trace(final Object message) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
+ log(SimpleLog.LOG_LEVEL_TRACE, message, null);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#trace(Object, Throwable)
+ */
+ @Override
+ public final void trace(final Object message, final Throwable t) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) {
+ log(SimpleLog.LOG_LEVEL_TRACE, message, t);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
+ *
+ * @param message to log
+ * @see org.apache.commons.logging.Log#warn(Object)
+ */
+ @Override
+ public final void warn(final Object message) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
+ log(SimpleLog.LOG_LEVEL_WARN, message, null);
+ }
+ }
+
+ /**
+ * Logs a message with {@code org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN}.
+ *
+ * @param message to log
+ * @param t log this cause
+ * @see org.apache.commons.logging.Log#warn(Object, Throwable)
+ */
+ @Override
+ public final void warn(final Object message, final Throwable t) {
+ if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) {
+ log(SimpleLog.LOG_LEVEL_WARN, message, t);
+ }
+ }
+
+ /**
+ * Write the content of the message accumulated in the specified
+ * {@code StringBuffer} to the appropriate output destination. The
+ * default implementation writes to {@code System.err}.
+ *
+ * @param buffer A {@code StringBuffer} containing the accumulated
+ * text to be logged
+ */
+ private void write(final Object buffer) {
+ System.err.println(buffer.toString());
+ }
+
+ /**
+ * Write the content of the message accumulated in the specified
+ * {@code StringBuffer} to the appropriate output destination. The
+ * default implementation writes to {@code System.err}.
+ *
+ * @param buffer A {@code StringBuffer} containing the accumulated
+ * text to be logged
+ */
+ protected void write(final StringBuffer buffer) {
+ System.err.println(buffer.toString());
+ }
}
diff --git a/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java b/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java
index 751b8f5..e332954 100644
--- a/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java
+++ b/src/main/java/org/apache/commons/logging/impl/WeakHashtable.java
@@ -109,6 +109,144 @@ import java.util.Set;
*/
public final class WeakHashtable extends Hashtable {
+ /** Entry implementation */
+ private final static class Entry implements Map.Entry {
+
+ private final Object key;
+ private final Object value;
+
+ private Entry(final Object key, final Object value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ boolean result = false;
+ if (o instanceof Map.Entry) {
+ final 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;
+ }
+
+ @Override
+ public Object getKey() {
+ return key;
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return (getKey()==null ? 0 : getKey().hashCode()) ^
+ (getValue()==null ? 0 : getValue().hashCode());
+ }
+
+ @Override
+ public Object setValue(final Object value) {
+ throw new UnsupportedOperationException("Entry.setValue is not supported.");
+ }
+ }
+
+ /** Wrapper giving correct symantics for equals and hash code */
+ private final static class Referenced {
+
+ private final WeakReference reference;
+ private final int hashCode;
+
+ /**
+ *
+ * @throws NullPointerException if referant is {@code null}
+ */
+ private Referenced(final Object referant) {
+ reference = new WeakReference(referant);
+ // Calc a permanent hashCode so calls to Hashtable.remove()
+ // work if the WeakReference has been cleared
+ hashCode = referant.hashCode();
+ }
+
+ /**
+ *
+ * @throws NullPointerException if key is {@code null}
+ */
+ private Referenced(final Object key, final 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();
+
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ boolean result = false;
+ if (o instanceof Referenced) {
+ final Referenced otherKey = (Referenced) o;
+ final Object thisKeyValue = getValue();
+ final Object otherKeyValue = otherKey.getValue();
+ if (thisKeyValue == null) {
+ result = otherKeyValue == null;
+
+ // Since our hash code was calculated from the original
+ // non-null referant, the above check breaks the
+ // hash code/equals contract, as two cleared Referenced
+ // objects could test equal but have different hash codes.
+ // We can reduce (not eliminate) the chance of this
+ // happening by comparing hash codes.
+ result = result && this.hashCode() == otherKey.hashCode();
+ // In any case, as our c'tor does not allow null referants
+ // and Hashtable does not do equality checks between
+ // existing keys, normal hashtable operations should never
+ // result in an equals comparison between null referants
+ }
+ else
+ {
+ result = thisKeyValue.equals(otherKeyValue);
+ }
+ }
+ return result;
+ }
+
+ private Object getValue() {
+ return reference.get();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
+ /**
+ * WeakReference subclass that holds a hard reference to an
+ * associated {@code value} and also makes accessible
+ * the Referenced object holding it.
+ */
+ private final static class WeakKey extends WeakReference {
+
+ private final Referenced referenced;
+
+ private WeakKey(final Object key,
+ final ReferenceQueue queue,
+ final Referenced referenced) {
+ super(key, queue);
+ this.referenced = referenced;
+ }
+
+ private Referenced getReferenced() {
+ return referenced;
+ }
+ }
+
/** Serializable version identifier. */
private static final long serialVersionUID = -1546036869799732453L;
@@ -186,6 +324,15 @@ public final class WeakHashtable extends Hashtable {
return super.get(referenceKey);
}
+ /**
+ *@see Hashtable
+ */
+ @Override
+ public boolean isEmpty() {
+ purge();
+ return super.isEmpty();
+ }
+
/**
*@see Hashtable
*/
@@ -224,6 +371,41 @@ public final class WeakHashtable extends Hashtable {
return unreferencedKeys;
}
+ /**
+ * Purges all entries whose wrapped keys
+ * have been garbage collected.
+ */
+ private void purge() {
+ final List toRemove = new ArrayList();
+ synchronized (queue) {
+ WeakKey key;
+ while ((key = (WeakKey) queue.poll()) != null) {
+ toRemove.add(key.getReferenced());
+ }
+ }
+
+ // LOGGING-119: do the actual removal of the keys outside the sync block
+ // to prevent deadlock scenarios as purge() may be called from
+ // non-synchronized methods too
+ final int size = toRemove.size();
+ for (int i = 0; i < size; i++) {
+ super.remove(toRemove.get(i));
+ }
+ }
+
+ /**
+ * Purges one entry whose wrapped key
+ * has been garbage collected.
+ */
+ private void purgeOne() {
+ synchronized (queue) {
+ final WeakKey key = (WeakKey) queue.poll();
+ if (key != null) {
+ super.remove(key.getReferenced());
+ }
+ }
+ }
+
/**
*@see Hashtable
*/
@@ -263,12 +445,13 @@ public final class WeakHashtable extends Hashtable {
}
/**
- *@see Hashtable
+ * @see Hashtable
*/
@Override
- public Collection values() {
+ protected void rehash() {
+ // purge here to save the effort of rehashing dead entries
purge();
- return super.values();
+ super.rehash();
}
/**
@@ -289,15 +472,6 @@ public final class WeakHashtable extends Hashtable {
return super.remove(new Referenced(key));
}
- /**
- *@see Hashtable
- */
- @Override
- public boolean isEmpty() {
- purge();
- return super.isEmpty();
- }
-
/**
*@see Hashtable
*/
@@ -317,185 +491,11 @@ public final class WeakHashtable extends Hashtable {
}
/**
- * @see Hashtable
+ *@see Hashtable
*/
@Override
- protected void rehash() {
- // purge here to save the effort of rehashing dead entries
+ public Collection values() {
purge();
- super.rehash();
+ return super.values();
}
-
- /**
- * Purges all entries whose wrapped keys
- * have been garbage collected.
- */
- private void purge() {
- final List toRemove = new ArrayList();
- synchronized (queue) {
- WeakKey key;
- while ((key = (WeakKey) queue.poll()) != null) {
- toRemove.add(key.getReferenced());
- }
- }
-
- // LOGGING-119: do the actual removal of the keys outside the sync block
- // to prevent deadlock scenarios as purge() may be called from
- // non-synchronized methods too
- final int size = toRemove.size();
- for (int i = 0; i < size; i++) {
- super.remove(toRemove.get(i));
- }
- }
-
- /**
- * Purges one entry whose wrapped key
- * has been garbage collected.
- */
- private void purgeOne() {
- synchronized (queue) {
- final 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(final Object key, final Object value) {
- this.key = key;
- this.value = value;
- }
-
- @Override
- public boolean equals(final Object o) {
- boolean result = false;
- if (o instanceof Map.Entry) {
- final 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;
- }
-
- @Override
- public int hashCode() {
- return (getKey()==null ? 0 : getKey().hashCode()) ^
- (getValue()==null ? 0 : getValue().hashCode());
- }
-
- @Override
- public Object setValue(final Object value) {
- throw new UnsupportedOperationException("Entry.setValue is not supported.");
- }
-
- @Override
- public Object getValue() {
- return value;
- }
-
- @Override
- public Object getKey() {
- return key;
- }
- }
-
- /** Wrapper giving correct symantics for equals and hash code */
- private final static class Referenced {
-
- private final WeakReference reference;
- private final int hashCode;
-
- /**
- *
- * @throws NullPointerException if referant is {@code null}
- */
- private Referenced(final Object referant) {
- reference = new WeakReference(referant);
- // Calc a permanent hashCode so calls to Hashtable.remove()
- // work if the WeakReference has been cleared
- hashCode = referant.hashCode();
- }
-
- /**
- *
- * @throws NullPointerException if key is {@code null}
- */
- private Referenced(final Object key, final 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();
-
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- private Object getValue() {
- return reference.get();
- }
-
- @Override
- public boolean equals(final Object o) {
- boolean result = false;
- if (o instanceof Referenced) {
- final Referenced otherKey = (Referenced) o;
- final Object thisKeyValue = getValue();
- final Object otherKeyValue = otherKey.getValue();
- if (thisKeyValue == null) {
- result = otherKeyValue == null;
-
- // Since our hash code was calculated from the original
- // non-null referant, the above check breaks the
- // hash code/equals contract, as two cleared Referenced
- // objects could test equal but have different hash codes.
- // We can reduce (not eliminate) the chance of this
- // happening by comparing hash codes.
- result = result && this.hashCode() == otherKey.hashCode();
- // In any case, as our c'tor does not allow null referants
- // and Hashtable does not do equality checks between
- // existing keys, normal hashtable operations should never
- // result in an equals comparison between null referants
- }
- else
- {
- result = thisKeyValue.equals(otherKeyValue);
- }
- }
- return result;
- }
- }
-
- /**
- * WeakReference subclass that holds a hard reference to an
- * associated {@code value} and also makes accessible
- * the Referenced object holding it.
- */
- private final static class WeakKey extends WeakReference {
-
- private final Referenced referenced;
-
- private WeakKey(final Object key,
- final ReferenceQueue queue,
- final Referenced referenced) {
- super(key, queue);
- this.referenced = referenced;
- }
-
- private Referenced getReferenced() {
- return referenced;
- }
- }
}
diff --git a/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java b/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java
index 890de7f..28b515f 100644
--- a/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java
+++ b/src/test/java/org/apache/commons/logging/AltHashtableTestCase.java
@@ -60,21 +60,6 @@ public class AltHashtableTestCase extends TestCase {
AltHashtable.class.getName());
}
- /**
- * Verify that initialising the LogFactory class will cause it
- * to instantiate an object of type specified in system property
- * "org.apache.commons.logging.LogFactory.HashtableImpl".
- */
- public void testType() {
- // Here, the reference to the LogFactory class should cause the
- // class to be loaded and initialized. It will see the property
- // set and use the AltHashtable class. If other tests in this
- // class have already been run within the same classloader then
- // LogFactory will already have been initialized, but that
- // doesn't change the effectiveness of this test.
- assertTrue(LogFactory.factories instanceof AltHashtable);
- }
-
/**
* Verify that when LogFactory sees a context-classloader for the
* first time that it creates a new entry in the LogFactory.factories
@@ -91,4 +76,19 @@ public class AltHashtableTestCase extends TestCase {
assertEquals(contextLoader, AltHashtable.lastKey);
assertNotNull(AltHashtable.lastValue);
}
+
+ /**
+ * Verify that initialising the LogFactory class will cause it
+ * to instantiate an object of type specified in system property
+ * "org.apache.commons.logging.LogFactory.HashtableImpl".
+ */
+ public void testType() {
+ // Here, the reference to the LogFactory class should cause the
+ // class to be loaded and initialized. It will see the property
+ // set and use the AltHashtable class. If other tests in this
+ // class have already been run within the same classloader then
+ // LogFactory will already have been initialized, but that
+ // doesn't change the effectiveness of this test.
+ assertTrue(LogFactory.factories instanceof AltHashtable);
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java b/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java
index 967e426..d4b5e40 100644
--- a/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java
+++ b/src/test/java/org/apache/commons/logging/BadHashtablePropertyTestCase.java
@@ -25,11 +25,11 @@ import java.util.Hashtable;
*/
public class BadHashtablePropertyTestCase extends TestCase {
- public void testType() {
- assertTrue(LogFactory.factories instanceof Hashtable);
- }
-
public void testPutCalled() throws Exception {
LogFactory.getLog(BadHashtablePropertyTestCase.class);
}
+
+ public void testType() {
+ assertTrue(LogFactory.factories instanceof Hashtable);
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java b/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java
index 95d7973..838073d 100644
--- a/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java
+++ b/src/test/java/org/apache/commons/logging/BasicOperationsTestCase.java
@@ -27,18 +27,6 @@ import junit.framework.TestCase;
*/
public class BasicOperationsTestCase extends TestCase
{
- public void testIsEnabledClassLog()
- {
- final Log log = LogFactory.getLog(BasicOperationsTestCase.class);
- executeIsEnabledTest(log);
- }
-
- public void testIsEnabledNamedLog()
- {
- final Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName());
- executeIsEnabledTest(log);
- }
-
public void executeIsEnabledTest(final Log log)
{
try
@@ -57,16 +45,22 @@ public class BasicOperationsTestCase extends TestCase
}
}
- public void testMessageWithoutExceptionClassLog()
+ public void executeMessageWithExceptionTest(final Log log)
{
- final Log log = LogFactory.getLog(BasicOperationsTestCase.class);
- executeMessageWithoutExceptionTest(log);
- }
-
- public void testMessageWithoutExceptionNamedLog()
- {
- final Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName());
- executeMessageWithoutExceptionTest(log);
+ try
+ {
+ log.trace("Hello, Mum", new ArithmeticException());
+ log.debug("Hello, Mum", new ArithmeticException());
+ log.info("Hello, Mum", new ArithmeticException());
+ log.warn("Hello, Mum", new ArithmeticException());
+ log.error("Hello, Mum", new ArithmeticException());
+ log.fatal("Hello, Mum", new ArithmeticException());
+ }
+ catch (final Throwable t)
+ {
+ t.printStackTrace();
+ fail("Exception thrown: " + t);
+ }
}
public void executeMessageWithoutExceptionTest(final Log log)
@@ -87,6 +81,18 @@ public class BasicOperationsTestCase extends TestCase
}
}
+ public void testIsEnabledClassLog()
+ {
+ final Log log = LogFactory.getLog(BasicOperationsTestCase.class);
+ executeIsEnabledTest(log);
+ }
+
+ public void testIsEnabledNamedLog()
+ {
+ final Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName());
+ executeIsEnabledTest(log);
+ }
+
public void testMessageWithExceptionClassLog()
{
final Log log = LogFactory.getLog(BasicOperationsTestCase.class);
@@ -99,21 +105,15 @@ public class BasicOperationsTestCase extends TestCase
executeMessageWithExceptionTest(log);
}
- public void executeMessageWithExceptionTest(final Log log)
+ public void testMessageWithoutExceptionClassLog()
{
- try
- {
- log.trace("Hello, Mum", new ArithmeticException());
- log.debug("Hello, Mum", new ArithmeticException());
- log.info("Hello, Mum", new ArithmeticException());
- log.warn("Hello, Mum", new ArithmeticException());
- log.error("Hello, Mum", new ArithmeticException());
- log.fatal("Hello, Mum", new ArithmeticException());
- }
- catch (final Throwable t)
- {
- t.printStackTrace();
- fail("Exception thrown: " + t);
- }
+ final Log log = LogFactory.getLog(BasicOperationsTestCase.class);
+ executeMessageWithoutExceptionTest(log);
+ }
+
+ public void testMessageWithoutExceptionNamedLog()
+ {
+ final Log log = LogFactory.getLog(BasicOperationsTestCase.class.getName());
+ executeMessageWithoutExceptionTest(log);
}
}
diff --git a/src/test/java/org/apache/commons/logging/GarbageCollectionHelper.java b/src/test/java/org/apache/commons/logging/GarbageCollectionHelper.java
index 75d449d..0748b06 100644
--- a/src/test/java/org/apache/commons/logging/GarbageCollectionHelper.java
+++ b/src/test/java/org/apache/commons/logging/GarbageCollectionHelper.java
@@ -27,23 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
// after: https://github.com/apache/logging-log4j2/blob/c47e98423b461731f7791fcb9ea1079cd451f365/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
public final class GarbageCollectionHelper implements Closeable, Runnable {
- private static final OutputStream SINK = new OutputStream() {
- @Override
- public void write(int b) {
- }
-
- @Override
- public void write(byte[] b) {
- }
-
- @Override
- public void write(byte[] b, int off, int len) {
- }
- };
- private final AtomicBoolean running = new AtomicBoolean();
- private final CountDownLatch latch = new CountDownLatch(1);
- private final Thread gcThread = new Thread(new GcTask());
-
class GcTask implements Runnable {
@Override
public void run() {
@@ -64,13 +47,23 @@ public final class GarbageCollectionHelper implements Closeable, Runnable {
}
}
}
-
- @Override
- public void run() {
- if (running.compareAndSet(false, true)) {
- gcThread.start();
+ private static final OutputStream SINK = new OutputStream() {
+ @Override
+ public void write(byte[] b) {
}
- }
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ }
+
+ @Override
+ public void write(int b) {
+ }
+ };
+ private final AtomicBoolean running = new AtomicBoolean();
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ private final Thread gcThread = new Thread(new GcTask());
@Override
public void close() {
@@ -82,5 +75,12 @@ public final class GarbageCollectionHelper implements Closeable, Runnable {
throw new RuntimeException(e);
}
}
+
+ @Override
+ public void run() {
+ if (running.compareAndSet(false, true)) {
+ gcThread.start();
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/LoadTestCase.java b/src/test/java/org/apache/commons/logging/LoadTestCase.java
index 874a7cc..2257324 100644
--- a/src/test/java/org/apache/commons/logging/LoadTestCase.java
+++ b/src/test/java/org/apache/commons/logging/LoadTestCase.java
@@ -22,10 +22,6 @@ import junit.framework.TestCase;
* test to emulate container and application isolated from container
*/
public class LoadTestCase extends TestCase{
- //TODO: need some way to add service provider packages
- static private String[] LOG_PCKG = {"org.apache.commons.logging",
- "org.apache.commons.logging.impl"};
-
/**
* A custom classloader which "duplicates" logging classes available
* in the parent classloader into itself.
@@ -92,6 +88,41 @@ public class LoadTestCase extends TestCase{
}
+ //TODO: need some way to add service provider packages
+ static private String[] LOG_PCKG = {"org.apache.commons.logging",
+ "org.apache.commons.logging.impl"};
+
+
+ private ClassLoader origContextClassLoader;
+
+ private void execute(final Class cls) throws Exception {
+ cls.getConstructor().newInstance();
+ }
+
+ /**
+ * Load class UserClass via a temporary classloader which is a child of
+ * the classloader used to load this test class.
+ */
+ private Class reload() throws Exception {
+ Class testObjCls = null;
+ final AppClassLoader appLoader = new AppClassLoader(this.getClass().getClassLoader());
+ try {
+
+ testObjCls = appLoader.loadClass(UserClass.class.getName());
+
+ } catch (final ClassNotFoundException cnfe) {
+ throw cnfe;
+ } catch (final Throwable t) {
+ t.printStackTrace();
+ fail("AppClassLoader failed ");
+ }
+
+ assertSame("app isolated", testObjCls.getClassLoader(), appLoader);
+
+ return testObjCls;
+
+ }
+
/**
* Call the static setAllowFlawedContext method on the specified class
@@ -104,6 +135,18 @@ public class LoadTestCase extends TestCase{
m.invoke(null, state);
}
+ @Override
+ public void setUp() {
+ // save state before test starts so we can restore it when test ends
+ origContextClassLoader = Thread.currentThread().getContextClassLoader();
+ }
+
+ @Override
+ public void tearDown() {
+ // restore original state so a test can't stuff up later tests.
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+ }
+
/**
* Test what happens when we play various classloader tricks like those
* that happen in web and j2ee containers.
@@ -170,47 +213,4 @@ public class LoadTestCase extends TestCase{
// expected
}
}
-
- /**
- * Load class UserClass via a temporary classloader which is a child of
- * the classloader used to load this test class.
- */
- private Class reload() throws Exception {
- Class testObjCls = null;
- final AppClassLoader appLoader = new AppClassLoader(this.getClass().getClassLoader());
- try {
-
- testObjCls = appLoader.loadClass(UserClass.class.getName());
-
- } catch (final ClassNotFoundException cnfe) {
- throw cnfe;
- } catch (final Throwable t) {
- t.printStackTrace();
- fail("AppClassLoader failed ");
- }
-
- assertSame("app isolated", testObjCls.getClassLoader(), appLoader);
-
- return testObjCls;
-
- }
-
-
- private void execute(final Class cls) throws Exception {
- cls.getConstructor().newInstance();
- }
-
- @Override
- public void setUp() {
- // save state before test starts so we can restore it when test ends
- origContextClassLoader = Thread.currentThread().getContextClassLoader();
- }
-
- @Override
- public void tearDown() {
- // restore original state so a test can't stuff up later tests.
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
- }
-
- private ClassLoader origContextClassLoader;
}
diff --git a/src/test/java/org/apache/commons/logging/PathableClassLoader.java b/src/test/java/org/apache/commons/logging/PathableClassLoader.java
index 136d8e8..2b36d22 100644
--- a/src/test/java/org/apache/commons/logging/PathableClassLoader.java
+++ b/src/test/java/org/apache/commons/logging/PathableClassLoader.java
@@ -96,94 +96,6 @@ public class PathableClassLoader extends URLClassLoader {
super(NO_URLS, parent);
}
- /**
- * Allow caller to explicitly add paths. Generally this not a good idea;
- * use addLogicalLib instead, then define the location for that logical
- * library in the build.xml file.
- */
- @Override
- public void addURL(final URL url) {
- super.addURL(url);
- }
-
- /**
- * Specify whether this classloader should ask the parent classloader
- * to resolve a class first, before trying to resolve it via its own
- * classpath.
- *
- * Checking with the parent first is the normal approach for java, but
- * components within containers such as servlet engines can use
- * child-first lookup instead, to allow the components to override libs
- * which are visible in shared classloaders provided by the container.
- *
- * Note that the method getResources always behaves as if parentFirst=true,
- * because of limitations in java 1.4; see the javadoc for method
- * getResourcesInOrder for details.
- *
- * This value defaults to true.
- */
- public void setParentFirst(final boolean state) {
- parentFirst = state;
- }
-
- /**
- * For classes with the specified prefix, get them from the system
- * classpath which is active at the point this method is called.
- *
- * This method is just a shortcut for
- *
- * Of course, this assumes that the classes of interest are already
- * in the classpath of the system classloader.
- */
- public void useSystemLoader(final String prefix) {
- useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
-
- }
-
- /**
- * Specify a classloader to use for specific java packages.
- *
- * The specified classloader is normally a loader that is NOT
- * an ancestor of this classloader. In particular, this loader
- * may have the bootloader as its parent, but be configured to
- * see specific other classes (eg the junit library loaded
- * via the system classloader).
- *
- * The differences between using this method, and using
- * addLogicalLib are:
- *
+ * Note that it's not possible to override the inherited getResources, as
+ * it's declared final in java1.4 (thought that's been removed for 1.5).
+ * The inherited implementation always behaves as if parentFirst=true.
+ */
+ public Enumeration getResourcesInOrder(final String name) throws IOException {
+ if (parentFirst) {
+ return super.getResources(name);
+ }
+ final Enumeration localUrls = super.findResources(name);
+
+ final ClassLoader parent = getParent();
+ if (parent == null) {
+ // Alas, there is no method to get matching resources
+ // from a null (BOOT) parent classloader. Calling
+ // ClassLoader.getSystemClassLoader isn't right. Maybe
+ // calling Class.class.getResources(name) would do?
+ //
+ // However for the purposes of unit tests, we can
+ // simply assume that no relevant resources are
+ // loadable from the parent; unit tests will never be
+ // putting any of their resources in a "boot" classloader
+ // path!
+ return localUrls;
+ }
+ final Enumeration parentUrls = parent.getResources(name);
+
+ final ArrayList localItems = toList(localUrls);
+ final ArrayList parentItems = toList(parentUrls);
+ localItems.addAll(parentItems);
+ return Collections.enumeration(localItems);
+ }
+
/**
* If the classloader that loaded this class has this logical lib in its
* path, then return the matching URL otherwise return null.
@@ -328,56 +335,23 @@ public class PathableClassLoader extends URLClassLoader {
}
/**
- * Same as parent class method except that when parentFirst is false
- * the resource is looked for in the local classpath before the parent
- * loader is consulted.
- */
- @Override
- public URL getResource(final String name) {
- if (parentFirst) {
- return super.getResource(name);
- }
- final URL local = super.findResource(name);
- if (local != null) {
- return local;
- }
- return super.getResource(name);
- }
-
- /**
- * Emulate a proper implementation of getResources which respects the
- * setting for parentFirst.
+ * Specify whether this classloader should ask the parent classloader
+ * to resolve a class first, before trying to resolve it via its own
+ * classpath.
*
- * Note that it's not possible to override the inherited getResources, as
- * it's declared final in java1.4 (thought that's been removed for 1.5).
- * The inherited implementation always behaves as if parentFirst=true.
+ * Checking with the parent first is the normal approach for java, but
+ * components within containers such as servlet engines can use
+ * child-first lookup instead, to allow the components to override libs
+ * which are visible in shared classloaders provided by the container.
+ *
+ * Note that the method getResources always behaves as if parentFirst=true,
+ * because of limitations in java 1.4; see the javadoc for method
+ * getResourcesInOrder for details.
+ *
+ * This value defaults to true.
*/
- public Enumeration getResourcesInOrder(final String name) throws IOException {
- if (parentFirst) {
- return super.getResources(name);
- }
- final Enumeration localUrls = super.findResources(name);
-
- final ClassLoader parent = getParent();
- if (parent == null) {
- // Alas, there is no method to get matching resources
- // from a null (BOOT) parent classloader. Calling
- // ClassLoader.getSystemClassLoader isn't right. Maybe
- // calling Class.class.getResources(name) would do?
- //
- // However for the purposes of unit tests, we can
- // simply assume that no relevant resources are
- // loadable from the parent; unit tests will never be
- // putting any of their resources in a "boot" classloader
- // path!
- return localUrls;
- }
- final Enumeration parentUrls = parent.getResources(name);
-
- final ArrayList localItems = toList(localUrls);
- final ArrayList parentItems = toList(parentUrls);
- localItems.addAll(parentItems);
- return Collections.enumeration(localItems);
+ public void setParentFirst(final boolean state) {
+ parentFirst = state;
}
/**
@@ -400,25 +374,51 @@ public class PathableClassLoader extends URLClassLoader {
}
/**
- * Same as parent class method except that when parentFirst is false
- * the resource is looked for in the local classpath before the parent
- * loader is consulted.
+ * Specify a classloader to use for specific java packages.
+ *
+ * The specified classloader is normally a loader that is NOT
+ * an ancestor of this classloader. In particular, this loader
+ * may have the bootloader as its parent, but be configured to
+ * see specific other classes (eg the junit library loaded
+ * via the system classloader).
+ *
+ * The differences between using this method, and using
+ * addLogicalLib are:
+ *
+ * This method is just a shortcut for
+ *
+ * Of course, this assumes that the classes of interest are already
+ * in the classpath of the system classloader.
+ */
+ public void useSystemLoader(final String prefix) {
+ useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
+
}
}
diff --git a/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java b/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java
index 03fb52b..88b18ec 100644
--- a/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java
+++ b/src/test/java/org/apache/commons/logging/impl/WeakHashtableTestCase.java
@@ -31,22 +31,39 @@ import junit.framework.TestCase;
public class WeakHashtableTestCase extends TestCase {
+ public static class StupidThread extends Thread {
+
+ public StupidThread(final String name) {
+ super(name);
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < RUN_LOOPS; i++) {
+ hashtable.put("key" + ":" + i % 10, Boolean.TRUE);
+ if (i % 50 == 0) {
+ yield();
+ }
+ }
+ }
+ }
private static final int WAIT_FOR_THREAD_COMPLETION = 5000; // 5 seconds
private static final int RUN_LOOPS = 3000;
private static final int OUTER_LOOP = 400;
+
private static final int THREAD_COUNT = 10;
private static WeakHashtable hashtable;
/** Maximum number of iterations before our test fails */
private static final int MAX_GC_ITERATIONS = 50;
-
private WeakHashtable weakHashtable;
private Long keyOne;
private Long keyTwo;
private Long keyThree;
private Long valueOne;
private Long valueTwo;
+
private Long valueThree;
public WeakHashtableTestCase(final String testName) {
@@ -162,6 +179,33 @@ public class WeakHashtableTestCase extends TestCase {
assertTrue(keySet.contains(keyThree));
}
+ public void testLOGGING_119() throws Exception {
+ final Thread [] t = new Thread[THREAD_COUNT];
+ for (int j=1; j <= OUTER_LOOP; j++) {
+ hashtable = new WeakHashtable();
+ for (int i = 0; i < t.length; i++) {
+ t[i] = new StupidThread("Thread:" + i);
+ t[i].setDaemon(true); // Otherwise we cannot exit
+ t[i].start();
+ }
+ for (final Thread element : t) {
+ element.join(WAIT_FOR_THREAD_COMPLETION);
+ if (element.isAlive()) {
+ break; // at least one thread is stuck
+ }
+ }
+ int active=0;
+ for (final Thread element : t) {
+ if (element.isAlive()) {
+ active++;
+ }
+ }
+ if (active > 0) {
+ fail("Attempt: " + j + " Stuck threads: " + active);
+ }
+ }
+ }
+
/** Tests public Object put(Object key, Object value) */
public void testPut() throws Exception {
final Long anotherKey = new Long(2004);
@@ -266,48 +310,4 @@ public class WeakHashtableTestCase extends TestCase {
// Test that the released objects are not taking space in the table
assertEquals("underlying table not emptied", 0, weakHashtable.size());
}
-
- public static class StupidThread extends Thread {
-
- public StupidThread(final String name) {
- super(name);
- }
-
- @Override
- public void run() {
- for (int i = 0; i < RUN_LOOPS; i++) {
- hashtable.put("key" + ":" + i % 10, Boolean.TRUE);
- if (i % 50 == 0) {
- yield();
- }
- }
- }
- }
-
- public void testLOGGING_119() throws Exception {
- final Thread [] t = new Thread[THREAD_COUNT];
- for (int j=1; j <= OUTER_LOOP; j++) {
- hashtable = new WeakHashtable();
- for (int i = 0; i < t.length; i++) {
- t[i] = new StupidThread("Thread:" + i);
- t[i].setDaemon(true); // Otherwise we cannot exit
- t[i].start();
- }
- for (final Thread element : t) {
- element.join(WAIT_FOR_THREAD_COMPLETION);
- if (element.isAlive()) {
- break; // at least one thread is stuck
- }
- }
- int active=0;
- for (final Thread element : t) {
- if (element.isAlive()) {
- active++;
- }
- }
- if (active > 0) {
- fail("Attempt: " + j + " Stuck threads: " + active);
- }
- }
- }
}
diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java
index 5fe968a..15793f6 100644
--- a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java
+++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigAPITestCase.java
@@ -30,10 +30,6 @@ import org.apache.commons.logging.PathableTestSuite;
public class CustomConfigAPITestCase extends CustomConfigTestCase {
- public CustomConfigAPITestCase(final String name) {
- super(name);
- }
-
/**
* Return the tests included in this test suite.
*/
@@ -58,4 +54,8 @@ public class CustomConfigAPITestCase extends CustomConfigTestCase {
final Class testClass = child.loadClass(CustomConfigAPITestCase.class.getName());
return new PathableTestSuite(testClass, child);
}
+
+ public CustomConfigAPITestCase(final String name) {
+ super(name);
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java
index 710fa00..f15d26b 100644
--- a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java
+++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigFullTestCase.java
@@ -32,11 +32,6 @@ import org.apache.commons.logging.PathableClassLoader;
public class CustomConfigFullTestCase extends CustomConfigTestCase {
- public CustomConfigFullTestCase(final String name) {
- super(name);
- }
-
-
/**
* Return the tests included in this test suite.
*/
@@ -60,4 +55,9 @@ public class CustomConfigFullTestCase extends CustomConfigTestCase {
final Class testClass = child.loadClass(CustomConfigFullTestCase.class.getName());
return new PathableTestSuite(testClass, child);
}
+
+
+ public CustomConfigFullTestCase(final String name) {
+ super(name);
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java
index 0c6abbb..5f7cf83 100644
--- a/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java
+++ b/src/test/java/org/apache/commons/logging/jdk14/CustomConfigTestCase.java
@@ -48,82 +48,6 @@ public class CustomConfigTestCase extends DefaultConfigTestCase {
// ----------------------------------------------------------- Constructors
- /**
- * Construct a new instance of this test case. The customized {@code Handler} we will be using. The underlying {@code Handler}s we will be using. The underlying {@code Logger} we will be using. The underlying {@code LogManager} we will be using. The message levels that should have been logged. The message strings that should have been logged. The customized {@code Handler} we will be using. The underlying {@code Handler}s we will be using. The underlying {@code Logger} we will be using. The underlying {@code LogManager} we will be using. The message levels that should have been logged. The message strings that should have been logged. Construct a new instance of this test case. Construct a new instance of this test case. The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected. The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected. Construct a new instance of this test case.
- * This method also sets the logging level to INFO so that we
- * can test whether messages are getting properly filtered.
- */
- public abstract void setUpTestAppender(List logEvents) throws Exception;
-
- /**
- * Test that a LogFactory gets created as expected.
- */
- public void testCreateFactory() {
- final LogFactory factory = LogFactory.getFactory();
- assertNotNull("LogFactory exists", factory);
- assertEquals("LogFactory class",
- "org.apache.commons.logging.impl.LogFactoryImpl",
- factory.getClass().getName());
-
- final String[] names = factory.getAttributeNames();
- assertNotNull("Names exists", names);
- assertEquals("Names empty", 0, names.length);
- }
-
- /**
- * Verify that we can log messages without exceptions.
- */
- public void testPlainMessages() throws Exception {
- final List logEvents = new ArrayList();
- setUpTestAppender(logEvents);
- final Log log = LogFactory.getLog("test-category");
- logPlainMessages(log);
- checkLoggingEvents(logEvents, false);
- }
-
- /**
- * Verify that we can log exception messages.
- */
- public void testExceptionMessages() throws Exception {
- final List logEvents = new ArrayList();
- setUpTestAppender(logEvents);
- final Log log = LogFactory.getLog("test-category");
- logExceptionMessages(log);
- checkLoggingEvents(logEvents, true);
- }
-
- /**
- * Test Serializability of Log instance
- */
- public void testSerializable() throws Exception {
- final List logEvents = new ArrayList();
- setUpTestAppender(logEvents);
- final Log log = LogFactory.getLog("test-category");
-
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- final ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(log);
- oos.close();
- final ByteArrayInputStream bais =
- new ByteArrayInputStream(baos.toByteArray());
- final ObjectInputStream ois = new ObjectInputStream(bais);
- final Log newLog = (Log) ois.readObject();
- ois.close();
-
- // Check the characteristics of the resulting object
- logExceptionMessages(newLog);
- checkLoggingEvents(logEvents, true);
- }
-
/**
* Verify that the TestAppender has received the expected
* number of messages. This assumes that:
@@ -178,6 +93,18 @@ public abstract class StandardTests extends TestCase {
assertEquals("Exception data incorrect", ev.throwable!=null, thrown);
}
+ /**
+ * Log messages with exceptions
+ */
+ private void logExceptionMessages(final Log log) {
+ final Throwable t = new DummyException();
+ log.trace("trace", t); // Should not actually get logged
+ log.debug("debug", t); // Should not actually get logged
+ log.info("info", t);
+ log.warn("warn", t);
+ log.error("error", t);
+ log.fatal("fatal", t);
+ }
/**
* Log plain messages.
@@ -192,15 +119,88 @@ public abstract class StandardTests extends TestCase {
}
/**
- * Log messages with exceptions
+ * Set up instance variables required by this test case.
*/
- private void logExceptionMessages(final Log log) {
- final Throwable t = new DummyException();
- log.trace("trace", t); // Should not actually get logged
- log.debug("debug", t); // Should not actually get logged
- log.info("info", t);
- log.warn("warn", t);
- log.error("error", t);
- log.fatal("fatal", t);
+ @Override
+ public void setUp() throws Exception {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Modify log4j's setup so that all messages actually logged get redirected
+ * into the specified list.
+ *
+ * This method also sets the logging level to INFO so that we
+ * can test whether messages are getting properly filtered.
+ */
+ public abstract void setUpTestAppender(List logEvents) throws Exception;
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ @Override
+ public void tearDown() {
+ LogFactory.releaseAll();
+ }
+
+ /**
+ * Test that a LogFactory gets created as expected.
+ */
+ public void testCreateFactory() {
+ final LogFactory factory = LogFactory.getFactory();
+ assertNotNull("LogFactory exists", factory);
+ assertEquals("LogFactory class",
+ "org.apache.commons.logging.impl.LogFactoryImpl",
+ factory.getClass().getName());
+
+ final String[] names = factory.getAttributeNames();
+ assertNotNull("Names exists", names);
+ assertEquals("Names empty", 0, names.length);
+ }
+
+ /**
+ * Verify that we can log exception messages.
+ */
+ public void testExceptionMessages() throws Exception {
+ final List logEvents = new ArrayList();
+ setUpTestAppender(logEvents);
+ final Log log = LogFactory.getLog("test-category");
+ logExceptionMessages(log);
+ checkLoggingEvents(logEvents, true);
+ }
+
+
+ /**
+ * Verify that we can log messages without exceptions.
+ */
+ public void testPlainMessages() throws Exception {
+ final List logEvents = new ArrayList();
+ setUpTestAppender(logEvents);
+ final Log log = LogFactory.getLog("test-category");
+ logPlainMessages(log);
+ checkLoggingEvents(logEvents, false);
+ }
+
+ /**
+ * Test Serializability of Log instance
+ */
+ public void testSerializable() throws Exception {
+ final List logEvents = new ArrayList();
+ setUpTestAppender(logEvents);
+ final Log log = LogFactory.getLog("test-category");
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(log);
+ oos.close();
+ final ByteArrayInputStream bais =
+ new ByteArrayInputStream(baos.toByteArray());
+ final ObjectInputStream ois = new ObjectInputStream(bais);
+ final Log newLog = (Log) ois.readObject();
+ ois.close();
+
+ // Check the characteristics of the resulting object
+ logExceptionMessages(newLog);
+ checkLoggingEvents(logEvents, true);
}
}
diff --git a/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java b/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java
index 6214176..b5a42a0 100644
--- a/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java
+++ b/src/test/java/org/apache/commons/logging/log4j/log4j12/TestAppender.java
@@ -33,6 +33,12 @@ import org.apache.log4j.spi.LoggingEvent;
public class TestAppender extends AppenderSkeleton {
+ // The set of logged events for this appender
+ private final List events;
+
+ // ----------------------------------------------------- Instance Variables
+
+
/**
* Constructor.
*/
@@ -40,12 +46,6 @@ public class TestAppender extends AppenderSkeleton {
events = logEvents;
}
- // ----------------------------------------------------- Instance Variables
-
-
- // The set of logged events for this appender
- private final List events;
-
// ------------------------------------------------------- Appender Methods
diff --git a/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java b/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java
index b999b4c..9abc18c 100644
--- a/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java
+++ b/src/test/java/org/apache/commons/logging/logkit/StandardTestCase.java
@@ -42,21 +42,6 @@ public class StandardTestCase extends AbstractLogTest {
// ----------------------------------------------------- Instance Variables
- /**
- * The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected. The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected.
- * This method works in conjunction with testResetProps2. There is no
- * way of knowing which test method junit will run first, but it doesn't
- * matter; whichever one of them runs first will modify the system properties.
- * If the PathableTestSuite isn't resetting the system properties then whichever
- * of them runs second will fail. Of course if other methods are run in-between
- * then those methods might also fail...
- */
- public void testResetProps1() {
- checkAndSetProperties();
- }
-
- /**
- * See testResetProps1.
- */
- public void testResetProps2() {
- checkAndSetProperties();
- }
-
- /**
- * Verify that the context classloader is a custom one, then reset it to
- * a non-custom one.
- */
- private static void checkAndSetContext() {
- final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
- assertEquals("ContextLoader is of unexpected type",
- contextLoader.getClass().getName(),
- PathableClassLoader.class.getName());
-
- final URL[] noUrls = new URL[0];
- Thread.currentThread().setContextClassLoader(new URLClassLoader(noUrls));
- }
-
/**
* Verify that when a test method modifies the context classloader it is
* reset before the next test is run.
@@ -117,4 +95,26 @@ public class GeneralTestCase extends TestCase {
public void testResetContext2() {
checkAndSetContext();
}
+
+ /**
+ * Verify that when a test method modifies the system properties they are
+ * reset before the next test is run.
+ *
+ * This method works in conjunction with testResetProps2. There is no
+ * way of knowing which test method junit will run first, but it doesn't
+ * matter; whichever one of them runs first will modify the system properties.
+ * If the PathableTestSuite isn't resetting the system properties then whichever
+ * of them runs second will fail. Of course if other methods are run in-between
+ * then those methods might also fail...
+ */
+ public void testResetProps1() {
+ checkAndSetProperties();
+ }
+
+ /**
+ * See testResetProps1.
+ */
+ public void testResetProps2() {
+ checkAndSetProperties();
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java b/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java
index a2b13b7..8a4c937 100644
--- a/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java
+++ b/src/test/java/org/apache/commons/logging/pathable/ParentFirstTestCase.java
@@ -89,6 +89,19 @@ public class ParentFirstTestCase extends TestCase {
return new PathableTestSuite(testClass, context);
}
+ /**
+ * Utility method to convert an enumeration-of-URLs into an array of URLs.
+ */
+ private static URL[] toURLArray(final Enumeration e) {
+ final ArrayList l = new ArrayList();
+ while (e.hasMoreElements()) {
+ final URL u = (URL) e.nextElement();
+ l.add(u);
+ }
+ final URL[] tmp = new URL[l.size()];
+ return (URL[]) l.toArray(tmp);
+ }
+
/**
* Utility method to return the set of all classloaders in the
* parent chain starting from the one that loaded the class for
@@ -218,6 +231,34 @@ public class ParentFirstTestCase extends TestCase {
resource.toString().indexOf("/commons-logging-1.") > 0);
}
+ /**
+ * Test that getResourceAsStream works.
+ */
+ public void testResourceAsStream() throws Exception {
+ java.io.InputStream is;
+
+ // verify the classloader hierarchy
+ final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ final ClassLoader childLoader = contextLoader.getParent();
+ final ClassLoader parentLoader = childLoader.getParent();
+ final ClassLoader bootLoader = parentLoader.getParent();
+ assertNull("Unexpected classloader hierarchy", bootLoader);
+
+ // getResourceAsStream where no instances exist
+ is = childLoader.getResourceAsStream("nosuchfile");
+ assertNull("Invalid resource returned non-null stream", is);
+
+ // getResourceAsStream where resource does exist
+ is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
+ assertNotNull("Null returned for valid resource", is);
+ is.close();
+
+ // It would be nice to test parent-first ordering here, but that would require
+ // having a resource with the same name in both the parent and child loaders,
+ // but with different contents. That's a little tricky to set up so we'll
+ // skip that for now.
+ }
+
/**
* Test that the various flavours of ClassLoader.getResources work as expected.
*/
@@ -265,45 +306,4 @@ public class ParentFirstTestCase extends TestCase {
urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
}
-
- /**
- * Utility method to convert an enumeration-of-URLs into an array of URLs.
- */
- private static URL[] toURLArray(final Enumeration e) {
- final ArrayList l = new ArrayList();
- while (e.hasMoreElements()) {
- final URL u = (URL) e.nextElement();
- l.add(u);
- }
- final URL[] tmp = new URL[l.size()];
- return (URL[]) l.toArray(tmp);
- }
-
- /**
- * Test that getResourceAsStream works.
- */
- public void testResourceAsStream() throws Exception {
- java.io.InputStream is;
-
- // verify the classloader hierarchy
- final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
- final ClassLoader childLoader = contextLoader.getParent();
- final ClassLoader parentLoader = childLoader.getParent();
- final ClassLoader bootLoader = parentLoader.getParent();
- assertNull("Unexpected classloader hierarchy", bootLoader);
-
- // getResourceAsStream where no instances exist
- is = childLoader.getResourceAsStream("nosuchfile");
- assertNull("Invalid resource returned non-null stream", is);
-
- // getResourceAsStream where resource does exist
- is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
- assertNotNull("Null returned for valid resource", is);
- is.close();
-
- // It would be nice to test parent-first ordering here, but that would require
- // having a resource with the same name in both the parent and child loaders,
- // but with different contents. That's a little tricky to set up so we'll
- // skip that for now.
- }
}
diff --git a/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java b/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java
index 0621603..b68b08a 100644
--- a/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java
+++ b/src/test/java/org/apache/commons/logging/security/MockSecurityManager.java
@@ -28,9 +28,9 @@ import java.security.Permissions;
*/
public class MockSecurityManager extends SecurityManager {
- private final Permissions permissions = new Permissions();
private static final Permission setSecurityManagerPerm =
new RuntimePermission("setSecurityManager");
+ private final Permissions permissions = new Permissions();
private int untrustedCodeCount;
@@ -46,18 +46,6 @@ public class MockSecurityManager extends SecurityManager {
permissions.add(p);
}
- /**
- * This returns the number of times that a check of a permission failed
- * due to stack-walking tracing up into untrusted code. Any non-zero
- * value indicates a bug in JCL, ie a situation where code was not
- * correctly wrapped in an AccessController block. The result of such a
- * bug is that signing JCL is not sufficient to allow JCL to perform
- * the operation; the caller would need to be signed too.
- */
- public int getUntrustedCodeCount() {
- return untrustedCodeCount;
- }
-
@Override
public void checkPermission(final Permission p) throws SecurityException {
if (setSecurityManagerPerm.implies(p)) {
@@ -147,4 +135,16 @@ public class MockSecurityManager extends SecurityManager {
}
}
}
+
+ /**
+ * This returns the number of times that a check of a permission failed
+ * due to stack-walking tracing up into untrusted code. Any non-zero
+ * value indicates a bug in JCL, ie a situation where code was not
+ * correctly wrapped in an AccessController block. The result of such a
+ * bug is that signing JCL is not sufficient to allow JCL to perform
+ * the operation; the caller would need to be signed too.
+ */
+ public int getUntrustedCodeCount() {
+ return untrustedCodeCount;
+ }
}
diff --git a/src/test/java/org/apache/commons/logging/security/SecurityAllowedTestCase.java b/src/test/java/org/apache/commons/logging/security/SecurityAllowedTestCase.java
index 4cdd442..7b156a6 100644
--- a/src/test/java/org/apache/commons/logging/security/SecurityAllowedTestCase.java
+++ b/src/test/java/org/apache/commons/logging/security/SecurityAllowedTestCase.java
@@ -43,8 +43,6 @@ import org.apache.commons.logging.PathableTestSuite;
*/
public class SecurityAllowedTestCase extends TestCase
{
- private SecurityManager oldSecMgr;
-
// Dummy special hashtable, so we can tell JCL to use this instead of
// the standard one.
public static class CustomHashtable extends Hashtable {
@@ -69,6 +67,8 @@ public class SecurityAllowedTestCase extends TestCase
return new PathableTestSuite(testClass, parent);
}
+ private SecurityManager oldSecMgr;
+
@Override
public void setUp() {
// save security manager so it can be restored in tearDown
diff --git a/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java b/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java
index 17525ec..7a8957e 100644
--- a/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java
+++ b/src/test/java/org/apache/commons/logging/security/SecurityForbiddenTestCase.java
@@ -48,9 +48,6 @@ import org.apache.commons.logging.PathableTestSuite;
*/
public class SecurityForbiddenTestCase extends TestCase
{
- private SecurityManager oldSecMgr;
- private ClassLoader otherClassLoader;
-
// Dummy special hashtable, so we can tell JCL to use this instead of
// the standard one.
public static class CustomHashtable extends Hashtable {
@@ -60,7 +57,6 @@ public class SecurityForbiddenTestCase extends TestCase
*/
private static final long serialVersionUID = 7224652794746236024L;
}
-
/**
* Return the tests included in this test suite.
*/
@@ -75,6 +71,27 @@ public class SecurityForbiddenTestCase extends TestCase
return new PathableTestSuite(testClass, parent);
}
+ private SecurityManager oldSecMgr;
+
+ private ClassLoader otherClassLoader;
+
+ /**
+ * Loads a class with the given classloader.
+ */
+ private Object loadClass(final String name, final ClassLoader classLoader) {
+ try {
+ final Class clazz = classLoader.loadClass(name);
+ final Object obj = clazz.getConstructor().newInstance();
+ return obj;
+ } catch ( final Exception e ) {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ fail("Unexpected exception:" + e.getMessage() + ":" + sw.toString());
+ }
+ return null;
+ }
+
@Override
public void setUp() {
// save security manager so it can be restored in tearDown
@@ -175,21 +192,4 @@ public class SecurityForbiddenTestCase extends TestCase
fail("Unexpected exception:" + t.getMessage() + ":" + sw.toString());
}
}
-
- /**
- * Loads a class with the given classloader.
- */
- private Object loadClass(final String name, final ClassLoader classLoader) {
- try {
- final Class clazz = classLoader.loadClass(name);
- final Object obj = clazz.getConstructor().newInstance();
- return obj;
- } catch ( final Exception e ) {
- final StringWriter sw = new StringWriter();
- final PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- fail("Unexpected exception:" + e.getMessage() + ":" + sw.toString());
- }
- return null;
- }
}
diff --git a/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java
index f5f71b2..21aee0c 100644
--- a/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java
+++ b/src/test/java/org/apache/commons/logging/simple/CustomConfigTestCase.java
@@ -37,52 +37,6 @@ import org.apache.commons.logging.impl.SimpleLog;
*/
public class CustomConfigTestCase extends DefaultConfigTestCase {
- /**
- * The expected log records. The message levels that should have been logged. The message strings that should have been logged.
@@ -108,37 +62,23 @@ public class CustomConfigTestCase extends DefaultConfigTestCase {
}
/**
- * Tear down instance variables required by this test case.
+ * The message levels that should have been logged. The expected log records. The message strings that should have been logged. The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected.
@@ -75,78 +65,14 @@ public class DefaultConfigTestCase extends TestCase {
}
/**
- * Set system properties that will control the LogFactory/Log objects
- * when they are created. Subclasses can override this method to
- * define properties that suit them.
+ * The {@link LogFactory} implementation we have selected. The {@link Log} implementation we have selected.
+ * LogFactory.getFactory().setAttribute(...)
+ *
+ * This must be done before the first Log object is created; configuration
+ * changes after that point will be ignored.
+ *
- * useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
- *
- *
- *
- */
- public void useExplicitLoader(final String prefix, final ClassLoader loader) {
- if (lookasides == null) {
- lookasides = new HashMap();
- }
- lookasides.put(prefix, loader);
- }
-
- /**
- * Specify a collection of logical libraries. See addLogicalLib.
- */
- public void addLogicalLib(final String[] logicalLibs) {
- for (final String logicalLib : logicalLibs) {
- addLogicalLib(logicalLib);
- }
- }
-
/**
* Specify a logical library to be included in the classpath used to
* locate classes.
@@ -231,6 +143,101 @@ public class PathableClassLoader extends URLClassLoader {
+ " as a System property.");
}
+ /**
+ * Specify a collection of logical libraries. See addLogicalLib.
+ */
+ public void addLogicalLib(final String[] logicalLibs) {
+ for (final String logicalLib : logicalLibs) {
+ addLogicalLib(logicalLib);
+ }
+ }
+
+ /**
+ * Allow caller to explicitly add paths. Generally this not a good idea;
+ * use addLogicalLib instead, then define the location for that logical
+ * library in the build.xml file.
+ */
+ @Override
+ public void addURL(final URL url) {
+ super.addURL(url);
+ }
+
+ /**
+ * Same as parent class method except that when parentFirst is false
+ * the resource is looked for in the local classpath before the parent
+ * loader is consulted.
+ */
+ @Override
+ public URL getResource(final String name) {
+ if (parentFirst) {
+ return super.getResource(name);
+ }
+ final URL local = super.findResource(name);
+ if (local != null) {
+ return local;
+ }
+ return super.getResource(name);
+ }
+
+ /**
+ * Same as parent class method except that when parentFirst is false
+ * the resource is looked for in the local classpath before the parent
+ * loader is consulted.
+ */
+ @Override
+ public InputStream getResourceAsStream(final String name) {
+ if (parentFirst) {
+ return super.getResourceAsStream(name);
+ }
+ final URL local = super.findResource(name);
+ if (local != null) {
+ try {
+ return local.openStream();
+ } catch (final IOException e) {
+ // TODO: check if this is right or whether we should
+ // fall back to trying parent. The javadoc doesn't say...
+ return null;
+ }
+ }
+ return super.getResourceAsStream(name);
+ }
+
+ /**
+ * Emulate a proper implementation of getResources which respects the
+ * setting for parentFirst.
+ *
+ *
*/
- @Override
- public InputStream getResourceAsStream(final String name) {
- if (parentFirst) {
- return super.getResourceAsStream(name);
+ public void useExplicitLoader(final String prefix, final ClassLoader loader) {
+ if (lookasides == null) {
+ lookasides = new HashMap();
}
- final URL local = super.findResource(name);
- if (local != null) {
- try {
- return local.openStream();
- } catch (final IOException e) {
- // TODO: check if this is right or whether we should
- // fall back to trying parent. The javadoc doesn't say...
- return null;
- }
- }
- return super.getResourceAsStream(name);
+ lookasides.put(prefix, loader);
+ }
+
+ /**
+ * For classes with the specified prefix, get them from the system
+ * classpath which is active at the point this method is called.
+ *
+ * useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
+ *
+ *