/*
* Copyright 2008 the original author or authors.
* Copyright 2005 Sun Microsystems, Inc.
*
* Licensed 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 org.rioproject.log;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.*;
/**
* The LoggerConfig class has been provided for dynamic services to specify
* {@link java.util.logging.Logger} attributes ({@link java.util.logging.Level},
* {@link java.util.logging.Handler}, etc...) and have the attributes established
* without depending on machine resident <code>logger.properties</code>
* configuration attributes to be set.
*
* @author Dennis Reedy
*/
@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
public class LoggerConfig implements Serializable {
static final long serialVersionUID = 1L;
/** The Logger name */
private String loggerName;
/**
* The log level specifying which message levels will be logged by the
* logger
*/
private Level level;
/**
* Specify whether or not the logger should send its output to it's parent
* Logger
*/
private boolean useParentHandlers;
/** Name of ResourceBundle to be used for localizing messages for the logger */
private String resourceBundleName;
/** Array of LogHandlerConfig instances */
private LogHandlerConfig[] handlers;
/** The logger for this utility */
private static Logger myLogger = Logger.getLogger("org.rioproject.log");
/** An instance of the Logger */
private transient Logger logger;
/* Instance of a HandlerFilter (if we created one) */
private transient HandlerFilter handlerFilter;
/**
* Create a LoggerConfig, send logging output to its parent's handlers, with
* no resource bundle
*
* @param loggerName The Logger name
* @param level Set the log level specifying which message levels will be
* logged by the logger
*/
public LoggerConfig(String loggerName, Level level) {
this(loggerName, level, true, null, new LogHandlerConfig[0]);
}
/**
* Create a LoggerConfig, send logging output to its parent's handlers, with
* no resource bundle
*
* @param loggerName The Logger name
* @param level Set the log level specifying which message levels will be
* logged by the logger
* @param handlerConfigs An Array of LogHandlerConfig instances
*/
public LoggerConfig(String loggerName, Level level, LogHandlerConfig... handlerConfigs) {
this(loggerName, level, true, null, handlerConfigs);
}
/**
* Create a LoggerConfig with no resource bundle
*
* @param loggerName The Logger name
* @param level Set the log level specifying which message levels will be
* logged by the logger
* @param useParentHandlers Specify whether or not this logger should send
* its output to it's parent Logger. This means that any LogRecords will
* also be written to the parent's Handlers, and potentially to its parent,
* recursively up the namespace
* @param handlerConfigs An Array of LogHandlerConfig instances
*/
public LoggerConfig(String loggerName, Level level, boolean useParentHandlers, LogHandlerConfig... handlerConfigs) {
this(loggerName, level, useParentHandlers, null, handlerConfigs);
}
/**
* Create a LoggerConfig
*
* @param loggerName The Logger name
* @param level Set the log level specifying which message levels will be
* logged by the logger
* @param useParentHandlers Specify whether or not this logger should send
* its output to it's parent Logger. If true, then LogRecords will also be
* written to the parent's Handlers, and potentially to its parent,
* recursively up the namespace
* @param resourceBundleName Name of ResourceBundle to be used for
* localizing messages for the logger
* @param handlerConfigs An Array of LogHandlerConfig instances
*/
public LoggerConfig(String loggerName,
Level level,
boolean useParentHandlers,
String resourceBundleName,
LogHandlerConfig... handlerConfigs) {
if(loggerName == null)
throw new IllegalArgumentException("loggerName is null");
if(level == null)
throw new IllegalArgumentException("level is null");
this.loggerName = loggerName;
this.level = level;
this.resourceBundleName = resourceBundleName;
this.useParentHandlers = useParentHandlers;
if(handlerConfigs != null) {
handlers = new LogHandlerConfig[handlerConfigs.length];
System.arraycopy(handlerConfigs, 0, handlers, 0, handlers.length);
} else {
if(!useParentHandlers) {
throw new IllegalArgumentException("The logger must include a Handler " +
"if it will not delegate to parent handlers.");
}
handlers = new LogHandlerConfig[0];
}
}
public void close() {
if(logger!=null) {
for (Handler h : logger.getHandlers()) {
if(handlerFilter!=null && h.getFilter()!=null && h.getFilter().equals(handlerFilter)) {
h.setFilter(null);
}
}
}
}
/**
* Get the Logger, configured with attributes
*
* @return A configured Logger
*/
public Logger getLogger() {
if(logger != null)
return (logger);
logger = LogManager.getLogManager().getLogger(loggerName);
if(logger==null) {
if(resourceBundleName != null) {
logger = Logger.getLogger(loggerName, resourceBundleName);
} else {
logger = Logger.getLogger(loggerName);
}
}
/*
* Set the level of the logger to the declared level. This will change
* the level for all named logger instances
*/
if(myLogger.isLoggable(Level.FINEST))
myLogger.finest("Logger ["+loggerName+"] Level set to : "+level.getName());
logger.setLevel(level);
if(myLogger.isLoggable(Level.FINEST))
myLogger.finest("Logger ["+loggerName+"] uses parent handlers : "+ useParentHandlers);
logger.setUseParentHandlers(useParentHandlers);
if(handlers.length>0) {
for (LogHandlerConfig h : handlers) {
String candidateHandler = h.getHandlerClassName();
try {
boolean handlerClassAssociated = false;
Handler[] currentHandlers = logger.getHandlers();
for (Handler currentHandler : currentHandlers) {
if (currentHandler.getClass().getName().equals(candidateHandler)) {
handlerClassAssociated = true;
break;
}
}
if (!handlerClassAssociated) {
if (myLogger.isLoggable(Level.FINEST))
myLogger.finest("Logger ["+loggerName+"] adding Handler ["+h.getHandlerClassName()+"]");
Handler handler = h.getHandler();
if(h.getLevel()==null) {
handler.setLevel(level);
} else {
handler.setLevel(h.getLevel());
}
if (logger.getUseParentHandlers()) {
handlerFilter = new HandlerFilter(logger, handler);
handler.setFilter(handlerFilter);
}
logger.addHandler(handler);
}
} catch (Throwable t) {
myLogger.log(Level.WARNING,
"Getting Handler [" + h.getHandlerClassName() + "] for Logger [" + loggerName + "]",
(t.getCause() == null ? t : t.getCause()));
}
}
} else {
List<Handler> handlers = collectHandlers(logger);
setHandlerLevel(handlers, level);
}
return (logger);
}
/**
* @return Get the Logger name
*/
public String getLoggerName() {
return (loggerName);
}
/**
* @return Get the Level for the Logger
*/
public Level getLoggerLevel() {
return (level);
}
/**
* Override hashCode
*/
public int hashCode() {
int hc = 17;
hc = 37*hc+loggerName.hashCode();
hc = 37*hc+level.hashCode();
for (LogHandlerConfig handler : handlers)
hc = 37 * hc + handler.hashCode();
return(hc);
}
/**
* Override equals
*/
public boolean equals(Object obj) {
if(this == obj)
return(true);
if(!(obj instanceof LoggerConfig))
return(false);
LoggerConfig that = (LoggerConfig)obj;
if(this.loggerName.equals(that.loggerName) &&
this.level.equals(that.level)) {
if(this.handlers.length == that.handlers.length) {
for (LogHandlerConfig handler : this.handlers) {
boolean matched = false;
for (int j = 0; j < that.handlers.length; j++) {
if (handler.equals(that.handlers[j])) {
matched = true;
break;
}
}
if (!matched)
return (false);
}
}
return(true);
}
return(false);
}
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("Logger=").append(loggerName)
.append(", Level=").append(level)
.append(", UseParentHandlers=").append(useParentHandlers).append("\n");
for (LogHandlerConfig handler : handlers) {
buffer.append("Handler=").append(handler.getHandlerClassName());
if (handler.formatterClassName != null) {
buffer.append(", Formatter=").append(handler.formatterClassName).append("\n");
} else {
buffer.append("\n");
}
}
return (buffer.toString());
}
/**
* Determine if the current Logger is a new Logger
*
* @param lConf The LoggerConfig to examine
* @param loggerConfigs The current LoggerConfig
* @return True if the Logger is new, false otherwise
*/
public static boolean isNewLogger(LoggerConfig lConf, LoggerConfig[] loggerConfigs) {
boolean matched = false;
for (LoggerConfig loggerConfig : loggerConfigs) {
if (loggerConfig.getLoggerName().equals(lConf.getLoggerName())) {
matched = true;
}
}
return !matched;
}
/**
* Determine if the LoggerConfig provides a Level change
*
* @param lConf The LoggerConfig to examine
* @param loggerConfigs The current LoggerConfig
* @return True if the Level requires changing, false otherwise
*/
public static boolean levelChanged(LoggerConfig lConf, LoggerConfig[] loggerConfigs) {
boolean levelChanged = false;
for (LoggerConfig loggerConfig : loggerConfigs) {
if (loggerConfig.getLoggerName().equals(lConf.getLoggerName())) {
if (!loggerConfig.getLoggerLevel().
equals(lConf.getLoggerLevel())) {
levelChanged = true;
break;
}
}
}
return(levelChanged);
}
/**
* Filter for Handlers that handles parent Handler delegation. This avoids
* multiple records being logged
*/
private class HandlerFilter implements Filter {
Logger logger;
Handler handler;
private HandlerFilter(Logger logger, Handler handler) {
this.logger = logger;
this.handler = handler;
}
public boolean isLoggable(LogRecord logRecord) {
Level level = logRecord.getLevel();
if(level.intValue()<handler.getLevel().intValue())
return(false);
Logger rootLogger = null;
Logger temp = logger;
while(rootLogger == null) {
Logger l = temp.getParent();
Handler[] handlers = l.getHandlers();
for (Handler h : handlers) {
if(h.getClass().getName().equals(handler.getClass().getName()) &&
h.getLevel().intValue() >= handler.getLevel().intValue()) {
return (level.intValue() < h.getLevel().intValue());
}
}
if(l.getName().equals(""))
rootLogger = l;
else
temp = l;
}
return(true);
}
}
private List<Handler> collectHandlers(Logger l) {
List<Handler> handlers = new ArrayList<Handler>();
if(l.getHandlers().length>0) {
Collections.addAll(handlers, l.getHandlers());
}
if(l.getUseParentHandlers() && l.getParent()!=null) {
handlers.addAll(collectHandlers(l.getParent()));
}
return handlers;
}
private void setHandlerLevel(List<Handler> handlers, Level level) {
for(Handler h : handlers) {
Level was = h.getLevel();
if(level.intValue()<h.getLevel().intValue()) {
h.setLevel(level);
}
}
}
/**
* Provide a way to pass parameter lists around.
*/
public static class FormalArgument implements Serializable {
static final long serialVersionUID = 1L;
private String dataType;
private String value;
/**
* Constructs a model of a single argument in a parameter list.
*
* @param dataType The argument's data type.
* @param value The argument's value.
*/
public FormalArgument (String dataType, String value) {
this.dataType = dataType;
this.value = value;
}
/**
* @return Returns the argument's data type.
*/
public String getDataType() {
return(dataType);
}
/**
* @return Returns the argument's value.
*/
public String getValue() {
return(value);
}
}
/**
* LogerHandlerConfig provides attributes needed to create a
* {@link java.util.logging.Handler}
*/
public static class LogHandlerConfig implements Serializable {
static final long serialVersionUID = 1L;
/** The log Handler */
private transient Handler handler;
/** The log Handler classname */
private String handlerClassName;
/** The Filter classname */
private String formatterClassName;
/**
* The log level specifying which message levels will be logged by the
* Handler
*/
private Level level;
/** Parameters for Handler construction */
private List<FormalArgument> handlerArgList = new LinkedList<FormalArgument>();
/**
* Create a LogHandlerConfig
*
* @param handlerClassName The class name (suitable for Class.forName use)
* of a log {@link java.util.logging.Handler} to receive logging messages
*/
public LogHandlerConfig(String handlerClassName) {
this(handlerClassName, null, null, null);
}
/**
* Create a LogHandlerConfig
*
* @param handlerClassName The class name (suitable for Class.forName use)
* of a log {@link java.util.logging.Handler} to receive logging messages
* @param level Set the log level specifying which message levels will
* be logged by the Handler. Message levels lower than this value will
* be discarded
*/
public LogHandlerConfig(String handlerClassName, Level level) {
this(handlerClassName);
this.level = level;
}
/**
* Create a LogHandlerConfig
*
* @param handlerClassName The class name (suitable for Class.forName
* use) of a log Handler to receive logging messages
* @param level Set the log level specifying which message levels will
* be logged by the Handler. Message levels lower than this value will
* be discarded
* @param params Constructor parameters to create the Handler
* @param formatterClassName The class name (suitable for
* Class.forName use) of a java.util.logging.Formatter to use with the
* Handler
*/
public LogHandlerConfig(String handlerClassName,
Level level,
List<FormalArgument> params,
String formatterClassName) {
if(handlerClassName == null)
throw new IllegalArgumentException("handlerClassName is null");
this.handlerClassName = handlerClassName;
this.level = level;
if(params != null)
handlerArgList.addAll(params);
this.formatterClassName = formatterClassName;
}
/**
* Create a LogHandlerConfig.
*
* @param handler A log Handler to receive logging messages
*/
public LogHandlerConfig(Handler handler) {
if(handler == null)
throw new IllegalArgumentException("handler is null");
this.handler = handler;
this.handlerClassName = handler.getClass().getName();
}
/**
* Create a LogHandlerConfig.
*
* @param handler A log Handler to receive logging messages
* @param level Set the log level specifying which message levels will
* be logged by the Handler. Message levels lower than this value will
* be discarded
*/
public LogHandlerConfig(Handler handler, Level level) {
if(handler == null)
throw new IllegalArgumentException("handler is null");
this.handler = handler;
this.level = level;
this.handlerClassName = handler.getClass().getName();
}
/**
* Get the handler class name
*
* @return String The Handler class name
*/
String getHandlerClassName() {
return (handlerClassName);
}
/**
* Override hashCode
*/
public int hashCode() {
int hc = 17;
hc = 37*hc+handlerClassName.hashCode();
return(hc);
}
/**
* Override equals
*/
public boolean equals(Object obj) {
if(this == obj)
return(true);
if(!(obj instanceof LogHandlerConfig))
return(false);
LogHandlerConfig that = (LogHandlerConfig)obj;
if(this.handlerClassName.equals(that.handlerClassName))
return(true);
return(false);
}
/**
* Get the handler
*
* @return Handler A suitable Handler
*
* @throws Exception if the handler cannot be loaded
*/
public Handler getHandler() throws Exception {
if(handler == null) {
Class handlerClass =
Thread.currentThread().getContextClassLoader().
loadClass(handlerClassName);
if(!handlerArgList.isEmpty()) {
Class[] parameterTypes = getParameterTypes();
Object[] initArgs = getInitArgs();
java.lang.reflect.Constructor constructor =
handlerClass.getConstructor(parameterTypes);
handler = (Handler)constructor.newInstance(initArgs);
} else {
handler = (Handler)handlerClass.newInstance();
}
//handler.setLevel(level);
if(formatterClassName != null) {
Class formatClass =
Class.forName(formatterClassName,
true,
Thread.currentThread().getContextClassLoader());
Formatter formatter = (Formatter)formatClass.newInstance();
handler.setFormatter(formatter);
}
}
return (handler);
}
/**
* Get the {@link java.util.logging.Level} to set for the handler
*
* @return The {@link java.util.logging.Level} for the handler, or null
* to use the enclosing Logger's level
*/
public Level getLevel() {
return level;
}
/*
* Get the parameter types for the Handler
*/
private Class[] getParameterTypes() throws Exception {
List<Class> classes = new LinkedList<Class> ();
for (FormalArgument arg : handlerArgList) {
String param = arg.getDataType();
if (param.equals("boolean"))
classes.add(Boolean.TYPE);
else if (param.equals("byte"))
classes.add(Byte.TYPE);
else if (param.equals("short"))
classes.add(Short.TYPE);
else if (param.equals("int"))
classes.add(Integer.TYPE);
else if (param.equals("long"))
classes.add(Long.TYPE);
else if (param.equals("float"))
classes.add(Float.TYPE);
else if (param.equals("double"))
classes.add(Double.TYPE);
else
classes.add(Class.forName(param,
true,
this.getClass().getClassLoader()));
}
return classes.toArray(new Class[classes.size()]);
}
/*
* Get the initialization arguments based on parameter types for the
* Handler
*/
private Object[] getInitArgs() throws Exception {
List<Object> values = new LinkedList<Object>();
for (FormalArgument arg : handlerArgList) {
String type = arg.getDataType();
String value = arg.getValue();
if (type.equals("boolean"))
values.add(Boolean.valueOf(value));
else if (type.equals("byte"))
values.add(new Byte(value));
else if (type.equals("short"))
values.add(new Short(value));
else if (type.equals("int"))
values.add(new Integer(value));
else if (type.equals("long"))
values.add(new Long(value));
else if (type.equals("float"))
values.add(new Float(value));
else if (type.equals("double"))
values.add(new Double(value));
else {
Class clazz = Class.forName(type,
true,
this.getClass().getClassLoader());
if (clazz == String.class) {
value = transformString(value);
values.add(value);
} else if (clazz == Boolean.class)
values.add(Boolean.valueOf(value));
else if (clazz == Byte.class)
values.add(new Byte(value));
else if (clazz == Short.class)
values.add(new Short(value));
else if (clazz == Integer.class)
values.add(new Integer(value));
else if (clazz == Long.class)
values.add(new Long(value));
else if (clazz == Float.class)
values.add(new Float(value));
else if (clazz == Double.class)
values.add(new Double(value));
}
}
return values.toArray(new Object[values.size()]);
}
/**
* Transform a String that has control characters into a formatted
* String with machine dependant File.separator and System property
* values set
*
* @param input The input string
*
* @return A transformed string
*/
String transformString(final String input) throws Exception {
StringBuilder buffer = new StringBuilder();
int index;
String transformed = input;
while ((index = input.indexOf("${")) != -1) {
buffer.append(input.substring(0, index));
int end = input.indexOf("}");
if(end == -1)
continue;
String value = input.substring(index + 2, end);
if(value.equals("/")) {
buffer.append(File.separator);
} else {
String property = System.getProperty(value);
if(property == null)
throw new Exception("Cannot resolve property ["+value+"]");
buffer.append(property);
}
transformed = buffer.toString() + input.substring(end + 1);
buffer.delete(0, buffer.length());
}
return (transformed);
}
}
}