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 java.util.logging;
019:
020: import java.beans.PropertyChangeListener;
021: import java.beans.PropertyChangeSupport;
022: import java.io.BufferedInputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.lang.management.ManagementFactory;
028: import java.lang.reflect.Method;
029: import java.security.AccessController;
030: import java.security.PrivilegedAction;
031: import java.util.Collection;
032: import java.util.Enumeration;
033: import java.util.Hashtable;
034: import java.util.Iterator;
035: import java.util.Properties;
036: import java.util.Set;
037: import java.util.StringTokenizer;
038:
039: import javax.management.MBeanServer;
040: import javax.management.ObjectInstance;
041: import javax.management.ObjectName;
042:
043: import org.apache.harmony.logging.internal.nls.Messages;
044:
045: /**
046: * <code>LogManager</code> is used to manage named <code>Logger</code>s and
047: * any shared logging properties.
048: * <p>
049: * There is one global <code>LogManager</code> instance in the application,
050: * which can be obtained by calling the static method
051: * <code>LogManager.getLogManager()</code>.
052: * </p>
053: * <p>
054: * All methods on this type can be taken as being thread safe.
055: * </p>
056: * <p>
057: * The <code>LogManager</code> class can be specified by the
058: * "java.util.logging.manager" system property. If the property is unavailable
059: * or invalid <code>java.util.logging.LogManager</code> will be used by
060: * default.
061: * </p>
062: * <p>
063: * On initialization, <code>LogManager</code> reads its configuration data
064: * from a properties file, which by default is the "lib/logging.properties" file
065: * in the JRE directory.
066: * </p>
067: * <p>
068: * However, two system properties can be used instead to customize the
069: * initialization of the <code>LogManager</code>:
070: * <ul>
071: * <li>"java.util.logging.config.class"</li>
072: * <li>"java.util.logging.config.file"</li>
073: * </ul>
074: * </p>
075: * <p>
076: * These properties can be set either by using the Preferences API, as a command
077: * line option or by passing the appropriate system property definitions to
078: * JNI_CreateJavaVM.
079: * </p>
080: * <p>
081: * The "java.util.logging.config.class" property should specify a class name. If
082: * it is set, this class will be loaded and instantiated during
083: * <code>LogManager</code>'s initialization, so that this object's default
084: * constructor can read the initial configuration and define properties for the
085: * <code>LogManager</code>.
086: * </p>
087: * <p>
088: * The "java.util.logging.config.file" system property can be used to specify a
089: * properties file if the "java.util.logging.config.class" property has not been
090: * used. This file will be read instead of the default properties file.
091: * </p>
092: * <p>
093: * Some global logging properties are as follows:
094: * <ul>
095: * <li>"handlers" - a list of handler classes, separated by whitespace. These
096: * classes must be subclasses of <code>Handler</code> and must have a public
097: * no-argument constructor. They will be registered with the root
098: * <code>Logger</code>.</li>
099: * <li>"config" - a list of configuration classes, separated by whitespace.
100: * These classes should also have a public no-argument default constructor,
101: * which should contain all the code for applying that configuration to the
102: * logging system.
103: * </ul>
104: * </p>
105: * <p>
106: * Besides global properties, properties for individual <code>Loggers</code>
107: * and <code>Handlers</code> can be specified in the property files. The names
108: * of these properties will start with the fully qualified name of the handler
109: * or logger.
110: * </p>
111: * <p>
112: * The <code>LogManager</code> organizes <code>Loggers</code> based on their
113: * fully qualified names. For example, "x.y.z" is child of "x.y".
114: * </p>
115: * <p>
116: * Levels for <code>Loggers</code> can be defined by properties whose name end
117: * with ".level". For example, "alogger.level = 4" sets the level for the logger
118: * "alogger" to 4, Any children of "alogger" will also be given the level 4
119: * unless specified lower down in the properties file. The property ".level"
120: * will set the log level for the root logger.
121: * </p>
122: *
123: */
124: public class LogManager {
125: /*
126: * ------------------------------------------------------------------- Class
127: * variables
128: * -------------------------------------------------------------------
129: */
130:
131: // The line separator of the underlying OS
132: // Use privileged code to read the line.separator system property
133: private static final String lineSeparator = getPrivilegedSystemProperty("line.separator"); //$NON-NLS-1$
134:
135: // The shared logging permission
136: private static final LoggingPermission perm = new LoggingPermission(
137: "control", null); //$NON-NLS-1$
138:
139: // the singleton instance
140: static LogManager manager;
141:
142: /**
143: * <p>
144: * The String value of the {@link LoggingMXBean}'s ObjectName.
145: * </p>
146: */
147: public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; //$NON-NLS-1$
148:
149: /**
150: * Get the <code>LoggingMXBean</code> instance
151: *
152: * @return the <code>LoggingMXBean</code> instance
153: */
154: public static LoggingMXBean getLoggingMXBean() {
155: try {
156: ObjectName loggingMXBeanName = new ObjectName(
157: LOGGING_MXBEAN_NAME);
158: MBeanServer platformBeanServer = ManagementFactory
159: .getPlatformMBeanServer();
160: Set loggingMXBeanSet = platformBeanServer.queryMBeans(
161: loggingMXBeanName, null);
162:
163: if (loggingMXBeanSet.size() != 1) {
164: // logging.21=There Can Be Only One logging MX bean.
165: throw new AssertionError(Messages
166: .getString("logging.21")); //$NON-NLS-1$
167: }
168:
169: Iterator i = loggingMXBeanSet.iterator();
170: ObjectInstance loggingMXBeanOI = (ObjectInstance) i.next();
171: String lmxbcn = loggingMXBeanOI.getClassName();
172: Class lmxbc = Class.forName(lmxbcn);
173: Method giMethod = lmxbc.getDeclaredMethod("getInstance"); //$NON-NLS-1$
174: giMethod.setAccessible(true);
175: LoggingMXBean lmxb = (LoggingMXBean) giMethod.invoke(null,
176: new Object[] {});
177:
178: return lmxb;
179: } catch (Exception e) {
180: // TODO
181: // e.printStackTrace();
182: }
183: // logging.22=Exception occurred while getting the logging MX bean.
184: throw new AssertionError(Messages.getString("logging.22")); //$NON-NLS-1$
185: }
186:
187: // FIXME: use weak reference to avoid heap memory leak
188: private Hashtable<String, Logger> loggers;
189:
190: // the configuration properties
191: private Properties props;
192:
193: // the property change listener
194: private PropertyChangeSupport listeners;
195:
196: static {
197: // init LogManager singleton instance
198: AccessController.doPrivileged(new PrivilegedAction<Object>() {
199: public Object run() {
200: String className = System
201: .getProperty("java.util.logging.manager"); //$NON-NLS-1$
202:
203: if (null != className) {
204: manager = (LogManager) getInstanceByClass(className);
205: }
206: if (null == manager) {
207: manager = new LogManager();
208: }
209:
210: // read configuration
211: try {
212: manager.readConfiguration();
213: } catch (Exception e) {
214: e.printStackTrace();
215: }
216:
217: // if global logger has been initialized, set root as its parent
218: Logger root = new Logger("", null); //$NON-NLS-1$
219: root.setLevel(Level.INFO);
220: Logger.global.setParent(root);
221:
222: manager.addLogger(root);
223: manager.addLogger(Logger.global);
224: return null;
225: }
226: });
227: }
228:
229: /**
230: * Default constructor. This is not public because there should be only one
231: * <code>LogManager</code> instance, which can be get by
232: * <code>LogManager.getLogManager(</code>. This is protected so that
233: * application can subclass the object.
234: */
235: protected LogManager() {
236: loggers = new Hashtable<String, Logger>();
237: props = new Properties();
238: listeners = new PropertyChangeSupport(this );
239: // add shutdown hook to ensure that the associated resource will be
240: // freed when JVM exits
241: AccessController.doPrivileged(new PrivilegedAction<Void>() {
242: public Void run() {
243: Runtime.getRuntime().addShutdownHook(new Thread() {
244: @Override
245: public void run() {
246: reset();
247: }
248: });
249: return null;
250: }
251: });
252: }
253:
254: /*
255: * Package private utilities Returns the line separator of the underlying
256: * OS.
257: */
258: static String getSystemLineSeparator() {
259: return lineSeparator;
260: }
261:
262: /**
263: * Check that the caller has <code>LoggingPermission("control")</code> so
264: * that it is trusted to modify the configuration for logging framework. If
265: * the check passes, just return, otherwise <code>SecurityException</code>
266: * will be thrown.
267: *
268: * @throws SecurityException
269: * if there is a security manager in operation and the invoker
270: * of this method does not have the required security permission
271: * <code>LoggingPermission("control")</code>
272: */
273: public void checkAccess() {
274: if (null != System.getSecurityManager()) {
275: System.getSecurityManager().checkPermission(perm);
276: }
277: }
278:
279: /**
280: * Add a given logger into the hierarchical namespace. The
281: * <code>Logger.addLogger()</code> factory methods call this method to add
282: * newly created Logger. This returns false if a logger with the given name
283: * has existed in the namespace
284: * <p>
285: * Note that the <code>LogManager</code> may only retain weak references
286: * to registered loggers. In order to prevent <code>Logger</code> objects
287: * from being unexpectedly garbage collected it is necessary for
288: * <i>applications</i> to maintain references to them.
289: * </p>
290: *
291: * @param logger
292: * the logger to be added
293: * @return true if the given logger is added into the namespace
294: * successfully, false if the logger of given name has existed in
295: * the namespace
296: */
297: public synchronized boolean addLogger(Logger logger) {
298: String name = logger.getName();
299: if (null != loggers.get(name)) {
300: return false;
301: }
302: addToFamilyTree(logger, name);
303: loggers.put(name, logger);
304: logger.setManager(this );
305: return true;
306: }
307:
308: private void addToFamilyTree(Logger logger, String name) {
309: Logger parent = null;
310: // find parent
311: int lastSeparator;
312: String parentName = name;
313: while ((lastSeparator = parentName.lastIndexOf('.')) != -1) {
314: parentName = parentName.substring(0, lastSeparator);
315: parent = loggers.get(parentName);
316: if (parent != null) {
317: logger.internalSetParent(parent);
318: break;
319: } else if (getProperty(parentName + ".level") != null || //$NON-NLS-1$
320: getProperty(parentName + ".handlers") != null) { //$NON-NLS-1$
321: parent = Logger.getLogger(parentName);
322: logger.internalSetParent(parent);
323: break;
324: }
325: }
326: if (parent == null && null != (parent = loggers.get(""))) { //$NON-NLS-1$
327: logger.internalSetParent(parent);
328: }
329:
330: // find children
331: // TODO: performance can be improved here?
332: Collection<Logger> allLoggers = loggers.values();
333: for (final Logger child : allLoggers) {
334: Logger oldParent = child.getParent();
335: if (parent == oldParent
336: && (name.length() == 0 || child.getName()
337: .startsWith(name + '.'))) {
338: final Logger this Logger = logger;
339: AccessController
340: .doPrivileged(new PrivilegedAction<Object>() {
341: public Object run() {
342: child.setParent(this Logger);
343: return null;
344: }
345: });
346: if (null != oldParent) {
347: // -- remove from old parent as the parent has been changed
348: oldParent.removeChild(child);
349: }
350: }
351: }
352: }
353:
354: /**
355: * Get the logger with the given name
356: *
357: * @param name
358: * name of logger
359: * @return logger with given name, or null if nothing is found
360: */
361: public synchronized Logger getLogger(String name) {
362: return loggers.get(name);
363: }
364:
365: /**
366: * Get a <code>Enumeration</code> of all registered logger names
367: *
368: * @return enumeration of registered logger names
369: */
370: public synchronized Enumeration<String> getLoggerNames() {
371: return loggers.keys();
372: }
373:
374: /**
375: * Get the global <code>LogManager</code> instance
376: *
377: * @return the global <code>LogManager</code> instance
378: */
379: public static LogManager getLogManager() {
380: return manager;
381: }
382:
383: /**
384: * Get the value of property with given name
385: *
386: * @param name
387: * the name of property
388: * @return the value of property
389: */
390: public String getProperty(String name) {
391: return props.getProperty(name);
392: }
393:
394: /**
395: * Re-initialize the properties and configuration. The initialization
396: * process is same as the <code>LogManager</code> instantiation.
397: * <p>
398: * A <code>PropertyChangeEvent</code> must be fired.
399: * </p>
400: *
401: * @throws IOException
402: * if any IO related problems happened
403: * @throws SecurityException
404: * if security manager exists and it determines that caller does
405: * not have the required permissions to perform this action
406: */
407: public void readConfiguration() throws IOException {
408: checkAccess();
409: // check config class
410: String configClassName = System
411: .getProperty("java.util.logging.config.class"); //$NON-NLS-1$
412: if (null == configClassName
413: || null == getInstanceByClass(configClassName)) {
414: // if config class failed, check config file
415: String configFile = System
416: .getProperty("java.util.logging.config.file"); //$NON-NLS-1$
417:
418: if (null == configFile) {
419: // if cannot find configFile, use default logging.properties
420: configFile = new StringBuilder()
421: .append(System.getProperty("java.home")).append(File.separator) //$NON-NLS-1$
422: .append("lib").append(File.separator).append( //$NON-NLS-1$
423: "logging.properties").toString(); //$NON-NLS-1$
424: }
425:
426: InputStream input = null;
427: try {
428: input = new BufferedInputStream(new FileInputStream(
429: configFile));
430: readConfigurationImpl(input);
431: } finally {
432: if (input != null) {
433: try {
434: input.close();
435: } catch (Exception e) {// ignore
436: }
437: }
438: }
439: }
440: }
441:
442: // use privilege code to get system property
443: static String getPrivilegedSystemProperty(final String key) {
444: return AccessController
445: .doPrivileged(new PrivilegedAction<String>() {
446: public String run() {
447: return System.getProperty(key);
448: }
449: });
450: }
451:
452: // use SystemClassLoader to load class from system classpath
453: static Object getInstanceByClass(final String className) {
454: try {
455: Class<?> clazz = ClassLoader.getSystemClassLoader()
456: .loadClass(className);
457: return clazz.newInstance();
458: } catch (Exception e) {
459: try {
460: Class<?> clazz = Thread.currentThread()
461: .getContextClassLoader().loadClass(className);
462: return clazz.newInstance();
463: } catch (Exception innerE) {
464: // logging.20=Loading class "{0}" failed
465: System.err.println(Messages.getString(
466: "logging.20", className)); //$NON-NLS-1$
467: System.err.println(innerE);
468: return null;
469: }
470: }
471:
472: }
473:
474: // actual initialization process from a given input stream
475: private synchronized void readConfigurationImpl(InputStream ins)
476: throws IOException {
477: reset();
478: props.load(ins);
479:
480: // parse property "config" and apply setting
481: String configs = props.getProperty("config"); //$NON-NLS-1$
482: if (null != configs) {
483: StringTokenizer st = new StringTokenizer(configs, " "); //$NON-NLS-1$
484: while (st.hasMoreTokens()) {
485: String configerName = st.nextToken();
486: getInstanceByClass(configerName);
487: }
488: }
489:
490: // set levels for logger
491: Collection<Logger> allLoggers = loggers.values();
492: for (Logger logger : allLoggers) {
493: String property = props.getProperty(logger.getName()
494: + ".level"); //$NON-NLS-1$
495: if (null != property) {
496: logger.setLevel(Level.parse(property));
497: }
498: }
499: listeners.firePropertyChange(null, null, null);
500: }
501:
502: /**
503: * Re-initialize the properties and configuration from the given
504: * <code>InputStream</code>
505: * <p>
506: * A <code>PropertyChangeEvent</code> must be fired.
507: * </p>
508: *
509: * @param ins
510: * the input stream.
511: * @throws IOException
512: * if any IO related problems happened
513: * @throws SecurityException
514: * if security manager exists and it determines that caller does
515: * not have the required permissions to perform this action
516: */
517: public void readConfiguration(InputStream ins) throws IOException {
518: checkAccess();
519: readConfigurationImpl(ins);
520: }
521:
522: /**
523: * Reset configuration.
524: * <p>
525: * All handlers are closed and removed from any named loggers. All loggers'
526: * level is set to null, except the root logger's level is set to
527: * <code>Level.INFO</code>.
528: * </p>
529: *
530: * @throws SecurityException
531: * if security manager exists and it determines that caller does
532: * not have the required permissions to perform this action
533: */
534: public void reset() {
535: checkAccess();
536: props = new Properties();
537: Enumeration<String> names = getLoggerNames();
538: while (names.hasMoreElements()) {
539: String name = names.nextElement();
540: Logger logger = getLogger(name);
541: if (logger != null) {
542: logger.reset();
543: }
544: }
545: Logger root = loggers.get(""); //$NON-NLS-1$
546: if (null != root) {
547: root.setLevel(Level.INFO);
548: }
549: }
550:
551: /**
552: * Add a <code>PropertyChangeListener</code>, which will be invoked when
553: * the properties are reread.
554: *
555: * @param l
556: * the <code>PropertyChangeListener</code> to be added
557: * @throws SecurityException
558: * if security manager exists and it determines that caller does
559: * not have the required permissions to perform this action
560: */
561: public void addPropertyChangeListener(PropertyChangeListener l) {
562: if (l == null) {
563: throw new NullPointerException();
564: }
565: checkAccess();
566: listeners.addPropertyChangeListener(l);
567: }
568:
569: /**
570: * Remove a <code>PropertyChangeListener</code>, do nothing if the given
571: * listener is not found.
572: *
573: * @param l
574: * the <code>PropertyChangeListener</code> to be removed
575: * @throws SecurityException
576: * if security manager exists and it determines that caller does
577: * not have the required permissions to perform this action
578: */
579: public void removePropertyChangeListener(PropertyChangeListener l) {
580: checkAccess();
581: listeners.removePropertyChangeListener(l);
582: }
583: }
|