001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.logging.impl; 019 020import java.io.PrintWriter; 021import java.io.StringWriter; 022import java.lang.reflect.Constructor; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.net.URL; 026import java.security.AccessController; 027import java.security.PrivilegedAction; 028import java.util.Hashtable; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogConfigurationException; 032import org.apache.commons.logging.LogFactory; 033 034/** 035 * Concrete subclass of {@link LogFactory} that implements the 036 * following algorithm to dynamically select a logging implementation 037 * class to instantiate a wrapper for: 038 * <ul> 039 * <li>Use a factory configuration attribute named 040 * {@code org.apache.commons.logging.Log} to identify the 041 * requested implementation class.</li> 042 * <li>Use the {@code org.apache.commons.logging.Log} system property 043 * to identify the requested implementation class.</li> 044 * <li>If <em>Log4J</em> is available, return an instance of 045 * {@code org.apache.commons.logging.impl.Log4JLogger}.</li> 046 * <li>If <em>JDK 1.4 or later</em> is available, return an instance of 047 * {@code org.apache.commons.logging.impl.Jdk14Logger}.</li> 048 * <li>Otherwise, return an instance of 049 * {@code org.apache.commons.logging.impl.SimpleLog}.</li> 050 * </ul> 051 * <p> 052 * If the selected {@link Log} implementation class has a 053 * {@code setLogFactory()} method that accepts a {@link LogFactory} 054 * parameter, this method will be called on each newly created instance 055 * to identify the associated factory. This makes factory configuration 056 * attributes available to the Log instance, if it so desires. 057 * </p> 058 * <p> 059 * This factory will remember previously created {@code Log} instances 060 * for the same name, and will return them on repeated requests to the 061 * {@code getInstance()} method. 062 * </p> 063 */ 064public class LogFactoryImpl extends LogFactory { 065 066 /** Log4JLogger class name */ 067 private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger"; 068 069 /** Jdk14Logger class name */ 070 private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger"; 071 072 /** Jdk13LumberjackLogger class name */ 073 private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger"; 074 075 /** SimpleLog class name */ 076 private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog"; 077 078 private static final String PKG_IMPL="org.apache.commons.logging.impl."; 079 080 private static final int PKG_LEN = PKG_IMPL.length(); 081 082 /** 083 * An empty immutable {@code String} array. 084 */ 085 private static final String[] EMPTY_STRING_ARRAY = {}; 086 087 /** 088 * The name ({@code org.apache.commons.logging.Log}) of the system 089 * property identifying our {@link Log} implementation class. 090 */ 091 public static final String LOG_PROPERTY = "org.apache.commons.logging.Log"; 092 093 /** 094 * The deprecated system property used for backwards compatibility with 095 * old versions of JCL. 096 */ 097 protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log"; 098 099 /** 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}