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