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