1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.ArrayList;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37
38
39
40
41
42
43
44
45 public class PojoPropertyUtilsBean extends PropertyUtilsBean {
46
47 public static final Logger LOG = Logger.getLogger(PojoPropertyUtilsBean.class.getName());
48
49
50 public PojoPropertyUtilsBean() {
51 super();
52 }
53
54
55 public Object getProperty(Object bean, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
56
57 if (!(bean instanceof PojoForm))
58 return super.getProperty(bean, key);
59
60 PojoForm form = (PojoForm) bean;
61 Map unconvertedValues = form.getUnconvertedValues();
62
63 if (unconvertedValues.containsKey(key))
64 return unconvertedValues.get(key);
65
66 Object val = getNestedProperty(bean, key);
67 Class type = (val!=null)?val.getClass():null;
68 if ( type == null ) {
69 try {
70 type = getPropertyType(bean, key);
71 } catch ( Exception ex ) {
72 type = String.class;
73 LOG.warn( "Unable to get property type for Class: " + bean.getClass().getName() + "/Property: " + key );
74 }
75 }
76 return (Formatter.isSupportedType(type) ? form.formatValue(val, key, type) : val);
77
78 }
79
80
81 private Map<String,List<Method>> cache = new HashMap<String, List<Method>>();
82 private static Map<String,Method> readMethodCache = new HashMap<String, Method>();
83 private IntrospectionException introspectionException = new IntrospectionException( "" );
84
85 public Object fastGetNestedProperty(Object obj, String propertyName) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
86
87
88 List<Method> methods = (List<Method>) cache.get(propertyName + obj.getClass().getName());
89 if (methods == null) {
90 methods = new ArrayList<Method>();
91 Object currentObj = obj;
92 Class<?> currentObjClass = currentObj.getClass();
93
94 for (String currentPropertyName : propertyName.split("\\.") ) {
95 String cacheKey = currentObjClass.getName() + currentPropertyName;
96 Method readMethod = readMethodCache.get( cacheKey );
97 if ( readMethod == null ) {
98 synchronized (readMethodCache) {
99
100
101 if ( readMethodCache.containsKey(cacheKey) ) {
102 throw introspectionException;
103 }
104 try {
105 try {
106 readMethod = currentObjClass.getMethod("get" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
107 } catch (NoSuchMethodException e) {
108 readMethod = currentObjClass.getMethod("is" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
109 }
110 } catch ( NoSuchMethodException ex ) {
111
112 readMethodCache.put( cacheKey, null );
113 throw introspectionException;
114 }
115 readMethodCache.put(cacheKey, readMethod );
116 }
117 }
118 methods.add(readMethod);
119 currentObj = readMethod.invoke(currentObj, (Object[])null);
120 currentObjClass = currentObj.getClass();
121 }
122 synchronized (cache) {
123 cache.put(propertyName + obj.getClass().getName(), methods);
124 }
125 }
126
127 for ( Method method : methods ) {
128 obj = method.invoke(obj, (Object[])null);
129 }
130
131
132
133 return obj;
134 }
135
136
137
138
139
140
141
142
143 public Object getNestedProperty(Object arg0, String arg1) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
144
145 try {
146 try {
147 return fastGetNestedProperty(arg0, arg1);
148 }
149 catch (Exception e) {
150 return super.getNestedProperty(arg0, arg1);
151 }
152 }
153 catch (NestedNullException e) {
154 return getUnreachableNestedProperty(arg0, arg1);
155 }
156 catch (InvocationTargetException e1) {
157 return getUnreachableNestedProperty(arg0, arg1);
158 }
159
160
161 }
162
163
164
165
166
167 private Object getUnreachableNestedProperty(Object arg0, String arg1) {
168 try {
169 PropertyDescriptor propertyDescriptor = getPropertyDescriptor(arg0, arg1);
170 if (propertyDescriptor == null || Collection.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
171 return null;
172 }
173 } catch (IllegalAccessException e) {
174
175 } catch (InvocationTargetException e) {
176
177 } catch (NoSuchMethodException e) {
178
179 }
180
181 return "";
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202 public void setNestedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
203
204 if (bean == null) {
205 if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
206 return;
207 }
208 if (name == null) {
209 throw new IllegalArgumentException("No name specified");
210 }
211
212 Object propBean = null;
213 int indexOfINDEXED_DELIM = -1;
214 int indexOfMAPPED_DELIM = -1;
215 while (true) {
216 int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
217 if (delim < 0) {
218 break;
219 }
220 String next = name.substring(0, delim);
221 indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
222 indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
223 if (bean instanceof Map) {
224 propBean = ((Map) bean).get(next);
225 }
226 else if (indexOfMAPPED_DELIM >= 0) {
227 propBean = getMappedProperty(bean, next);
228 }
229 else if (indexOfINDEXED_DELIM >= 0) {
230 propBean = getIndexedProperty(bean, next);
231 }
232 else {
233 propBean = getSimpleProperty(bean, next);
234 }
235 if (ObjectUtils.isNull(propBean)) {
236 Class propertyType = getPropertyType(bean, next);
237 if (propertyType != null) {
238 Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
239 setSimpleProperty(bean, next, newInstance);
240 propBean = getSimpleProperty(bean, next);
241 }
242 }
243 bean = propBean;
244 name = name.substring(delim + 1);
245 }
246
247 indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
248 indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
249
250 if (bean instanceof Map) {
251
252 PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
253 if (descriptor == null) {
254
255 ((Map) bean).put(name, value);
256 }
257 else {
258
259 setSimpleProperty(bean, name, value);
260 }
261 }
262 else if (indexOfMAPPED_DELIM >= 0) {
263 setMappedProperty(bean, name, value);
264 }
265 else if (indexOfINDEXED_DELIM >= 0) {
266 setIndexedProperty(bean, name, value);
267 }
268 else {
269 setSimpleProperty(bean, name, value);
270 }
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
297 if (bean == null) {
298 if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name);
299 return null;
300 }
301 if (name == null) {
302 throw new IllegalArgumentException("No name specified");
303 }
304 try {
305
306 Object propBean = null;
307 while (true) {
308 int delim = findNextNestedIndex(name);
309
310 if (delim < 0) {
311 break;
312 }
313 String next = name.substring(0, delim);
314 int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
315 int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
316 if (indexOfMAPPED_DELIM >= 0 && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
317 propBean = getMappedProperty(bean, next);
318 }
319 else {
320 if (indexOfINDEXED_DELIM >= 0) {
321 propBean = getIndexedProperty(bean, next);
322 }
323 else {
324 propBean = getSimpleProperty(bean, next);
325 }
326 }
327 if (ObjectUtils.isNull(propBean)) {
328 Class propertyType = getPropertyType(bean, next);
329 if (propertyType != null) {
330 Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
331 setSimpleProperty(bean, next, newInstance);
332 propBean = getSimpleProperty(bean, next);
333 }
334 }
335 bean = propBean;
336 name = name.substring(delim + 1);
337 }
338
339
340 int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
341 if (left >= 0) {
342 name = name.substring(0, left);
343 }
344 left = name.indexOf(PropertyUtils.MAPPED_DELIM);
345 if (left >= 0) {
346 name = name.substring(0, left);
347 }
348
349
350
351 if ((bean == null) || (name == null)) {
352 return (null);
353 }
354
355 PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
356 if (descriptors != null) {
357
358 for (int i = 0; i < descriptors.length; i++) {
359 if (name.equals(descriptors[i].getName()))
360 return (descriptors[i]);
361 }
362 }
363
364 PropertyDescriptor result = null;
365 FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
366 if (mappedDescriptors == null) {
367 mappedDescriptors = new FastHashMap();
368 mappedDescriptors.setFast(true);
369 }
370 result = (PropertyDescriptor) mappedDescriptors.get(name);
371 if (result == null) {
372
373 try {
374 result = new MappedPropertyDescriptor(name, bean.getClass());
375 }
376 catch (IntrospectionException ie) {
377 }
378 if (result != null) {
379 mappedDescriptors.put(name, result);
380 }
381 }
382
383 return result;
384 } catch ( RuntimeException ex ) {
385 LOG.error( "Unable to get property descriptor for " + bean.getClass().getName() + " . " + name
386 + "\n" + ex.getClass().getName() + ": " + ex.getMessage() );
387 throw ex;
388 }
389 }
390
391
392 private int findNextNestedIndex(String expression)
393 {
394
395
396 int bracketCount = 0;
397 for (int i=0, size=expression.length(); i<size ; i++) {
398 char at = expression.charAt(i);
399 switch (at) {
400 case PropertyUtils.NESTED_DELIM:
401 if (bracketCount < 1) {
402 return i;
403 }
404 break;
405
406 case PropertyUtils.MAPPED_DELIM:
407 case PropertyUtils.INDEXED_DELIM:
408
409 ++bracketCount;
410 break;
411
412 case PropertyUtils.MAPPED_DELIM2:
413 case PropertyUtils.INDEXED_DELIM2:
414
415 --bracketCount;
416 break;
417 }
418 }
419
420 return -1;
421 }
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 public void setSimpleProperty(Object bean,
443 String name, Object value)
444 throws IllegalAccessException, InvocationTargetException,
445 NoSuchMethodException {
446
447 if (bean == null) {
448 if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
449 return;
450 }
451 if (name == null) {
452 throw new IllegalArgumentException("No name specified");
453 }
454
455
456 if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
457 throw new IllegalArgumentException
458 ("Nested property names are not allowed");
459 } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
460 throw new IllegalArgumentException
461 ("Indexed property names are not allowed");
462 } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
463 throw new IllegalArgumentException
464 ("Mapped property names are not allowed");
465 }
466
467
468 PropertyDescriptor descriptor =
469 getPropertyDescriptor(bean, name);
470 if (descriptor == null) {
471 throw new NoSuchMethodException("Unknown property '" +
472 name + "'");
473 }
474 Method writeMethod = getWriteMethod(descriptor);
475 if (writeMethod == null) {
476
477 LOG.warn("Bean: " + bean.getClass().getName() + ", Property '" + name + "' has no setter method");
478 return;
479 }
480
481
482 Object values[] = new Object[1];
483 values[0] = value;
484 if (LOG.isDebugEnabled()) {
485 String valueClassName =
486 value == null ? "<null>" : value.getClass().getName();
487 LOG.debug("setSimpleProperty: Invoking method " + writeMethod
488 + " with value " + value + " (class " + valueClassName + ")");
489 }
490
491
492 invokeMethod(writeMethod, bean, values);
493
494 }
495
496
497 private Object invokeMethod(
498 Method method,
499 Object bean,
500 Object[] values)
501 throws
502 IllegalAccessException,
503 InvocationTargetException {
504 try {
505
506 return method.invoke(bean, values);
507
508 } catch (IllegalArgumentException e) {
509
510 LOG.error("Method invocation failed.", e);
511 throw new IllegalArgumentException(
512 "Cannot invoke " + method.getDeclaringClass().getName() + "."
513 + method.getName() + " - " + e.getMessage());
514
515 }
516 }
517
518 }