001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * <p> 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 * <p> 009 * or (per the licensee's choosing) 010 * <p> 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 * <p> 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 143 public Class<?> getObjClass() { 144 return objClass; 145 } 146 147 public void addComplexProperty(String name, Object complexProperty) { 148 Method adderMethod = aggregationAssessor.findAdderMethod(name); 149 // first let us use the addXXX method 150 if (adderMethod != null) { 151 Class<?>[] paramTypes = adderMethod.getParameterTypes(); 152 if (!isSanityCheckSuccessful(name, adderMethod, paramTypes, complexProperty)) { 153 return; 154 } 155 invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty); 156 } else { 157 addError("Could not find method [" + "add" + name + "] in class [" + objClass.getName() + "]."); 158 } 159 } 160 161 void invokeMethodWithSingleParameterOnThisObject(Method method, Object parameter) { 162 Class<?> ccc = parameter.getClass(); 163 try { 164 method.invoke(this.obj, parameter); 165 } catch (Exception e) { 166 addError("Could not invoke method " + method.getName() + " in class " + obj.getClass().getName() 167 + " with parameter of type " + ccc.getName(), e); 168 } 169 } 170 171 public void addBasicProperty(String name, String strValue) { 172 173 if (strValue == null) { 174 return; 175 } 176 177 name = StringUtil.capitalizeFirstLetter(name); 178 Method adderMethod = aggregationAssessor.findAdderMethod(name); 179 180 if (adderMethod == null) { 181 addError("No adder for property [" + name + "]."); 182 return; 183 } 184 185 Class<?>[] paramTypes = adderMethod.getParameterTypes(); 186 isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue); 187 188 Object arg; 189 try { 190 arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]); 191 } catch (Throwable t) { 192 addError("Conversion to type [" + paramTypes[0] + "] failed. ", t); 193 return; 194 } 195 if (arg != null) { 196 invokeMethodWithSingleParameterOnThisObject(adderMethod, arg); 197 } 198 } 199 200 public void setComplexProperty(String name, Object complexProperty) { 201 Method setter = aggregationAssessor.findSetterMethod(name); 202 203 if (setter == null) { 204 addWarn("Not setter method for property [" + name + "] in " + obj.getClass().getName()); 205 206 return; 207 } 208 209 Class<?>[] paramTypes = setter.getParameterTypes(); 210 211 if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) { 212 return; 213 } 214 try { 215 invokeMethodWithSingleParameterOnThisObject(setter, complexProperty); 216 217 } catch (Exception e) { 218 addError("Could not set component " + obj + " for parent component " + obj, e); 219 } 220 } 221 222 private boolean isSanityCheckSuccessful(String name, Method method, Class<?>[] params, Object complexProperty) { 223 Class<?> ccc = complexProperty.getClass(); 224 if (params.length != 1) { 225 addError("Wrong number of parameters in setter method for property [" + name + "] in " 226 + obj.getClass().getName()); 227 228 return false; 229 } 230 231 if (!params[0].isAssignableFrom(complexProperty.getClass())) { 232 addError("A \"" + ccc.getName() + "\" object is not assignable to a \"" + params[0].getName() 233 + "\" variable."); 234 addError("The class \"" + params[0].getName() + "\" was loaded by "); 235 addError("[" + params[0].getClassLoader() + "] whereas object of type "); 236 addError("\"" + ccc.getName() + "\" was loaded by [" + ccc.getClassLoader() + "]."); 237 return false; 238 } 239 240 return true; 241 } 242 243 public Object getObj() { 244 return obj; 245 } 246 247 248 public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType, 249 DefaultNestedComponentRegistry registry) { 250 return aggregationAssessor.getClassNameViaImplicitRules(name, aggregationType, registry); 251 } 252 253 public Class<?> getTypeForComplexProperty(String nestedElementTagName, AggregationType aggregationType) { 254 255 Method aMethod = null; 256 switch (aggregationType) { 257 case AS_COMPLEX_PROPERTY: 258 aMethod = aggregationAssessor.findSetterMethod(nestedElementTagName); 259 break; 260 case AS_COMPLEX_PROPERTY_COLLECTION: 261 aMethod = aggregationAssessor.findAdderMethod(nestedElementTagName); 262 } 263 264 265 checkParameterCount(aMethod, nestedElementTagName); 266 267 Class<?>[] paramTypes = aMethod.getParameterTypes(); 268 return paramTypes[0]; 269 270 } 271 272 private void checkParameterCount(Method aMethod, String nestedElementTagName) { 273 if(aMethod == null) { 274 String msg = "Could not find method for property [" + nestedElementTagName + "]."; 275 addError(msg); 276 throw new IllegalStateException(msg); 277 } 278 int parameterCount = aMethod.getParameterCount(); 279 if (parameterCount != 1) { 280 String msg = "Expected ["+aMethod.getName()+"] for property [" + nestedElementTagName + "] to have exactly one parameter."; 281 addError(msg); 282 throw new IllegalStateException(msg); 283 } 284 } 285}