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(&quot;name&quot;, &quot;Joe&quot;);
040 * ps.set(&quot;age&quot;, &quot;32&quot;);
041 * ps.set(&quot;isMale&quot;, &quot;true&quot;);
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}