1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.juli;
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.URLClassLoader;
25 import java.security.AccessController;
26 import java.security.PrivilegedAction;
27 import java.util.Collections;
28 import java.util.Enumeration;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.StringTokenizer;
34 import java.util.WeakHashMap;
35 import java.util.logging.Handler;
36 import java.util.logging.Level;
37 import java.util.logging.LogManager;
38 import java.util.logging.Logger;
39
40
41 /**
42 * Per classloader LogManager implementation.
43 */
44 public class ClassLoaderLogManager extends LogManager {
45
46
47 // -------------------------------------------------------------- Variables
48
49
50 /**
51 * Map containing the classloader information, keyed per classloader. A
52 * weak hashmap is used to ensure no classloader reference is leaked from
53 * application redeployment.
54 */
55 protected final Map<ClassLoader, ClassLoaderLogInfo> classLoaderLoggers =
56 new WeakHashMap<ClassLoader, ClassLoaderLogInfo>();
57
58
59 /**
60 * This prefix is used to allow using prefixes for the properties names
61 * of handlers and their subcomponents.
62 */
63 protected ThreadLocal<String> prefix = new ThreadLocal<String>();
64
65
66 // --------------------------------------------------------- Public Methods
67
68
69 /**
70 * Add the specified logger to the classloader local configuration.
71 *
72 * @param logger The logger to be added
73 */
74 public synchronized boolean addLogger(final Logger logger) {
75
76 final String loggerName = logger.getName();
77
78 ClassLoader classLoader =
79 Thread.currentThread().getContextClassLoader();
80 ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
81 if (info.loggers.containsKey(loggerName)) {
82 return false;
83 }
84 info.loggers.put(loggerName, logger);
85
86 // Apply initial level for new logger
87 final String levelString = getProperty(loggerName + ".level");
88 if (levelString != null) {
89 try {
90 AccessController.doPrivileged(new PrivilegedAction() {
91 public Object run() {
92 logger.setLevel(Level.parse(levelString.trim()));
93 return null;
94 }
95 });
96 } catch (IllegalArgumentException e) {
97 // Leave level set to null
98 }
99 }
100
101 // If any parent loggers have levels definied, make sure they are
102 // instantiated
103 int dotIndex = loggerName.lastIndexOf('.');
104 while (dotIndex >= 0) {
105 final String parentName = loggerName.substring(0, dotIndex);
106 if (getProperty(parentName + ".level") != null) {
107 Logger.getLogger(parentName);
108 break;
109 }
110 dotIndex = loggerName.lastIndexOf('.', dotIndex - 1);
111 }
112
113 // Find associated node
114 LogNode node = info.rootNode.findNode(loggerName);
115 node.logger = logger;
116
117 // Set parent logger
118 Logger parentLogger = node.findParentLogger();
119 if (parentLogger != null) {
120 doSetParentLogger(logger, parentLogger);
121 }
122
123 // Tell children we are their new parent
124 node.setParentLogger(logger);
125
126 // Add associated handlers, if any are defined using the .handlers property.
127 // In this case, handlers of the parent logger(s) will not be used
128 String handlers = getProperty(loggerName + ".handlers");
129 if (handlers != null) {
130 logger.setUseParentHandlers(false);
131 StringTokenizer tok = new StringTokenizer(handlers, ",");
132 while (tok.hasMoreTokens()) {
133 String handlerName = (tok.nextToken().trim());
134 Handler handler = null;
135 ClassLoader current = classLoader;
136 while (current != null) {
137 info = (ClassLoaderLogInfo) classLoaderLoggers.get(current);
138 if (info != null) {
139 handler = (Handler) info.handlers.get(handlerName);
140 if (handler != null) {
141 break;
142 }
143 }
144 current = current.getParent();
145 }
146 if (handler != null) {
147 logger.addHandler(handler);
148 }
149 }
150 }
151
152 // Parse useParentHandlers to set if the logger should delegate to its parent.
153 // Unlike java.util.logging, the default is to not delegate if a list of handlers
154 // has been specified for the logger.
155 String useParentHandlersString = getProperty(loggerName + ".useParentHandlers");
156 if (Boolean.valueOf(useParentHandlersString).booleanValue()) {
157 logger.setUseParentHandlers(true);
158 }
159
160 return true;
161 }
162
163
164 /**
165 * Get the logger associated with the specified name inside
166 * the classloader local configuration. If this returns null,
167 * and the call originated for Logger.getLogger, a new
168 * logger with the specified name will be instantiated and
169 * added using addLogger.
170 *
171 * @param name The name of the logger to retrieve
172 */
173 public synchronized Logger getLogger(final String name) {
174 ClassLoader classLoader = Thread.currentThread()
175 .getContextClassLoader();
176 return (Logger) getClassLoaderInfo(classLoader).loggers.get(name);
177 }
178
179
180 /**
181 * Get an enumeration of the logger names currently defined in the
182 * classloader local configuration.
183 */
184 public synchronized Enumeration<String> getLoggerNames() {
185 ClassLoader classLoader = Thread.currentThread()
186 .getContextClassLoader();
187 return Collections.enumeration(getClassLoaderInfo(classLoader).loggers.keySet());
188 }
189
190
191 /**
192 * Get the value of the specified property in the classloader local
193 * configuration.
194 *
195 * @param name The property name
196 */
197 public String getProperty(String name) {
198 ClassLoader classLoader = Thread.currentThread()
199 .getContextClassLoader();
200 String prefix = (String) this.prefix.get();
201 if (prefix != null) {
202 name = prefix + name;
203 }
204 ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
205 String result = info.props.getProperty(name);
206 // If the property was not found, and the current classloader had no
207 // configuration (property list is empty), look for the parent classloader
208 // properties.
209 if ((result == null) && (info.props.isEmpty())) {
210 ClassLoader current = classLoader.getParent();
211 while (current != null) {
212 info = (ClassLoaderLogInfo) classLoaderLoggers.get(current);
213 if (info != null) {
214 result = info.props.getProperty(name);
215 if ((result != null) || (!info.props.isEmpty())) {
216 break;
217 }
218 }
219 current = current.getParent();
220 }
221 if (result == null) {
222 result = super.getProperty(name);
223 }
224 }
225 // Simple property replacement (mostly for folder names)
226 if (result != null) {
227 result = replace(result);
228 }
229 return result;
230 }
231
232
233 public void readConfiguration()
234 throws IOException, SecurityException {
235
236 checkAccess();
237
238 readConfiguration(Thread.currentThread().getContextClassLoader());
239
240 }
241
242 public void readConfiguration(InputStream is)
243 throws IOException, SecurityException {
244
245 checkAccess();
246 reset();
247
248 readConfiguration(is, Thread.currentThread().getContextClassLoader());
249
250 }
251
252 // ------------------------------------------------------ Protected Methods
253
254
255 /**
256 * Retrieve the configuration associated with the specified classloader. If
257 * it does not exist, it will be created.
258 *
259 * @param classLoader The classloader for which we will retrieve or build the
260 * configuration
261 */
262 protected ClassLoaderLogInfo getClassLoaderInfo(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.get(classLoader);
282 }
283 return info;
284 }
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).findResource("logging.properties") != null)) {
301 is = classLoader.getResourceAsStream("logging.properties");
302 }
303 if ((is == null) && (classLoader == ClassLoader.getSystemClassLoader())) {
304 String configFileStr = System.getProperty("java.util.logging.config.file");
305 if (configFileStr != null) {
306 try {
307 is = new FileInputStream(replace(configFileStr));
308 } catch (IOException e) {
309 // Ignore
310 }
311 }
312 // Try the default JVM configuration
313 if (is == null) {
314 File defaultFile = new File(new File(System.getProperty("java.home"), "lib"),
315 "logging.properties");
316 try {
317 is = new FileInputStream(defaultFile);
318 } catch (IOException e) {
319 // Critical problem, do something ...
320 }
321 }
322 }
323
324 Logger localRootLogger = new RootLogger();
325 if (is == null) {
326 // Retrieve the root logger of the parent classloader instead
327 ClassLoader current = classLoader.getParent();
328 ClassLoaderLogInfo info = null;
329 while (current != null && info == null) {
330 info = getClassLoaderInfo(current);
331 current = current.getParent();
332 }
333 if (info != null) {
334 localRootLogger.setParent(info.rootNode.logger);
335 }
336 }
337 ClassLoaderLogInfo info =
338 new ClassLoaderLogInfo(new LogNode(null, localRootLogger));
339 classLoaderLoggers.put(classLoader, info);
340
341 if (is != null) {
342 readConfiguration(is, classLoader);
343 }
344 addLogger(localRootLogger);
345
346 }
347
348
349 /**
350 * Load specified configuration.
351 *
352 * @param is InputStream to the properties file
353 * @param classLoader for which the configuration will be loaded
354 * @throws IOException If something wrong happens during loading
355 */
356 protected void readConfiguration(InputStream is, ClassLoader classLoader)
357 throws IOException {
358
359 ClassLoaderLogInfo info =
360 (ClassLoaderLogInfo) classLoaderLoggers.get(classLoader);
361
362 try {
363 info.props.load(is);
364 } catch (IOException e) {
365 // Report error
366 System.err.println("Configuration error");
367 e.printStackTrace();
368 } finally {
369 try {
370 is.close();
371 } catch (Throwable t) {}
372 }
373
374 // Create handlers for the root logger of this classloader
375 String rootHandlers = info.props.getProperty(".handlers");
376 String handlers = info.props.getProperty("handlers");
377 Logger localRootLogger = info.rootNode.logger;
378 if (handlers != null) {
379 StringTokenizer tok = new StringTokenizer(handlers, ",");
380 while (tok.hasMoreTokens()) {
381 String handlerName = (tok.nextToken().trim());
382 String handlerClassName = handlerName;
383 String prefix = "";
384 if (handlerClassName.length() <= 0) {
385 continue;
386 }
387 // Parse and remove a prefix (prefix start with a digit, such as
388 // "10WebappFooHanlder.")
389 if (Character.isDigit(handlerClassName.charAt(0))) {
390 int pos = handlerClassName.indexOf('.');
391 if (pos >= 0) {
392 prefix = handlerClassName.substring(0, pos + 1);
393 handlerClassName = handlerClassName.substring(pos + 1);
394 }
395 }
396 try {
397 this.prefix.set(prefix);
398 Handler handler =
399 (Handler) classLoader.loadClass(handlerClassName).newInstance();
400 // The specification strongly implies all configuration should be done
401 // during the creation of the handler object.
402 // This includes setting level, filter, formatter and encoding.
403 this.prefix.set(null);
404 info.handlers.put(handlerName, handler);
405 if (rootHandlers == null) {
406 localRootLogger.addHandler(handler);
407 }
408 } catch (Exception e) {
409 // Report error
410 System.err.println("Handler error");
411 e.printStackTrace();
412 }
413 }
414
415 }
416
417 }
418
419
420 /**
421 * Set parent child relationship between the two specified loggers.
422 *
423 * @param logger
424 * @param parent
425 */
426 protected static void doSetParentLogger(final Logger logger,
427 final Logger parent) {
428 AccessController.doPrivileged(new PrivilegedAction() {
429 public Object run() {
430 logger.setParent(parent);
431 return null;
432 }
433 });
434 }
435
436
437 /**
438 * System property replacement in the given string.
439 *
440 * @param str The original string
441 * @return the modified string
442 */
443 protected String replace(String str) {
444 String result = str;
445 int pos_start = result.indexOf("${");
446 if (pos_start != -1) {
447 int pos_end = result.indexOf('}');
448 if (pos_end != -1) {
449 String propName = result.substring(pos_start + 2, pos_end);
450 String replacement = System.getProperty(propName);
451 if (replacement != null) {
452 if(pos_start >0) {
453 result = result.substring(0,pos_start) +
454 replacement + replace(result.substring(pos_end + 1));
455 } else {
456 result = replacement + replace(result.substring(pos_end + 1));
457 }
458 }
459 }
460 }
461 return result;
462 }
463
464
465 // ---------------------------------------------------- LogNode Inner Class
466
467
468 protected static final class LogNode {
469 Logger logger;
470
471 protected final Map<String, LogNode> children =
472 new HashMap<String, LogNode>();
473
474 protected final LogNode parent;
475
476 LogNode(final LogNode parent, final Logger logger) {
477 this.parent = parent;
478 this.logger = logger;
479 }
480
481 LogNode(final LogNode parent) {
482 this(parent, null);
483 }
484
485 LogNode findNode(String name) {
486 LogNode currentNode = this;
487 if (logger.getName().equals(name)) {
488 return this;
489 }
490 while (name != null) {
491 final int dotIndex = name.indexOf('.');
492 final String nextName;
493 if (dotIndex < 0) {
494 nextName = name;
495 name = null;
496 } else {
497 nextName = name.substring(0, dotIndex);
498 name = name.substring(dotIndex + 1);
499 }
500 LogNode childNode = (LogNode) currentNode.children
501 .get(nextName);
502 if (childNode == null) {
503 childNode = new LogNode(currentNode);
504 currentNode.children.put(nextName, childNode);
505 }
506 currentNode = childNode;
507 }
508 return currentNode;
509 }
510
511 Logger findParentLogger() {
512 Logger logger = null;
513 LogNode node = parent;
514 while (node != null && logger == null) {
515 logger = node.logger;
516 node = node.parent;
517 }
518 return logger;
519 }
520
521 void setParentLogger(final Logger parent) {
522 for (final Iterator iter = children.values().iterator(); iter
523 .hasNext();) {
524 final LogNode childNode = (LogNode) iter.next();
525 if (childNode.logger == null) {
526 childNode.setParentLogger(parent);
527 } else {
528 doSetParentLogger(childNode.logger, parent);
529 }
530 }
531 }
532
533 }
534
535
536 // -------------------------------------------- ClassLoaderInfo Inner Class
537
538
539 protected static final class ClassLoaderLogInfo {
540 final LogNode rootNode;
541 final Map<String, Logger> loggers = new HashMap<String, Logger>();
542 final Map<String, Handler> handlers = new HashMap<String, Handler>();
543 final Properties props = new Properties();
544
545 ClassLoaderLogInfo(final LogNode rootNode) {
546 this.rootNode = rootNode;
547 }
548
549 }
550
551
552 // ------------------------------------------------- RootLogger Inner Class
553
554
555 /**
556 * This class is needed to instantiate the root of each per classloader
557 * hierarchy.
558 */
559 protected class RootLogger extends Logger {
560 public RootLogger() {
561 super("", null);
562 }
563 }
564
565
566 }