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