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