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