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