/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.util.logging;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowCloseListener;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.GWT;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
/**
* <code>LogManager</code> is used to maintain configuration properties of the
* logging framework, and to manage a hierarchical namespace of all named
* <code>Logger</code> objects.
* <p>
* There is only one global <code>LogManager</code> instance in the
* application, which can be get by calling static method
* <code>LogManager.getLogManager()</code>. This instance is created and
* initialized during class initialization and cannot be changed.
* </p>
* <p>
* The <code>LogManager</code> class can be specified by
* java.util.logging.manager system property, if the property is unavailable or
* invalid, the default class <code>java.util.logging.LogManager</code> will
* be used.
* </p>
* <p>
* When initialization, <code>LogManager</code> read its configuration from a
* properties file, which by default is the "lib/logging.properties" in the JRE
* directory.
* </p>
* <p>
* However, two optional system properties can be used to customize the initial
* configuration process of <code>LogManager</code>.
* <ul>
* <li>"java.util.logging.config.class"</li>
* <li>"java.util.logging.config.file"</li>
* </ul>
* </p>
* <p>
* These two properties can be set in three ways, by the Preferences API, by the
* "java" command line property definitions, or by system property definitions
* passed to JNI_CreateJavaVM.
* </p>
* <p>
* The "java.util.logging.config.class" should specifies a class name. If it is
* set, this given class will be loaded and instantiated during
* <code>LogManager</code> initialization, so that this object's default
* constructor can read the initial configuration and define properties for
* <code>LogManager</code>.
* </p>
* <p>
* If "java.util.logging.config.class" property is not set, or it is invalid, or
* some exception is thrown during the instantiation, then the
* "java.util.logging.config.file" system property can be used to specify a
* properties file. The <code>LogManager</code> will read initial
* configuration from this file.
* </p>
* <p>
* If neither of these properties is defined, or some exception is thrown
* during these two properties using, the <code>LogManager</code> will read
* its initial configuration from default properties file, as described above.
* </p>
* <p>
* The global logging properties may include:
* <ul>
* <li>"handlers". This property's values should be a list of class names for
* handler classes separated by whitespace, these classes must be subclasses of
* <code>Handler</code> and each must have a default constructor, these
* classes will be loaded, instantiated and registered as handlers on the root
* <code>Logger</code> (the <code>Logger</code> named ""). These
* <code>Handler</code>s maybe initialized lazily.</li>
* <li>"config". The property defines a list of class names separated by
* whitespace. Each class must have a default constructor, in which it can
* update the logging configuration, such as levels, handlers, or filters for
* some logger, etc. These classes will be loaded and instantiated during
* <code>LogManager</code> configuration</li>
* </ul>
* </p>
* <p>
* This class, together with any handler and configuration classes associated
* with it, <b>must</b> be loaded from the system classpath when
* <code>LogManager</code> configuration occurs.
* </p>
* <p>
* Besides global properties, the properties for loggers and Handlers can be
* specified in the property files. The names of these properties will start
* with the complete dot separated names for the handlers or loggers.
* </p>
* <p>
* In the <code>LogManager</code>'s hierarchical namespace,
* <code>Loggers</code> are organized based on their dot separated names. For
* example, "x.y.z" is child of "x.y".
* </p>
* <p>
* Levels for <code>Loggers</code> can be defined by properties whose name end
* with ".level". Thus "alogger.level" defines a level for the logger named as
* "alogger" and for all its children in the naming hierarchy. Log levels
* properties are read and applied in the same order as they are specified in
* the property file. The root logger's level can be defined by the property
* named as ".level".
* </p>
* <p>
* All methods on this type can be taken as being thread safe.
* </p>
*
*/
public class LogManager {
/*
* -------------------------------------------------------------------
* Class variables
* -------------------------------------------------------------------
*/
// The line separator of the underlying OS
// Use privileged code to read the line.separator system property
private static final String lineSeparator = "/";
// the singleton instance
static LogManager manager;
/**
* <p>The String value of the {@link LoggingMXBean}'s ObjectName.</p>
*/
public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; //$NON-NLS-1$
public static LoggingMXBean getLoggingMXBean() {
// logging.0=This method is not currently implemented.
throw new AssertionError("This method is not currently implemented."); //$NON-NLS-1$
}
/*
* -------------------------------------------------------------------
* Instance variables
* -------------------------------------------------------------------
*/
private HashMap loggers;
// the configuration properties
private HashMap props;
// the property change listener
private PropertyChangeSupport listeners;
/*
* -------------------------------------------------------------------
* Global initialization
* -------------------------------------------------------------------
*/
static {
// init LogManager singleton instance
manager = new LogManager();
// if global logger has been initialized, set root as its parent
Logger root = new Logger("", null); //$NON-NLS-1$
boolean levelSet = false;
final Element[] metas = getMetas();
if (metas != null) {
for (int i=0; i < metas.length; i++) {
final Element meta = metas[i];
final String name = DOM.getAttribute(meta, "name");
if ("gwtx:property".equals(name)) {
final String content = DOM.getAttribute(meta, "content");
final int eq = content.indexOf("=");
if (eq != -1) {
final String cname = content.substring(0, eq);
final String cvalue = content.substring(eq+1);
if ((".level").equals(cname)) {
root.setLevel(Level.parse(cvalue));
levelSet = true;
}
}
}
}
}
if (!levelSet) {
root.setLevel(Level.INFO);
}
Logger.global.setParent(root);
manager.addLogger(root);
manager.addLogger(Logger.global);
}
static Element[] getMetas() {
final JavaScriptObject metasHandel = getMetaTags();
final int metasLen = getMetaTagsLength(metasHandel);
final Element[] metas = new Element[metasLen];
for (int i=0; i < metasLen; i++) {
metas[i] = getMetaTag(metasHandel, i);
}
return metas;
}
private static native JavaScriptObject getMetaTags() /*-{
return $doc.getElementsByTagName("meta");
}-*/;
private static native int getMetaTagsLength(JavaScriptObject metas) /*-{
return metas.length;
}-*/;
private static native Element getMetaTag(JavaScriptObject metas, int i) /*-{
return metas[i];
}-*/;
/**
* Default constructor. This is not public because there should be only one
* <code>LogManager</code> instance, which can be get by
* <code>LogManager.getLogManager(</code>. This is protected so that
* application can subclass the object.
*/
protected LogManager() {
loggers = new HashMap();
props = new HashMap();
listeners = new PropertyChangeSupport(this);
// add shutdown hook to ensure that the associated resource will be
// freed when JVM exits
Window.addWindowCloseListener(new WindowCloseListener() {
public String onWindowClosing() {
return null; // nothing
}
public void onWindowClosed() {
reset();
}
});
}
/*
* -------------------------------------------------------------------
* Methods
* -------------------------------------------------------------------
*/
/*
* Package private utilities Returns the line separator of the underlying
* OS.
*/
static String getSystemLineSeparator() {
return lineSeparator;
}
/**
* Check that the caller has <code>LoggingPermission("control")</code> so
* that it is trusted to modify the configuration for logging framework. If
* the check passes, just return, otherwise <code>SecurityException</code>
* will be thrown.
*
* @throws SecurityException
* if there is a security manager in operation and the invoker
* of this method does not have the required security permission
* <code>LoggingPermission("control")</code>
*/
public void checkAccess() {
}
/**
* Add a given logger into the hierarchical namespace. The
* <code>Logger.addLogger()</code> factory methods call this method to add
* newly created Logger. This returns false if a logger with the given name
* has existed in the namespace
* <p>
* Note that the <code>LogManager</code> may only retain weak references
* to registered loggers. In order to prevent <code>Logger</code> objects
* from being unexpectedly garbage collected it is necessary for
* <i>applications</i> to maintain references to them.
* </p>
*
* @param logger
* the logger to be added
* @return true if the given logger is added into the namespace
* successfully, false if the logger of given name has existed in
* the namespace
*/
public synchronized boolean addLogger(Logger logger) {
String name = logger.getName();
if (null != loggers.get(name)) {
return false;
}
addToFamilyTree(logger, name);
loggers.put(name, logger);
logger.setManager(this);
return true;
}
private void addToFamilyTree(Logger logger, String name) {
Logger parent = null;
// find parent
int lastSeparator;
String parentName = name;
while ((lastSeparator = parentName.lastIndexOf('.')) != -1) {
parentName = parentName.substring(0, lastSeparator);
parent = (Logger)loggers.get(parentName);
if (parent != null) {
logger.internalSetParent(parent);
break;
}else if(getProperty(parentName+".level") != null || getProperty(parentName+".handlers") != null){//$NON-NLS-1$ //$NON-NLS-2$
parent = Logger.getLogger(parentName);
logger.internalSetParent(parent);
break;
}
}
if (parent == null && null != (parent = (Logger)loggers.get(""))) { //$NON-NLS-1$
logger.internalSetParent(parent);
}
// find children
//TODO: performance can be improved here?
Collection allLoggers = loggers.values();
for (Iterator it = allLoggers.iterator(); it.hasNext();) {
Logger child = (Logger)it.next();
Logger oldParent = child.getParent();
if (parent == oldParent
&& (name.length() == 0 || child.getName().startsWith(
name + '.'))) {
child.setParent(logger);
if (null != oldParent) {
//-- remove from old parent as the parent has been changed
oldParent.removeChild(child);
}
}
}
}
/**
* Get the logger with the given name
*
* @param name
* name of logger
* @return logger with given name, or null if nothing is found
*/
public synchronized Logger getLogger(String name) {
return (Logger)loggers.get(name);
}
/**
* Get a <code>Enumeration</code> of all registered logger names
*
* @return enumeration of registered logger names
*/
public synchronized Enumeration getLoggerNames() {
final Iterator iter = loggers.keySet().iterator();
return new Enumeration() {
public boolean hasMoreElements() {
return iter.hasNext();
}
public Object nextElement() {
return iter.next();
}
};
}
/**
* Get the global <code>LogManager</code> instance
*
* @return the global <code>LogManager</code> instance
*/
public static LogManager getLogManager() {
return manager;
}
/**
* Get the value of property with given name
*
* @param name
* the name of property
* @return the value of property
*/
public String getProperty(String name) {
return (String)props.get(name);
}
/**
* Reset configuration.
* <p>
* All handlers are closed and removed from any named loggers. All loggers'
* level is set to null, except the root logger's level is set to
* <code>Level.INFO</code>.
* </p>
*
* @throws SecurityException
* if security manager exists and it determines that caller does
* not have the required permissions to perform this action
*/
public void reset() {
props = new HashMap();
Enumeration names = getLoggerNames();
while(names.hasMoreElements()){
String name = (String)names.nextElement();
Logger logger = getLogger(name);
if(logger != null){
logger.reset();
}
}
Logger root = (Logger)loggers.get(""); //$NON-NLS-1$
if (null != root) {
root.setLevel(Level.INFO);
}
}
/**
* Add a <code>PropertyChangeListener</code>, which will be invoked when
* the properties are reread.
*
* @param l
* the <code>PropertyChangeListener</code> to be added
* @throws SecurityException
* if security manager exists and it determines that caller does
* not have the required permissions to perform this action
*/
public void addPropertyChangeListener(PropertyChangeListener l) {
if(l == null){
throw new NullPointerException();
}
listeners.addPropertyChangeListener(l);
}
/**
* Remove a <code>PropertyChangeListener</code>, do nothing if the given
* listener is not found.
*
* @param l
* the <code>PropertyChangeListener</code> to be removed
* @throws SecurityException
* if security manager exists and it determines that caller does
* not have the required permissions to perform this action
*/
public void removePropertyChangeListener(PropertyChangeListener l) {
listeners.removePropertyChangeListener(l);
}
}