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 org.apache.log4j.Appender;
23  import org.apache.log4j.Level;
24  import org.apache.log4j.Priority;
25  import org.apache.log4j.spi.ErrorHandler;
26  import org.apache.log4j.spi.OptionHandler;
27  import org.apache.logging.log4j.Logger;
28  import org.apache.logging.log4j.core.util.OptionConverter;
29  import org.apache.logging.log4j.status.StatusLogger;
30  
31  import java.beans.BeanInfo;
32  import java.beans.IntrospectionException;
33  import java.beans.Introspector;
34  import java.beans.PropertyDescriptor;
35  import java.io.InterruptedIOException;
36  import java.lang.reflect.InvocationTargetException;
37  import java.lang.reflect.Method;
38  import java.util.Properties;
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 Logger LOGGER = StatusLogger.getLogger();
60      protected Object obj;
61      protected PropertyDescriptor[] props;
62  
63      /**
64       * Create a new PropertySetter for the specified Object. This is done
65       * in prepartion for invoking {@link #setProperty} one or more times.
66       *
67       * @param obj the object for which to set properties
68       */
69      public PropertySetter(Object obj) {
70          this.obj = obj;
71      }
72  
73      /**
74       * Set the properties of an object passed as a parameter in one
75       * go. The <code>properties</code> are parsed relative to a
76       * <code>prefix</code>.
77       *
78       * @param obj        The object to configure.
79       * @param properties A java.util.Properties containing keys and values.
80       * @param prefix     Only keys having the specified prefix will be set.
81       */
82      public static void setProperties(Object obj, Properties properties, String prefix) {
83          new PropertySetter(obj).setProperties(properties, prefix);
84      }
85  
86      /**
87       * Uses JavaBeans {@link Introspector} to computer setters of object to be
88       * configured.
89       */
90      protected void introspect() {
91          try {
92              BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
93              props = bi.getPropertyDescriptors();
94          } catch (IntrospectionException ex) {
95              LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage());
96              props = new PropertyDescriptor[0];
97          }
98      }
99  
100     /**
101      * Set the properites for the object that match the
102      * <code>prefix</code> passed as parameter.
103      * @param properties The properties.
104      * @param prefix The prefix of the properties to use.
105      */
106     public void setProperties(Properties properties, String prefix) {
107         int len = prefix.length();
108 
109         for (String key : properties.stringPropertyNames()) {
110 
111             // handle only properties that start with the desired prefix.
112             if (key.startsWith(prefix)) {
113 
114 
115                 // ignore key if it contains dots after the prefix
116                 if (key.indexOf('.', len + 1) > 0) {
117                     continue;
118                 }
119 
120                 String value = OptionConverter.findAndSubst(key, properties);
121                 key = key.substring(len);
122                 if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) {
123                     continue;
124                 }
125                 //
126                 //   if the property type is an OptionHandler
127                 //     (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)
128                 PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));
129                 if (prop != null
130                         && OptionHandler.class.isAssignableFrom(prop.getPropertyType())
131                         && prop.getWriteMethod() != null) {
132                     OptionHandler opt = (OptionHandler)
133                             OptionConverter.instantiateByKey(properties, prefix + key,
134                                     prop.getPropertyType(),
135                                     null);
136                     PropertySetter setter = new PropertySetter(opt);
137                     setter.setProperties(properties, prefix + key + ".");
138                     try {
139                         prop.getWriteMethod().invoke(this.obj, opt);
140                     } catch (InvocationTargetException ex) {
141                         if (ex.getTargetException() instanceof InterruptedException
142                                 || ex.getTargetException() instanceof InterruptedIOException) {
143                             Thread.currentThread().interrupt();
144                         }
145                         LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
146                     } catch (IllegalAccessException | RuntimeException ex) {
147                         LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
148                     }
149                     continue;
150                 }
151 
152                 setProperty(key, value);
153             }
154         }
155         activate();
156     }
157 
158     /**
159      * Set a property on this PropertySetter's Object. If successful, this
160      * method will invoke a setter method on the underlying Object. The
161      * setter is the one for the specified property name and the value is
162      * determined partly from the setter argument type and partly from the
163      * value specified in the call to this method.
164      *
165      * <p>If the setter expects a String no conversion is necessary.
166      * If it expects an int, then an attempt is made to convert 'value'
167      * to an int using new Integer(value). If the setter expects a boolean,
168      * the conversion is by new Boolean(value).
169      *
170      * @param name  name of the property
171      * @param value String value of the property
172      */
173     public void setProperty(String name, String value) {
174         if (value == null) {
175             return;
176         }
177 
178         name = Introspector.decapitalize(name);
179         PropertyDescriptor prop = getPropertyDescriptor(name);
180 
181         //LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType());
182 
183         if (prop == null) {
184             LOGGER.warn("No such property [" + name + "] in " +
185                     obj.getClass().getName() + ".");
186         } else {
187             try {
188                 setProperty(prop, name, value);
189             } catch (PropertySetterException ex) {
190                 LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause);
191             }
192         }
193     }
194 
195     /**
196      * Set the named property given a {@link PropertyDescriptor}.
197      *
198      * @param prop  A PropertyDescriptor describing the characteristics
199      *              of the property to set.
200      * @param name  The named of the property to set.
201      * @param value The value of the property.
202      * @throws PropertySetterException if no setter is available.
203      */
204     public void setProperty(PropertyDescriptor prop, String name, String value)
205             throws PropertySetterException {
206         Method setter = prop.getWriteMethod();
207         if (setter == null) {
208             throw new PropertySetterException("No setter for property [" + name + "].");
209         }
210         Class<?>[] paramTypes = setter.getParameterTypes();
211         if (paramTypes.length != 1) {
212             throw new PropertySetterException("#params for setter != 1");
213         }
214 
215         Object arg;
216         try {
217             arg = convertArg(value, paramTypes[0]);
218         } catch (Throwable t) {
219             throw new PropertySetterException("Conversion to type [" + paramTypes[0] +
220                     "] failed. Reason: " + t);
221         }
222         if (arg == null) {
223             throw new PropertySetterException(
224                     "Conversion to type [" + paramTypes[0] + "] failed.");
225         }
226         LOGGER.debug("Setting property [" + name + "] to [" + arg + "].");
227         try {
228             setter.invoke(obj, arg);
229         } catch (InvocationTargetException ex) {
230             if (ex.getTargetException() instanceof InterruptedException
231                     || ex.getTargetException() instanceof InterruptedIOException) {
232                 Thread.currentThread().interrupt();
233             }
234             throw new PropertySetterException(ex);
235         } catch (IllegalAccessException | RuntimeException ex) {
236             throw new PropertySetterException(ex);
237         }
238     }
239 
240 
241     /**
242      * Convert <code>val</code> a String parameter to an object of a
243      * given type.
244      * @param val The value to convert.
245      * @param type The type of the value to convert to.
246      * @return The result of the conversion.
247      */
248     protected Object convertArg(String val, Class<?> type) {
249         if (val == null) {
250             return null;
251         }
252 
253         String v = val.trim();
254         if (String.class.isAssignableFrom(type)) {
255             return val;
256         } else if (Integer.TYPE.isAssignableFrom(type)) {
257             return Integer.parseInt(v);
258         } else if (Long.TYPE.isAssignableFrom(type)) {
259             return Long.parseLong(v);
260         } else if (Boolean.TYPE.isAssignableFrom(type)) {
261             if ("true".equalsIgnoreCase(v)) {
262                 return Boolean.TRUE;
263             } else if ("false".equalsIgnoreCase(v)) {
264                 return Boolean.FALSE;
265             }
266         } else if (Priority.class.isAssignableFrom(type)) {
267             return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG);
268         } else if (ErrorHandler.class.isAssignableFrom(type)) {
269             return OptionConverter.instantiateByClassName(v,
270                     ErrorHandler.class, null);
271         }
272         return null;
273     }
274 
275 
276     protected PropertyDescriptor getPropertyDescriptor(String name) {
277         if (props == null) {
278             introspect();
279         }
280         for (PropertyDescriptor prop : props) {
281             if (name.equals(prop.getName())) {
282                 return prop;
283             }
284         }
285         return null;
286     }
287 
288     public void activate() {
289         if (obj instanceof OptionHandler) {
290             ((OptionHandler) obj).activateOptions();
291         }
292     }
293 }