View Javadoc

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