View Javadoc

1   /*
2    * Copyright 2004 Jonathan M. Lehr
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
12   * governing permissions and limitations under the License.
13   * 
14   * MODIFIED BY THE KUALI FOUNDATION
15   */
16   
17  // begin Kuali Foundation modification
18  package org.kuali.rice.kns.web.struts.form.pojo;
19  
20  import org.apache.commons.beanutils.MappedPropertyDescriptor;
21  import org.apache.commons.beanutils.NestedNullException;
22  import org.apache.commons.beanutils.PropertyUtils;
23  import org.apache.commons.beanutils.PropertyUtilsBean;
24  import org.apache.commons.collections.FastHashMap;
25  import org.apache.log4j.Logger;
26  import org.kuali.rice.core.web.format.Formatter;
27  import org.kuali.rice.krad.util.ObjectUtils;
28  
29  import java.beans.IntrospectionException;
30  import java.beans.PropertyDescriptor;
31  import java.lang.reflect.InvocationTargetException;
32  import java.lang.reflect.Method;
33  import java.util.Map;
34  
35  
36  /**
37   * begin Kuali Foundation modification
38   * This class is used to access the properties of a Pojo bean.
39   * deleted author tag
40   * end Kuali Foundation modification
41   */
42  // Kuali Foundation modification: class originally SLPropertyUtilsBean
43  public class PojoPropertyUtilsBean extends PropertyUtilsBean {
44  
45      public static final Logger LOG = Logger.getLogger(PojoPropertyUtilsBean.class.getName());
46  
47  	// begin Kuali Foundation modification
48      public PojoPropertyUtilsBean() {
49          super();
50      }
51      // end Kuali Foundation modification
52  
53      public Object getProperty(Object bean, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
54          // begin Kuali Foundation modification
55          if (!(bean instanceof PojoForm))
56              return super.getProperty(bean, key);
57  
58          PojoForm form = (PojoForm) bean;
59          Map unconvertedValues = form.getUnconvertedValues();
60  
61          if (unconvertedValues.containsKey(key))
62              return unconvertedValues.get(key);
63  
64          Object val = getNestedProperty(bean, key);
65          Class type = (val!=null)?val.getClass():null;
66          if ( type == null ) {
67              try {
68                  type = getPropertyType(bean, key);
69              } catch ( Exception ex ) {
70                  type = String.class;
71                  LOG.warn( "Unable to get property type for Class: " + bean.getClass().getName() + "/Property: " + key );
72              }
73          }
74          return (Formatter.isSupportedType(type) ? form.formatValue(val, key, type) : val);
75          // end Kuali Foundation modification
76      }
77  
78  
79      /**
80       * begin Kuali Foundation modification
81       * removed comments and @<no space>since javadoc attribute
82       * end Kuali Foundation modification
83       * @see org.apache.commons.beanutils.PropertyUtilsBean#getNestedProperty(java.lang.Object, java.lang.String)
84       */
85      public Object getNestedProperty(Object arg0, String arg1) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
86  		// begin Kuali Foundation modification
87          try {
88              return super.getNestedProperty(arg0, arg1);
89          }
90          catch (NestedNullException e) {
91              return "";
92          }
93          catch (InvocationTargetException e1) {
94              return "";
95          }
96          // removed commented code
97          // end Kuali Foundation modification
98      }
99  
100 
101     // begin Kuali Foundation modification 
102     /**
103      * begin Kuali Foundation modification
104      * Set the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
105      *
106      * @param bean Bean whose property is to be modified
107      * @param name Possibly nested name of the property to be modified
108      * @param value Value to which the property is to be set
109      *
110      * @exception IllegalAccessException if the caller does not have access to the property accessor method
111      * @exception IllegalArgumentException if <code>bean</code> or <code>name</code> is null
112      * @exception IllegalArgumentException if a nested reference to a property returns null
113      * @exception InvocationTargetException if the property accessor method throws an exception
114      * @exception NoSuchMethodException if an accessor method for this propety cannot be found
115      * end Kuali Foundation modification
116      */
117     public void setNestedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
118 
119         if (bean == null) {
120         	if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
121         	return;
122         }
123         if (name == null) {
124             throw new IllegalArgumentException("No name specified");
125         }
126 
127         Object propBean = null;
128         int indexOfINDEXED_DELIM = -1;
129         int indexOfMAPPED_DELIM = -1;
130         while (true) {
131             int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
132             if (delim < 0) {
133                 break;
134             }
135             String next = name.substring(0, delim);
136             indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
137             indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
138             if (bean instanceof Map) {
139                 propBean = ((Map) bean).get(next);
140             }
141             else if (indexOfMAPPED_DELIM >= 0) {
142                 propBean = getMappedProperty(bean, next);
143             }
144             else if (indexOfINDEXED_DELIM >= 0) {
145                 propBean = getIndexedProperty(bean, next);
146             }
147             else {
148                 propBean = getSimpleProperty(bean, next);
149             }
150             if (ObjectUtils.isNull(propBean)) {
151                 Class propertyType = getPropertyType(bean, next);
152                 if (propertyType != null) {
153                 	Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
154                     setSimpleProperty(bean, next, newInstance);
155                     propBean = getSimpleProperty(bean, next);
156                 }
157             }
158             bean = propBean;
159             name = name.substring(delim + 1);
160         }
161 
162         indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
163         indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
164 
165         if (bean instanceof Map) {
166             // check to see if the class has a standard property
167             PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
168             if (descriptor == null) {
169                 // no - then put the value into the map
170                 ((Map) bean).put(name, value);
171             }
172             else {
173                 // yes - use that instead
174                 setSimpleProperty(bean, name, value);
175             }
176         }
177         else if (indexOfMAPPED_DELIM >= 0) {
178             setMappedProperty(bean, name, value);
179         }
180         else if (indexOfINDEXED_DELIM >= 0) {
181             setIndexedProperty(bean, name, value);
182         }
183         else {
184             setSimpleProperty(bean, name, value);
185         }
186     }
187     // end Kuali Foundation modification
188 
189 	// begin Kuali Foundation modification
190     /**
191      * <p>
192      * Retrieve the property descriptor for the specified property of the specified bean, or return <code>null</code> if there is
193      * no such descriptor. This method resolves indexed and nested property references in the same manner as other methods in this
194      * class, except that if the last (or only) name element is indexed, the descriptor for the last resolved property itself is
195      * returned.
196      * </p>
197      *
198      * <p>
199      * <strong>FIXME </strong>- Does not work with DynaBeans.
200      * </p>
201      *
202      * @param bean Bean for which a property descriptor is requested
203      * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
204      *
205      * @exception IllegalAccessException if the caller does not have access to the property accessor method
206      * @exception IllegalArgumentException if <code>bean</code> or <code>name</code> is null
207      * @exception IllegalArgumentException if a nested reference to a property returns null
208      * @exception InvocationTargetException if the property accessor method throws an exception
209      * @exception NoSuchMethodException if an accessor method for this propety cannot be found
210      */
211     public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
212         if (bean == null) {
213         	if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name);
214         	return null;
215         }
216         if (name == null) {
217             throw new IllegalArgumentException("No name specified");
218         }
219         try {
220             // Resolve nested references
221             Object propBean = null;
222             while (true) {
223                 int delim = findNextNestedIndex(name);
224                 //int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
225                 if (delim < 0) {
226                     break;
227                 }
228                 String next = name.substring(0, delim);
229                 int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
230                 int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
231                 if (indexOfMAPPED_DELIM >= 0 && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
232                     propBean = getMappedProperty(bean, next);
233                 }
234                 else {
235                     if (indexOfINDEXED_DELIM >= 0) {
236                         propBean = getIndexedProperty(bean, next);
237                     }
238                     else {
239                         propBean = getSimpleProperty(bean, next);
240                     }
241                 }
242                 if (ObjectUtils.isNull(propBean)) {
243                     Class propertyType = getPropertyType(bean, next);
244                     if (propertyType != null) {
245                     	Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
246                         setSimpleProperty(bean, next, newInstance);
247                         propBean = getSimpleProperty(bean, next);
248                     }
249                 }
250                 bean = propBean;
251                 name = name.substring(delim + 1);
252             }
253     
254             // Remove any subscript from the final name value
255             int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
256             if (left >= 0) {
257                 name = name.substring(0, left);
258             }
259             left = name.indexOf(PropertyUtils.MAPPED_DELIM);
260             if (left >= 0) {
261                 name = name.substring(0, left);
262             }
263     
264             // Look up and return this property from our cache
265             // creating and adding it to the cache if not found.
266             if ((bean == null) || (name == null)) {
267                 return (null);
268             }
269     
270             PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
271             if (descriptors != null) {
272     
273                 for (int i = 0; i < descriptors.length; i++) {
274                     if (name.equals(descriptors[i].getName()))
275                         return (descriptors[i]);
276                 }
277             }
278     
279             PropertyDescriptor result = null;
280             FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
281             if (mappedDescriptors == null) {
282                 mappedDescriptors = new FastHashMap();
283                 mappedDescriptors.setFast(true);
284             }
285             result = (PropertyDescriptor) mappedDescriptors.get(name);
286             if (result == null) {
287                 // not found, try to create it
288                 try {
289                     result = new MappedPropertyDescriptor(name, bean.getClass());
290                 }
291                 catch (IntrospectionException ie) {
292                 }
293                 if (result != null) {
294                     mappedDescriptors.put(name, result);
295                 }
296             }
297     
298             return result;
299         } catch ( RuntimeException ex ) {
300             LOG.error( "Unable to get property descriptor for " + bean.getClass().getName() + " . " + name
301                     + "\n" + ex.getClass().getName() + ": " + ex.getMessage() );
302             throw ex;
303         }
304     }
305     // end Kuali Foundation modification
306 
307     private int findNextNestedIndex(String expression)
308     {
309         // walk back from the end to the start
310         // and find the first index that
311         int bracketCount = 0;
312         for (int i=0, size=expression.length(); i<size ; i++) {
313             char at = expression.charAt(i);
314             switch (at) {
315                 case PropertyUtils.NESTED_DELIM:
316                     if (bracketCount < 1) {
317                         return i;
318                     }
319                     break;
320 
321                 case PropertyUtils.MAPPED_DELIM:
322                 case PropertyUtils.INDEXED_DELIM:
323                     // not bothered which
324                     ++bracketCount;
325                     break;
326 
327                 case PropertyUtils.MAPPED_DELIM2:
328                 case PropertyUtils.INDEXED_DELIM2:
329                     // not bothered which
330                     --bracketCount;
331                     break;
332             }
333         }
334         // can't find any
335         return -1;
336     }
337 
338     /**
339      * Set the value of the specified simple property of the specified bean,
340      * with no type conversions.
341      *
342      * @param bean Bean whose property is to be modified
343      * @param name Name of the property to be modified
344      * @param value Value to which the property should be set
345      *
346      * @exception IllegalAccessException if the caller does not have
347      *  access to the property accessor method
348      * @exception IllegalArgumentException if <code>bean</code> or
349      *  <code>name</code> is null
350      * @exception IllegalArgumentException if the property name is
351      *  nested or indexed
352      * @exception InvocationTargetException if the property accessor method
353      *  throws an exception
354      * @exception NoSuchMethodException if an accessor method for this
355      *  propety cannot be found
356      */
357     public void setSimpleProperty(Object bean,
358                                          String name, Object value)
359             throws IllegalAccessException, InvocationTargetException,
360             NoSuchMethodException {
361 
362         if (bean == null) {
363         	if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
364         	return;
365         }
366         if (name == null) {
367             throw new IllegalArgumentException("No name specified");
368         }
369 
370         // Validate the syntax of the property name
371         if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
372             throw new IllegalArgumentException
373                     ("Nested property names are not allowed");
374         } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
375             throw new IllegalArgumentException
376                     ("Indexed property names are not allowed");
377         } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
378             throw new IllegalArgumentException
379                     ("Mapped property names are not allowed");
380         }
381 
382         // Retrieve the property setter method for the specified property
383         PropertyDescriptor descriptor =
384                 getPropertyDescriptor(bean, name);
385         if (descriptor == null) {
386             throw new NoSuchMethodException("Unknown property '" +
387                     name + "'");
388         }
389         Method writeMethod = getWriteMethod(descriptor);
390         if (writeMethod == null) {
391             //throw new NoSuchMethodException("Property '" + name + "' has no setter method");
392         	LOG.warn("Bean: " + bean.getClass().getName() + ", Property '" + name + "' has no setter method");
393         	return;
394         }
395 
396         // Call the property setter method
397         Object values[] = new Object[1];
398         values[0] = value;
399         if (LOG.isDebugEnabled()) {
400             String valueClassName =
401                 value == null ? "<null>" : value.getClass().getName();
402             LOG.debug("setSimpleProperty: Invoking method " + writeMethod
403                       + " with value " + value + " (class " + valueClassName + ")");
404         }
405         
406         
407         invokeMethod(writeMethod, bean, values);
408 
409     }
410     
411     /** This just catches and wraps IllegalArgumentException. */
412     private Object invokeMethod(
413                         Method method, 
414                         Object bean, 
415                         Object[] values) 
416                             throws
417                                 IllegalAccessException,
418                                 InvocationTargetException {
419         try {
420             
421             return method.invoke(bean, values);
422         
423         } catch (IllegalArgumentException e) {
424             
425             LOG.error("Method invocation failed.", e);
426             throw new IllegalArgumentException(
427                 "Cannot invoke " + method.getDeclaringClass().getName() + "." 
428                 + method.getName() + " - " + e.getMessage());
429             
430         }
431     }
432 
433 }