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// Contributors: Georg Lundesgaard 019 020package org.apache.log4j.config; 021 022import java.beans.BeanInfo; 023import java.beans.IntrospectionException; 024import java.beans.Introspector; 025import java.beans.PropertyDescriptor; 026import java.io.InterruptedIOException; 027import java.lang.reflect.InvocationTargetException; 028import java.lang.reflect.Method; 029import java.util.Properties; 030 031import org.apache.log4j.Appender; 032import org.apache.log4j.Level; 033import org.apache.log4j.Priority; 034import org.apache.log4j.spi.ErrorHandler; 035import org.apache.log4j.spi.OptionHandler; 036import org.apache.logging.log4j.Logger; 037import org.apache.logging.log4j.core.util.OptionConverter; 038import org.apache.logging.log4j.status.StatusLogger; 039 040/** 041 * General purpose Object property setter. Clients repeatedly invokes 042 * {@link #setProperty setProperty(name,value)} in order to invoke setters 043 * on the Object specified in the constructor. This class relies on the 044 * JavaBeans {@link Introspector} to analyze the given Object Class using 045 * reflection. 046 * 047 * <p>Usage: 048 * <pre> 049 * PropertySetter ps = new PropertySetter(anObject); 050 * ps.set("name", "Joe"); 051 * ps.set("age", "32"); 052 * ps.set("isMale", "true"); 053 * </pre> 054 * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), 055 * and setMale(true) if such methods exist with those signatures. 056 * Otherwise an {@link IntrospectionException} are thrown. 057 */ 058public class PropertySetter { 059 private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {}; 060 private static Logger LOGGER = StatusLogger.getLogger(); 061 protected Object obj; 062 protected PropertyDescriptor[] props; 063 064 /** 065 * Create a new PropertySetter for the specified Object. This is done 066 * in prepartion for invoking {@link #setProperty} one or more times. 067 * 068 * @param obj the object for which to set properties 069 */ 070 public PropertySetter(Object obj) { 071 this.obj = obj; 072 } 073 074 /** 075 * Set the properties of an object passed as a parameter in one 076 * go. The <code>properties</code> are parsed relative to a 077 * <code>prefix</code>. 078 * 079 * @param obj The object to configure. 080 * @param properties A java.util.Properties containing keys and values. 081 * @param prefix Only keys having the specified prefix will be set. 082 */ 083 public static void setProperties(Object obj, Properties properties, String prefix) { 084 new PropertySetter(obj).setProperties(properties, prefix); 085 } 086 087 /** 088 * Uses JavaBeans {@link Introspector} to computer setters of object to be 089 * configured. 090 */ 091 protected void introspect() { 092 try { 093 BeanInfo bi = Introspector.getBeanInfo(obj.getClass()); 094 props = bi.getPropertyDescriptors(); 095 } catch (IntrospectionException ex) { 096 LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage()); 097 props = EMPTY_PROPERTY_DESCRIPTOR_ARRAY; 098 } 099 } 100 101 /** 102 * Set the properites for the object that match the 103 * <code>prefix</code> passed as parameter. 104 * @param properties The properties. 105 * @param prefix The prefix of the properties to use. 106 */ 107 public void setProperties(Properties properties, String prefix) { 108 int len = prefix.length(); 109 110 for (String key : properties.stringPropertyNames()) { 111 112 // handle only properties that start with the desired prefix. 113 if (key.startsWith(prefix)) { 114 115 116 // ignore key if it contains dots after the prefix 117 if (key.indexOf('.', len + 1) > 0) { 118 continue; 119 } 120 121 String value = OptionConverter.findAndSubst(key, properties); 122 key = key.substring(len); 123 if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) { 124 continue; 125 } 126 // 127 // if the property type is an OptionHandler 128 // (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender) 129 PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key)); 130 if (prop != null 131 && OptionHandler.class.isAssignableFrom(prop.getPropertyType()) 132 && prop.getWriteMethod() != null) { 133 OptionHandler opt = (OptionHandler) 134 OptionConverter.instantiateByKey(properties, prefix + key, 135 prop.getPropertyType(), 136 null); 137 PropertySetter setter = new PropertySetter(opt); 138 setter.setProperties(properties, prefix + key + "."); 139 try { 140 prop.getWriteMethod().invoke(this.obj, opt); 141 } catch (InvocationTargetException ex) { 142 if (ex.getTargetException() instanceof InterruptedException 143 || ex.getTargetException() instanceof InterruptedIOException) { 144 Thread.currentThread().interrupt(); 145 } 146 LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex); 147 } catch (IllegalAccessException | RuntimeException ex) { 148 LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex); 149 } 150 continue; 151 } 152 153 setProperty(key, value); 154 } 155 } 156 activate(); 157 } 158 159 /** 160 * Set a property on this PropertySetter's Object. If successful, this 161 * method will invoke a setter method on the underlying Object. The 162 * setter is the one for the specified property name and the value is 163 * determined partly from the setter argument type and partly from the 164 * value specified in the call to this method. 165 * 166 * <p>If the setter expects a String no conversion is necessary. 167 * If it expects an int, then an attempt is made to convert 'value' 168 * to an int using new Integer(value). If the setter expects a boolean, 169 * the conversion is by new Boolean(value). 170 * 171 * @param name name of the property 172 * @param value String value of the property 173 */ 174 public void setProperty(String name, String value) { 175 if (value == null) { 176 return; 177 } 178 179 name = Introspector.decapitalize(name); 180 PropertyDescriptor prop = getPropertyDescriptor(name); 181 182 //LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType()); 183 184 if (prop == null) { 185 LOGGER.warn("No such property [" + name + "] in " + 186 obj.getClass().getName() + "."); 187 } else { 188 try { 189 setProperty(prop, name, value); 190 } catch (PropertySetterException ex) { 191 LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause); 192 } 193 } 194 } 195 196 /** 197 * Set the named property given a {@link PropertyDescriptor}. 198 * 199 * @param prop A PropertyDescriptor describing the characteristics 200 * of the property to set. 201 * @param name The named of the property to set. 202 * @param value The value of the property. 203 * @throws PropertySetterException if no setter is available. 204 */ 205 public void setProperty(PropertyDescriptor prop, String name, String value) 206 throws PropertySetterException { 207 Method setter = prop.getWriteMethod(); 208 if (setter == null) { 209 throw new PropertySetterException("No setter for property [" + name + "]."); 210 } 211 Class<?>[] paramTypes = setter.getParameterTypes(); 212 if (paramTypes.length != 1) { 213 throw new PropertySetterException("#params for setter != 1"); 214 } 215 216 Object arg; 217 try { 218 arg = convertArg(value, paramTypes[0]); 219 } catch (Throwable t) { 220 throw new PropertySetterException("Conversion to type [" + paramTypes[0] + 221 "] failed. Reason: " + t); 222 } 223 if (arg == null) { 224 throw new PropertySetterException( 225 "Conversion to type [" + paramTypes[0] + "] failed."); 226 } 227 LOGGER.debug("Setting property [" + name + "] to [" + arg + "]."); 228 try { 229 setter.invoke(obj, arg); 230 } catch (InvocationTargetException ex) { 231 if (ex.getTargetException() instanceof InterruptedException 232 || ex.getTargetException() instanceof InterruptedIOException) { 233 Thread.currentThread().interrupt(); 234 } 235 throw new PropertySetterException(ex); 236 } catch (IllegalAccessException | RuntimeException ex) { 237 throw new PropertySetterException(ex); 238 } 239 } 240 241 242 /** 243 * Convert <code>val</code> a String parameter to an object of a 244 * given type. 245 * @param val The value to convert. 246 * @param type The type of the value to convert to. 247 * @return The result of the conversion. 248 */ 249 protected Object convertArg(String val, Class<?> type) { 250 if (val == null) { 251 return null; 252 } 253 254 String v = val.trim(); 255 if (String.class.isAssignableFrom(type)) { 256 return val; 257 } else if (Integer.TYPE.isAssignableFrom(type)) { 258 return Integer.parseInt(v); 259 } else if (Long.TYPE.isAssignableFrom(type)) { 260 return Long.parseLong(v); 261 } else if (Boolean.TYPE.isAssignableFrom(type)) { 262 if ("true".equalsIgnoreCase(v)) { 263 return Boolean.TRUE; 264 } else if ("false".equalsIgnoreCase(v)) { 265 return Boolean.FALSE; 266 } 267 } else if (Priority.class.isAssignableFrom(type)) { 268 return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG); 269 } else if (ErrorHandler.class.isAssignableFrom(type)) { 270 return OptionConverter.instantiateByClassName(v, 271 ErrorHandler.class, null); 272 } 273 return null; 274 } 275 276 277 protected PropertyDescriptor getPropertyDescriptor(String name) { 278 if (props == null) { 279 introspect(); 280 } 281 for (PropertyDescriptor prop : props) { 282 if (name.equals(prop.getName())) { 283 return prop; 284 } 285 } 286 return null; 287 } 288 289 public void activate() { 290 if (obj instanceof OptionHandler) { 291 ((OptionHandler) obj).activateOptions(); 292 } 293 } 294}