View Javadoc

1   /*
2    * Copyright 1999,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  // Contributors:  Georg Lundesgaard
18  package ch.qos.logback.core.joran.spi;
19  
20  import java.beans.BeanInfo;
21  import java.beans.IntrospectionException;
22  import java.beans.Introspector;
23  import java.beans.MethodDescriptor;
24  import java.beans.PropertyDescriptor;
25  import java.lang.annotation.Annotation;
26  import java.lang.reflect.Method;
27  import java.lang.reflect.Modifier;
28  
29  import ch.qos.logback.core.CoreConstants;
30  import ch.qos.logback.core.joran.action.IADataForComplexProperty;
31  import ch.qos.logback.core.spi.ContextAwareBase;
32  import ch.qos.logback.core.util.AggregationType;
33  import ch.qos.logback.core.util.PropertySetterException;
34  
35  /**
36   * General purpose Object property setter. Clients repeatedly invokes
37   * {@link #setProperty setProperty(name,value)} in order to invoke setters on
38   * the Object specified in the constructor. This class relies on the JavaBeans
39   * {@link Introspector} to analyze the given Object Class using reflection.
40   * 
41   * <p> Usage:
42   * 
43   * <pre>
44   * PropertySetter ps = new PropertySetter(anObject);
45   * ps.set(&quot;name&quot;, &quot;Joe&quot;);
46   * ps.set(&quot;age&quot;, &quot;32&quot;);
47   * ps.set(&quot;isMale&quot;, &quot;true&quot;);
48   * </pre>
49   * 
50   * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and
51   * setMale(true) if such methods exist with those signatures. Otherwise an
52   * {@link IntrospectionException} are thrown.
53   * 
54   * @author Anders Kristensen
55   * @author Ceki Gulcu
56   */
57  public class PropertySetter extends ContextAwareBase {
58    private static final Class[] STING_CLASS_PARAMETER = new Class[] { String.class };
59  
60    protected Object obj;
61    protected Class objClass;
62    protected PropertyDescriptor[] propertyDescriptors;
63    protected MethodDescriptor[] methodDescriptors;
64  
65    /**
66     * Create a new PropertySetter for the specified Object. This is done in
67     * preparation for invoking {@link #setProperty} one or more times.
68     * 
69     * @param obj
70     *                the object for which to set properties
71     */
72    public PropertySetter(Object obj) {
73      this.obj = obj;
74      this.objClass = obj.getClass();
75    }
76  
77    /**
78     * Uses JavaBeans {@link Introspector} to computer setters of object to be
79     * configured.
80     */
81    protected void introspect() {
82      try {
83        BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
84        propertyDescriptors = bi.getPropertyDescriptors();
85        methodDescriptors = bi.getMethodDescriptors();
86      } catch (IntrospectionException ex) {
87        addError("Failed to introspect " + obj + ": " + ex.getMessage());
88        propertyDescriptors = new PropertyDescriptor[0];
89        methodDescriptors = new MethodDescriptor[0];
90      }
91    }
92  
93    /**
94     * Set a property on this PropertySetter's Object. If successful, this method
95     * will invoke a setter method on the underlying Object. The setter is the one
96     * for the specified property name and the value is determined partly from the
97     * setter argument type and partly from the value specified in the call to
98     * this method.
99     * 
100    * <p> If the setter expects a String no conversion is necessary. If it
101    * expects an int, then an attempt is made to convert 'value' to an int using
102    * new Integer(value). If the setter expects a boolean, the conversion is by
103    * new Boolean(value).
104    * 
105    * @param name
106    *                name of the property
107    * @param value
108    *                String value of the property
109    */
110   public void setProperty(String name, String value) {
111     if (value == null) {
112       return;
113     }
114 
115     name = Introspector.decapitalize(name);
116 
117     PropertyDescriptor prop = getPropertyDescriptor(name);
118 
119     if (prop == null) {
120       addWarn("No such property [" + name + "] in " + objClass.getName() + ".");
121     } else {
122       try {
123         setProperty(prop, name, value);
124       } catch (PropertySetterException ex) {
125         addWarn("Failed to set property [" + name + "] to value \"" + value
126             + "\". ", ex);
127       }
128     }
129   }
130 
131   /**
132    * Set the named property given a {@link PropertyDescriptor}.
133    * 
134    * @param prop
135    *                A PropertyDescriptor describing the characteristics of the
136    *                property to set.
137    * @param name
138    *                The named of the property to set.
139    * @param value
140    *                The value of the property.
141    */
142   public void setProperty(PropertyDescriptor prop, String name, String value)
143       throws PropertySetterException {
144     Method setter = prop.getWriteMethod();
145 
146     if (setter == null) {
147       throw new PropertySetterException("No setter for property [" + name
148           + "].");
149     }
150 
151     Class[] paramTypes = setter.getParameterTypes();
152 
153     if (paramTypes.length != 1) {
154       throw new PropertySetterException("#params for setter != 1");
155     }
156 
157     Object arg;
158 
159     try {
160       arg = convertArg(value, paramTypes[0]);
161     } catch (Throwable t) {
162       throw new PropertySetterException("Conversion to type [" + paramTypes[0]
163           + "] failed. ", t);
164     }
165 
166     if (arg == null) {
167       throw new PropertySetterException("Conversion to type [" + paramTypes[0]
168           + "] failed.");
169     }
170     try {
171       setter.invoke(obj, arg);
172     } catch (Exception ex) {
173       throw new PropertySetterException(ex);
174     }
175   }
176 
177   public AggregationType computeAggregationType(String name) {
178     String cName = capitalizeFirstLetter(name);
179 
180     Method addMethod = findAdderMethod(cName);
181 
182     // if the
183     if (addMethod != null) {
184       AggregationType type = computeRawAggregationType(addMethod);
185       switch (type) {
186       case NOT_FOUND:
187         return AggregationType.NOT_FOUND;
188       case AS_BASIC_PROPERTY:
189         return AggregationType.AS_BASIC_PROPERTY_COLLECTION;
190       case AS_COMPLEX_PROPERTY:
191         return AggregationType.AS_COMPLEX_PROPERTY_COLLECTION;
192       }
193     }
194 
195     Method setterMethod = findSetterMethod(name);
196     if (setterMethod != null) {
197       return computeRawAggregationType(setterMethod);
198     } else {
199       // we have failed
200       return AggregationType.NOT_FOUND;
201     }
202   }
203 
204   private Method findAdderMethod(String name) {
205     name = capitalizeFirstLetter(name);
206     Method adderMethod = getMethod("add" + name);
207     return adderMethod;
208   }
209 
210   private Method findSetterMethod(String name) {
211     String dName = Introspector.decapitalize(name);
212     PropertyDescriptor propertyDescriptor = getPropertyDescriptor(dName);
213     if (propertyDescriptor != null) {
214       return propertyDescriptor.getWriteMethod();
215     } else {
216       return null;
217     }
218   }
219 
220   private Class<?> getParameterClassForMethod(Method method) {
221     if (method == null) {
222       return null;
223     }
224     Class[] classArray = method.getParameterTypes();
225     if (classArray.length != 1) {
226       return null;
227     } else {
228       return classArray[0];
229     }
230   }
231 
232   private AggregationType computeRawAggregationType(Method method) {
233     Class<?> parameterClass = getParameterClassForMethod(method);
234     if (parameterClass == null) {
235       return AggregationType.NOT_FOUND;
236     } else {
237       Package p = parameterClass.getPackage();
238       if (parameterClass.isPrimitive()) {
239         return AggregationType.AS_BASIC_PROPERTY;
240       } else if (p != null && "java.lang".equals(p.getName())) {
241         return AggregationType.AS_BASIC_PROPERTY;
242       } else if (isBuildableFromString(parameterClass)) {
243         return AggregationType.AS_BASIC_PROPERTY;
244       } else if (parameterClass.isEnum()) {
245         return AggregationType.AS_BASIC_PROPERTY;
246       } else {
247         return AggregationType.AS_COMPLEX_PROPERTY;
248       }
249     }
250   }
251 
252   public Class findUnequivocallyInstantiableClass(
253       IADataForComplexProperty actionData) {
254     Class<?> clazz;
255     AggregationType at = actionData.getAggregationType();
256     switch (at) {
257     case AS_COMPLEX_PROPERTY:
258       Method setterMethod = findSetterMethod(actionData
259           .getComplexPropertyName());
260       clazz = getParameterClassForMethod(setterMethod);
261       if (clazz != null && isUnequivocallyInstantiable(clazz)) {
262         return clazz;
263       } else {
264         return null;
265       }
266     case AS_COMPLEX_PROPERTY_COLLECTION:
267       Method adderMethod = findAdderMethod(actionData.getComplexPropertyName());
268       clazz = getParameterClassForMethod(adderMethod);
269       if (clazz != null && isUnequivocallyInstantiable(clazz)) {
270         return clazz;
271       } else {
272         return null;
273       }
274     default:
275       throw new IllegalArgumentException(at
276           + " is not valid type in this method");
277     }
278   }
279 
280   /**
281    * Can the given clazz instantiable with certainty?
282    * 
283    * @param clazz The class to test for instantiability
284    * @return true if clazz can be instantiated, and false otherwise.
285    */
286   private boolean isUnequivocallyInstantiable(Class<?> clazz) {
287     if (clazz.isInterface()) {
288       return false;
289     }
290     // checking for constructors would be more elegant, but in
291     // classes without any declared constructors, Class.getConstructor()
292     // returns null.
293     Object o;
294     try {
295       o = clazz.newInstance();
296       if (o != null) {
297         return true;
298       } else {
299         return false;
300       }
301     } catch (InstantiationException e) {
302       return false;
303     } catch (IllegalAccessException e) {
304       return false;
305     }
306   }
307 
308   public Class getObjClass() {
309     return objClass;
310   }
311 
312   public void addComplexProperty(String name, Object complexProperty) {
313     Method adderMethod = findAdderMethod(name);
314     // first let us use the addXXX method
315     if (adderMethod != null) {
316       Class[] paramTypes = adderMethod.getParameterTypes();
317       if (!isSanityCheckSuccessful(name, adderMethod, paramTypes,
318           complexProperty)) {
319         return;
320       }
321       invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty);
322     } else {
323       addError("Could not find method [" + "add" + name + "] in class ["
324           + objClass.getName() + "].");
325     }
326   }
327 
328   void invokeMethodWithSingleParameterOnThisObject(Method method,
329       Object parameter) {
330     Class ccc = parameter.getClass();
331     try {
332       method.invoke(this.obj, parameter);
333     } catch (Exception e) {
334       addError("Could not invoke method " + method.getName() + " in class "
335           + obj.getClass().getName() + " with parameter of type "
336           + ccc.getName(), e);
337     }
338   }
339 
340   @SuppressWarnings("unchecked")
341   public void addBasicProperty(String name, String strValue) {
342 
343     if (strValue == null) {
344       return;
345     }
346 
347     name = capitalizeFirstLetter(name);
348     Method adderMethod = findAdderMethod(name);
349 
350     if (adderMethod == null) {
351       addError("No adder for property [" + name + "].");
352       return;
353     }
354 
355     Class[] paramTypes = adderMethod.getParameterTypes();
356     isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue);
357 
358     Object arg;
359     try {
360       arg = convertArg(strValue, paramTypes[0]);
361     } catch (Throwable t) {
362       addError("Conversion to type [" + paramTypes[0] + "] failed. ", t);
363       return;
364     }
365     if (arg != null) {
366       invokeMethodWithSingleParameterOnThisObject(adderMethod, strValue);
367     }
368   }
369 
370   public void setComplexProperty(String name, Object complexProperty) {
371     String dName = Introspector.decapitalize(name);
372     PropertyDescriptor propertyDescriptor = getPropertyDescriptor(dName);
373 
374     if (propertyDescriptor == null) {
375       addWarn("Could not find PropertyDescriptor for [" + name + "] in "
376           + objClass.getName());
377 
378       return;
379     }
380 
381     Method setter = propertyDescriptor.getWriteMethod();
382 
383     if (setter == null) {
384       addWarn("Not setter method for property [" + name + "] in "
385           + obj.getClass().getName());
386 
387       return;
388     }
389 
390     Class[] paramTypes = setter.getParameterTypes();
391 
392     if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) {
393       return;
394     }
395     try {
396       invokeMethodWithSingleParameterOnThisObject(setter, complexProperty);
397 
398     } catch (Exception e) {
399       addError("Could not set component " + obj + " for parent component "
400           + obj, e);
401     }
402   }
403 
404   private boolean isSanityCheckSuccessful(String name, Method method,
405       Class<?>[] params, Object complexProperty) {
406     Class ccc = complexProperty.getClass();
407     if (params.length != 1) {
408       addError("Wrong number of parameters in setter method for property ["
409           + name + "] in " + obj.getClass().getName());
410 
411       return false;
412     }
413 
414     if (!params[0].isAssignableFrom(complexProperty.getClass())) {
415       addError("A \"" + ccc.getName() + "\" object is not assignable to a \""
416           + params[0].getName() + "\" variable.");
417       addError("The class \"" + params[0].getName() + "\" was loaded by ");
418       addError("[" + params[0].getClassLoader() + "] whereas object of type ");
419       addError("\"" + ccc.getName() + "\" was loaded by ["
420           + ccc.getClassLoader() + "].");
421       return false;
422     }
423 
424     return true;
425   }
426 
427   private String capitalizeFirstLetter(String name) {
428     return name.substring(0, 1).toUpperCase() + name.substring(1);
429   }
430 
431   /**
432    * Convert <code>val</code> a String parameter to an object of a given type.
433    */
434   protected Object convertArg(String val, Class<?> type) {
435     if (val == null) {
436       return null;
437     }
438     String v = val.trim();
439     if (String.class.isAssignableFrom(type)) {
440       return val;
441     } else if (Integer.TYPE.isAssignableFrom(type)) {
442       return new Integer(v);
443     } else if (Long.TYPE.isAssignableFrom(type)) {
444       return new Long(v);
445     } else if (Float.TYPE.isAssignableFrom(type)) {
446       return new Float(v);
447     } else if (Double.TYPE.isAssignableFrom(type)) {
448       return new Double(v);
449     } else if (Boolean.TYPE.isAssignableFrom(type)) {
450       if ("true".equalsIgnoreCase(v)) {
451         return Boolean.TRUE;
452       } else if ("false".equalsIgnoreCase(v)) {
453         return Boolean.FALSE;
454       }
455     } else if (type.isEnum()) {
456       return convertEnum(val, type);
457     } else if (isBuildableFromString(type)) {
458       return buildFromString(type, val);
459     }
460 
461     return null;
462   }
463 
464   boolean isBuildableFromString(Class<?> parameterClass) {
465     try {
466       Method valueOfMethod = parameterClass.getMethod(CoreConstants.VALUE_OF,
467           STING_CLASS_PARAMETER);
468       int mod = valueOfMethod.getModifiers();
469       if (Modifier.isStatic(mod)) {
470         return true;
471       }
472     } catch (SecurityException e) {
473       // nop
474     } catch (NoSuchMethodException e) {
475       // nop
476     }
477     return false;
478   }
479 
480   Object buildFromString(Class<?> type, String val) {
481     try {
482       Method valueOfMethod = type.getMethod(CoreConstants.VALUE_OF,
483           STING_CLASS_PARAMETER);
484       return valueOfMethod.invoke(null, val);
485     } catch (Exception e) {
486       addError("Failed to invoke " + CoreConstants.VALUE_OF
487           + "{} method in class [" + type.getName() + "] with value [" + val
488           + "]");
489       return null;
490     }
491   }
492 
493   protected Object convertEnum(String val, Class<?> type) {
494     try {
495       Method m = type.getMethod(CoreConstants.VALUE_OF, STING_CLASS_PARAMETER);
496       return m.invoke(null, val);
497     } catch (Exception e) {
498       addError("Failed to convert value [" + val + "] to enum ["
499           + type.getName() + "]", e);
500     }
501     return null;
502   }
503 
504   protected Method getMethod(String methodName) {
505     if (methodDescriptors == null) {
506       introspect();
507     }
508 
509     for (int i = 0; i < methodDescriptors.length; i++) {
510       if (methodName.equals(methodDescriptors[i].getName())) {
511         return methodDescriptors[i].getMethod();
512       }
513     }
514 
515     return null;
516   }
517 
518   protected PropertyDescriptor getPropertyDescriptor(String name) {
519     if (propertyDescriptors == null) {
520       introspect();
521     }
522 
523     for (int i = 0; i < propertyDescriptors.length; i++) {
524       // System.out.println("Comparing " + name + " against "
525       // + propertyDescriptors[i].getName());
526       if (name.equals(propertyDescriptors[i].getName())) {
527         // System.out.println("matched");
528         return propertyDescriptors[i];
529       }
530     }
531 
532     return null;
533   }
534 
535   public Object getObj() {
536     return obj;
537   }
538 
539   Method getRelevantMethod(String name, AggregationType aggregationType) {
540     String cName = capitalizeFirstLetter(name);
541     Method relevantMethod;
542     if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY_COLLECTION) {
543       relevantMethod = findAdderMethod(cName);
544     } else if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY) {
545       relevantMethod = findSetterMethod(cName);
546     } else {
547       throw new IllegalStateException(aggregationType + " not allowed here");
548     }
549     return relevantMethod;
550   }
551 
552   <T extends Annotation> T getAnnotation(String name, Class<T> annonationClass,
553       Method relevantMethod) {
554 
555     if (relevantMethod != null) {
556       return relevantMethod.getAnnotation(annonationClass);
557     } else {
558       return null;
559     }
560   }
561 
562   Class getDefaultClassNameByAnnonation(String name, Method relevantMethod) {
563     DefaultClass defaultClassAnnon = getAnnotation(name, DefaultClass.class,
564         relevantMethod);
565     if (defaultClassAnnon != null) {
566       Class defaultClass = defaultClassAnnon.value();
567       return defaultClass;
568     }
569     return null;
570   }
571 
572   Class getByConcreteType(String name, Method relevantMethod) {
573     
574     Class<?> paramType = getParameterClassForMethod(relevantMethod);
575     if (paramType == null) {
576       return null;
577     }
578     
579     boolean isUnequivocallyInstantiable = isUnequivocallyInstantiable(paramType);
580     if(isUnequivocallyInstantiable) {
581       return paramType;
582     } else {
583       return null;
584     }
585 
586   }
587 
588   public Class getClassNameViaImplicitRules(String name,
589       AggregationType aggregationType, DefaultNestedComponentRegistry registry) {
590 
591     Class registryResult = registry.findDefaultComponentType(obj.getClass(), name);
592     if(registryResult!= null) {
593       return registryResult;
594     }
595     // find the relevant method for the given property name and aggregationType
596     Method relevantMethod = getRelevantMethod(name, aggregationType);
597     if (relevantMethod == null) {
598       return null;
599     }
600     Class byAnnotation = getDefaultClassNameByAnnonation(name, relevantMethod);
601     if (byAnnotation != null) {
602       return byAnnotation;
603     }
604     return getByConcreteType(name, relevantMethod);
605   }
606 
607 }