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 java.lang.annotation.Annotation; 018import java.lang.reflect.InvocationTargetException; 019import java.lang.reflect.Method; 020 021import ch.qos.logback.core.joran.spi.DefaultClass; 022import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry; 023import ch.qos.logback.core.joran.util.beans.BeanDescription; 024import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache; 025import ch.qos.logback.core.joran.util.beans.BeanUtil; 026import ch.qos.logback.core.spi.ContextAwareBase; 027import ch.qos.logback.core.util.AggregationType; 028import ch.qos.logback.core.util.PropertySetterException; 029 030/** 031 * General purpose Object property setter. Clients repeatedly invokes 032 * {@link #setProperty setProperty(name,value)} in order to invoke setters on 033 * the Object specified in the constructor. This class relies on reflection to 034 * analyze the given Object Class. 035 * 036 * <p> 037 * Usage: 038 * 039 * <pre> 040 * PropertySetter ps = new PropertySetter(anObject); 041 * ps.set("name", "Joe"); 042 * ps.set("age", "32"); 043 * ps.set("isMale", "true"); 044 * </pre> 045 * 046 * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and 047 * setMale(true) if such methods exist with those signatures. Otherwise an 048 * {@link PropertySetterException} is thrown. 049 * 050 * @author Anders Kristensen 051 * @author Ceki Gulcu 052 */ 053public class PropertySetter extends ContextAwareBase { 054 055 protected final Object obj; 056 protected final Class<?> objClass; 057 protected final BeanDescription beanDescription; 058 059 /** 060 * Create a new PropertySetter for the specified Object. This is done in 061 * preparation for invoking {@link #setProperty} one or more times. 062 * 063 * @param obj the object for which to set properties 064 */ 065 public PropertySetter(BeanDescriptionCache beanDescriptionCache, Object obj) { 066 this.obj = obj; 067 this.objClass = obj.getClass(); 068 this.beanDescription = beanDescriptionCache.getBeanDescription(objClass); 069 } 070 071 /** 072 * Set a property on this PropertySetter's Object. If successful, this method 073 * will invoke a setter method on the underlying Object. The setter is the one 074 * for the specified property name and the value is determined partly from the 075 * setter argument type and partly from the value specified in the call to this 076 * method. 077 * 078 * <p> 079 * If the setter expects a String no conversion is necessary. If it expects an 080 * int, then an attempt is made to convert 'value' to an int using new 081 * Integer(value). If the setter expects a boolean, the conversion is by new 082 * Boolean(value). 083 * 084 * @param name name of the property 085 * @param value String value of the property 086 */ 087 public void setProperty(String name, String value) { 088 if (value == null) { 089 return; 090 } 091 Method setter = findSetterMethod(name); 092 if (setter == null) { 093 addWarn("No setter for property [" + name + "] in " + objClass.getName() + "."); 094 } else { 095 try { 096 setProperty(setter, name, value); 097 } catch (PropertySetterException ex) { 098 addWarn("Failed to set property [" + name + "] to value \"" + value + "\". ", ex); 099 } 100 } 101 } 102 103 /** 104 * Set the named property given a {@link PropertyDescriptor}. 105 * 106 * @param prop A PropertyDescriptor describing the characteristics of the 107 * property to set. 108 * @param name The named of the property to set. 109 * @param value The value of the property. 110 */ 111 private void setProperty(Method setter, String name, String value) throws PropertySetterException { 112 Class<?>[] paramTypes = setter.getParameterTypes(); 113 114 Object arg; 115 116 try { 117 arg = StringToObjectConverter.convertArg(this, value, paramTypes[0]); 118 } catch (Throwable t) { 119 throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed. ", t); 120 } 121 122 if (arg == null) { 123 throw new PropertySetterException("Conversion to type [" + paramTypes[0] + "] failed."); 124 } 125 try { 126 setter.invoke(obj, arg); 127 } catch (Exception ex) { 128 throw new PropertySetterException(ex); 129 } 130 } 131 132 public AggregationType computeAggregationType(String name) { 133 String cName = capitalizeFirstLetter(name); 134 135 Method addMethod = findAdderMethod(cName); 136 137 if (addMethod != null) { 138 AggregationType type = computeRawAggregationType(addMethod); 139 switch (type) { 140 case NOT_FOUND: 141 return AggregationType.NOT_FOUND; 142 case AS_BASIC_PROPERTY: 143 return AggregationType.AS_BASIC_PROPERTY_COLLECTION; 144 145 case AS_COMPLEX_PROPERTY: 146 return AggregationType.AS_COMPLEX_PROPERTY_COLLECTION; 147 case AS_BASIC_PROPERTY_COLLECTION: 148 case AS_COMPLEX_PROPERTY_COLLECTION: 149 addError("Unexpected AggregationType " + type); 150 } 151 } 152 153 Method setter = findSetterMethod(name); 154 if (setter != null) { 155 return computeRawAggregationType(setter); 156 } else { 157 // we have failed 158 return AggregationType.NOT_FOUND; 159 } 160 } 161 162 private Method findAdderMethod(String name) { 163 String propertyName = BeanUtil.toLowerCamelCase(name); 164 return beanDescription.getAdder(propertyName); 165 } 166 167 private Method findSetterMethod(String name) { 168 String propertyName = BeanUtil.toLowerCamelCase(name); 169 return beanDescription.getSetter(propertyName); 170 } 171 172 private Class<?> getParameterClassForMethod(Method method) { 173 if (method == null) { 174 return null; 175 } 176 Class<?>[] classArray = method.getParameterTypes(); 177 if (classArray.length != 1) { 178 return null; 179 } else { 180 return classArray[0]; 181 } 182 } 183 184 private AggregationType computeRawAggregationType(Method method) { 185 Class<?> parameterClass = getParameterClassForMethod(method); 186 if (parameterClass == null) { 187 return AggregationType.NOT_FOUND; 188 } 189 if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) { 190 return AggregationType.AS_BASIC_PROPERTY; 191 } else { 192 return AggregationType.AS_COMPLEX_PROPERTY; 193 } 194 } 195 196 /** 197 * Can the given clazz instantiable with certainty? 198 * 199 * @param clazz The class to test for instantiability 200 * @return true if clazz can be instantiated, and false otherwise. 201 */ 202 private boolean isUnequivocallyInstantiable(Class<?> clazz) { 203 if (clazz.isInterface()) { 204 return false; 205 } 206 // checking for constructors would be more elegant, but in 207 // classes without any declared constructors, Class.getConstructor() 208 // returns null. 209 Object o; 210 try { 211 o = clazz.getDeclaredConstructor().newInstance(); 212 if (o != null) { 213 return true; 214 } else { 215 return false; 216 } 217 } catch (InstantiationException | IllegalAccessException | InvocationTargetException 218 | NoSuchMethodException e) { 219 return false; 220 } 221 } 222 223 public Class<?> getObjClass() { 224 return objClass; 225 } 226 227 public void addComplexProperty(String name, Object complexProperty) { 228 Method adderMethod = findAdderMethod(name); 229 // first let us use the addXXX method 230 if (adderMethod != null) { 231 Class<?>[] paramTypes = adderMethod.getParameterTypes(); 232 if (!isSanityCheckSuccessful(name, adderMethod, paramTypes, complexProperty)) { 233 return; 234 } 235 invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty); 236 } else { 237 addError("Could not find method [" + "add" + name + "] in class [" + objClass.getName() + "]."); 238 } 239 } 240 241 void invokeMethodWithSingleParameterOnThisObject(Method method, Object parameter) { 242 Class<?> ccc = parameter.getClass(); 243 try { 244 method.invoke(this.obj, parameter); 245 } catch (Exception e) { 246 addError("Could not invoke method " + method.getName() + " in class " + obj.getClass().getName() 247 + " with parameter of type " + ccc.getName(), e); 248 } 249 } 250 251 public void addBasicProperty(String name, String strValue) { 252 253 if (strValue == null) { 254 return; 255 } 256 257 name = capitalizeFirstLetter(name); 258 Method adderMethod = findAdderMethod(name); 259 260 if (adderMethod == null) { 261 addError("No adder for property [" + name + "]."); 262 return; 263 } 264 265 Class<?>[] paramTypes = adderMethod.getParameterTypes(); 266 isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue); 267 268 Object arg; 269 try { 270 arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]); 271 } catch (Throwable t) { 272 addError("Conversion to type [" + paramTypes[0] + "] failed. ", t); 273 return; 274 } 275 if (arg != null) { 276 invokeMethodWithSingleParameterOnThisObject(adderMethod, strValue); 277 } 278 } 279 280 public void setComplexProperty(String name, Object complexProperty) { 281 Method setter = findSetterMethod(name); 282 283 if (setter == null) { 284 addWarn("Not setter method for property [" + name + "] in " + obj.getClass().getName()); 285 286 return; 287 } 288 289 Class<?>[] paramTypes = setter.getParameterTypes(); 290 291 if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) { 292 return; 293 } 294 try { 295 invokeMethodWithSingleParameterOnThisObject(setter, complexProperty); 296 297 } catch (Exception e) { 298 addError("Could not set component " + obj + " for parent component " + obj, e); 299 } 300 } 301 302 private boolean isSanityCheckSuccessful(String name, Method method, Class<?>[] params, Object complexProperty) { 303 Class<?> ccc = complexProperty.getClass(); 304 if (params.length != 1) { 305 addError("Wrong number of parameters in setter method for property [" + name + "] in " 306 + obj.getClass().getName()); 307 308 return false; 309 } 310 311 if (!params[0].isAssignableFrom(complexProperty.getClass())) { 312 addError("A \"" + ccc.getName() + "\" object is not assignable to a \"" + params[0].getName() 313 + "\" variable."); 314 addError("The class \"" + params[0].getName() + "\" was loaded by "); 315 addError("[" + params[0].getClassLoader() + "] whereas object of type "); 316 addError("\"" + ccc.getName() + "\" was loaded by [" + ccc.getClassLoader() + "]."); 317 return false; 318 } 319 320 return true; 321 } 322 323 private String capitalizeFirstLetter(String name) { 324 return name.substring(0, 1).toUpperCase() + name.substring(1); 325 } 326 327 public Object getObj() { 328 return obj; 329 } 330 331 Method getRelevantMethod(String name, AggregationType aggregationType) { 332 Method relevantMethod; 333 if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY_COLLECTION) { 334 relevantMethod = findAdderMethod(name); 335 } else if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY) { 336 relevantMethod = findSetterMethod(name); 337 } else { 338 throw new IllegalStateException(aggregationType + " not allowed here"); 339 } 340 return relevantMethod; 341 } 342 343 <T extends Annotation> T getAnnotation(String name, Class<T> annonationClass, Method relevantMethod) { 344 345 if (relevantMethod != null) { 346 return relevantMethod.getAnnotation(annonationClass); 347 } else { 348 return null; 349 } 350 } 351 352 Class<?> getDefaultClassNameByAnnonation(String name, Method relevantMethod) { 353 DefaultClass defaultClassAnnon = getAnnotation(name, DefaultClass.class, relevantMethod); 354 if (defaultClassAnnon != null) { 355 return defaultClassAnnon.value(); 356 } 357 return null; 358 } 359 360 Class<?> getByConcreteType(String name, Method relevantMethod) { 361 362 Class<?> paramType = getParameterClassForMethod(relevantMethod); 363 if (paramType == null) { 364 return null; 365 } 366 367 boolean isUnequivocallyInstantiable = isUnequivocallyInstantiable(paramType); 368 if (isUnequivocallyInstantiable) { 369 return paramType; 370 } else { 371 return null; 372 } 373 374 } 375 376 public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType, 377 DefaultNestedComponentRegistry registry) { 378 379 Class<?> registryResult = registry.findDefaultComponentType(obj.getClass(), name); 380 if (registryResult != null) { 381 return registryResult; 382 } 383 // find the relevant method for the given property name and aggregationType 384 Method relevantMethod = getRelevantMethod(name, aggregationType); 385 if (relevantMethod == null) { 386 return null; 387 } 388 Class<?> byAnnotation = getDefaultClassNameByAnnonation(name, relevantMethod); 389 if (byAnnotation != null) { 390 return byAnnotation; 391 } 392 return getByConcreteType(name, relevantMethod); 393 } 394 395}