View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  // Contributors:  Georg Lundesgaard
19  
20  package org.apache.log4j.config;
21  
22  import java.beans.BeanInfo;
23  import java.beans.IntrospectionException;
24  import java.beans.Introspector;
25  import java.beans.PropertyDescriptor;
26  import java.io.InterruptedIOException;
27  import java.lang.reflect.InvocationTargetException;
28  import java.lang.reflect.Method;
29  import java.util.Properties;
30  
31  import org.apache.log4j.Appender;
32  import org.apache.log4j.Level;
33  import org.apache.log4j.Priority;
34  import org.apache.log4j.spi.ErrorHandler;
35  import org.apache.log4j.spi.OptionHandler;
36  import org.apache.logging.log4j.Logger;
37  import org.apache.logging.log4j.core.util.OptionConverter;
38  import org.apache.logging.log4j.status.StatusLogger;
39  
40  /**
41   * General purpose Object property setter. Clients repeatedly invokes
42   * {@link #setProperty setProperty(name,value)} in order to invoke setters
43   * on the Object specified in the constructor. This class relies on the
44   * JavaBeans {@link Introspector} to analyze the given Object Class using
45   * reflection.
46   *
47   * <p>Usage:
48   * <pre>
49   * PropertySetter ps = new PropertySetter(anObject);
50   * ps.set("name", "Joe");
51   * ps.set("age", "32");
52   * ps.set("isMale", "true");
53   * </pre>
54   * will cause the invocations anObject.setName("Joe"), anObject.setAge(32),
55   * and setMale(true) if such methods exist with those signatures.
56   * Otherwise an {@link IntrospectionException} are thrown.
57   */
58  public class PropertySetter {
59      private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
60      private static Logger LOGGER = StatusLogger.getLogger();
61      protected Object obj;
62      protected PropertyDescriptor[] props;
63  
64      /**
65       * Create a new PropertySetter for the specified Object. This is done
66       * in prepartion for invoking {@link #setProperty} one or more times.
67       *
68       * @param obj the object for which to set properties
69       */
70      public PropertySetter(Object obj) {
71          this.obj = obj;
72      }
73  
74      /**
75       * Set the properties of an object passed as a parameter in one
76       * go. The <code>properties</code> are parsed relative to a
77       * <code>prefix</code>.
78       *
79       * @param obj        The object to configure.
80       * @param properties A java.util.Properties containing keys and values.
81       * @param prefix     Only keys having the specified prefix will be set.
82       */
83      public static void setProperties(Object obj, Properties properties, String prefix) {
84          new PropertySetter(obj).setProperties(properties, prefix);
85      }
86  
87      /**
88       * Uses JavaBeans {@link Introspector} to computer setters of object to be
89       * configured.
90       */
91      protected void introspect() {
92          try {
93              BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
94              props = bi.getPropertyDescriptors();
95          } catch (IntrospectionException ex) {
96              LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage());
97              props = EMPTY_PROPERTY_DESCRIPTOR_ARRAY;
98          }
99      }
100 
101     /**
102      * Set the properites for the object that match the
103      * <code>prefix</code> passed as parameter.
104      * @param properties The properties.
105      * @param prefix The prefix of the properties to use.
106      */
107     public void setProperties(Properties properties, String prefix) {
108         int len = prefix.length();
109 
110         for (String key : properties.stringPropertyNames()) {
111 
112             // handle only properties that start with the desired prefix.
113             if (key.startsWith(prefix)) {
114 
115 
116                 // ignore key if it contains dots after the prefix
117                 if (key.indexOf('.', len + 1) > 0) {
118                     continue;
119                 }
120 
121                 String value = OptionConverter.findAndSubst(key, properties);
122                 key = key.substring(len);
123                 if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) {
124                     continue;
125                 }
126                 //
127                 //   if the property type is an OptionHandler
128                 //     (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)
129                 PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));
130                 if (prop != null
131                         && OptionHandler.class.isAssignableFrom(prop.getPropertyType())
132                         && prop.getWriteMethod() != null) {
133                     OptionHandler opt = (OptionHandler)
134                             OptionConverter.instantiateByKey(properties, prefix + key,
135                                     prop.getPropertyType(),
136                                     null);
137                     PropertySetter setter = new PropertySetter(opt);
138                     setter.setProperties(properties, prefix + key + ".");
139                     try {
140                         prop.getWriteMethod().invoke(this.obj, opt);
141                     } catch (InvocationTargetException ex) {
142                         if (ex.getTargetException() instanceof InterruptedException
143                                 || ex.getTargetException() instanceof InterruptedIOException) {
144                             Thread.currentThread().interrupt();
145                         }
146                         LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
147                     } catch (IllegalAccessException | RuntimeException ex) {
148                         LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
149                     }
150                     continue;
151                 }
152 
153                 setProperty(key, value);
154             }
155         }
156         activate();
157     }
158 
159     /**
160      * Set a property on this PropertySetter's Object. If successful, this
161      * method will invoke a setter method on the underlying Object. The
162      * setter is the one for the specified property name and the value is
163      * determined partly from the setter argument type and partly from the
164      * value specified in the call to this method.
165      *
166      * <p>If the setter expects a String no conversion is necessary.
167      * If it expects an int, then an attempt is made to convert 'value'
168      * to an int using new Integer(value). If the setter expects a boolean,
169      * the conversion is by new Boolean(value).
170      *
171      * @param name  name of the property
172      * @param value String value of the property
173      */
174     public void setProperty(String name, String value) {
175         if (value == null) {
176             return;
177         }
178 
179         name = Introspector.decapitalize(name);
180         PropertyDescriptor prop = getPropertyDescriptor(name);
181 
182         //LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType());
183 
184         if (prop == null) {
185             LOGGER.warn("No such property [" + name + "] in " +
186                     obj.getClass().getName() + ".");
187         } else {
188             try {
189                 setProperty(prop, name, value);
190             } catch (PropertySetterException ex) {
191                 LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause);
192             }
193         }
194     }
195 
196     /**
197      * Set the named property given a {@link PropertyDescriptor}.
198      *
199      * @param prop  A PropertyDescriptor describing the characteristics
200      *              of the property to set.
201      * @param name  The named of the property to set.
202      * @param value The value of the property.
203      * @throws PropertySetterException if no setter is available.
204      */
205     public void setProperty(PropertyDescriptor prop, String name, String value)
206             throws PropertySetterException {
207         Method setter = prop.getWriteMethod();
208         if (setter == null) {
209             throw new PropertySetterException("No setter for property [" + name + "].");
210         }
211         Class<?>[] paramTypes = setter.getParameterTypes();
212         if (paramTypes.length != 1) {
213             throw new PropertySetterException("#params for setter != 1");
214         }
215 
216         Object arg;
217         try {
218             arg = convertArg(value, paramTypes[0]);
219         } catch (Throwable t) {
220             throw new PropertySetterException("Conversion to type [" + paramTypes[0] +
221                     "] failed. Reason: " + t);
222         }
223         if (arg == null) {
224             throw new PropertySetterException(
225                     "Conversion to type [" + paramTypes[0] + "] failed.");
226         }
227         LOGGER.debug("Setting property [" + name + "] to [" + arg + "].");
228         try {
229             setter.invoke(obj, arg);
230         } catch (InvocationTargetException ex) {
231             if (ex.getTargetException() instanceof InterruptedException
232                     || ex.getTargetException() instanceof InterruptedIOException) {
233                 Thread.currentThread().interrupt();
234             }
235             throw new PropertySetterException(ex);
236         } catch (IllegalAccessException | RuntimeException ex) {
237             throw new PropertySetterException(ex);
238         }
239     }
240 
241 
242     /**
243      * Convert <code>val</code> a String parameter to an object of a
244      * given type.
245      * @param val The value to convert.
246      * @param type The type of the value to convert to.
247      * @return The result of the conversion.
248      */
249     protected Object convertArg(String val, Class<?> type) {
250         if (val == null) {
251             return null;
252         }
253 
254         String v = val.trim();
255         if (String.class.isAssignableFrom(type)) {
256             return val;
257         } else if (Integer.TYPE.isAssignableFrom(type)) {
258             return Integer.parseInt(v);
259         } else if (Long.TYPE.isAssignableFrom(type)) {
260             return Long.parseLong(v);
261         } else if (Boolean.TYPE.isAssignableFrom(type)) {
262             if ("true".equalsIgnoreCase(v)) {
263                 return Boolean.TRUE;
264             } else if ("false".equalsIgnoreCase(v)) {
265                 return Boolean.FALSE;
266             }
267         } else if (Priority.class.isAssignableFrom(type)) {
268             return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG);
269         } else if (ErrorHandler.class.isAssignableFrom(type)) {
270             return OptionConverter.instantiateByClassName(v,
271                     ErrorHandler.class, null);
272         }
273         return null;
274     }
275 
276 
277     protected PropertyDescriptor getPropertyDescriptor(String name) {
278         if (props == null) {
279             introspect();
280         }
281         for (PropertyDescriptor prop : props) {
282             if (name.equals(prop.getName())) {
283                 return prop;
284             }
285         }
286         return null;
287     }
288 
289     public void activate() {
290         if (obj instanceof OptionHandler) {
291             ((OptionHandler) obj).activateOptions();
292         }
293     }
294 }