001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014// Contributors: Georg Lundesgaard 015package ch.qos.logback.core.joran.util; 016 017import ch.qos.logback.core.Context; 018import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry; 019import ch.qos.logback.core.joran.util.beans.BeanDescription; 020import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache; 021import ch.qos.logback.core.spi.ContextAwareBase; 022import ch.qos.logback.core.util.AggregationType; 023import ch.qos.logback.core.util.PropertySetterException; 024import ch.qos.logback.core.util.StringUtil; 025 026import java.lang.reflect.Method; 027 028/** 029 * General purpose Object property setter. Clients repeatedly invokes 030 * {@link #setProperty setProperty(name,value)} in order to invoke setters on 031 * the Object specified in the constructor. This class relies on reflection to 032 * analyze the given Object Class. 033 * 034 * <p> 035 * Usage: 036 * 037 * <pre> 038 * PropertySetter ps = new PropertySetter(anObject); 039 * ps.set("name", "Joe"); 040 * ps.set("age", "32"); 041 * ps.set("isMale", "true"); 042 * </pre> 043 * 044 * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and 045 * setMale(true) if such methods exist with those signatures. Otherwise an 046 * {@link PropertySetterException} is thrown. 047 * 048 * @author Anders Kristensen 049 * @author Ceki Gulcu 050 */ 051public class PropertySetter extends ContextAwareBase { 052 053 protected final Object obj; 054 protected final Class<?> objClass; 055 protected final BeanDescription beanDescription; 056 protected final AggregationAssessor aggregationAssessor; 057 058 /** 059 * Create a new PropertySetter for the specified Object. This is done in 060 * preparation for invoking {@link #setProperty} one or more times. 061 * 062 * @param obj the object for which to set properties 063 */ 064 public PropertySetter(BeanDescriptionCache beanDescriptionCache, Object obj) { 065 this.obj = obj; 066 this.objClass = obj.getClass(); 067 this.beanDescription = beanDescriptionCache.getBeanDescription(objClass); 068 this.aggregationAssessor = new AggregationAssessor(beanDescriptionCache, this.objClass); 069 } 070 071 @Override 072 public void setContext(Context context) { 073 super.setContext(context); 074 aggregationAssessor.setContext(context); 075 } 076 077 078 /** 079 * Set a property on this PropertySetter's Object. If successful, this method 080 * will invoke a setter method on the underlying Object. The setter is the one 081 * for the specified property name and the value is determined partly from the 082 * setter argument type and partly from the value specified in the call to this 083 * method. 084 * 085 * <p> 086 * If the setter expects a String no conversion is necessary. If it expects an 087 * int, then an attempt is made to convert 'value' to an int using new 088 * Integer(value). If the setter expects a boolean, the conversion is by new 089 * Boolean(value). 090 * 091 * @param name name of the property 092 * @param value String value of the property 093 */ 094 public void setProperty(String name, String value) { 095 if (value == null) { 096 return; 097 } 098 Method setter = aggregationAssessor.findSetterMethod(name); 099 if (setter == null) { 100 addWarn("No setter for property [" + name + "] in " + objClass.getName() + "."); 101 } else { 102 try { 103 setProperty(setter, value); 104 } catch (PropertySetterException ex) { 105 addWarn("Failed to set property [" + name + "] to value \"" + value + "\". ", ex); 106 } 107 } 108 } 109 110 /** 111 * Set the named property using a {@link Method setter}. 112 * 113 * @param setter A Method describing the characteristics of the 114 * property to set. 115 * @param value The value of the property. 116 */ 117 private void setProperty(Method setter, String value) throws PropertySetterException { 118 Class<?>[] paramTypes = setter.getParameterTypes(); 119 120 Object arg; 121 122 try { 123 arg = StringToObjectConverter.convertArg(this, value, paramTypes[0]); 124 } catch (Throwable t) { 125 throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed. ", t); 126 } 127 128 if (arg == null) { 129 throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed."); 130 } 131 try { 132 setter.invoke(obj, arg); 133 } catch (Exception ex) { 134 throw new PropertySetterException(ex); 135 } 136 } 137 138 public AggregationType computeAggregationType(String name) { 139 return this.aggregationAssessor.computeAggregationType(name); 140 } 141 142// private Method findAdderMethod(String name) { 143// String propertyName = BeanUtil.toLowerCamelCase(name); 144// return beanDescription.getAdder(propertyName); 145// } 146// 147// private Method findSetterMethod(String name) { 148// String propertyName = BeanUtil.toLowerCamelCase(name); 149// return beanDescription.getSetter(propertyName); 150// } 151 152// private Class<?> getParameterClassForMethod(Method method) { 153// if (method == null) { 154// return null; 155// } 156// Class<?>[] classArray = method.getParameterTypes(); 157// if (classArray.length != 1) { 158// return null; 159// } else { 160// return classArray[0]; 161// } 162// } 163 164// private AggregationType computeRawAggregationType(Method method) { 165// Class<?> parameterClass = getParameterClassForMethod(method); 166// if (parameterClass == null) { 167// return AggregationType.NOT_FOUND; 168// } 169// if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) { 170// return AggregationType.AS_BASIC_PROPERTY; 171// } else { 172// return AggregationType.AS_COMPLEX_PROPERTY; 173// } 174// } 175 176 177 178 public Class<?> getObjClass() { 179 return objClass; 180 } 181 182 public void addComplexProperty(String name, Object complexProperty) { 183 Method adderMethod = aggregationAssessor.findAdderMethod(name); 184 // first let us use the addXXX method 185 if (adderMethod != null) { 186 Class<?>[] paramTypes = adderMethod.getParameterTypes(); 187 if (!isSanityCheckSuccessful(name, adderMethod, paramTypes, complexProperty)) { 188 return; 189 } 190 invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty); 191 } else { 192 addError("Could not find method [" + "add" + name + "] in class [" + objClass.getName() + "]."); 193 } 194 } 195 196 void invokeMethodWithSingleParameterOnThisObject(Method method, Object parameter) { 197 Class<?> ccc = parameter.getClass(); 198 try { 199 method.invoke(this.obj, parameter); 200 } catch (Exception e) { 201 addError("Could not invoke method " + method.getName() + " in class " + obj.getClass().getName() 202 + " with parameter of type " + ccc.getName(), e); 203 } 204 } 205 206 public void addBasicProperty(String name, String strValue) { 207 208 if (strValue == null) { 209 return; 210 } 211 212 name = StringUtil.capitalizeFirstLetter(name); 213 Method adderMethod =aggregationAssessor.findAdderMethod(name); 214 215 if (adderMethod == null) { 216 addError("No adder for property [" + name + "]."); 217 return; 218 } 219 220 Class<?>[] paramTypes = adderMethod.getParameterTypes(); 221 isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue); 222 223 Object arg; 224 try { 225 arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]); 226 } catch (Throwable t) { 227 addError("Conversion to type [" + paramTypes[0] + "] failed. ", t); 228 return; 229 } 230 if (arg != null) { 231 invokeMethodWithSingleParameterOnThisObject(adderMethod, strValue); 232 } 233 } 234 235 public void setComplexProperty(String name, Object complexProperty) { 236 Method setter = aggregationAssessor.findSetterMethod(name); 237 238 if (setter == null) { 239 addWarn("Not setter method for property [" + name + "] in " + obj.getClass().getName()); 240 241 return; 242 } 243 244 Class<?>[] paramTypes = setter.getParameterTypes(); 245 246 if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) { 247 return; 248 } 249 try { 250 invokeMethodWithSingleParameterOnThisObject(setter, complexProperty); 251 252 } catch (Exception e) { 253 addError("Could not set component " + obj + " for parent component " + obj, e); 254 } 255 } 256 257 private boolean isSanityCheckSuccessful(String name, Method method, Class<?>[] params, Object complexProperty) { 258 Class<?> ccc = complexProperty.getClass(); 259 if (params.length != 1) { 260 addError("Wrong number of parameters in setter method for property [" + name + "] in " 261 + obj.getClass().getName()); 262 263 return false; 264 } 265 266 if (!params[0].isAssignableFrom(complexProperty.getClass())) { 267 addError("A \"" + ccc.getName() + "\" object is not assignable to a \"" + params[0].getName() 268 + "\" variable."); 269 addError("The class \"" + params[0].getName() + "\" was loaded by "); 270 addError("[" + params[0].getClassLoader() + "] whereas object of type "); 271 addError("\"" + ccc.getName() + "\" was loaded by [" + ccc.getClassLoader() + "]."); 272 return false; 273 } 274 275 return true; 276 } 277 278 public Object getObj() { 279 return obj; 280 } 281 282 283 public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType, 284 DefaultNestedComponentRegistry registry) { 285 return aggregationAssessor.getClassNameViaImplicitRules(name, aggregationType, registry); 286 } 287 288 289 290 291 292 }