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