001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.juli;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.net.URLClassLoader;
025: import java.security.AccessController;
026: import java.security.PrivilegedAction;
027: import java.util.Collections;
028: import java.util.Enumeration;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.Map;
032: import java.util.Properties;
033: import java.util.StringTokenizer;
034: import java.util.WeakHashMap;
035: import java.util.logging.Handler;
036: import java.util.logging.Level;
037: import java.util.logging.LogManager;
038: import java.util.logging.Logger;
039:
040: /**
041: * Per classloader LogManager implementation.
042: */
043: public class ClassLoaderLogManager extends LogManager {
044:
045: // -------------------------------------------------------------- Variables
046:
047: /**
048: * Map containing the classloader information, keyed per classloader. A
049: * weak hashmap is used to ensure no classloader reference is leaked from
050: * application redeployment.
051: */
052: protected final Map<ClassLoader, ClassLoaderLogInfo> classLoaderLoggers = new WeakHashMap<ClassLoader, ClassLoaderLogInfo>();
053:
054: /**
055: * This prefix is used to allow using prefixes for the properties names
056: * of handlers and their subcomponents.
057: */
058: protected ThreadLocal<String> prefix = new ThreadLocal<String>();
059:
060: // --------------------------------------------------------- Public Methods
061:
062: /**
063: * Add the specified logger to the classloader local configuration.
064: *
065: * @param logger The logger to be added
066: */
067: public synchronized boolean addLogger(final Logger logger) {
068:
069: final String loggerName = logger.getName();
070:
071: ClassLoader classLoader = Thread.currentThread()
072: .getContextClassLoader();
073: ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
074: if (info.loggers.containsKey(loggerName)) {
075: return false;
076: }
077: info.loggers.put(loggerName, logger);
078:
079: // Apply initial level for new logger
080: final String levelString = getProperty(loggerName + ".level");
081: if (levelString != null) {
082: try {
083: AccessController.doPrivileged(new PrivilegedAction() {
084: public Object run() {
085: logger
086: .setLevel(Level.parse(levelString
087: .trim()));
088: return null;
089: }
090: });
091: } catch (IllegalArgumentException e) {
092: // Leave level set to null
093: }
094: }
095:
096: // If any parent loggers have levels definied, make sure they are
097: // instantiated
098: int dotIndex = loggerName.lastIndexOf('.');
099: while (dotIndex >= 0) {
100: final String parentName = loggerName.substring(0, dotIndex);
101: if (getProperty(parentName + ".level") != null) {
102: Logger.getLogger(parentName);
103: break;
104: }
105: dotIndex = loggerName.lastIndexOf('.', dotIndex - 1);
106: }
107:
108: // Find associated node
109: LogNode node = info.rootNode.findNode(loggerName);
110: node.logger = logger;
111:
112: // Set parent logger
113: Logger parentLogger = node.findParentLogger();
114: if (parentLogger != null) {
115: doSetParentLogger(logger, parentLogger);
116: }
117:
118: // Tell children we are their new parent
119: node.setParentLogger(logger);
120:
121: // Add associated handlers, if any are defined using the .handlers property.
122: // In this case, handlers of the parent logger(s) will not be used
123: String handlers = getProperty(loggerName + ".handlers");
124: if (handlers != null) {
125: logger.setUseParentHandlers(false);
126: StringTokenizer tok = new StringTokenizer(handlers, ",");
127: while (tok.hasMoreTokens()) {
128: String handlerName = (tok.nextToken().trim());
129: Handler handler = null;
130: ClassLoader current = classLoader;
131: while (current != null) {
132: info = (ClassLoaderLogInfo) classLoaderLoggers
133: .get(current);
134: if (info != null) {
135: handler = (Handler) info.handlers
136: .get(handlerName);
137: if (handler != null) {
138: break;
139: }
140: }
141: current = current.getParent();
142: }
143: if (handler != null) {
144: logger.addHandler(handler);
145: }
146: }
147: }
148:
149: // Parse useParentHandlers to set if the logger should delegate to its parent.
150: // Unlike java.util.logging, the default is to not delegate if a list of handlers
151: // has been specified for the logger.
152: String useParentHandlersString = getProperty(loggerName
153: + ".useParentHandlers");
154: if (Boolean.valueOf(useParentHandlersString).booleanValue()) {
155: logger.setUseParentHandlers(true);
156: }
157:
158: return true;
159: }
160:
161: /**
162: * Get the logger associated with the specified name inside
163: * the classloader local configuration. If this returns null,
164: * and the call originated for Logger.getLogger, a new
165: * logger with the specified name will be instantiated and
166: * added using addLogger.
167: *
168: * @param name The name of the logger to retrieve
169: */
170: public synchronized Logger getLogger(final String name) {
171: ClassLoader classLoader = Thread.currentThread()
172: .getContextClassLoader();
173: return (Logger) getClassLoaderInfo(classLoader).loggers
174: .get(name);
175: }
176:
177: /**
178: * Get an enumeration of the logger names currently defined in the
179: * classloader local configuration.
180: */
181: public synchronized Enumeration<String> getLoggerNames() {
182: ClassLoader classLoader = Thread.currentThread()
183: .getContextClassLoader();
184: return Collections
185: .enumeration(getClassLoaderInfo(classLoader).loggers
186: .keySet());
187: }
188:
189: /**
190: * Get the value of the specified property in the classloader local
191: * configuration.
192: *
193: * @param name The property name
194: */
195: public String getProperty(String name) {
196: ClassLoader classLoader = Thread.currentThread()
197: .getContextClassLoader();
198: String prefix = (String) this .prefix.get();
199: if (prefix != null) {
200: name = prefix + name;
201: }
202: ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
203: String result = info.props.getProperty(name);
204: // If the property was not found, and the current classloader had no
205: // configuration (property list is empty), look for the parent classloader
206: // properties.
207: if ((result == null) && (info.props.isEmpty())) {
208: ClassLoader current = classLoader.getParent();
209: while (current != null) {
210: info = (ClassLoaderLogInfo) classLoaderLoggers
211: .get(current);
212: if (info != null) {
213: result = info.props.getProperty(name);
214: if ((result != null) || (!info.props.isEmpty())) {
215: break;
216: }
217: }
218: current = current.getParent();
219: }
220: if (result == null) {
221: result = super .getProperty(name);
222: }
223: }
224: // Simple property replacement (mostly for folder names)
225: if (result != null) {
226: result = replace(result);
227: }
228: return result;
229: }
230:
231: public void readConfiguration() throws IOException,
232: SecurityException {
233:
234: checkAccess();
235:
236: readConfiguration(Thread.currentThread()
237: .getContextClassLoader());
238:
239: }
240:
241: public void readConfiguration(InputStream is) throws IOException,
242: SecurityException {
243:
244: checkAccess();
245: reset();
246:
247: readConfiguration(is, Thread.currentThread()
248: .getContextClassLoader());
249:
250: }
251:
252: // ------------------------------------------------------ Protected Methods
253:
254: /**
255: * Retrieve the configuration associated with the specified classloader. If
256: * it does not exist, it will be created.
257: *
258: * @param classLoader The classloader for which we will retrieve or build the
259: * configuration
260: */
261: protected ClassLoaderLogInfo getClassLoaderInfo(
262: ClassLoader classLoader) {
263:
264: if (classLoader == null) {
265: classLoader = ClassLoader.getSystemClassLoader();
266: }
267: ClassLoaderLogInfo info = (ClassLoaderLogInfo) classLoaderLoggers
268: .get(classLoader);
269: if (info == null) {
270: final ClassLoader classLoaderParam = classLoader;
271: AccessController.doPrivileged(new PrivilegedAction() {
272: public Object run() {
273: try {
274: readConfiguration(classLoaderParam);
275: } catch (IOException e) {
276: // Ignore
277: }
278: return null;
279: }
280: });
281: info = (ClassLoaderLogInfo) classLoaderLoggers
282: .get(classLoader);
283: }
284: return info;
285: }
286:
287: /**
288: * Read configuration for the specified classloader.
289: *
290: * @param classLoader
291: * @throws IOException Errot
292: */
293: protected void readConfiguration(ClassLoader classLoader)
294: throws IOException {
295:
296: InputStream is = null;
297: // Special case for URL classloaders which are used in containers:
298: // only look in the local repositories to avoid redefining loggers 20 times
299: if ((classLoader instanceof URLClassLoader)
300: && (((URLClassLoader) classLoader)
301: .findResource("logging.properties") != null)) {
302: is = classLoader.getResourceAsStream("logging.properties");
303: }
304: if ((is == null)
305: && (classLoader == ClassLoader.getSystemClassLoader())) {
306: String configFileStr = System
307: .getProperty("java.util.logging.config.file");
308: if (configFileStr != null) {
309: try {
310: is = new FileInputStream(replace(configFileStr));
311: } catch (IOException e) {
312: // Ignore
313: }
314: }
315: // Try the default JVM configuration
316: if (is == null) {
317: File defaultFile = new File(new File(System
318: .getProperty("java.home"), "lib"),
319: "logging.properties");
320: try {
321: is = new FileInputStream(defaultFile);
322: } catch (IOException e) {
323: // Critical problem, do something ...
324: }
325: }
326: }
327:
328: Logger localRootLogger = new RootLogger();
329: if (is == null) {
330: // Retrieve the root logger of the parent classloader instead
331: ClassLoader current = classLoader.getParent();
332: ClassLoaderLogInfo info = null;
333: while (current != null && info == null) {
334: info = getClassLoaderInfo(current);
335: current = current.getParent();
336: }
337: if (info != null) {
338: localRootLogger.setParent(info.rootNode.logger);
339: }
340: }
341: ClassLoaderLogInfo info = new ClassLoaderLogInfo(new LogNode(
342: null, localRootLogger));
343: classLoaderLoggers.put(classLoader, info);
344:
345: if (is != null) {
346: readConfiguration(is, classLoader);
347: }
348: addLogger(localRootLogger);
349:
350: }
351:
352: /**
353: * Load specified configuration.
354: *
355: * @param is InputStream to the properties file
356: * @param classLoader for which the configuration will be loaded
357: * @throws IOException If something wrong happens during loading
358: */
359: protected void readConfiguration(InputStream is,
360: ClassLoader classLoader) throws IOException {
361:
362: ClassLoaderLogInfo info = (ClassLoaderLogInfo) classLoaderLoggers
363: .get(classLoader);
364:
365: try {
366: info.props.load(is);
367: } catch (IOException e) {
368: // Report error
369: System.err.println("Configuration error");
370: e.printStackTrace();
371: } finally {
372: try {
373: is.close();
374: } catch (Throwable t) {
375: }
376: }
377:
378: // Create handlers for the root logger of this classloader
379: String rootHandlers = info.props.getProperty(".handlers");
380: String handlers = info.props.getProperty("handlers");
381: Logger localRootLogger = info.rootNode.logger;
382: if (handlers != null) {
383: StringTokenizer tok = new StringTokenizer(handlers, ",");
384: while (tok.hasMoreTokens()) {
385: String handlerName = (tok.nextToken().trim());
386: String handlerClassName = handlerName;
387: String prefix = "";
388: if (handlerClassName.length() <= 0) {
389: continue;
390: }
391: // Parse and remove a prefix (prefix start with a digit, such as
392: // "10WebappFooHanlder.")
393: if (Character.isDigit(handlerClassName.charAt(0))) {
394: int pos = handlerClassName.indexOf('.');
395: if (pos >= 0) {
396: prefix = handlerClassName.substring(0, pos + 1);
397: handlerClassName = handlerClassName
398: .substring(pos + 1);
399: }
400: }
401: try {
402: this .prefix.set(prefix);
403: Handler handler = (Handler) classLoader.loadClass(
404: handlerClassName).newInstance();
405: // The specification strongly implies all configuration should be done
406: // during the creation of the handler object.
407: // This includes setting level, filter, formatter and encoding.
408: this .prefix.set(null);
409: info.handlers.put(handlerName, handler);
410: if (rootHandlers == null) {
411: localRootLogger.addHandler(handler);
412: }
413: } catch (Exception e) {
414: // Report error
415: System.err.println("Handler error");
416: e.printStackTrace();
417: }
418: }
419:
420: }
421:
422: }
423:
424: /**
425: * Set parent child relationship between the two specified loggers.
426: *
427: * @param logger
428: * @param parent
429: */
430: protected static void doSetParentLogger(final Logger logger,
431: final Logger parent) {
432: AccessController.doPrivileged(new PrivilegedAction() {
433: public Object run() {
434: logger.setParent(parent);
435: return null;
436: }
437: });
438: }
439:
440: /**
441: * System property replacement in the given string.
442: *
443: * @param str The original string
444: * @return the modified string
445: */
446: protected String replace(String str) {
447: String result = str;
448: if (result.startsWith("${")) {
449: int pos = result.indexOf('}');
450: if (pos != -1) {
451: String propName = result.substring(2, pos);
452: String replacement = System.getProperty(propName);
453: if (replacement != null) {
454: result = replacement + result.substring(pos + 1);
455: }
456: }
457: }
458: return result;
459: }
460:
461: // ---------------------------------------------------- LogNode Inner Class
462:
463: protected static final class LogNode {
464: Logger logger;
465:
466: protected final Map<String, LogNode> children = new HashMap<String, LogNode>();
467:
468: protected final LogNode parent;
469:
470: LogNode(final LogNode parent, final Logger logger) {
471: this .parent = parent;
472: this .logger = logger;
473: }
474:
475: LogNode(final LogNode parent) {
476: this (parent, null);
477: }
478:
479: LogNode findNode(String name) {
480: LogNode currentNode = this ;
481: if (logger.getName().equals(name)) {
482: return this ;
483: }
484: while (name != null) {
485: final int dotIndex = name.indexOf('.');
486: final String nextName;
487: if (dotIndex < 0) {
488: nextName = name;
489: name = null;
490: } else {
491: nextName = name.substring(0, dotIndex);
492: name = name.substring(dotIndex + 1);
493: }
494: LogNode childNode = (LogNode) currentNode.children
495: .get(nextName);
496: if (childNode == null) {
497: childNode = new LogNode(currentNode);
498: currentNode.children.put(nextName, childNode);
499: }
500: currentNode = childNode;
501: }
502: return currentNode;
503: }
504:
505: Logger findParentLogger() {
506: Logger logger = null;
507: LogNode node = parent;
508: while (node != null && logger == null) {
509: logger = node.logger;
510: node = node.parent;
511: }
512: return logger;
513: }
514:
515: void setParentLogger(final Logger parent) {
516: for (final Iterator iter = children.values().iterator(); iter
517: .hasNext();) {
518: final LogNode childNode = (LogNode) iter.next();
519: if (childNode.logger == null) {
520: childNode.setParentLogger(parent);
521: } else {
522: doSetParentLogger(childNode.logger, parent);
523: }
524: }
525: }
526:
527: }
528:
529: // -------------------------------------------- ClassLoaderInfo Inner Class
530:
531: protected static final class ClassLoaderLogInfo {
532: final LogNode rootNode;
533: final Map<String, Logger> loggers = new HashMap<String, Logger>();
534: final Map<String, Handler> handlers = new HashMap<String, Handler>();
535: final Properties props = new Properties();
536:
537: ClassLoaderLogInfo(final LogNode rootNode) {
538: this .rootNode = rootNode;
539: }
540:
541: }
542:
543: // ------------------------------------------------- RootLogger Inner Class
544:
545: /**
546: * This class is needed to instantiate the root of each per classloader
547: * hierarchy.
548: */
549: protected class RootLogger extends Logger {
550: public RootLogger() {
551: super ("", null);
552: }
553: }
554:
555: }
|