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