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.DynaBean;
20 import org.apache.commons.beanutils.DynaProperty;
21 import org.apache.commons.beanutils.MappedPropertyDescriptor;
22 import org.apache.commons.beanutils.MethodUtils;
23 import org.apache.commons.beanutils.NestedNullException;
24 import org.apache.commons.beanutils.PropertyUtils;
25 import org.apache.commons.beanutils.PropertyUtilsBean;
26 import org.apache.commons.beanutils.WrapDynaBean;
27 import org.apache.commons.collections.FastHashMap;
28 import org.apache.log4j.Logger;
29 import org.apache.ojb.broker.metadata.ClassDescriptor;
30 import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
31 import org.apache.ojb.broker.metadata.CollectionDescriptor;
32 import org.apache.ojb.broker.metadata.DescriptorRepository;
33 import org.apache.ojb.broker.metadata.MetadataManager;
34 import org.kuali.rice.core.web.format.Formatter;
35 import org.kuali.rice.krad.bo.PersistableBusinessObject;
36 import org.kuali.rice.krad.service.KRADServiceLocator;
37 import org.kuali.rice.krad.service.PersistenceStructureService;
38 import org.kuali.rice.krad.util.ObjectUtils;
39
40 import java.beans.IndexedPropertyDescriptor;
41 import java.beans.IntrospectionException;
42 import java.beans.PropertyDescriptor;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.Map;
50
51
52
53
54
55
56
57
58 public class PojoPropertyUtilsBean extends PropertyUtilsBean {
59
60 public static final Logger LOG = Logger.getLogger(PojoPropertyUtilsBean.class.getName());
61
62
63
64
65 public static interface CollectionItemClassProvider {
66 public Class getCollectionItemClass(Object bean, String property);
67 }
68
69
70
71
72 public static class PersistenceStructureServiceProvider implements CollectionItemClassProvider {
73 protected static PersistenceStructureService persistenceStructureService = null;
74 protected static PersistenceStructureService getPersistenceStructureService() {
75 if (persistenceStructureService == null) {
76 persistenceStructureService = KRADServiceLocator.getPersistenceStructureService();
77 }
78 return persistenceStructureService;
79 }
80
81 @Override
82 public Class getCollectionItemClass(Object bean, String property) {
83 Map<String, Class> collectionObjectTypes = getPersistenceStructureService().listCollectionObjectTypes(bean.getClass());
84 return collectionObjectTypes.get(property);
85 }
86 }
87
88
89 protected static CollectionItemClassProvider collectionItemClassProvider = new PersistenceStructureServiceProvider();
90
91
92 public PojoPropertyUtilsBean() {
93 super();
94 }
95
96
97 public Object getProperty(Object bean, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
98
99 if (!(bean instanceof PojoForm))
100 return super.getProperty(bean, key);
101
102 PojoForm form = (PojoForm) bean;
103 Map unconvertedValues = form.getUnconvertedValues();
104
105 if (unconvertedValues.containsKey(key))
106 return unconvertedValues.get(key);
107
108 Object val = getNestedProperty(bean, key);
109 Class type = (val!=null)?val.getClass():null;
110 if ( type == null ) {
111 try {
112 type = getPropertyType(bean, key);
113 } catch ( Exception ex ) {
114 type = String.class;
115 LOG.warn( "Unable to get property type for Class: " + bean.getClass().getName() + "/Property: " + key );
116 }
117 }
118 return (Formatter.isSupportedType(type) ? form.formatValue(val, key, type) : val);
119
120 }
121
122
123 private Map<String,List<Method>> cache = new HashMap<String, List<Method>>();
124 private static Map<String,Method> readMethodCache = new HashMap<String, Method>();
125 private IntrospectionException introspectionException = new IntrospectionException( "" );
126
127 public Object fastGetNestedProperty(Object obj, String propertyName) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
128
129
130 List<Method> methods = (List<Method>) cache.get(propertyName + obj.getClass().getName());
131 if (methods == null) {
132 methods = new ArrayList<Method>();
133 Object currentObj = obj;
134 Class<?> currentObjClass = currentObj.getClass();
135
136 for (String currentPropertyName : propertyName.split("\\.") ) {
137 String cacheKey = currentObjClass.getName() + currentPropertyName;
138 Method readMethod = readMethodCache.get( cacheKey );
139 if ( readMethod == null ) {
140 synchronized (readMethodCache) {
141
142
143 if ( readMethodCache.containsKey(cacheKey) ) {
144 throw introspectionException;
145 }
146 try {
147 try {
148 readMethod = currentObjClass.getMethod("get" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
149 } catch (NoSuchMethodException e) {
150 readMethod = currentObjClass.getMethod("is" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
151 }
152 } catch ( NoSuchMethodException ex ) {
153
154 readMethodCache.put( cacheKey, null );
155 throw introspectionException;
156 }
157 readMethodCache.put(cacheKey, readMethod );
158 }
159 }
160 methods.add(readMethod);
161 currentObjClass = readMethod.getReturnType();
162 }
163 synchronized (cache) {
164 cache.put(propertyName + obj.getClass().getName(), methods);
165 }
166 }
167
168 for ( Method method : methods ) {
169 obj = method.invoke(obj, (Object[])null);
170 }
171
172
173
174 return obj;
175 }
176
177
178
179
180
181
182 @Override
183 public boolean isWriteable(Object bean, String name) {
184
185 if (bean == null) {
186 throw new IllegalArgumentException("No bean specified");
187 }
188 if (name == null) {
189 throw new IllegalArgumentException("No name specified for bean class '" +
190 bean.getClass() + "'");
191 }
192
193
194 name = getResolver().getProperty(name);
195
196
197
198 if (bean instanceof WrapDynaBean) {
199 bean = ((WrapDynaBean)bean).getInstance();
200 }
201
202
203 if (bean instanceof DynaBean) {
204
205 return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
206 } else {
207 try {
208 PropertyDescriptor desc =
209 getPropertyDescriptor(bean, name);
210 if (desc != null) {
211 Method writeMethod = desc.getWriteMethod();
212 if (writeMethod == null) {
213 if (desc instanceof IndexedPropertyDescriptor) {
214 writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
215 } else if (desc instanceof MappedPropertyDescriptor) {
216 writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
217 }
218 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
219 }
220 return (writeMethod != null);
221 } else {
222 return (false);
223 }
224 } catch (IllegalAccessException e) {
225 return (false);
226 } catch (InvocationTargetException e) {
227 return (false);
228 } catch (NoSuchMethodException e) {
229 return (false);
230 }
231 }
232
233 }
234
235
236
237
238
239
240
241 public Object getNestedProperty(Object arg0, String arg1) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
242
243 try {
244 try {
245 return fastGetNestedProperty(arg0, arg1);
246 }
247 catch (Exception e) {
248 return super.getNestedProperty(arg0, arg1);
249 }
250 }
251 catch (NestedNullException e) {
252 return getUnreachableNestedProperty(arg0, arg1);
253 }
254 catch (InvocationTargetException e1) {
255 return getUnreachableNestedProperty(arg0, arg1);
256 }
257
258
259 }
260
261
262
263
264
265 public Object getIndexedProperty(Object bean, String name, int index) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
266 try {
267 return super.getIndexedProperty(bean, name, index);
268 } catch (IndexOutOfBoundsException ioobe) {
269 return generateIndexedProperty(bean, name, index, ioobe);
270 }
271 }
272
273 protected Object generateIndexedProperty(Object nestedBean, String property, int index, IndexOutOfBoundsException ioobe) throws IllegalAccessException, InvocationTargetException,
274 NoSuchMethodException {
275
276 if (!(nestedBean instanceof PersistableBusinessObject)) throw ioobe;
277
278
279 if (!List.class.isAssignableFrom(getPropertyType(nestedBean, property))) throw ioobe;
280
281 List list= (List) getProperty(nestedBean, property);
282
283 Class c = collectionItemClassProvider.getCollectionItemClass(nestedBean, property);
284
285 if (c == null) {
286 throw new RuntimeException("Unable to determined item class for collection '" + property + "' on bean of type '" + nestedBean.getClass() + "'");
287 }
288
289 Object value;
290 try {
291 value = c.newInstance();
292 } catch (InstantiationException ie) {
293 throw new RuntimeException("Error instantiating item class: " + c);
294 }
295
296
297 while (list.size() <= index) {
298 list.add(null);
299 }
300 list.set(index, value);
301
302 return super.getIndexedProperty(nestedBean, property, index);
303 }
304
305
306
307
308
309 private Object getUnreachableNestedProperty(Object arg0, String arg1) {
310 try {
311 PropertyDescriptor propertyDescriptor = getPropertyDescriptor(arg0, arg1);
312 if (propertyDescriptor == null || Collection.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
313 return null;
314 }
315 } catch (IllegalAccessException e) {
316
317 } catch (InvocationTargetException e) {
318
319 } catch (NoSuchMethodException e) {
320
321 }
322
323 return "";
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public void setNestedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
345
346 if (bean == null) {
347 if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
348 return;
349 }
350 if (name == null) {
351 throw new IllegalArgumentException("No name specified");
352 }
353
354 Object propBean = null;
355 int indexOfINDEXED_DELIM = -1;
356 int indexOfMAPPED_DELIM = -1;
357 while (true) {
358 int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
359 if (delim < 0) {
360 break;
361 }
362 String next = name.substring(0, delim);
363 indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
364 indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
365 if (bean instanceof Map) {
366 propBean = ((Map) bean).get(next);
367 }
368 else if (indexOfMAPPED_DELIM >= 0) {
369 propBean = getMappedProperty(bean, next);
370 }
371 else if (indexOfINDEXED_DELIM >= 0) {
372 propBean = getIndexedProperty(bean, next);
373 }
374 else {
375 propBean = getSimpleProperty(bean, next);
376 }
377 if (ObjectUtils.isNull(propBean)) {
378 Class propertyType = getPropertyType(bean, next);
379 if (propertyType != null) {
380 Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
381 setSimpleProperty(bean, next, newInstance);
382 propBean = getSimpleProperty(bean, next);
383 }
384 }
385 bean = propBean;
386 name = name.substring(delim + 1);
387 }
388
389 indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
390 indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
391
392 if (bean instanceof Map) {
393
394 PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
395 if (descriptor == null) {
396
397 ((Map) bean).put(name, value);
398 }
399 else {
400
401 setSimpleProperty(bean, name, value);
402 }
403 }
404 else if (indexOfMAPPED_DELIM >= 0) {
405 setMappedProperty(bean, name, value);
406 }
407 else if (indexOfINDEXED_DELIM >= 0) {
408 setIndexedProperty(bean, name, value);
409 }
410 else {
411 setSimpleProperty(bean, name, value);
412 }
413 }
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438 public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
439 if (bean == null) {
440 if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name);
441 return null;
442 }
443 if (name == null) {
444 throw new IllegalArgumentException("No name specified");
445 }
446 try {
447
448 Object propBean = null;
449 while (true) {
450 int delim = findNextNestedIndex(name);
451
452 if (delim < 0) {
453 break;
454 }
455 String next = name.substring(0, delim);
456 int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
457 int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
458 if (indexOfMAPPED_DELIM >= 0 && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
459 propBean = getMappedProperty(bean, next);
460 }
461 else {
462 if (indexOfINDEXED_DELIM >= 0) {
463 propBean = getIndexedProperty(bean, next);
464 }
465 else {
466 propBean = getSimpleProperty(bean, next);
467 }
468 }
469 if (ObjectUtils.isNull(propBean)) {
470 Class propertyType = getPropertyType(bean, next);
471 if (propertyType != null) {
472 Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
473 setSimpleProperty(bean, next, newInstance);
474 propBean = getSimpleProperty(bean, next);
475 }
476 }
477 bean = propBean;
478 name = name.substring(delim + 1);
479 }
480
481
482 int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
483 if (left >= 0) {
484 name = name.substring(0, left);
485 }
486 left = name.indexOf(PropertyUtils.MAPPED_DELIM);
487 if (left >= 0) {
488 name = name.substring(0, left);
489 }
490
491
492
493 if ((bean == null) || (name == null)) {
494 return (null);
495 }
496
497 PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
498 if (descriptors != null) {
499
500 for (int i = 0; i < descriptors.length; i++) {
501 if (name.equals(descriptors[i].getName()))
502 return (descriptors[i]);
503 }
504 }
505
506 PropertyDescriptor result = null;
507 FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
508 if (mappedDescriptors == null) {
509 mappedDescriptors = new FastHashMap();
510 mappedDescriptors.setFast(true);
511 }
512 result = (PropertyDescriptor) mappedDescriptors.get(name);
513 if (result == null) {
514
515 try {
516 result = new MappedPropertyDescriptor(name, bean.getClass());
517 }
518 catch (IntrospectionException ie) {
519 }
520 if (result != null) {
521 mappedDescriptors.put(name, result);
522 }
523 }
524
525 return result;
526 } catch ( RuntimeException ex ) {
527 LOG.error( "Unable to get property descriptor for " + bean.getClass().getName() + " . " + name
528 + "\n" + ex.getClass().getName() + ": " + ex.getMessage() );
529 throw ex;
530 }
531 }
532
533
534 private int findNextNestedIndex(String expression)
535 {
536
537
538 int bracketCount = 0;
539 for (int i=0, size=expression.length(); i<size ; i++) {
540 char at = expression.charAt(i);
541 switch (at) {
542 case PropertyUtils.NESTED_DELIM:
543 if (bracketCount < 1) {
544 return i;
545 }
546 break;
547
548 case PropertyUtils.MAPPED_DELIM:
549 case PropertyUtils.INDEXED_DELIM:
550
551 ++bracketCount;
552 break;
553
554 case PropertyUtils.MAPPED_DELIM2:
555 case PropertyUtils.INDEXED_DELIM2:
556
557 --bracketCount;
558 break;
559 }
560 }
561
562 return -1;
563 }
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584 public void setSimpleProperty(Object bean,
585 String name, Object value)
586 throws IllegalAccessException, InvocationTargetException,
587 NoSuchMethodException {
588
589 if (bean == null) {
590 if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
591 return;
592 }
593 if (name == null) {
594 throw new IllegalArgumentException("No name specified");
595 }
596
597
598 if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
599 throw new IllegalArgumentException
600 ("Nested property names are not allowed");
601 } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
602 throw new IllegalArgumentException
603 ("Indexed property names are not allowed");
604 } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
605 throw new IllegalArgumentException
606 ("Mapped property names are not allowed");
607 }
608
609
610 PropertyDescriptor descriptor =
611 getPropertyDescriptor(bean, name);
612 if (descriptor == null) {
613 throw new NoSuchMethodException("Unknown property '" +
614 name + "'");
615 }
616 Method writeMethod = getWriteMethod(descriptor);
617 if (writeMethod == null) {
618
619 LOG.warn("Bean: " + bean.getClass().getName() + ", Property '" + name + "' has no setter method");
620 return;
621 }
622
623
624 Object values[] = new Object[1];
625 values[0] = value;
626 if (LOG.isDebugEnabled()) {
627 String valueClassName =
628 value == null ? "<null>" : value.getClass().getName();
629 LOG.debug("setSimpleProperty: Invoking method " + writeMethod
630 + " with value " + value + " (class " + valueClassName + ")");
631 }
632
633
634 invokeMethod(writeMethod, bean, values);
635
636 }
637
638
639 private Object invokeMethod(
640 Method method,
641 Object bean,
642 Object[] values)
643 throws
644 IllegalAccessException,
645 InvocationTargetException {
646 try {
647
648 return method.invoke(bean, values);
649
650 } catch (IllegalArgumentException e) {
651
652 LOG.error("Method invocation failed.", e);
653 throw new IllegalArgumentException(
654 "Cannot invoke " + method.getDeclaringClass().getName() + "."
655 + method.getName() + " - " + e.getMessage());
656
657 }
658 }
659
660 public Class getPropertyType(Object bean, String name)
661 throws IllegalAccessException, InvocationTargetException,
662 NoSuchMethodException {
663
664 if (bean == null) {
665 throw new IllegalArgumentException("No bean specified");
666 }
667 if (name == null) {
668 throw new IllegalArgumentException("No name specified for bean class '" +
669 bean.getClass() + "'");
670 }
671
672
673 while (getResolver().hasNested(name)) {
674 String next = getResolver().next(name);
675 Object nestedBean = getProperty(bean, next);
676 if (nestedBean == null) {
677 Class<?>[] paramTypes = {};
678 Method method = null;
679 try {
680 method = bean.getClass().getMethod("get" + next.substring(0, 1).toUpperCase() + next.substring(1), (Class[])null);
681 } catch (NoSuchMethodException e) {
682 method = bean.getClass().getMethod("is" + next.substring(0, 1).toUpperCase() + next.substring(1), (Class[])null);
683 }
684 try {
685 nestedBean = ObjectUtils.createNewObjectFromClass(method.getReturnType());
686 } catch (RuntimeException e) {
687 NestedNullException nne = new NestedNullException
688 ("Null property value for '" + next +
689 "' on bean class '" + bean.getClass() + "'");
690 nne.initCause(e);
691 throw nne;
692 }
693 }
694 bean = nestedBean;
695 name = getResolver().remove(name);
696 }
697
698
699 name = getResolver().getProperty(name);
700
701
702 if (bean instanceof DynaBean) {
703 DynaProperty descriptor =
704 ((DynaBean) bean).getDynaClass().getDynaProperty(name);
705 if (descriptor == null) {
706 return (null);
707 }
708 Class type = descriptor.getType();
709 if (type == null) {
710 return (null);
711 } else if (type.isArray()) {
712 return (type.getComponentType());
713 } else {
714 return (type);
715 }
716 }
717
718 PropertyDescriptor descriptor =
719 getPropertyDescriptor(bean, name);
720 if (descriptor == null) {
721 return (null);
722 } else if (descriptor instanceof IndexedPropertyDescriptor) {
723 return (((IndexedPropertyDescriptor) descriptor).
724 getIndexedPropertyType());
725 } else if (descriptor instanceof MappedPropertyDescriptor) {
726 return (((MappedPropertyDescriptor) descriptor).
727 getMappedPropertyType());
728 } else {
729 return (descriptor.getPropertyType());
730 }
731
732 }
733 }