001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018// Contributors:  Georg Lundesgaard
019
020package org.apache.log4j.config;
021
022import org.apache.log4j.Appender;
023import org.apache.log4j.Level;
024import org.apache.log4j.Priority;
025import org.apache.log4j.helpers.LogLog;
026import org.apache.log4j.helpers.OptionConverter;
027import org.apache.log4j.spi.OptionHandler;
028import org.apache.log4j.spi.ErrorHandler;
029
030import java.beans.BeanInfo;
031import java.beans.IntrospectionException;
032import java.beans.Introspector;
033import java.beans.PropertyDescriptor;
034import java.io.InterruptedIOException;
035import java.lang.reflect.InvocationTargetException;
036import java.lang.reflect.Method;
037import java.util.Enumeration;
038import java.util.Properties;
039
040/**
041   General purpose Object property setter. Clients repeatedly invokes
042   {@link #setProperty setProperty(name,value)} in order to invoke setters
043   on the Object specified in the constructor. This class relies on the
044   JavaBeans {@link Introspector} to analyze the given Object Class using
045   reflection.
046   
047   <p>Usage:
048   <pre>
049     PropertySetter ps = new PropertySetter(anObject);
050     ps.set("name", "Joe");
051     ps.set("age", "32");
052     ps.set("isMale", "true");
053   </pre>
054   will cause the invocations anObject.setName("Joe"), anObject.setAge(32),
055   and setMale(true) if such methods exist with those signatures.
056   Otherwise an {@link IntrospectionException} are thrown.
057  
058   @author Anders Kristensen
059   @since 1.1
060 */
061public class PropertySetter {
062  protected Object obj;
063  protected PropertyDescriptor[] props;
064  
065  /**
066    Create a new PropertySetter for the specified Object. This is done
067    in prepartion for invoking {@link #setProperty} one or more times.
068    
069    @param obj  the object for which to set properties
070   */
071  public
072  PropertySetter(Object obj) {
073    this.obj = obj;
074  }
075  
076  /**
077     Uses JavaBeans {@link Introspector} to computer setters of object to be
078     configured.
079   */
080  protected
081  void introspect() {
082    try {
083      BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
084      props = bi.getPropertyDescriptors();
085    } catch (IntrospectionException ex) {
086      LogLog.error("Failed to introspect "+obj+": " + ex.getMessage());
087      props = new PropertyDescriptor[0];
088    }
089  }
090  
091
092  /**
093     Set the properties of an object passed as a parameter in one
094     go. The <code>properties</code> are parsed relative to a
095     <code>prefix</code>.
096
097     @param obj The object to configure.
098     @param properties A java.util.Properties containing keys and values.
099     @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}