1/*
2 * Copyright 2001-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.apache.commons.logging.impl;
18
19
20import java.lang.reflect.Constructor;
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.net.URL;
24import java.util.Enumeration;
25import java.util.Hashtable;
26import java.util.Vector;
27
28import org.apache.commons.logging.Log;
29import org.apache.commons.logging.LogConfigurationException;
30import org.apache.commons.logging.LogFactory;
31
32
33/**
34 * <p>Concrete subclass of {@link LogFactory} that implements the
35 * following algorithm to dynamically select a logging implementation
36 * class to instantiate a wrapper for.</p>
37 * <ul>
38 * <li>Use a factory configuration attribute named
39 *     <code>org.apache.commons.logging.Log</code> to identify the
40 *     requested implementation class.</li>
41 * <li>Use the <code>org.apache.commons.logging.Log</code> system property
42 *     to identify the requested implementation class.</li>
43 * <li>If <em>Log4J</em> is available, return an instance of
44 *     <code>org.apache.commons.logging.impl.Log4JLogger</code>.</li>
45 * <li>If <em>JDK 1.4 or later</em> is available, return an instance of
46 *     <code>org.apache.commons.logging.impl.Jdk14Logger</code>.</li>
47 * <li>Otherwise, return an instance of
48 *     <code>org.apache.commons.logging.impl.SimpleLog</code>.</li>
49 * </ul>
50 *
51 * <p>If the selected {@link Log} implementation class has a
52 * <code>setLogFactory()</code> method that accepts a {@link LogFactory}
53 * parameter, this method will be called on each newly created instance
54 * to identify the associated factory.  This makes factory configuration
55 * attributes available to the Log instance, if it so desires.</p>
56 *
57 * <p>This factory will remember previously created <code>Log</code> instances
58 * for the same name, and will return them on repeated requests to the
59 * <code>getInstance()</code> method.</p>
60 *
61 * @author Rod Waldhoff
62 * @author Craig R. McClanahan
63 * @author Richard A. Sitze
64 * @author Brian Stansberry
65 * @version $Revision: 399224 $ $Date: 2006-05-03 10:25:54 +0100 (Wed, 03 May 2006) $
66 */
67
68public class LogFactoryImpl extends LogFactory {
69
70
71    /** Log4JLogger class name */
72    private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
73    /** Jdk14Logger class name */
74    private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger";
75    /** Jdk13LumberjackLogger class name */
76    private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger";
77    /** SimpleLog class name */
78    private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog";
79
80    private static final String PKG_IMPL="org.apache.commons.logging.impl.";
81    private static final int PKG_LEN = PKG_IMPL.length();
82
83    // ----------------------------------------------------------- Constructors
84
85
86
87    /**
88     * Public no-arguments constructor required by the lookup mechanism.
89     */
90    public LogFactoryImpl() {
91        super();
92        initDiagnostics();  // method on this object
93        if (isDiagnosticsEnabled()) {
94            logDiagnostic("Instance created.");
95        }
96    }
97
98
99    // ----------------------------------------------------- Manifest Constants
100
101
102    /**
103     * The name (<code>org.apache.commons.logging.Log</code>) of the system
104     * property identifying our {@link Log} implementation class.
105     */
106    public static final String LOG_PROPERTY =
107        "org.apache.commons.logging.Log";
108
109
110    /**
111     * The deprecated system property used for backwards compatibility with
112     * old versions of JCL.
113     */
114    protected static final String LOG_PROPERTY_OLD =
115        "org.apache.commons.logging.log";
116
117    /**
118     * The name (<code>org.apache.commons.logging.Log.allowFlawedContext</code>)
119     * of the system property which can be set true/false to
120     * determine system behaviour when a bad context-classloader is encountered.
121     * When set to false, a LogConfigurationException is thrown if
122     * LogFactoryImpl is loaded via a child classloader of the TCCL (this
123     * should never happen in sane systems).
124     *
125     * Default behaviour: true (tolerates bad context classloaders)
126     *
127     * See also method setAttribute.
128     */
129    public static final String ALLOW_FLAWED_CONTEXT_PROPERTY =
130        "org.apache.commons.logging.Log.allowFlawedContext";
131
132    /**
133     * The name (<code>org.apache.commons.logging.Log.allowFlawedDiscovery</code>)
134     * of the system property which can be set true/false to
135     * determine system behaviour when a bad logging adapter class is
136     * encountered during logging discovery. When set to false, an
137     * exception will be thrown and the app will fail to start. When set
138     * to true, discovery will continue (though the user might end up
139     * with a different logging implementation than they expected).
140     *
141     * Default behaviour: true (tolerates bad logging adapters)
142     *
143     * See also method setAttribute.
144     */
145    public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY =
146        "org.apache.commons.logging.Log.allowFlawedDiscovery";
147
148    /**
149     * The name (<code>org.apache.commons.logging.Log.allowFlawedHierarchy</code>)
150     * of the system property which can be set true/false to
151     * determine system behaviour when a logging adapter class is
152     * encountered which has bound to the wrong Log class implementation.
153     * When set to false, an exception will be thrown and the app will fail
154     * to start. When set to true, discovery will continue (though the user
155     * might end up with a different logging implementation than they expected).
156     *
157     * Default behaviour: true (tolerates bad Log class hierarchy)
158     *
159     * See also method setAttribute.
160     */
161    public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY =
162        "org.apache.commons.logging.Log.allowFlawedHierarchy";
163
164
165    /**
166     * The names of classes that will be tried (in order) as logging
167     * adapters. Each class is expected to implement the Log interface,
168     * and to throw NoClassDefFound or ExceptionInInitializerError when
169     * loaded if the underlying logging library is not available. Any
170     * other error indicates that the underlying logging library is available
171     * but broken/unusable for some reason.
172     */
173    private static final String[] classesToDiscover = {
174            LOGGING_IMPL_LOG4J_LOGGER,
175            "org.apache.commons.logging.impl.Jdk14Logger",
176            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
177            "org.apache.commons.logging.impl.SimpleLog"
178    };
179
180
181    // ----------------------------------------------------- Instance Variables
182
183    /**
184     * Determines whether logging classes should be loaded using the thread-context
185     * classloader, or via the classloader that loaded this LogFactoryImpl class.
186     */
187    private boolean useTCCL = true;
188
189    /**
190     * The string prefixed to every message output by the logDiagnostic method.
191     */
192    private String diagnosticPrefix;
193
194
195    /**
196     * Configuration attributes.
197     */
198    protected Hashtable attributes = new Hashtable();
199
200
201    /**
202     * The {@link org.apache.commons.logging.Log} instances that have
203     * already been created, keyed by logger name.
204     */
205    protected Hashtable instances = new Hashtable();
206
207
208    /**
209     * Name of the class implementing the Log interface.
210     */
211    private String logClassName;
212
213
214    /**
215     * The one-argument constructor of the
216     * {@link org.apache.commons.logging.Log}
217     * implementation class that will be used to create new instances.
218     * This value is initialized by <code>getLogConstructor()</code>,
219     * and then returned repeatedly.
220     */
221    protected Constructor logConstructor = null;
222
223
224    /**
225     * The signature of the Constructor to be used.
226     */
227    protected Class logConstructorSignature[] =
228    { java.lang.String.class };
229
230
231    /**
232     * The one-argument <code>setLogFactory</code> method of the selected
233     * {@link org.apache.commons.logging.Log} method, if it exists.
234     */
235    protected Method logMethod = null;
236
237
238    /**
239     * The signature of the <code>setLogFactory</code> method to be used.
240     */
241    protected Class logMethodSignature[] =
242    { LogFactory.class };
243
244    /**
245     * See getBaseClassLoader and initConfiguration.
246     */
247    private boolean allowFlawedContext;
248
249    /**
250     * See handleFlawedDiscovery and initConfiguration.
251     */
252    private boolean allowFlawedDiscovery;
253
254    /**
255     * See handleFlawedHierarchy and initConfiguration.
256     */
257    private boolean allowFlawedHierarchy;
258
259    // --------------------------------------------------------- Public Methods
260
261
262    /**
263     * Return the configuration attribute with the specified name (if any),
264     * or <code>null</code> if there is no such attribute.
265     *
266     * @param name Name of the attribute to return
267     */
268    public Object getAttribute(String name) {
269
270        return (attributes.get(name));
271
272    }
273
274
275    /**
276     * Return an array containing the names of all currently defined
277     * configuration attributes.  If there are no such attributes, a zero
278     * length array is returned.
279     */
280    public String[] getAttributeNames() {
281
282        Vector names = new Vector();
283        Enumeration keys = attributes.keys();
284        while (keys.hasMoreElements()) {
285            names.addElement((String) keys.nextElement());
286        }
287        String results[] = new String[names.size()];
288        for (int i = 0; i < results.length; i++) {
289            results[i] = (String) names.elementAt(i);
290        }
291        return (results);
292
293    }
294
295
296    /**
297     * Convenience method to derive a name from the specified class and
298     * call <code>getInstance(String)</code> with it.
299     *
300     * @param clazz Class for which a suitable Log name will be derived
301     *
302     * @exception LogConfigurationException if a suitable <code>Log</code>
303     *  instance cannot be returned
304     */
305    public Log getInstance(Class clazz) throws LogConfigurationException {
306
307        return (getInstance(clazz.getName()));
308
309    }
310
311
312    /**
313     * <p>Construct (if necessary) and return a <code>Log</code> instance,
314     * using the factory's current set of configuration attributes.</p>
315     *
316     * <p><strong>NOTE</strong> - Depending upon the implementation of
317     * the <code>LogFactory</code> you are using, the <code>Log</code>
318     * instance you are returned may or may not be local to the current
319     * application, and may or may not be returned again on a subsequent
320     * call with the same name argument.</p>
321     *
322     * @param name Logical name of the <code>Log</code> instance to be
323     *  returned (the meaning of this name is only known to the underlying
324     *  logging implementation that is being wrapped)
325     *
326     * @exception LogConfigurationException if a suitable <code>Log</code>
327     *  instance cannot be returned
328     */
329    public Log getInstance(String name) throws LogConfigurationException {
330
331        Log instance = (Log) instances.get(name);
332        if (instance == null) {
333            instance = newInstance(name);
334            instances.put(name, instance);
335        }
336        return (instance);
337
338    }
339
340
341    /**
342     * Release any internal references to previously created
343     * {@link org.apache.commons.logging.Log}
344     * instances returned by this factory.  This is useful in environments
345     * like servlet containers, which implement application reloading by
346     * throwing away a ClassLoader.  Dangling references to objects in that
347     * class loader would prevent garbage collection.
348     */
349    public void release() {
350
351        logDiagnostic("Releasing all known loggers");
352        instances.clear();
353    }
354
355
356    /**
357     * Remove any configuration attribute associated with the specified name.
358     * If there is no such attribute, no action is taken.
359     *
360     * @param name Name of the attribute to remove
361     */
362    public void removeAttribute(String name) {
363
364        attributes.remove(name);
365
366    }
367
368
369    /**
370     * Set the configuration attribute with the specified name.  Calling
371     * this with a <code>null</code> value is equivalent to calling
372     * <code>removeAttribute(name)</code>.
373     * <p>
374     * This method can be used to set logging configuration programmatically
375     * rather than via system properties. It can also be used in code running
376     * within a container (such as a webapp) to configure behaviour on a
377     * per-component level instead of globally as system properties would do.
378     * To use this method instead of a system property, call
379     * <pre>
380     * LogFactory.getFactory().setAttribute(...)
381     * </pre>
382     * This must be done before the first Log object is created; configuration
383     * changes after that point will be ignored.
384     * <p>
385     * This method is also called automatically if LogFactory detects a
386     * commons-logging.properties file; every entry in that file is set
387     * automatically as an attribute here.
388     *
389     * @param name Name of the attribute to set
390     * @param value Value of the attribute to set, or <code>null</code>
391     *  to remove any setting for this attribute
392     */
393    public void setAttribute(String name, Object value) {
394
395        if (logConstructor != null) {
396            logDiagnostic("setAttribute: call too late; configuration already performed.");
397        }
398
399        if (value == null) {
400            attributes.remove(name);
401        } else {
402            attributes.put(name, value);
403        }
404
405        if (name.equals(TCCL_KEY)) {
406            useTCCL = Boolean.valueOf(value.toString()).booleanValue();
407        }
408
409    }
410
411
412    // ------------------------------------------------------
413    // Static Methods
414    //
415    // These methods only defined as workarounds for a java 1.2 bug;
416    // theoretically none of these are needed.
417    // ------------------------------------------------------
418
419    /**
420     * Gets the context classloader.
421     * This method is a workaround for a java 1.2 compiler bug.
422     * @since 1.1
423     */
424    protected static ClassLoader getContextClassLoader() throws LogConfigurationException {
425        return LogFactory.getContextClassLoader();
426    }
427
428
429    /**
430     * Workaround for bug in Java1.2; in theory this method is not needed.
431     * See LogFactory.isDiagnosticsEnabled.
432     */
433    protected static boolean isDiagnosticsEnabled() {
434        return LogFactory.isDiagnosticsEnabled();
435    }
436
437
438    /**
439     * Workaround for bug in Java1.2; in theory this method is not needed.
440     * See LogFactory.getClassLoader.
441     * @since 1.1
442     */
443    protected static ClassLoader getClassLoader(Class clazz) {
444        return LogFactory.getClassLoader(clazz);
445    }
446
447
448    // ------------------------------------------------------ Protected Methods
449
450    /**
451     * Calculate and cache a string that uniquely identifies this instance,
452     * including which classloader the object was loaded from.
453     * <p>
454     * This string will later be prefixed to each "internal logging" message
455     * emitted, so that users can clearly see any unexpected behaviour.
456     * <p>
457     * Note that this method does not detect whether internal logging is
458     * enabled or not, nor where to output stuff if it is; that is all
459     * handled by the parent LogFactory class. This method just computes
460     * its own unique prefix for log messages.
461     */
462    private void initDiagnostics() {
463        // It would be nice to include an identifier of the context classloader
464        // that this LogFactoryImpl object is responsible for. However that
465        // isn't possible as that information isn't available. It is possible
466        // to figure this out by looking at the logging from LogFactory to
467        // see the context & impl ids from when this object was instantiated,
468        // in order to link the impl id output as this object's prefix back to
469        // the context it is intended to manage.
470        // Note that this prefix should be kept consistent with that
471        // in LogFactory.
472        Class clazz = this.getClass();
473        ClassLoader classLoader = getClassLoader(clazz);
474        String classLoaderName;
475        try {
476            if (classLoader == null) {
477                classLoaderName = "BOOTLOADER";
478            } else {
479                classLoaderName = objectId(classLoader);
480            }
481        } catch(SecurityException e) {
482            classLoaderName = "UNKNOWN";
483        }
484        diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] ";
485    }
486
487
488    /**
489     * Output a diagnostic message to a user-specified destination (if the
490     * user has enabled diagnostic logging).
491     *
492     * @param msg diagnostic message
493     * @since 1.1
494     */
495    protected void logDiagnostic(String msg) {
496        if (isDiagnosticsEnabled()) {
497            logRawDiagnostic(diagnosticPrefix + msg);
498        }
499    }
500
501    /**
502     * Return the fully qualified Java classname of the {@link Log}
503     * implementation we will be using.
504     *
505     * @deprecated  Never invoked by this class; subclasses should not assume
506     *              it will be.
507     */
508    protected String getLogClassName() {
509
510        if (logClassName == null) {
511            discoverLogImplementation(getClass().getName());
512        }
513
514        return logClassName;
515    }
516
517
518    /**
519     * <p>Return the <code>Constructor</code> that can be called to instantiate
520     * new {@link org.apache.commons.logging.Log} instances.</p>
521     *
522     * <p><strong>IMPLEMENTATION NOTE</strong> - Race conditions caused by
523     * calling this method from more than one thread are ignored, because
524     * the same <code>Constructor</code> instance will ultimately be derived
525     * in all circumstances.</p>
526     *
527     * @exception LogConfigurationException if a suitable constructor
528     *  cannot be returned
529     *
530     * @deprecated  Never invoked by this class; subclasses should not assume
531     *              it will be.
532     */
533    protected Constructor getLogConstructor()
534        throws LogConfigurationException {
535
536        // Return the previously identified Constructor (if any)
537        if (logConstructor == null) {
538            discoverLogImplementation(getClass().getName());
539        }
540
541        return logConstructor;
542    }
543
544
545    /**
546     * Is <em>JDK 1.3 with Lumberjack</em> logging available?
547     *
548     * @deprecated  Never invoked by this class; subclasses should not assume
549     *              it will be.
550     */
551    protected boolean isJdk13LumberjackAvailable() {
552        return isLogLibraryAvailable(
553                "Jdk13Lumberjack",
554                "org.apache.commons.logging.impl.Jdk13LumberjackLogger");
555    }
556
557
558    /**
559     * <p>Return <code>true</code> if <em>JDK 1.4 or later</em> logging
560     * is available.  Also checks that the <code>Throwable</code> class
561     * supports <code>getStackTrace()</code>, which is required by
562     * Jdk14Logger.</p>
563     *
564     * @deprecated  Never invoked by this class; subclasses should not assume
565     *              it will be.
566     */
567    protected boolean isJdk14Available() {
568        return isLogLibraryAvailable(
569                "Jdk14",
570                "org.apache.commons.logging.impl.Jdk14Logger");
571    }
572
573
574    /**
575     * Is a <em>Log4J</em> implementation available?
576     *
577     * @deprecated  Never invoked by this class; subclasses should not assume
578     *              it will be.
579     */
580    protected boolean isLog4JAvailable() {
581        return isLogLibraryAvailable(
582                "Log4J",
583                LOGGING_IMPL_LOG4J_LOGGER);
584    }
585
586
587    /**
588     * Create and return a new {@link org.apache.commons.logging.Log}
589     * instance for the specified name.
590     *
591     * @param name Name of the new logger
592     *
593     * @exception LogConfigurationException if a new instance cannot
594     *  be created
595     */
596    protected Log newInstance(String name) throws LogConfigurationException {
597
598        Log instance = null;
599        try {
600            if (logConstructor == null) {
601                instance = discoverLogImplementation(name);
602            }
603            else {
604                Object params[] = { name };
605                instance = (Log) logConstructor.newInstance(params);
606            }
607
608            if (logMethod != null) {
609                Object params[] = { this };
610                logMethod.invoke(instance, params);
611            }
612
613            return (instance);
614
615        } catch (LogConfigurationException lce) {
616
617            // this type of exception means there was a problem in discovery
618            // and we've already output diagnostics about the issue, etc.;
619            // just pass it on
620            throw (LogConfigurationException) lce;
621
622        } catch (InvocationTargetException e) {
623            // A problem occurred invoking the Constructor or Method
624            // previously discovered
625            Throwable c = e.getTargetException();
626            if (c != null) {
627                throw new LogConfigurationException(c);
628            } else {
629                throw new LogConfigurationException(e);
630            }
631        } catch (Throwable t) {
632            // A problem occurred invoking the Constructor or Method
633            // previously discovered
634            throw new LogConfigurationException(t);
635        }
636    }
637
638
639    //  ------------------------------------------------------ Private Methods
640
641    /**
642     * Utility method to check whether a particular logging library is
643     * present and available for use. Note that this does <i>not</i>
644     * affect the future behaviour of this class.
645     */
646    private boolean isLogLibraryAvailable(String name, String classname) {
647        if (isDiagnosticsEnabled()) {
648            logDiagnostic("Checking for '" + name + "'.");
649        }
650        try {
651            Log log = createLogFromClass(
652                        classname,
653                        this.getClass().getName(), // dummy category
654                        false);
655
656            if (log == null) {
657                if (isDiagnosticsEnabled()) {
658                    logDiagnostic("Did not find '" + name + "'.");
659                }
660                return false;
661            } else {
662                if (isDiagnosticsEnabled()) {
663                    logDiagnostic("Found '" + name + "'.");
664                }
665                return true;
666            }
667        } catch(LogConfigurationException e) {
668            if (isDiagnosticsEnabled()) {
669                logDiagnostic("Logging system '" + name + "' is available but not useable.");
670            }
671            return false;
672        }
673    }
674
675    /**
676     * Attempt to find an attribute (see method setAttribute) or a
677     * system property with the provided name and return its value.
678     * <p>
679     * The attributes associated with this object are checked before
680     * system properties in case someone has explicitly called setAttribute,
681     * or a configuration property has been set in a commons-logging.properties
682     * file.
683     *
684     * @return the value associated with the property, or null.
685     */
686    private String getConfigurationValue(String property) {
687        if (isDiagnosticsEnabled()) {
688            logDiagnostic("[ENV] Trying to get configuration for item " + property);
689        }
690
691        Object valueObj =  getAttribute(property);
692        if (valueObj != null) {
693            if (isDiagnosticsEnabled()) {
694                logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property);
695            }
696            return valueObj.toString();
697        }
698
699        if (isDiagnosticsEnabled()) {
700            logDiagnostic("[ENV] No LogFactory attribute found for " + property);
701        }
702
703        try {
704            String value = System.getProperty(property);
705            if (value != null) {
706                if (isDiagnosticsEnabled()) {
707                    logDiagnostic("[ENV] Found system property [" + value + "] for " + property);
708                }
709                return value;
710            }
711
712            if (isDiagnosticsEnabled()) {
713                logDiagnostic("[ENV] No system property found for property " + property);
714            }
715        } catch (SecurityException e) {
716            if (isDiagnosticsEnabled()) {
717                logDiagnostic("[ENV] Security prevented reading system property " + property);
718            }
719        }
720
721        if (isDiagnosticsEnabled()) {
722            logDiagnostic("[ENV] No configuration defined for item " + property);
723        }
724
725        return null;
726    }
727
728    /**
729     * Get the setting for the user-configurable behaviour specified by key.
730     * If nothing has explicitly been set, then return dflt.
731     */
732    private boolean getBooleanConfiguration(String key, boolean dflt) {
733        String val = getConfigurationValue(key);
734        if (val == null)
735            return dflt;
736        return Boolean.valueOf(val).booleanValue();
737    }
738
739    /**
740     * Initialize a number of variables that control the behaviour of this
741     * class and that can be tweaked by the user. This is done when the first
742     * logger is created, not in the constructor of this class, because we
743     * need to give the user a chance to call method setAttribute in order to
744     * configure this object.
745     */
746    private void initConfiguration() {
747        allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true);
748        allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true);
749        allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true);
750    }
751
752
753    /**
754     * Attempts to create a Log instance for the given category name.
755     * Follows the discovery process described in the class javadoc.
756     *
757     * @param logCategory the name of the log category
758     *
759     * @throws LogConfigurationException if an error in discovery occurs,
760     * or if no adapter at all can be instantiated
761     */
762    private Log discoverLogImplementation(String logCategory)
763    throws LogConfigurationException
764    {
765        if (isDiagnosticsEnabled()) {
766            logDiagnostic("Discovering a Log implementation...");
767        }
768
769        initConfiguration();
770
771        Log result = null;
772
773        // See if the user specified the Log implementation to use
774        String specifiedLogClassName = findUserSpecifiedLogClassName();
775
776        if (specifiedLogClassName != null) {
777            if (isDiagnosticsEnabled()) {
778                logDiagnostic("Attempting to load user-specified log class '" +
779                    specifiedLogClassName + "'...");
780            }
781
782            result = createLogFromClass(specifiedLogClassName,
783                                        logCategory,
784                                        true);
785            if (result == null) {
786                StringBuffer messageBuffer =  new StringBuffer("User-specified log class '");
787                messageBuffer.append(specifiedLogClassName);
788                messageBuffer.append("' cannot be found or is not useable.");
789
790                // Mistyping or misspelling names is a common fault.
791                // Construct a good error message, if we can
792                if (specifiedLogClassName != null) {
793                    informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
794                    informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
795                    informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
796                    informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
797                }
798                throw new LogConfigurationException(messageBuffer.toString());
799            }
800
801            return result;
802        }
803
804        // No user specified log; try to discover what's on the classpath
805        //
806        // Note that we deliberately loop here over classesToDiscover and
807        // expect method createLogFromClass to loop over the possible source
808        // classloaders. The effect is:
809        //   for each discoverable log adapter
810        //      for each possible classloader
811        //          see if it works
812        //
813        // It appears reasonable at first glance to do the opposite:
814        //   for each possible classloader
815        //     for each discoverable log adapter
816        //        see if it works
817        //
818        // The latter certainly has advantages for user-installable logging
819        // libraries such as log4j; in a webapp for example this code should
820        // first check whether the user has provided any of the possible
821        // logging libraries before looking in the parent classloader.
822        // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4,
823        // and SimpleLog will always work in any JVM. So the loop would never
824        // ever look for logging libraries in the parent classpath. Yet many
825        // users would expect that putting log4j there would cause it to be
826        // detected (and this is the historical JCL behaviour). So we go with
827        // the first approach. A user that has bundled a specific logging lib
828        // in a webapp should use a commons-logging.properties file or a
829        // service file in META-INF to force use of that logging lib anyway,
830        // rather than relying on discovery.
831
832        if (isDiagnosticsEnabled()) {
833            logDiagnostic(
834                "No user-specified Log implementation; performing discovery" +
835            	" using the standard supported logging implementations...");
836        }
837        for(int i=0; (i<classesToDiscover.length) && (result == null); ++i) {
838            result = createLogFromClass(classesToDiscover[i], logCategory, true);
839        }
840
841        if (result == null) {
842            throw new LogConfigurationException
843                        ("No suitable Log implementation");
844        }
845
846        return result;
847    }
848
849
850    /**
851     * Appends message if the given name is similar to the candidate.
852     * @param messageBuffer <code>StringBuffer</code> the message should be appended to,
853     * not null
854     * @param name the (trimmed) name to be test against the candidate, not null
855     * @param candidate the candidate name (not null)
856     */
857    private void informUponSimilarName(final StringBuffer messageBuffer, final String name,
858            final String candidate) {
859        if (name.equals(candidate)) {
860            // Don't suggest a name that is exactly the same as the one the
861            // user tried...
862            return;
863        }
864
865        // If the user provides a name that is in the right package, and gets
866        // the first 5 characters of the adapter class right (ignoring case),
867        // then suggest the candidate adapter class name.
868        if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) {
869            messageBuffer.append(" Did you mean '");
870            messageBuffer.append(candidate);
871            messageBuffer.append("'?");
872        }
873    }
874
875
876    /**
877     * Checks system properties and the attribute map for
878     * a Log implementation specified by the user under the
879     * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}.
880     *
881     * @return classname specified by the user, or <code>null</code>
882     */
883    private String findUserSpecifiedLogClassName()
884    {
885        if (isDiagnosticsEnabled()) {
886            logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'");
887        }
888        String specifiedClass = (String) getAttribute(LOG_PROPERTY);
889
890        if (specifiedClass == null) { // @deprecated
891            if (isDiagnosticsEnabled()) {
892                logDiagnostic("Trying to get log class from attribute '" +
893                              LOG_PROPERTY_OLD + "'");
894            }
895            specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD);
896        }
897
898        if (specifiedClass == null) {
899            if (isDiagnosticsEnabled()) {
900                logDiagnostic("Trying to get log class from system property '" +
901                          LOG_PROPERTY + "'");
902            }
903            try {
904                specifiedClass = System.getProperty(LOG_PROPERTY);
905            } catch (SecurityException e) {
906                if (isDiagnosticsEnabled()) {
907                    logDiagnostic("No access allowed to system property '" +
908                        LOG_PROPERTY + "' - " + e.getMessage());
909                }
910            }
911        }
912
913        if (specifiedClass == null) { // @deprecated
914            if (isDiagnosticsEnabled()) {
915                logDiagnostic("Trying to get log class from system property '" +
916                          LOG_PROPERTY_OLD + "'");
917            }
918            try {
919                specifiedClass = System.getProperty(LOG_PROPERTY_OLD);
920            } catch (SecurityException e) {
921                if (isDiagnosticsEnabled()) {
922                    logDiagnostic("No access allowed to system property '" +
923                        LOG_PROPERTY_OLD + "' - " + e.getMessage());
924                }
925            }
926        }
927
928        // Remove any whitespace; it's never valid in a classname so its
929        // presence just means a user mistake. As we know what they meant,
930        // we may as well strip the spaces.
931        if (specifiedClass != null) {
932            specifiedClass = specifiedClass.trim();
933        }
934
935        return specifiedClass;
936    }
937
938
939    /**
940     * Attempts to load the given class, find a suitable constructor,
941     * and instantiate an instance of Log.
942     *
943     * @param logAdapterClassName classname of the Log implementation
944     *
945     * @param logCategory  argument to pass to the Log implementation's
946     * constructor
947     *
948     * @param affectState  <code>true</code> if this object's state should
949     * be affected by this method call, <code>false</code> otherwise.
950     *
951     * @return  an instance of the given class, or null if the logging
952     * library associated with the specified adapter is not available.
953     *
954     * @throws LogConfigurationException if there was a serious error with
955     * configuration and the handleFlawedDiscovery method decided this
956     * problem was fatal.
957     */
958    private Log createLogFromClass(String logAdapterClassName,
959                                   String logCategory,
960                                   boolean affectState)
961            throws LogConfigurationException {
962
963        if (isDiagnosticsEnabled()) {
964            logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'");
965        }
966
967        Object[] params = { logCategory };
968        Log logAdapter = null;
969        Constructor constructor = null;
970
971        Class logAdapterClass = null;
972        ClassLoader currentCL = getBaseClassLoader();
973
974        for(;;) {
975            // Loop through the classloader hierarchy trying to find
976            // a viable classloader.
977            logDiagnostic(
978                    "Trying to load '"
979                    + logAdapterClassName
980                    + "' from classloader "
981                    + objectId(currentCL));
982            try {
983                if (isDiagnosticsEnabled()) {
984                    // Show the location of the first occurrence of the .class file
985                    // in the classpath. This is the location that ClassLoader.loadClass
986                    // will load the class from -- unless the classloader is doing
987                    // something weird.
988                    URL url;
989                    String resourceName = logAdapterClassName.replace('.', '/') + ".class";
990                    if (currentCL != null) {
991                        url = currentCL.getResource(resourceName );
992                    } else {
993                        url = ClassLoader.getSystemResource(resourceName + ".class");
994                    }
995
996                    if (url == null) {
997                        logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found.");
998                    } else {
999                        logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'");
1000                    }
1001                }
1002
1003                Class c = null;
1004                try {
1005                    c = Class.forName(logAdapterClassName, true, currentCL);
1006                } catch (ClassNotFoundException originalClassNotFoundException) {
1007                    // The current classloader was unable to find the log adapter
1008                    // in this or any ancestor classloader. There's no point in
1009                    // trying higher up in the hierarchy in this case..
1010                    String msg = "" + originalClassNotFoundException.getMessage();
1011                    logDiagnostic(
1012                        "The log adapter '"
1013                        + logAdapterClassName
1014                        + "' is not available via classloader "
1015                        + objectId(currentCL)
1016                        + ": "
1017                        + msg.trim());
1018                    try {
1019                        // Try the class classloader.
1020                        // This may work in cases where the TCCL
1021                        // does not contain the code executed or JCL.
1022                        // This behaviour indicates that the application
1023                        // classloading strategy is not consistent with the
1024                        // Java 1.2 classloading guidelines but JCL can
1025                        // and so should handle this case.
1026                        c = Class.forName(logAdapterClassName);
1027                    } catch (ClassNotFoundException secondaryClassNotFoundException) {
1028                        // no point continuing: this adapter isn't available
1029                        msg = "" + secondaryClassNotFoundException.getMessage();
1030                        logDiagnostic(
1031                            "The log adapter '"
1032                            + logAdapterClassName
1033                            + "' is not available via the LogFactoryImpl class classloader: "
1034                            + msg.trim());
1035                        break;
1036                    }
1037                }
1038
1039                constructor = c.getConstructor(logConstructorSignature);
1040                Object o = constructor.newInstance(params);
1041
1042                // Note that we do this test after trying to create an instance
1043                // [rather than testing Log.class.isAssignableFrom(c)] so that
1044                // we don't complain about Log hierarchy problems when the
1045                // adapter couldn't be instantiated anyway.
1046                if (o instanceof Log) {
1047                    logAdapterClass = c;
1048                    logAdapter = (Log) o;
1049                    break;
1050                }
1051
1052                // Oops, we have a potential problem here. An adapter class
1053                // has been found and its underlying lib is present too, but
1054                // there are multiple Log interface classes available making it
1055                // impossible to cast to the type the caller wanted. We
1056                // certainly can't use this logger, but we need to know whether
1057                // to keep on discovering or terminate now.
1058                //
1059                // The handleFlawedHierarchy method will throw
1060                // LogConfigurationException if it regards this problem as
1061                // fatal, and just return if not.
1062                handleFlawedHierarchy(currentCL, c);
1063            } catch (NoClassDefFoundError e) {
1064                // We were able to load the adapter but it had references to
1065                // other classes that could not be found. This simply means that
1066                // the underlying logger library is not present in this or any
1067                // ancestor classloader. There's no point in trying higher up
1068                // in the hierarchy in this case..
1069                String msg = "" + e.getMessage();
1070                logDiagnostic(
1071                    "The log adapter '"
1072                    + logAdapterClassName
1073                    + "' is missing dependencies when loaded via classloader "
1074                    + objectId(currentCL)
1075                    + ": "
1076                    + msg.trim());
1077                break;
1078            } catch (ExceptionInInitializerError e) {
1079                // A static initializer block or the initializer code associated
1080                // with a static variable on the log adapter class has thrown
1081                // an exception.
1082                //
1083                // We treat this as meaning the adapter's underlying logging
1084                // library could not be found.
1085                String msg = "" + e.getMessage();
1086                logDiagnostic(
1087                    "The log adapter '"
1088                    + logAdapterClassName
1089                    + "' is unable to initialize itself when loaded via classloader "
1090                    + objectId(currentCL)
1091                    + ": "
1092                    + msg.trim());
1093                break;
1094            } catch(LogConfigurationException e) {
1095                // call to handleFlawedHierarchy above must have thrown
1096                // a LogConfigurationException, so just throw it on
1097                throw e;
1098            } catch(Throwable t) {
1099                // handleFlawedDiscovery will determine whether this is a fatal
1100                // problem or not. If it is fatal, then a LogConfigurationException
1101                // will be thrown.
1102                handleFlawedDiscovery(logAdapterClassName, currentCL, t);
1103            }
1104
1105            if (currentCL == null) {
1106                break;
1107            }
1108
1109            // try the parent classloader
1110            currentCL = currentCL.getParent();
1111        }
1112
1113        if ((logAdapter != null) && affectState) {
1114            // We've succeeded, so set instance fields
1115            this.logClassName   = logAdapterClassName;
1116            this.logConstructor = constructor;
1117
1118            // Identify the <code>setLogFactory</code> method (if there is one)
1119            try {
1120                this.logMethod = logAdapterClass.getMethod("setLogFactory",
1121                                               logMethodSignature);
1122                logDiagnostic("Found method setLogFactory(LogFactory) in '"
1123                              + logAdapterClassName + "'");
1124            } catch (Throwable t) {
1125                this.logMethod = null;
1126                logDiagnostic(
1127                    "[INFO] '" + logAdapterClassName
1128                    + "' from classloader " + objectId(currentCL)
1129                    + " does not declare optional method "
1130                    + "setLogFactory(LogFactory)");
1131            }
1132
1133            logDiagnostic(
1134                "Log adapter '" + logAdapterClassName
1135                + "' from classloader " + objectId(logAdapterClass.getClassLoader())
1136                + " has been selected for use.");
1137        }
1138
1139        return logAdapter;
1140    }
1141
1142
1143    /**
1144     * Return the classloader from which we should try to load the logging
1145     * adapter classes.
1146     * <p>
1147     * This method usually returns the context classloader. However if it
1148     * is discovered that the classloader which loaded this class is a child
1149     * of the context classloader <i>and</i> the allowFlawedContext option
1150     * has been set then the classloader which loaded this class is returned
1151     * instead.
1152     * <p>
1153     * The only time when the classloader which loaded this class is a
1154     * descendant (rather than the same as or an ancestor of the context
1155     * classloader) is when an app has created custom classloaders but
1156     * failed to correctly set the context classloader. This is a bug in
1157     * the calling application; however we provide the option for JCL to
1158     * simply generate a warning rather than fail outright.
1159     *
1160     */
1161    private ClassLoader getBaseClassLoader() throws LogConfigurationException {
1162        ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class);
1163
1164        if (useTCCL == false) {
1165            return thisClassLoader;
1166        }
1167
1168        ClassLoader contextClassLoader = getContextClassLoader();
1169
1170        ClassLoader baseClassLoader = getLowestClassLoader(
1171                contextClassLoader, thisClassLoader);
1172
1173        if (baseClassLoader == null) {
1174           // The two classloaders are not part of a parent child relationship.
1175           // In some classloading setups (e.g. JBoss with its
1176           // UnifiedLoaderRepository) this can still work, so if user hasn't
1177           // forbidden it, just return the contextClassLoader.
1178           if (allowFlawedContext) {
1179              if (isDiagnosticsEnabled()) {
1180                   logDiagnostic(
1181                           "[WARNING] the context classloader is not part of a"
1182                           + " parent-child relationship with the classloader that"
1183                           + " loaded LogFactoryImpl.");
1184              }
1185              // If contextClassLoader were null, getLowestClassLoader() would
1186              // have returned thisClassLoader.  The fact we are here means
1187              // contextClassLoader is not null, so we can just return it.
1188              return contextClassLoader;
1189           }
1190           else {
1191            throw new LogConfigurationException(
1192                "Bad classloader hierarchy; LogFactoryImpl was loaded via"
1193                + " a classloader that is not related to the current context"
1194                + " classloader.");
1195           }
1196        }
1197
1198        if (baseClassLoader != contextClassLoader) {
1199            // We really should just use the contextClassLoader as the starting
1200            // point for scanning for log adapter classes. However it is expected
1201            // that there are a number of broken systems out there which create
1202            // custom classloaders but fail to set the context classloader so
1203            // we handle those flawed systems anyway.
1204            if (allowFlawedContext) {
1205                if (isDiagnosticsEnabled()) {
1206                    logDiagnostic(
1207                            "Warning: the context classloader is an ancestor of the"
1208                            + " classloader that loaded LogFactoryImpl; it should be"
1209                            + " the same or a descendant. The application using"
1210                            + " commons-logging should ensure the context classloader"
1211                            + " is used correctly.");
1212                }
1213            } else {
1214                throw new LogConfigurationException(
1215                        "Bad classloader hierarchy; LogFactoryImpl was loaded via"
1216                        + " a classloader that is not related to the current context"
1217                        + " classloader.");
1218            }
1219        }
1220
1221        return baseClassLoader;
1222    }
1223
1224    /**
1225     * Given two related classloaders, return the one which is a child of
1226     * the other.
1227     * <p>
1228     * @param c1 is a classloader (including the null classloader)
1229     * @param c2 is a classloader (including the null classloader)
1230     *
1231     * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor,
1232     * and null if neither is an ancestor of the other.
1233     */
1234    private ClassLoader getLowestClassLoader(ClassLoader c1, ClassLoader c2) {
1235        // TODO: use AccessController when dealing with classloaders here
1236
1237        if (c1 == null)
1238            return c2;
1239
1240        if (c2 == null)
1241            return c1;
1242
1243        ClassLoader current;
1244
1245        // scan c1's ancestors to find c2
1246        current = c1;
1247        while (current != null) {
1248            if (current == c2)
1249                return c1;
1250            current = current.getParent();
1251        }
1252
1253        // scan c2's ancestors to find c1
1254        current = c2;
1255        while (current != null) {
1256            if (current == c1)
1257                return c2;
1258            current = current.getParent();
1259        }
1260
1261        return null;
1262    }
1263
1264    /**
1265     * Generates an internal diagnostic logging of the discovery failure and
1266     * then throws a <code>LogConfigurationException</code> that wraps
1267     * the passed <code>Throwable</code>.
1268     *
1269     * @param logAdapterClassName is the class name of the Log implementation
1270     * that could not be instantiated. Cannot be <code>null</code>.
1271     *
1272     * @param classLoader is the classloader that we were trying to load the
1273     * logAdapterClassName from when the exception occurred.
1274     *
1275     * @param discoveryFlaw is the Throwable created by the classloader
1276     *
1277     * @throws LogConfigurationException    ALWAYS
1278     */
1279    private void handleFlawedDiscovery(String logAdapterClassName,
1280                                       ClassLoader classLoader,
1281                                       Throwable discoveryFlaw) {
1282
1283        if (isDiagnosticsEnabled()) {
1284            logDiagnostic("Could not instantiate Log '"
1285                      + logAdapterClassName + "' -- "
1286                      + discoveryFlaw.getClass().getName() + ": "
1287                      + discoveryFlaw.getLocalizedMessage());
1288        }
1289
1290        if (!allowFlawedDiscovery) {
1291            throw new LogConfigurationException(discoveryFlaw);
1292        }
1293    }
1294
1295
1296    /**
1297     * Report a problem loading the log adapter, then either return
1298     * (if the situation is considered recoverable) or throw a
1299     * LogConfigurationException.
1300     *  <p>
1301     * There are two possible reasons why we successfully loaded the
1302     * specified log adapter class then failed to cast it to a Log object:
1303     * <ol>
1304     * <li>the specific class just doesn't implement the Log interface
1305     *     (user screwed up), or
1306     * <li> the specified class has bound to a Log class loaded by some other
1307     *      classloader; Log@classloaderX cannot be cast to Log@classloaderY.
1308     * </ol>
1309     * <p>
1310     * Here we try to figure out which case has occurred so we can give the
1311     * user some reasonable feedback.
1312     *
1313     * @param badClassLoader is the classloader we loaded the problem class from,
1314     * ie it is equivalent to badClass.getClassLoader().
1315     *
1316     * @param badClass is a Class object with the desired name, but which
1317     * does not implement Log correctly.
1318     *
1319     * @throws LogConfigurationException when the situation
1320     * should not be recovered from.
1321     */
1322    private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass)
1323    throws LogConfigurationException {
1324
1325        boolean implementsLog = false;
1326        String logInterfaceName = Log.class.getName();
1327        Class interfaces[] = badClass.getInterfaces();
1328        for (int i = 0; i < interfaces.length; i++) {
1329            if (logInterfaceName.equals(interfaces[i].getName())) {
1330                implementsLog = true;
1331                break;
1332            }
1333        }
1334
1335        if (implementsLog) {
1336            // the class does implement an interface called Log, but
1337            // it is in the wrong classloader
1338            if (isDiagnosticsEnabled()) {
1339                try {
1340                    ClassLoader logInterfaceClassLoader = getClassLoader(Log.class);
1341                    logDiagnostic(
1342                        "Class '" + badClass.getName()
1343                        + "' was found in classloader "
1344                        + objectId(badClassLoader)
1345                        + ". It is bound to a Log interface which is not"
1346                        + " the one loaded from classloader "
1347                        + objectId(logInterfaceClassLoader));
1348                } catch (Throwable t) {
1349                    logDiagnostic(
1350                        "Error while trying to output diagnostics about"
1351                        + " bad class '" + badClass + "'");
1352                }
1353            }
1354
1355            if (!allowFlawedHierarchy) {
1356                StringBuffer msg = new StringBuffer();
1357                msg.append("Terminating logging for this context ");
1358                msg.append("due to bad log hierarchy. ");
1359                msg.append("You have more than one version of '");
1360                msg.append(Log.class.getName());
1361                msg.append("' visible.");
1362                if (isDiagnosticsEnabled()) {
1363                    logDiagnostic(msg.toString());
1364                }
1365                throw new LogConfigurationException(msg.toString());
1366            }
1367
1368            if (isDiagnosticsEnabled()) {
1369                StringBuffer msg = new StringBuffer();
1370                msg.append("Warning: bad log hierarchy. ");
1371                msg.append("You have more than one version of '");
1372                msg.append(Log.class.getName());
1373                msg.append("' visible.");
1374                logDiagnostic(msg.toString());
1375            }
1376        } else {
1377            // this is just a bad adapter class
1378            if (!allowFlawedDiscovery) {
1379                StringBuffer msg = new StringBuffer();
1380                msg.append("Terminating logging for this context. ");
1381                msg.append("Log class '");
1382                msg.append(badClass.getName());
1383                msg.append("' does not implement the Log interface.");
1384                if (isDiagnosticsEnabled()) {
1385                    logDiagnostic(msg.toString());
1386                }
1387
1388                throw new LogConfigurationException(msg.toString());
1389            }
1390
1391            if (isDiagnosticsEnabled()) {
1392                StringBuffer msg = new StringBuffer();
1393                msg.append("[WARNING] Log class '");
1394                msg.append(badClass.getName());
1395                msg.append("' does not implement the Log interface.");
1396                logDiagnostic(msg.toString());
1397            }
1398        }
1399    }
1400}
1401