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