blob: b328ec39d63da80f83e12503ff9b9f0ff30db4dc [file] [log] [blame]
/*
* 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.
*/
// Contributors: Georg Lundesgaard
package org.apache.log4j.config;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.Priority;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.OptionHandler;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.util.OptionConverter;
import org.apache.logging.log4j.status.StatusLogger;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.InterruptedIOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* General purpose Object property setter. Clients repeatedly invokes
* {@link #setProperty setProperty(name,value)} in order to invoke setters
* on the Object specified in the constructor. This class relies on the
* JavaBeans {@link Introspector} to analyze the given Object Class using
* reflection.
*
* <p>Usage:
* <pre>
* PropertySetter ps = new PropertySetter(anObject);
* ps.set("name", "Joe");
* ps.set("age", "32");
* ps.set("isMale", "true");
* </pre>
* will cause the invocations anObject.setName("Joe"), anObject.setAge(32),
* and setMale(true) if such methods exist with those signatures.
* Otherwise an {@link IntrospectionException} are thrown.
*/
public class PropertySetter {
private static final Logger LOGGER = StatusLogger.getLogger();
protected Object obj;
protected PropertyDescriptor[] props;
/**
* Create a new PropertySetter for the specified Object. This is done
* in prepartion for invoking {@link #setProperty} one or more times.
*
* @param obj the object for which to set properties
*/
public PropertySetter(Object obj) {
this.obj = obj;
}
/**
* Set the properties of an object passed as a parameter in one
* go. The <code>properties</code> are parsed relative to a
* <code>prefix</code>.
*
* @param obj The object to configure.
* @param properties A java.util.Properties containing keys and values.
* @param prefix Only keys having the specified prefix will be set.
*/
public static void setProperties(Object obj, Properties properties, String prefix) {
new PropertySetter(obj).setProperties(properties, prefix);
}
/**
* Uses JavaBeans {@link Introspector} to computer setters of object to be
* configured.
*/
protected void introspect() {
try {
BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
props = bi.getPropertyDescriptors();
} catch (IntrospectionException ex) {
LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage());
props = new PropertyDescriptor[0];
}
}
/**
* Set the properites for the object that match the
* <code>prefix</code> passed as parameter.
* @param properties The properties.
* @param prefix The prefix of the properties to use.
*/
public void setProperties(Properties properties, String prefix) {
int len = prefix.length();
for (String key : properties.stringPropertyNames()) {
// handle only properties that start with the desired prefix.
if (key.startsWith(prefix)) {
// ignore key if it contains dots after the prefix
if (key.indexOf('.', len + 1) > 0) {
continue;
}
String value = OptionConverter.findAndSubst(key, properties);
key = key.substring(len);
if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) {
continue;
}
//
// if the property type is an OptionHandler
// (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)
PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));
if (prop != null
&& OptionHandler.class.isAssignableFrom(prop.getPropertyType())
&& prop.getWriteMethod() != null) {
OptionHandler opt = (OptionHandler)
OptionConverter.instantiateByKey(properties, prefix + key,
prop.getPropertyType(),
null);
PropertySetter setter = new PropertySetter(opt);
setter.setProperties(properties, prefix + key + ".");
try {
prop.getWriteMethod().invoke(this.obj, opt);
} catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof InterruptedException
|| ex.getTargetException() instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
} catch (IllegalAccessException | RuntimeException ex) {
LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
}
continue;
}
setProperty(key, value);
}
}
activate();
}
/**
* Set a property on this PropertySetter's Object. If successful, this
* method will invoke a setter method on the underlying Object. The
* setter is the one for the specified property name and the value is
* determined partly from the setter argument type and partly from the
* value specified in the call to this method.
*
* <p>If the setter expects a String no conversion is necessary.
* If it expects an int, then an attempt is made to convert 'value'
* to an int using new Integer(value). If the setter expects a boolean,
* the conversion is by new Boolean(value).
*
* @param name name of the property
* @param value String value of the property
*/
public void setProperty(String name, String value) {
if (value == null) {
return;
}
name = Introspector.decapitalize(name);
PropertyDescriptor prop = getPropertyDescriptor(name);
//LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType());
if (prop == null) {
LOGGER.warn("No such property [" + name + "] in " +
obj.getClass().getName() + ".");
} else {
try {
setProperty(prop, name, value);
} catch (PropertySetterException ex) {
LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause);
}
}
}
/**
* Set the named property given a {@link PropertyDescriptor}.
*
* @param prop A PropertyDescriptor describing the characteristics
* of the property to set.
* @param name The named of the property to set.
* @param value The value of the property.
* @throws PropertySetterException if no setter is available.
*/
public void setProperty(PropertyDescriptor prop, String name, String value)
throws PropertySetterException {
Method setter = prop.getWriteMethod();
if (setter == null) {
throw new PropertySetterException("No setter for property [" + name + "].");
}
Class<?>[] paramTypes = setter.getParameterTypes();
if (paramTypes.length != 1) {
throw new PropertySetterException("#params for setter != 1");
}
Object arg;
try {
arg = convertArg(value, paramTypes[0]);
} catch (Throwable t) {
throw new PropertySetterException("Conversion to type [" + paramTypes[0] +
"] failed. Reason: " + t);
}
if (arg == null) {
throw new PropertySetterException(
"Conversion to type [" + paramTypes[0] + "] failed.");
}
LOGGER.debug("Setting property [" + name + "] to [" + arg + "].");
try {
setter.invoke(obj, arg);
} catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof InterruptedException
|| ex.getTargetException() instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
throw new PropertySetterException(ex);
} catch (IllegalAccessException | RuntimeException ex) {
throw new PropertySetterException(ex);
}
}
/**
* Convert <code>val</code> a String parameter to an object of a
* given type.
* @param val The value to convert.
* @param type The type of the value to convert to.
* @return The result of the conversion.
*/
protected Object convertArg(String val, Class<?> type) {
if (val == null) {
return null;
}
String v = val.trim();
if (String.class.isAssignableFrom(type)) {
return val;
} else if (Integer.TYPE.isAssignableFrom(type)) {
return Integer.parseInt(v);
} else if (Long.TYPE.isAssignableFrom(type)) {
return Long.parseLong(v);
} else if (Boolean.TYPE.isAssignableFrom(type)) {
if ("true".equalsIgnoreCase(v)) {
return Boolean.TRUE;
} else if ("false".equalsIgnoreCase(v)) {
return Boolean.FALSE;
}
} else if (Priority.class.isAssignableFrom(type)) {
return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG);
} else if (ErrorHandler.class.isAssignableFrom(type)) {
return OptionConverter.instantiateByClassName(v,
ErrorHandler.class, null);
}
return null;
}
protected PropertyDescriptor getPropertyDescriptor(String name) {
if (props == null) {
introspect();
}
for (PropertyDescriptor prop : props) {
if (name.equals(prop.getName())) {
return prop;
}
}
return null;
}
public void activate() {
if (obj instanceof OptionHandler) {
((OptionHandler) obj).activateOptions();
}
}
}