1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
41
42
43
44
45
46 public class PojoPropertyUtilsBean extends PropertyUtilsBean {
47
48 public static final Logger LOG = Logger.getLogger(PojoPropertyUtilsBean.class.getName());
49
50
51 public PojoPropertyUtilsBean() {
52 super();
53 }
54
55
56 public Object getProperty(Object bean, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
57
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
79 }
80
81
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
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
101
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
113 readMethodCache.put( cacheKey, null );
114 throw introspectionException;
115
116
117
118
119
120
121
122
123
124
125
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
144
145 return obj;
146 }
147
148
149
150
151
152
153
154
155
156 public Object getNestedProperty(Object arg0, String arg1) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
157
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
173
174 }
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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
243 PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
244 if (descriptor == null) {
245
246 ((Map) bean).put(name, value);
247 }
248 else {
249
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
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
297 Object propBean = null;
298 while (true) {
299 int delim = findNextNestedIndex(name);
300
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
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
341
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
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
382
383 private int findNextNestedIndex(String expression)
384 {
385
386
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
400 ++bracketCount;
401 break;
402
403 case PropertyUtils.MAPPED_DELIM2:
404 case PropertyUtils.INDEXED_DELIM2:
405
406 --bracketCount;
407 break;
408 }
409 }
410
411 return -1;
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
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
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
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
468 LOG.warn("Bean: " + bean.getClass().getName() + ", Property '" + name + "' has no setter method");
469 return;
470 }
471
472
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
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 }