View Javadoc

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;
18  
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.security.AccessController;
27  import java.security.PrivilegedAction;
28  import java.util.Enumeration;
29  import java.util.Hashtable;
30  import java.util.Properties;
31  
32  
33  /***
34   * <p>Factory for creating {@link Log} instances, with discovery and
35   * configuration features similar to that employed by standard Java APIs
36   * such as JAXP.</p>
37   *
38   * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is heavily
39   * based on the SAXParserFactory and DocumentBuilderFactory implementations
40   * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.</p>
41   *
42   * @author Craig R. McClanahan
43   * @author Costin Manolache
44   * @author Richard A. Sitze
45   * @version $Revision: 1.27 $ $Date: 2004/06/06 21:15:12 $
46   */
47  
48  public abstract class LogFactory {
49  
50  
51      // ----------------------------------------------------- Manifest Constants
52  
53  
54      /***
55       * The name of the property used to identify the LogFactory implementation
56       * class name.
57       */
58      public static final String FACTORY_PROPERTY =
59          "org.apache.commons.logging.LogFactory";
60  
61      /***
62       * The fully qualified class name of the fallback <code>LogFactory</code>
63       * implementation class to use, if no other can be found.
64       */
65      public static final String FACTORY_DEFAULT =
66          "org.apache.commons.logging.impl.LogFactoryImpl";
67  
68      /***
69       * The name of the properties file to search for.
70       */
71      public static final String FACTORY_PROPERTIES =
72          "commons-logging.properties";
73  
74      /***
75       * JDK1.3+ <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider">
76       * 'Service Provider' specification</a>.
77       * 
78       */
79      protected static final String SERVICE_ID =
80          "META-INF/services/org.apache.commons.logging.LogFactory";
81  
82  
83      // ----------------------------------------------------------- Constructors
84  
85  
86      /***
87       * Protected constructor that is not available for public use.
88       */
89      protected LogFactory() { }
90  
91  
92      // --------------------------------------------------------- Public Methods
93  
94  
95      /***
96       * Return the configuration attribute with the specified name (if any),
97       * or <code>null</code> if there is no such attribute.
98       *
99       * @param name Name of the attribute to return
100      */
101     public abstract Object getAttribute(String name);
102 
103 
104     /***
105      * Return an array containing the names of all currently defined
106      * configuration attributes.  If there are no such attributes, a zero
107      * length array is returned.
108      */
109     public abstract String[] getAttributeNames();
110 
111 
112     /***
113      * Convenience method to derive a name from the specified class and
114      * call <code>getInstance(String)</code> with it.
115      *
116      * @param clazz Class for which a suitable Log name will be derived
117      *
118      * @exception LogConfigurationException if a suitable <code>Log</code>
119      *  instance cannot be returned
120      */
121     public abstract Log getInstance(Class clazz)
122         throws LogConfigurationException;
123 
124 
125     /***
126      * <p>Construct (if necessary) and return a <code>Log</code> instance,
127      * using the factory's current set of configuration attributes.</p>
128      *
129      * <p><strong>NOTE</strong> - Depending upon the implementation of
130      * the <code>LogFactory</code> you are using, the <code>Log</code>
131      * instance you are returned may or may not be local to the current
132      * application, and may or may not be returned again on a subsequent
133      * call with the same name argument.</p>
134      *
135      * @param name Logical name of the <code>Log</code> instance to be
136      *  returned (the meaning of this name is only known to the underlying
137      *  logging implementation that is being wrapped)
138      *
139      * @exception LogConfigurationException if a suitable <code>Log</code>
140      *  instance cannot be returned
141      */
142     public abstract Log getInstance(String name)
143         throws LogConfigurationException;
144 
145 
146     /***
147      * Release any internal references to previously created {@link Log}
148      * instances returned by this factory.  This is useful in environments
149      * like servlet containers, which implement application reloading by
150      * throwing away a ClassLoader.  Dangling references to objects in that
151      * class loader would prevent garbage collection.
152      */
153     public abstract void release();
154 
155 
156     /***
157      * Remove any configuration attribute associated with the specified name.
158      * If there is no such attribute, no action is taken.
159      *
160      * @param name Name of the attribute to remove
161      */
162     public abstract void removeAttribute(String name);
163 
164 
165     /***
166      * Set the configuration attribute with the specified name.  Calling
167      * this with a <code>null</code> value is equivalent to calling
168      * <code>removeAttribute(name)</code>.
169      *
170      * @param name Name of the attribute to set
171      * @param value Value of the attribute to set, or <code>null</code>
172      *  to remove any setting for this attribute
173      */
174     public abstract void setAttribute(String name, Object value);
175 
176 
177     // ------------------------------------------------------- Static Variables
178 
179 
180     /***
181      * The previously constructed <code>LogFactory</code> instances, keyed by
182      * the <code>ClassLoader</code> with which it was created.
183      */
184     protected static Hashtable factories = new Hashtable();
185 
186 
187     // --------------------------------------------------------- Static Methods
188 
189 
190     /***
191      * <p>Construct (if necessary) and return a <code>LogFactory</code>
192      * instance, using the following ordered lookup procedure to determine
193      * the name of the implementation class to be loaded.</p>
194      * <ul>
195      * <li>The <code>org.apache.commons.logging.LogFactory</code> system
196      *     property.</li>
197      * <li>The JDK 1.3 Service Discovery mechanism</li>
198      * <li>Use the properties file <code>commons-logging.properties</code>
199      *     file, if found in the class path of this class.  The configuration
200      *     file is in standard <code>java.util.Properties</code> format and
201      *     contains the fully qualified name of the implementation class
202      *     with the key being the system property defined above.</li>
203      * <li>Fall back to a default implementation class
204      *     (<code>org.apache.commons.logging.impl.LogFactoryImpl</code>).</li>
205      * </ul>
206      *
207      * <p><em>NOTE</em> - If the properties file method of identifying the
208      * <code>LogFactory</code> implementation class is utilized, all of the
209      * properties defined in this file will be set as configuration attributes
210      * on the corresponding <code>LogFactory</code> instance.</p>
211      *
212      * @exception LogConfigurationException if the implementation class is not
213      *  available or cannot be instantiated.
214      */
215     public static LogFactory getFactory() throws LogConfigurationException {
216 
217         // Identify the class loader we will be using
218         ClassLoader contextClassLoader =
219             (ClassLoader)AccessController.doPrivileged(
220                 new PrivilegedAction() {
221                     public Object run() {
222                         return getContextClassLoader();
223                     }
224                 });
225 
226         // Return any previously registered factory for this class loader
227         LogFactory factory = getCachedFactory(contextClassLoader);
228         if (factory != null)
229             return factory;
230 
231 
232         // Load properties file.
233         // Will be used one way or another in the end.
234 
235         Properties props=null;
236         try {
237             InputStream stream = getResourceAsStream(contextClassLoader,
238                                                      FACTORY_PROPERTIES);
239 
240             if (stream != null) {
241                 props = new Properties();
242                 props.load(stream);
243                 stream.close();
244             }
245         } catch (IOException e) {
246         } catch (SecurityException e) {
247         }
248 
249 
250         // First, try the system property
251         try {
252             String factoryClass = System.getProperty(FACTORY_PROPERTY);
253             if (factoryClass != null) {
254                 factory = newFactory(factoryClass, contextClassLoader);
255             }
256         } catch (SecurityException e) {
257             ;  // ignore
258         }
259 
260 
261         // Second, try to find a service by using the JDK1.3 jar
262         // discovery mechanism. This will allow users to plug a logger
263         // by just placing it in the lib/ directory of the webapp ( or in
264         // CLASSPATH or equivalent ). This is similar to the second
265         // step, except that it uses the (standard?) jdk1.3 location in the jar.
266 
267         if (factory == null) {
268             try {
269                 InputStream is = getResourceAsStream(contextClassLoader,
270                                                      SERVICE_ID);
271 
272                 if( is != null ) {
273                     // This code is needed by EBCDIC and other strange systems.
274                     // It's a fix for bugs reported in xerces
275                     BufferedReader rd;
276                     try {
277                         rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
278                     } catch (java.io.UnsupportedEncodingException e) {
279                         rd = new BufferedReader(new InputStreamReader(is));
280                     }
281 
282                     String factoryClassName = rd.readLine();
283                     rd.close();
284 
285                     if (factoryClassName != null &&
286                         ! "".equals(factoryClassName)) {
287 
288                         factory= newFactory( factoryClassName, contextClassLoader );
289                     }
290                 }
291             } catch( Exception ex ) {
292                 ;
293             }
294         }
295 
296 
297         // Third try a properties file.
298         // If the properties file exists, it'll be read and the properties
299         // used. IMHO ( costin ) System property and JDK1.3 jar service
300         // should be enough for detecting the class name. The properties
301         // should be used to set the attributes ( which may be specific to
302         // the webapp, even if a default logger is set at JVM level by a
303         // system property )
304 
305         if (factory == null  &&  props != null) {
306             String factoryClass = props.getProperty(FACTORY_PROPERTY);
307             if (factoryClass != null) {
308                 factory = newFactory(factoryClass, contextClassLoader);
309             }
310         }
311 
312 
313         // Fourth, try the fallback implementation class
314 
315         if (factory == null) {
316             factory = newFactory(FACTORY_DEFAULT, LogFactory.class.getClassLoader());
317         }
318 
319         if (factory != null) {
320             /***
321              * Always cache using context class loader.
322              */
323             cacheFactory(contextClassLoader, factory);
324 
325             if( props!=null ) {
326                 Enumeration names = props.propertyNames();
327                 while (names.hasMoreElements()) {
328                     String name = (String) names.nextElement();
329                     String value = props.getProperty(name);
330                     factory.setAttribute(name, value);
331                 }
332             }
333         }
334 
335         return factory;
336     }
337 
338 
339     /***
340      * Convenience method to return a named logger, without the application
341      * having to care about factories.
342      *
343      * @param clazz Class from which a log name will be derived
344      *
345      * @exception LogConfigurationException if a suitable <code>Log</code>
346      *  instance cannot be returned
347      */
348     public static Log getLog(Class clazz)
349         throws LogConfigurationException {
350 
351         return (getFactory().getInstance(clazz));
352 
353     }
354 
355 
356     /***
357      * Convenience method to return a named logger, without the application
358      * having to care about factories.
359      *
360      * @param name Logical name of the <code>Log</code> instance to be
361      *  returned (the meaning of this name is only known to the underlying
362      *  logging implementation that is being wrapped)
363      *
364      * @exception LogConfigurationException if a suitable <code>Log</code>
365      *  instance cannot be returned
366      */
367     public static Log getLog(String name)
368         throws LogConfigurationException {
369 
370         return (getFactory().getInstance(name));
371 
372     }
373 
374 
375     /***
376      * Release any internal references to previously created {@link LogFactory}
377      * instances that have been associated with the specified class loader
378      * (if any), after calling the instance method <code>release()</code> on
379      * each of them.
380      *
381      * @param classLoader ClassLoader for which to release the LogFactory
382      */
383     public static void release(ClassLoader classLoader) {
384 
385         synchronized (factories) {
386             LogFactory factory = (LogFactory) factories.get(classLoader);
387             if (factory != null) {
388                 factory.release();
389                 factories.remove(classLoader);
390             }
391         }
392 
393     }
394 
395 
396     /***
397      * Release any internal references to previously created {@link LogFactory}
398      * instances, after calling the instance method <code>release()</code> on
399      * each of them.  This is useful in environments like servlet containers,
400      * which implement application reloading by throwing away a ClassLoader.
401      * Dangling references to objects in that class loader would prevent
402      * garbage collection.
403      */
404     public static void releaseAll() {
405 
406         synchronized (factories) {
407             Enumeration elements = factories.elements();
408             while (elements.hasMoreElements()) {
409                 LogFactory element = (LogFactory) elements.nextElement();
410                 element.release();
411             }
412             factories.clear();
413         }
414 
415     }
416 
417 
418     // ------------------------------------------------------ Protected Methods
419 
420 
421     /***
422      * Return the thread context class loader if available.
423      * Otherwise return null.
424      *
425      * The thread context class loader is available for JDK 1.2
426      * or later, if certain security conditions are met.
427      *
428      * @exception LogConfigurationException if a suitable class loader
429      * cannot be identified.
430      */
431     protected static ClassLoader getContextClassLoader()
432         throws LogConfigurationException
433     {
434         ClassLoader classLoader = null;
435 
436         try {
437             // Are we running on a JDK 1.2 or later system?
438             Method method = Thread.class.getMethod("getContextClassLoader", null);
439 
440             // Get the thread context class loader (if there is one)
441             try {
442                 classLoader = (ClassLoader)method.invoke(Thread.currentThread(), null);
443             } catch (IllegalAccessException e) {
444                 throw new LogConfigurationException
445                     ("Unexpected IllegalAccessException", e);
446             } catch (InvocationTargetException e) {
447                 /***
448                  * InvocationTargetException is thrown by 'invoke' when
449                  * the method being invoked (getContextClassLoader) throws
450                  * an exception.
451                  *
452                  * getContextClassLoader() throws SecurityException when
453                  * the context class loader isn't an ancestor of the
454                  * calling class's class loader, or if security
455                  * permissions are restricted.
456                  *
457                  * In the first case (not related), we want to ignore and
458                  * keep going.  We cannot help but also ignore the second
459                  * with the logic below, but other calls elsewhere (to
460                  * obtain a class loader) will trigger this exception where
461                  * we can make a distinction.
462                  */
463                 if (e.getTargetException() instanceof SecurityException) {
464                     ;  // ignore
465                 } else {
466                     // Capture 'e.getTargetException()' exception for details
467                     // alternate: log 'e.getTargetException()', and pass back 'e'.
468                     throw new LogConfigurationException
469                         ("Unexpected InvocationTargetException", e.getTargetException());
470                 }
471             }
472         } catch (NoSuchMethodException e) {
473             // Assume we are running on JDK 1.1
474             classLoader = LogFactory.class.getClassLoader();
475         }
476 
477         // Return the selected class loader
478         return classLoader;
479     }
480 
481     /***
482      * Check cached factories (keyed by contextClassLoader)
483      */
484     private static LogFactory getCachedFactory(ClassLoader contextClassLoader)
485     {
486         LogFactory factory = null;
487 
488         if (contextClassLoader != null)
489             factory = (LogFactory) factories.get(contextClassLoader);
490 
491         return factory;
492     }
493 
494     private static void cacheFactory(ClassLoader classLoader, LogFactory factory)
495     {
496         if (classLoader != null && factory != null)
497             factories.put(classLoader, factory);
498     }
499 
500     /***
501      * Return a new instance of the specified <code>LogFactory</code>
502      * implementation class, loaded by the specified class loader.
503      * If that fails, try the class loader used to load this
504      * (abstract) LogFactory.
505      *
506      * @param factoryClass Fully qualified name of the <code>LogFactory</code>
507      *  implementation class
508      * @param classLoader ClassLoader from which to load this class
509      *
510      * @exception LogConfigurationException if a suitable instance
511      *  cannot be created
512      */
513     protected static LogFactory newFactory(final String factoryClass,
514                                            final ClassLoader classLoader)
515         throws LogConfigurationException
516     {
517         Object result = AccessController.doPrivileged(
518             new PrivilegedAction() {
519                 public Object run() {
520                     // This will be used to diagnose bad configurations
521                     // and allow a useful message to be sent to the user
522                     Class logFactoryClass = null;
523                     try {
524                         if (classLoader != null) {
525                             try {
526                                 // First the given class loader param (thread class loader)
527 
528                                 // Warning: must typecast here & allow exception
529                                 // to be generated/caught & recast properly.
530                                 logFactoryClass = classLoader.loadClass(factoryClass);
531                                 return (LogFactory) logFactoryClass.newInstance();
532 
533                             } catch (ClassNotFoundException ex) {
534                                 if (classLoader == LogFactory.class.getClassLoader()) {
535                                     // Nothing more to try, onwards.
536                                     throw ex;
537                                 }
538                                 // ignore exception, continue
539                             } catch (NoClassDefFoundError e) {
540                                 if (classLoader == LogFactory.class.getClassLoader()) {
541                                     // Nothing more to try, onwards.
542                                     throw e;
543                                 }
544 
545                             } catch(ClassCastException e){
546 
547                               if (classLoader == LogFactory.class.getClassLoader()) {
548                                     // Nothing more to try, onwards (bug in loader implementation).
549                                     throw e;
550                                }
551                             }
552                             // Ignore exception, continue
553                         }
554 
555                         /* At this point, either classLoader == null, OR
556                          * classLoader was unable to load factoryClass.
557                          * Try the class loader that loaded this class:
558                          * LogFactory.getClassLoader().
559                          *
560                          * Notes:
561                          * a) LogFactory.class.getClassLoader() may return 'null'
562                          *    if LogFactory is loaded by the bootstrap classloader.
563                          * b) The Java endorsed library mechanism is instead
564                          *    Class.forName(factoryClass);
565                          */
566                         // Warning: must typecast here & allow exception
567                         // to be generated/caught & recast properly.
568                         logFactoryClass = Class.forName(factoryClass);
569                         return (LogFactory) logFactoryClass.newInstance();
570                     } catch (Exception e) {
571                         // Check to see if we've got a bad configuration
572                         if (logFactoryClass != null
573                             && !LogFactory.class.isAssignableFrom(logFactoryClass)) {
574                             return new LogConfigurationException(
575                                 "The chosen LogFactory implementation does not extend LogFactory."
576                                 + " Please check your configuration.",
577                                 e);
578                         }
579                         return new LogConfigurationException(e);
580                     }
581                 }
582             });
583 
584         if (result instanceof LogConfigurationException)
585             throw (LogConfigurationException)result;
586 
587         return (LogFactory)result;
588     }
589 
590     private static InputStream getResourceAsStream(final ClassLoader loader,
591                                                    final String name)
592     {
593         return (InputStream)AccessController.doPrivileged(
594             new PrivilegedAction() {
595                 public Object run() {
596                     if (loader != null) {
597                         return loader.getResourceAsStream(name);
598                     } else {
599                         return ClassLoader.getSystemResourceAsStream(name);
600                     }
601                 }
602             });
603     }
604 }