View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  // Contributors:  Georg Lundesgaard
15  package ch.qos.logback.core.joran.util;
16  
17  import java.lang.annotation.Annotation;
18  import java.lang.reflect.InvocationTargetException;
19  import java.lang.reflect.Method;
20  
21  import ch.qos.logback.core.joran.spi.DefaultClass;
22  import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
23  import ch.qos.logback.core.joran.util.beans.BeanDescription;
24  import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
25  import ch.qos.logback.core.joran.util.beans.BeanUtil;
26  import ch.qos.logback.core.spi.ContextAwareBase;
27  import ch.qos.logback.core.util.AggregationType;
28  import ch.qos.logback.core.util.PropertySetterException;
29  
30  /**
31   * General purpose Object property setter. Clients repeatedly invokes
32   * {@link #setProperty setProperty(name,value)} in order to invoke setters on
33   * the Object specified in the constructor. This class relies on reflection
34   * to analyze the given Object Class.
35   *
36   * <p>
37   * Usage:
38   *
39   * <pre>
40   * PropertySetter ps = new PropertySetter(anObject);
41   * ps.set(&quot;name&quot;, &quot;Joe&quot;);
42   * ps.set(&quot;age&quot;, &quot;32&quot;);
43   * ps.set(&quot;isMale&quot;, &quot;true&quot;);
44   * </pre>
45   *
46   * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and
47   * setMale(true) if such methods exist with those signatures. Otherwise an
48   * {@link PropertySetterException} is thrown.
49   *
50   * @author Anders Kristensen
51   * @author Ceki Gulcu
52   */
53  public class PropertySetter extends ContextAwareBase {
54  
55      protected final Object obj;
56      protected final Class<?> objClass;
57      protected final BeanDescription beanDescription;
58  
59      /**
60       * Create a new PropertySetter for the specified Object. This is done in
61       * preparation for invoking {@link #setProperty} one or more times.
62       *
63       * @param obj
64       *          the object for which to set properties
65       */
66      public PropertySetter(BeanDescriptionCache beanDescriptionCache, Object obj) {
67          this.obj = obj;
68          this.objClass = obj.getClass();
69          this.beanDescription = beanDescriptionCache.getBeanDescription(objClass);
70      }
71  
72      /**
73       * Set a property on this PropertySetter's Object. If successful, this method
74       * will invoke a setter method on the underlying Object. The setter is the one
75       * for the specified property name and the value is determined partly from the
76       * setter argument type and partly from the value specified in the call to
77       * this method.
78       *
79       * <p>
80       * If the setter expects a String no conversion is necessary. If it expects an
81       * int, then an attempt is made to convert 'value' to an int using new
82       * Integer(value). If the setter expects a boolean, the conversion is by new
83       * Boolean(value).
84       *
85       * @param name
86       *          name of the property
87       * @param value
88       *          String value of the property
89       */
90      public void setProperty(String name, String value) {
91          if (value == null) {
92              return;
93          }
94          Method setter = findSetterMethod(name);
95          if (setter == null) {
96              addWarn("No setter for property [" + name + "] in " + objClass.getName() + ".");
97          } else {
98              try {
99                  setProperty(setter, name, value);
100             } catch (PropertySetterException ex) {
101                 addWarn("Failed to set property [" + name + "] to value \"" + value + "\". ", ex);
102             }
103         }
104     }
105 
106     /**
107      * Set the named property given a {@link PropertyDescriptor}.
108      *
109      * @param prop
110      *          A PropertyDescriptor describing the characteristics of the
111      *          property to set.
112      * @param name
113      *          The named of the property to set.
114      * @param value
115      *          The value of the property.
116      */
117     private void setProperty(Method setter, String name, 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         String cName = capitalizeFirstLetter(name);
140 
141         Method addMethod = findAdderMethod(cName);
142 
143         if (addMethod != null) {
144             AggregationType type = computeRawAggregationType(addMethod);
145             switch (type) {
146             case NOT_FOUND:
147                 return AggregationType.NOT_FOUND;
148             case AS_BASIC_PROPERTY:
149                 return AggregationType.AS_BASIC_PROPERTY_COLLECTION;
150 
151             case AS_COMPLEX_PROPERTY:
152                 return AggregationType.AS_COMPLEX_PROPERTY_COLLECTION;
153             case AS_BASIC_PROPERTY_COLLECTION:
154             case AS_COMPLEX_PROPERTY_COLLECTION:
155                 addError("Unexpected AggregationType " + type);
156             }
157         }
158 
159         Method setter = findSetterMethod(name);
160         if (setter != null) {
161             return computeRawAggregationType(setter);
162         } else {
163             // we have failed
164             return AggregationType.NOT_FOUND;
165         }
166     }
167 
168     private Method findAdderMethod(String name) {
169         String propertyName = BeanUtil.toLowerCamelCase(name);
170         return beanDescription.getAdder(propertyName);
171     }
172 
173     private Method findSetterMethod(String name) {
174         String propertyName = BeanUtil.toLowerCamelCase(name);
175         return beanDescription.getSetter(propertyName);
176     }
177 
178     private Class<?> getParameterClassForMethod(Method method) {
179         if (method == null) {
180             return null;
181         }
182         Class<?>[] classArray = method.getParameterTypes();
183         if (classArray.length != 1) {
184             return null;
185         } else {
186             return classArray[0];
187         }
188     }
189 
190     private AggregationType computeRawAggregationType(Method method) {
191         Class<?> parameterClass = getParameterClassForMethod(method);
192         if (parameterClass == null) {
193             return AggregationType.NOT_FOUND;
194         }
195         if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) {
196             return AggregationType.AS_BASIC_PROPERTY;
197         } else {
198             return AggregationType.AS_COMPLEX_PROPERTY;
199         }
200     }
201 
202     /**
203      * Can the given clazz instantiable with certainty?
204      *
205      * @param clazz
206      *          The class to test for instantiability
207      * @return true if clazz can be instantiated, and false otherwise.
208      */
209     private boolean isUnequivocallyInstantiable(Class<?> clazz) {
210         if (clazz.isInterface()) {
211             return false;
212         }
213         // checking for constructors would be more elegant, but in
214         // classes without any declared constructors, Class.getConstructor()
215         // returns null.
216         Object o;
217         try {
218             o = clazz.getDeclaredConstructor().newInstance();
219             if (o != null) {
220                 return true;
221             } else {
222                 return false;
223             }
224         } catch (InstantiationException|IllegalAccessException | InvocationTargetException | NoSuchMethodException  e) {
225             return false;
226         }  
227     }
228 
229     public Class<?> getObjClass() {
230         return objClass;
231     }
232 
233     public void addComplexProperty(String name, Object complexProperty) {
234         Method adderMethod = findAdderMethod(name);
235         // first let us use the addXXX method
236         if (adderMethod != null) {
237             Class<?>[] paramTypes = adderMethod.getParameterTypes();
238             if (!isSanityCheckSuccessful(name, adderMethod, paramTypes, complexProperty)) {
239                 return;
240             }
241             invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty);
242         } else {
243             addError("Could not find method [" + "add" + name + "] in class [" + objClass.getName() + "].");
244         }
245     }
246 
247     void invokeMethodWithSingleParameterOnThisObject(Method method, Object parameter) {
248         Class<?> ccc = parameter.getClass();
249         try {
250             method.invoke(this.obj, parameter);
251         } catch (Exception e) {
252             addError("Could not invoke method " + method.getName() + " in class " + obj.getClass().getName() + " with parameter of type " + ccc.getName(), e);
253         }
254     }
255 
256     public void addBasicProperty(String name, String strValue) {
257 
258         if (strValue == null) {
259             return;
260         }
261 
262         name = capitalizeFirstLetter(name);
263         Method adderMethod = findAdderMethod(name);
264 
265         if (adderMethod == null) {
266             addError("No adder for property [" + name + "].");
267             return;
268         }
269 
270         Class<?>[] paramTypes = adderMethod.getParameterTypes();
271         isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue);
272 
273         Object arg;
274         try {
275             arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]);
276         } catch (Throwable t) {
277             addError("Conversion to type [" + paramTypes[0] + "] failed. ", t);
278             return;
279         }
280         if (arg != null) {
281             invokeMethodWithSingleParameterOnThisObject(adderMethod, strValue);
282         }
283     }
284 
285     public void setComplexProperty(String name, Object complexProperty) {
286         Method setter = findSetterMethod(name);
287 
288         if (setter == null) {
289             addWarn("Not setter method for property [" + name + "] in " + obj.getClass().getName());
290 
291             return;
292         }
293 
294         Class<?>[] paramTypes = setter.getParameterTypes();
295 
296         if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) {
297             return;
298         }
299         try {
300             invokeMethodWithSingleParameterOnThisObject(setter, complexProperty);
301 
302         } catch (Exception e) {
303             addError("Could not set component " + obj + " for parent component " + obj, e);
304         }
305     }
306 
307     private boolean isSanityCheckSuccessful(String name, Method method, Class<?>[] params, Object complexProperty) {
308         Class<?> ccc = complexProperty.getClass();
309         if (params.length != 1) {
310             addError("Wrong number of parameters in setter method for property [" + name + "] in " + obj.getClass().getName());
311 
312             return false;
313         }
314 
315         if (!params[0].isAssignableFrom(complexProperty.getClass())) {
316             addError("A \"" + ccc.getName() + "\" object is not assignable to a \"" + params[0].getName() + "\" variable.");
317             addError("The class \"" + params[0].getName() + "\" was loaded by ");
318             addError("[" + params[0].getClassLoader() + "] whereas object of type ");
319             addError("\"" + ccc.getName() + "\" was loaded by [" + ccc.getClassLoader() + "].");
320             return false;
321         }
322 
323         return true;
324     }
325 
326     private String capitalizeFirstLetter(String name) {
327         return name.substring(0, 1).toUpperCase() + name.substring(1);
328     }
329 
330     public Object getObj() {
331         return obj;
332     }
333 
334     Method getRelevantMethod(String name, AggregationType aggregationType) {
335         Method relevantMethod;
336         if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY_COLLECTION) {
337             relevantMethod = findAdderMethod(name);
338         } else if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY) {
339             relevantMethod = findSetterMethod(name);
340         } else {
341             throw new IllegalStateException(aggregationType + " not allowed here");
342         }
343         return relevantMethod;
344     }
345 
346     <T extends Annotation> T getAnnotation(String name, Class<T> annonationClass, Method relevantMethod) {
347 
348         if (relevantMethod != null) {
349             return relevantMethod.getAnnotation(annonationClass);
350         } else {
351             return null;
352         }
353     }
354 
355     Class<?> getDefaultClassNameByAnnonation(String name, Method relevantMethod) {
356         DefaultClass defaultClassAnnon = getAnnotation(name, DefaultClass.class, relevantMethod);
357         if (defaultClassAnnon != null) {
358             return defaultClassAnnon.value();
359         }
360         return null;
361     }
362 
363     Class<?> getByConcreteType(String name, Method relevantMethod) {
364 
365         Class<?> paramType = getParameterClassForMethod(relevantMethod);
366         if (paramType == null) {
367             return null;
368         }
369 
370         boolean isUnequivocallyInstantiable = isUnequivocallyInstantiable(paramType);
371         if (isUnequivocallyInstantiable) {
372             return paramType;
373         } else {
374             return null;
375         }
376 
377     }
378 
379     public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType, DefaultNestedComponentRegistry registry) {
380 
381         Class<?> registryResult = registry.findDefaultComponentType(obj.getClass(), name);
382         if (registryResult != null) {
383             return registryResult;
384         }
385         // find the relevant method for the given property name and aggregationType
386         Method relevantMethod = getRelevantMethod(name, aggregationType);
387         if (relevantMethod == null) {
388             return null;
389         }
390         Class<?> byAnnotation = getDefaultClassNameByAnnonation(name, relevantMethod);
391         if (byAnnotation != null) {
392             return byAnnotation;
393         }
394         return getByConcreteType(name, relevantMethod);
395     }
396 
397 }