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  package org.kuali.rice.krad.util;
17  
18  import org.apache.commons.beanutils.NestedNullException;
19  import org.apache.commons.beanutils.PropertyUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.log4j.Logger;
22  import org.apache.ojb.broker.core.proxy.ProxyHelper;
23  import org.hibernate.collection.PersistentBag;
24  import org.hibernate.proxy.HibernateProxy;
25  import org.kuali.rice.core.api.search.SearchOperator;
26  import org.kuali.rice.core.api.util.cache.CopiedObject;
27  import org.kuali.rice.core.web.format.CollectionFormatter;
28  import org.kuali.rice.core.web.format.FormatException;
29  import org.kuali.rice.core.web.format.Formatter;
30  import org.kuali.rice.krad.bo.BusinessObject;
31  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
32  import org.kuali.rice.krad.bo.PersistableBusinessObject;
33  import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
34  import org.kuali.rice.krad.service.KRADServiceLocator;
35  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
36  import org.kuali.rice.krad.service.ModuleService;
37  import org.kuali.rice.krad.service.PersistenceStructureService;
38  
39  import javax.persistence.EntityNotFoundException;
40  import java.beans.PropertyDescriptor;
41  import java.io.ByteArrayInputStream;
42  import java.io.ByteArrayOutputStream;
43  import java.io.ObjectInputStream;
44  import java.io.ObjectOutputStream;
45  import java.io.Serializable;
46  import java.lang.reflect.Field;
47  import java.lang.reflect.InvocationTargetException;
48  import java.security.MessageDigest;
49  import java.util.Collection;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Map;
53  
54  /**
55   * This class contains various Object, Proxy, and serialization utilities.
56   */
57  public final class ObjectUtils {
58      private static final Logger LOG = Logger.getLogger(ObjectUtils.class);
59  
60      private ObjectUtils() {
61          throw new UnsupportedOperationException("do not call");
62      }
63  
64      /**
65       * Uses Serialization mechanism to create a deep copy of the given Object. As a special case, deepCopy of null returns null,
66       * just to make using this method simpler. For a detailed discussion see:
67       * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
68       *
69       * @param src
70       * @return deep copy of the given Serializable
71       */
72      public static Serializable deepCopy(Serializable src) {
73          CopiedObject co = deepCopyForCaching(src);
74          return co.getContent();
75      }
76  
77  
78      /**
79       * Uses Serialization mechanism to create a deep copy of the given Object, and returns a CacheableObject instance containing the
80       * deepCopy and its size in bytes. As a special case, deepCopy of null returns a cacheableObject containing null and a size of
81       * 0, to make using this method simpler. For a detailed discussion see:
82       * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
83       *
84       * @param src
85       * @return CopiedObject containing a deep copy of the given Serializable and its size in bytes
86       */
87      public static CopiedObject deepCopyForCaching(Serializable src) {
88          CopiedObject co = new CopiedObject();
89  
90          co.setContent(src);
91  
92          return co;
93      }
94  
95  
96      /**
97       * Converts the object to a byte array using the output stream.
98       *
99       * @param object
100      * @return byte array of the object
101      */
102     public static byte[] toByteArray(Object object) throws Exception {
103         ObjectOutputStream oos = null;
104         try {
105             ByteArrayOutputStream bos = new ByteArrayOutputStream(); // A
106             oos = new ObjectOutputStream(bos); // B
107             // serialize and pass the object
108             oos.writeObject(object); // C
109             // oos.flush(); // D
110             return bos.toByteArray();
111         } catch (Exception e) {
112             LOG.warn("Exception in ObjectUtil = " + e);
113             throw (e);
114         } finally {
115             if (oos != null) {
116                 oos.close();
117             }
118         }
119     }
120 
121     /**
122      * reconsitiutes the object that was converted into a byte array by toByteArray
123      *
124      * @param bytes
125      * @return
126      * @throws Exception
127      */
128     public static Object fromByteArray(byte[] bytes) throws Exception {
129         ObjectInputStream ois = null;
130         try {
131             ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
132             ois = new ObjectInputStream(bis);
133             Object obj = ois.readObject();
134             return obj;
135         } catch (Exception e) {
136             LOG.warn("Exception in ObjectUtil = " + e);
137             throw (e);
138         } finally {
139             if (ois != null) {
140                 ois.close();
141             }
142         }
143     }
144 
145     /**
146      * use MD5 to create a one way hash of an object
147      *
148      * @param object
149      * @return
150      */
151     public static String getMD5Hash(Object object) throws Exception {
152         try {
153             MessageDigest md = MessageDigest.getInstance("MD5");
154             md.update(toByteArray(object));
155             return new String(md.digest());
156         } catch (Exception e) {
157             LOG.warn(e);
158             throw e;
159         }
160     }
161 
162     /**
163      * Creates a new instance of a given BusinessObject, copying fields specified in template from the given source BO. For example,
164      * this can be used to create an AccountChangeDetail based on a particular Account.
165      *
166      * @param template a map defining the relationships between the fields of the newly created BO, and the source BO.  For each K (key), V (value)
167      *                 entry, the value of property V on the source BO will be assigned to the K property of the newly created BO
168      * @throws NoSuchMethodException
169      * @throws InvocationTargetException
170      * @throws IllegalAccessException
171      * @throws FormatException
172      * @see MaintenanceUtils
173      */
174 
175     public static BusinessObject createHybridBusinessObject(Class businessObjectClass, BusinessObject source, Map<String, String> template) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
176         BusinessObject obj = null;
177         try {
178             ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(businessObjectClass);
179             if (moduleService != null && moduleService.isExternalizable(businessObjectClass))
180                 obj = (BusinessObject) moduleService.createNewObjectFromExternalizableClass(businessObjectClass);
181             else
182                 obj = (BusinessObject) businessObjectClass.newInstance();
183         } catch (Exception e) {
184             throw new RuntimeException("Cannot instantiate " + businessObjectClass.getName(), e);
185         }
186 
187         createHybridBusinessObject(obj, source, template);
188 
189         return obj;
190     }
191 
192     public static void createHybridBusinessObject(BusinessObject businessObject, BusinessObject source, Map<String, String> template) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
193         for (String name : template.keySet()) {
194             String sourcePropertyName = template.get(name);
195             setObjectProperty(businessObject, name, easyGetPropertyType(source, sourcePropertyName), getPropertyValue(source, sourcePropertyName));
196         }
197     }
198 
199 
200     /**
201      * This method simply uses PojoPropertyUtilsBean logic to get the Class of a Class property.
202      * This method does not have any of the logic needed to obtain the Class of an element of a Collection specified in the DataDictionary.
203      *
204      * @param object       An instance of the Class of which we're trying to get the property Class.
205      * @param propertyName The name of the property.
206      * @return
207      * @throws IllegalAccessException
208      * @throws NoSuchMethodException
209      * @throws InvocationTargetException
210      */
211     static public Class easyGetPropertyType(Object object, String propertyName) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
212 
213         // FIXME (laran) This dependence should be inverted. Instead of having a core class
214         // depend on PojoPropertyUtilsBean, which is in the web layer, the web layer
215         // should depend downward to the core.
216         return PropertyUtils.getPropertyType(object, propertyName);
217 
218     }
219 
220     /**
221      * Returns the type of the property in the object. This implementation is not smart enough to look through a Collection to get the property type
222      * of an attribute of an element in the collection.
223      * <p/>
224      * NOTE: A patch file attached to https://test.kuali.org/jira/browse/KULRNE-4435 contains a modified version of this method which IS smart enough
225      * to look through Collections. This patch is currently under review.
226      *
227      * @param object                      An instance of the Class for which we're trying to get the property type.
228      * @param propertyName                The name of the property of the Class the Class of which we're trying to get. Dot notation is used to separate properties.
229      *                                    TODO: The rules about this dot notation needs to be explained in Confluence using examples.
230      * @param persistenceStructureService Needed to get the type of elements in a Collection from OJB.
231      * @return Object will be null if any parent property for the given property is null.
232      */
233     public static Class getPropertyType(Object object, String propertyName, PersistenceStructureService persistenceStructureService) {
234         if (object == null || propertyName == null) {
235             throw new RuntimeException("Business object and property name can not be null");
236         }
237 
238         Class propertyType = null;
239         try {
240             try {
241                 // Try to simply use the default or simple way of getting the property type.
242                 propertyType = PropertyUtils.getPropertyType(object, propertyName);
243             } catch (IllegalArgumentException ex) {
244                 // swallow the exception, propertyType stays null
245             } catch (NoSuchMethodException nsme) {
246                 // swallow the exception, propertyType stays null
247             }
248 
249             // if the property type as determined from the object is PersistableBusinessObject,
250             // then this must be an extension attribute -- attempt to get the property type from the
251             // persistence structure service
252             if (propertyType != null && propertyType.equals(PersistableBusinessObjectExtension.class)) {
253                 propertyType = persistenceStructureService.getBusinessObjectAttributeClass(
254                         ProxyHelper.getRealClass(object), propertyName);
255             }
256 
257             // If the easy way didn't work ...
258             if (null == propertyType && -1 != propertyName.indexOf('.')) {
259                 if (null == persistenceStructureService) {
260                     LOG.info("PropertyType couldn't be determined simply and no PersistenceStructureService was given. If you pass in a PersistenceStructureService I can look in other places to try to determine the type of the property.");
261                 } else {
262                     String prePeriod = StringUtils.substringBefore(propertyName, ".");
263                     String postPeriod = StringUtils.substringAfter(propertyName, ".");
264 
265                     Class prePeriodClass = getPropertyType(object, prePeriod, persistenceStructureService);
266                     Object prePeriodClassInstance = prePeriodClass.newInstance();
267                     propertyType = getPropertyType(prePeriodClassInstance, postPeriod, persistenceStructureService);
268                 }
269 
270             } else if (Collection.class.isAssignableFrom(propertyType)) {
271                 Map<String, Class> map = persistenceStructureService.listCollectionObjectTypes(object.getClass());
272                 propertyType = map.get(propertyName);
273             }
274 
275         } catch (Exception e) {
276             LOG.debug("unable to get property type for " + propertyName + " " + e.getMessage());
277             // continue and return null for propertyType
278         }
279 
280         return propertyType;
281     }
282 
283     /**
284      * Returns the value of the property in the object.
285      *
286      * @param businessObject
287      * @param propertyName
288      * @return Object will be null if any parent property for the given property is null.
289      */
290     public static Object getPropertyValue(Object businessObject, String propertyName) {
291         if (businessObject == null || propertyName == null) {
292             throw new RuntimeException("Business object and property name can not be null");
293         }
294 
295         Object propertyValue = null;
296         try {
297             propertyValue = PropertyUtils.getProperty(businessObject, propertyName);
298         } catch (NestedNullException e) {
299             // continue and return null for propertyValue
300         } catch (IllegalAccessException e1) {
301             LOG.error("error getting property value for  " + businessObject.getClass() + "." + propertyName + " " + e1.getMessage());
302             throw new RuntimeException("error getting property value for  " + businessObject.getClass() + "." + propertyName + " " + e1.getMessage(), e1);
303         } catch (InvocationTargetException e1) {
304             // continue and return null for propertyValue
305         } catch (NoSuchMethodException e1) {
306             LOG.error("error getting property value for  " + businessObject.getClass() + "." + propertyName + " " + e1.getMessage());
307             throw new RuntimeException("error getting property value for  " + businessObject.getClass() + "." + propertyName + " " + e1.getMessage(), e1);
308         }
309 
310         return propertyValue;
311     }
312 
313     /**
314      * Gets the property value from the business object, then based on the value
315      * type select a formatter and format the value
316      *
317      * @param businessObject BusinessObject instance that contains the property
318      * @param propertyName   Name of property in BusinessObject to get value for
319      * @param formatter      Default formatter to use (or null)
320      * @return Formatted property value as String, or empty string if value is null
321      */
322     public static String getFormattedPropertyValue(BusinessObject businessObject, String propertyName, Formatter formatter) {
323         String propValue = KRADConstants.EMPTY_STRING;
324 
325         Object prop = ObjectUtils.getPropertyValue(businessObject, propertyName);
326         if (formatter == null) {
327             propValue = formatPropertyValue(prop);
328         } else {
329             final Object formattedValue = formatter.format(prop);
330             if (formattedValue != null) {
331                 propValue = String.valueOf(formattedValue);
332             }
333         }
334 
335         return propValue;
336     }
337 
338     /**
339      * References the data dictionary to find any registered formatter class then if not found checks for associated formatter for the
340      * property type. Value is then formatted using the found Formatter
341      *
342      * @param businessObject BusinessObject instance that contains the property
343      * @param propertyName   Name of property in BusinessObject to get value for
344      * @return Formatted property value as String, or empty string if value is null
345      */
346     public static String getFormattedPropertyValueUsingDataDictionary(BusinessObject businessObject, String propertyName) {
347         Formatter formatter = getFormatterWithDataDictionary(businessObject, propertyName);
348 
349         return getFormattedPropertyValue(businessObject, propertyName, formatter);
350     }
351 
352     /**
353      * Based on the value type selects a formatter and returns the formatted
354      * value as a string
355      *
356      * @param propertyValue Object value to be formatted
357      * @return formatted value as a String
358      */
359     public static String formatPropertyValue(Object propertyValue) {
360         Object propValue = KRADConstants.EMPTY_STRING;
361 
362         Formatter formatter = null;
363         if (propertyValue != null) {
364             if (propertyValue instanceof Collection) {
365                 formatter = new CollectionFormatter();
366             } else {
367                 formatter = Formatter.getFormatter(propertyValue.getClass());
368             }
369 
370             propValue = formatter != null ? formatter.format(propertyValue) : propertyValue;
371         }
372 
373         return propValue != null ? String.valueOf(propValue) : KRADConstants.EMPTY_STRING;
374     }
375 
376     /**
377      * Sets the property of an object with the given value. Converts using the formatter of the type for the property.
378      * Note: propertyType does not need passed, is found by util method.
379      */
380     public static void setObjectProperty(Object bo, String propertyName, Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
381         Class propertyType = easyGetPropertyType(bo, propertyName);
382         setObjectProperty(bo, propertyName, propertyType, propertyValue);
383     }
384 
385 
386     /**
387      * Sets the property of an object with the given value. Converts using the formatter of the given type if one is found.
388      *
389      * @param bo
390      * @param propertyName
391      * @param propertyType
392      * @param propertyValue
393      * @throws NoSuchMethodException
394      * @throws InvocationTargetException
395      * @throws IllegalAccessException
396      */
397     public static void setObjectProperty(Object bo, String propertyName, Class propertyType, Object propertyValue)
398             throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
399         // reformat propertyValue, if necessary
400         boolean reformat = false;
401         if (propertyType != null) {
402             if (propertyValue != null && propertyType.isAssignableFrom(String.class)) {
403                 // always reformat if the destination is a String
404                 reformat = true;
405             } else if (propertyValue != null && !propertyType.isAssignableFrom(propertyValue.getClass())) {
406                 // otherwise, only reformat if the propertyValue can't be assigned into the property
407                 reformat = true;
408             }
409 
410             // attempting to set boolean fields to null throws an exception, set to false instead
411             if (boolean.class.isAssignableFrom(propertyType) && propertyValue == null) {
412                 propertyValue = false;
413             }
414         }
415 
416         Formatter formatter = getFormatterWithDataDictionary(bo, propertyName);
417         if (reformat && formatter != null) {
418             LOG.debug("reformatting propertyValue using Formatter " + formatter.getClass().getName());
419             propertyValue = formatter.convertFromPresentationFormat(propertyValue);
420         }
421 
422         // set property in the object
423         PropertyUtils.setNestedProperty(bo, propertyName, propertyValue);
424     }
425 
426 
427     /**
428      * Sets the property of an object with the given value. Converts using the given formatter, if it isn't null.
429      *
430      * @param formatter
431      * @param bo
432      * @param propertyName
433      * @param type
434      * @param propertyValue
435      * @throws NoSuchMethodException
436      * @throws InvocationTargetException
437      * @throws IllegalAccessException
438      */
439     public static void setObjectProperty(Formatter formatter, Object bo, String propertyName, Class type, Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
440 
441         // convert value using formatter for type
442         if (formatter != null) {
443             propertyValue = formatter.convertFromPresentationFormat(propertyValue);
444         }
445 
446         // set property in the object
447         PropertyUtils.setNestedProperty(bo, propertyName, propertyValue);
448     }
449 
450     /**
451      * Returns a Formatter instance for the given property name in the given given business object. First
452      * checks if a formatter is defined for the attribute in the data dictionary, is not found then returns
453      * the registered formatter for the property type in Formatter
454      *
455      * @param bo           - business object instance with property to get formatter for
456      * @param propertyName - name of property to get formatter for
457      * @return Formatter instance
458      */
459     public static Formatter getFormatterWithDataDictionary(Object bo, String propertyName) {
460         Formatter formatter = null;
461 
462         Class boClass = bo.getClass();
463         String boPropertyName = propertyName;
464 
465         // for collections, formatter should come from property on the collection type
466         if (StringUtils.contains(propertyName, "]")) {
467             Object collectionParent = getNestedValue(bo, StringUtils.substringBeforeLast(propertyName, "].") + "]");
468             if (collectionParent != null) {
469                 boClass = collectionParent.getClass();
470                 boPropertyName = StringUtils.substringAfterLast(propertyName, "].");
471             }
472         }
473 
474         Class<? extends Formatter> formatterClass = KRADServiceLocatorWeb.getDataDictionaryService().getAttributeFormatter(
475                 boClass, boPropertyName);
476         if (formatterClass == null) {
477             try {
478                 formatterClass = Formatter.findFormatter(getPropertyType(boClass.newInstance(), boPropertyName,
479                         KRADServiceLocator.getPersistenceStructureService()));
480             } catch (InstantiationException e) {
481                 LOG.warn("Unable to find a formater for bo class " + boClass + " and property " + boPropertyName);
482                 // just swallow the exception and let formatter be null
483             } catch (IllegalAccessException e) {
484                 LOG.warn("Unable to find a formater for bo class " + boClass + " and property " + boPropertyName);
485                 // just swallow the exception and let formatter be null
486             }
487         }
488 
489         if (formatterClass != null) {
490             try {
491                 formatter = formatterClass.newInstance();
492             } catch (Exception e) {
493                 throw new RuntimeException(
494                         "cannot create new instance of formatter class " + formatterClass.toString(), e);
495             }
496         }
497 
498         return formatter;
499     }
500 
501     /**
502      * Recursive; sets all occurences of the property in the object, its nested objects and its object lists with the given value.
503      *
504      * @param bo
505      * @param propertyName
506      * @param type
507      * @param propertyValue
508      * @throws NoSuchMethodException
509      * @throws InvocationTargetException
510      * @throws IllegalAccessException
511      */
512     public static void setObjectPropertyDeep(Object bo, String propertyName, Class type, Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
513 
514         // Base return cases to avoid null pointers & infinite loops
515         if (isNull(bo) || !PropertyUtils.isReadable(bo, propertyName) || (propertyValue != null && propertyValue.equals(getPropertyValue(bo, propertyName))) || (type != null && !type.equals(easyGetPropertyType(bo, propertyName)))) {
516             return;
517         }
518 
519         // need to materialize the updateable collections before resetting the property, because it may be used in the retrieval
520         materializeUpdateableCollections(bo);
521 
522         // Set the property in the BO
523         setObjectProperty(bo, propertyName, type, propertyValue);
524 
525         // Now drill down and check nested BOs and BO lists
526         PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(bo.getClass());
527         for (int i = 0; i < propertyDescriptors.length; i++) {
528 
529             PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
530 
531             // Business Objects
532             if (propertyDescriptor.getPropertyType() != null && (BusinessObject.class).isAssignableFrom(propertyDescriptor.getPropertyType()) && PropertyUtils.isReadable(bo, propertyDescriptor.getName())) {
533                 Object nestedBo = getPropertyValue(bo, propertyDescriptor.getName());
534                 if (nestedBo instanceof BusinessObject) {
535                     setObjectPropertyDeep((BusinessObject) nestedBo, propertyName, type, propertyValue);
536                 }
537             }
538 
539             // Lists
540             else if (propertyDescriptor.getPropertyType() != null && (List.class).isAssignableFrom(propertyDescriptor.getPropertyType()) && getPropertyValue(bo, propertyDescriptor.getName()) != null) {
541 
542                 List propertyList = (List) getPropertyValue(bo, propertyDescriptor.getName());
543                 for (Object listedBo : propertyList) {
544                     if (listedBo != null && listedBo instanceof BusinessObject) {
545                         setObjectPropertyDeep(listedBo, propertyName, type, propertyValue);
546                     }
547                 } // end for
548             }
549         } // end for
550     }
551 
552     /*
553     * Recursive up to a given depth; sets all occurences of the property in the object, its nested objects and its object lists with the given value.
554     */
555     public static void setObjectPropertyDeep(Object bo, String propertyName, Class type, Object propertyValue, int depth) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
556         // Base return cases to avoid null pointers & infinite loops
557         if (depth == 0 || isNull(bo) || !PropertyUtils.isReadable(bo, propertyName)) {
558             return;
559         }
560 
561         // need to materialize the updateable collections before resetting the property, because it may be used in the retrieval
562         materializeUpdateableCollections(bo);
563 
564         // Set the property in the BO
565         setObjectProperty(bo, propertyName, type, propertyValue);
566 
567         // Now drill down and check nested BOs and BO lists
568         PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(bo.getClass());
569         for (int i = 0; i < propertyDescriptors.length; i++) {
570             PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
571 
572             // Business Objects
573             if (propertyDescriptor.getPropertyType() != null && (BusinessObject.class).isAssignableFrom(propertyDescriptor.getPropertyType()) && PropertyUtils.isReadable(bo, propertyDescriptor.getName())) {
574                 Object nestedBo = getPropertyValue(bo, propertyDescriptor.getName());
575                 if (nestedBo instanceof BusinessObject) {
576                     setObjectPropertyDeep((BusinessObject) nestedBo, propertyName, type, propertyValue, depth - 1);
577                 }
578             }
579 
580             // Lists
581             else if (propertyDescriptor.getPropertyType() != null && (List.class).isAssignableFrom(propertyDescriptor.getPropertyType()) && getPropertyValue(bo, propertyDescriptor.getName()) != null) {
582 
583                 List propertyList = (List) getPropertyValue(bo, propertyDescriptor.getName());
584 
585                 // Complete Hibernate Hack - fetches the proxied List into the PersistenceContext and sets it on the BO Copy.
586                 if (propertyList instanceof PersistentBag) {
587                     try {
588                         PersistentBag bag = (PersistentBag) propertyList;
589                         PersistableBusinessObject pbo = (PersistableBusinessObject) KRADServiceLocator
590                                 .getEntityManagerFactory().createEntityManager().find(bo.getClass(), bag.getKey());
591                         Field field1 = pbo.getClass().getDeclaredField(propertyDescriptor.getName());
592                         Field field2 = bo.getClass().getDeclaredField(propertyDescriptor.getName());
593                         field1.setAccessible(true);
594                         field2.setAccessible(true);
595                         field2.set(bo, field1.get(pbo));
596                         propertyList = (List) getPropertyValue(bo, propertyDescriptor.getName());
597                         ;
598                     } catch (Exception e) {
599                         LOG.error(e.getMessage(), e);
600                     }
601                 }
602                 // End Complete Hibernate Hack
603 
604                 for (Object listedBo : propertyList) {
605                     if (listedBo != null && listedBo instanceof BusinessObject) {
606                         setObjectPropertyDeep(listedBo, propertyName, type, propertyValue, depth - 1);
607                     }
608                 } // end for
609             }
610         } // end for
611     }
612 
613     /**
614      * This method checks for updateable collections on the business object provided and materializes the corresponding collection proxies
615      *
616      * @param bo The business object for which you want unpdateable, proxied collections materialized
617      * @throws FormatException
618      * @throws IllegalAccessException
619      * @throws InvocationTargetException
620      * @throws NoSuchMethodException
621      */
622     public static void materializeUpdateableCollections(Object bo) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
623         if (isNotNull(bo)) {
624             PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(bo.getClass());
625             for (int i = 0; i < propertyDescriptors.length; i++) {
626                 if (KRADServiceLocator.getPersistenceStructureService().hasCollection(bo.getClass(), propertyDescriptors[i].getName()) && KRADServiceLocator
627                         .getPersistenceStructureService().isCollectionUpdatable(bo.getClass(), propertyDescriptors[i].getName())) {
628                     Collection updateableCollection = (Collection) getPropertyValue(bo, propertyDescriptors[i].getName());
629                     if ((updateableCollection != null) && ProxyHelper.isCollectionProxy(updateableCollection)) {
630                         materializeObjects(updateableCollection);
631                     }
632                 }
633             }
634         }
635     }
636 
637 
638     /**
639      * Removes all query characters from a string.
640      *
641      * @param string
642      * @return Cleaned string
643      */
644     public static String clean(String string) {
645         for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
646             string = StringUtils.replace(string, op.op(), KRADConstants.EMPTY_STRING);
647         }
648         return string;
649     }
650 
651 
652     /**
653      * Compares two {@link PersistableBusinessObject} instances for equality of type and key values.
654      *
655      * @param bo1
656      * @param bo2
657      * @return boolean indicating whether the two objects are equal.
658      */
659     public static boolean equalByKeys(PersistableBusinessObject bo1, PersistableBusinessObject bo2) {
660         boolean equal = true;
661 
662         if (bo1 == null && bo2 == null) {
663             equal = true;
664         } else if (bo1 == null || bo2 == null) {
665             equal = false;
666         } else if (!bo1.getClass().getName().equals(bo2.getClass().getName())) {
667             equal = false;
668         } else {
669             Map bo1Keys = KRADServiceLocator.getPersistenceService().getPrimaryKeyFieldValues(bo1);
670             Map bo2Keys = KRADServiceLocator.getPersistenceService().getPrimaryKeyFieldValues(bo2);
671             for (Iterator iter = bo1Keys.keySet().iterator(); iter.hasNext();) {
672                 String keyName = (String) iter.next();
673                 if (bo1Keys.get(keyName) != null && bo2Keys.get(keyName) != null) {
674                     if (!bo1Keys.get(keyName).toString().equals(bo2Keys.get(keyName).toString())) {
675                         equal = false;
676                     }
677                 } else {
678                     equal = false;
679                 }
680             }
681         }
682 
683 
684         return equal;
685     }
686 
687     /**
688      * Compares a business object with a List of {@link PersistableBusinessObject}s to determine if an object with the same key as the BO exists in the list.
689      *
690      * @param controlList - The list of items to check
691      * @param bo          - The BO whose keys we are looking for in the controlList
692      * @return boolean
693      */
694     public static boolean collectionContainsObjectWithIdentitcalKey(Collection<? extends PersistableBusinessObject> controlList, PersistableBusinessObject bo) {
695         boolean objectExistsInList = false;
696 
697         for (Iterator i = controlList.iterator(); i.hasNext();) {
698             if (equalByKeys((PersistableBusinessObject) i.next(), bo)) {
699                 return true;
700             }
701         }
702 
703         return objectExistsInList;
704     }
705 
706     /**
707      * Compares a business object with a Collection of {@link PersistableBusinessObject}s to count how many have the same key as the BO.
708      *
709      * @param collection - The collection of items to check
710      * @param bo         - The BO whose keys we are looking for in the collection
711      * @return how many have the same keys
712      */
713     public static int countObjectsWithIdentitcalKey(Collection<? extends PersistableBusinessObject> collection, PersistableBusinessObject bo) {
714         // todo: genericize collectionContainsObjectWithIdentitcalKey() to leverage this method?
715         int n = 0;
716         for (PersistableBusinessObject item : collection) {
717             if (equalByKeys(item, bo)) {
718                 n++;
719             }
720         }
721         return n;
722     }
723 
724     /**
725      * Compares a business object with a List of {@link PersistableBusinessObject}s to determine if an object with the same key as the BO exists in the list. If it
726      * does, the item is removed from the List. This is functionally similar to List.remove() that operates only on Key values.
727      *
728      * @param controlList - The list of items to check
729      * @param bo          - The BO whose keys we are looking for in the controlList
730      */
731 
732     public static void removeObjectWithIdentitcalKey(Collection<? extends PersistableBusinessObject> controlList, PersistableBusinessObject bo) {
733         for (Iterator<? extends PersistableBusinessObject> i = controlList.iterator(); i.hasNext();) {
734             PersistableBusinessObject listBo = i.next();
735             if (equalByKeys(listBo, bo)) {
736                 i.remove();
737             }
738         }
739     }
740 
741     /**
742      * Compares a business object with a List of BOs to determine if an object with the same key as the BO exists in the list. If it
743      * does, the item is returned.
744      *
745      * @param controlList - The list of items to check
746      * @param bo          - The BO whose keys we are looking for in the controlList
747      */
748 
749     public static BusinessObject retrieveObjectWithIdentitcalKey(Collection<? extends PersistableBusinessObject> controlList, PersistableBusinessObject bo) {
750         BusinessObject returnBo = null;
751 
752         for (Iterator<? extends PersistableBusinessObject> i = controlList.iterator(); i.hasNext();) {
753             PersistableBusinessObject listBo = i.next();
754             if (equalByKeys(listBo, bo)) {
755                 returnBo = listBo;
756             }
757         }
758 
759         return returnBo;
760     }
761 
762     /**
763      * Determines if a given string could represent a nested attribute of an object.
764      *
765      * @param attributeName
766      * @return true if the attribute is nested
767      */
768     public static boolean isNestedAttribute(String attributeName) {
769         boolean isNested = false;
770 
771         if (StringUtils.contains(attributeName, ".")) {
772             isNested = true;
773         }
774 
775         return isNested;
776     }
777 
778     /**
779      * Returns the prefix of a nested attribute name, or the empty string if the attribute name is not nested.
780      *
781      * @param attributeName
782      * @return everything BEFORE the last "." character in attributeName
783      */
784     public static String getNestedAttributePrefix(String attributeName) {
785         String prefix = "";
786 
787         if (StringUtils.contains(attributeName, ".")) {
788             prefix = StringUtils.substringBeforeLast(attributeName, ".");
789         }
790 
791         return prefix;
792     }
793 
794     /**
795      * Returns the primitive part of an attribute name string.
796      *
797      * @param attributeName
798      * @return everything AFTER the last "." character in attributeName
799      */
800     public static String getNestedAttributePrimitive(String attributeName) {
801         String primitive = attributeName;
802 
803         if (StringUtils.contains(attributeName, ".")) {
804             primitive = StringUtils.substringAfterLast(attributeName, ".");
805         }
806 
807         return primitive;
808     }
809 
810     /**
811      * This method is a OJB Proxy-safe way to test for null on a proxied object that may or may not be materialized yet. It is safe
812      * to use on a proxy (materialized or non-materialized) or on a non-proxy (ie, regular object). Note that this will force a
813      * materialization of the proxy if the object is a proxy and unmaterialized.
814      *
815      * @param object - any object, proxied or not, materialized or not
816      * @return true if the object (or underlying materialized object) is null, false otherwise
817      */
818     public static boolean isNull(Object object) {
819 
820         // regardless, if its null, then its null
821         if (object == null) {
822             return true;
823         }
824 
825         // only try to materialize the object to see if its null if this is a
826         // proxy object
827         if (ProxyHelper.isProxy(object) || ProxyHelper.isCollectionProxy(object)) {
828             if (ProxyHelper.getRealObject(object) == null) {
829                 return true;
830             }
831         }
832 
833         // JPA does not provide a way to determine if an object is a proxy, instead we invoke
834         // the equals method and catch an EntityNotFoundException
835         try {
836             object.equals(null);
837         } catch (EntityNotFoundException e) {
838             return true;
839         }
840 
841 
842         return false;
843     }
844 
845     /**
846      * This method is a OJB Proxy-safe way to test for notNull on a proxied object that may or may not be materialized yet. It is
847      * safe to use on a proxy (materialized or non-materialized) or on a non-proxy (ie, regular object). Note that this will force a
848      * materialization of the proxy if the object is a proxy and unmaterialized.
849      *
850      * @param object - any object, proxied or not, materialized or not
851      * @return true if the object (or underlying materialized object) is not null, true if its null
852      */
853     public static boolean isNotNull(Object object) {
854         return !ObjectUtils.isNull(object);
855     }
856 
857     /**
858      * Attempts to find the Class for the given potentially proxied object
859      *
860      * @param object the potentially proxied object to find the Class of
861      * @return the best Class which could be found for the given object
862      */
863     public static Class materializeClassForProxiedObject(Object object) {
864         if (object == null) {
865             return null;
866         }
867 
868         if (object instanceof HibernateProxy) {
869             final Class realClass = ((HibernateProxy) object).getHibernateLazyInitializer().getPersistentClass();
870             return realClass;
871         }
872 
873         if (ProxyHelper.isProxy(object) || ProxyHelper.isCollectionProxy(object)) {
874             return ProxyHelper.getRealClass(object);
875         }
876 
877         return object.getClass();
878     }
879 
880     /**
881      * This method runs the ObjectUtils.isNotNull() method for each item in a list of BOs. ObjectUtils.isNotNull() will materialize
882      * the objects if they are currently OJB proxies.
883      *
884      * @param possiblyProxiedObjects - a Collection of objects that may be proxies
885      */
886     public static void materializeObjects(Collection possiblyProxiedObjects) {
887         for (Iterator i = possiblyProxiedObjects.iterator(); i.hasNext();) {
888             ObjectUtils.isNotNull(i.next());
889         }
890     }
891 
892     /**
893      * This method attempts to materialize all of the proxied reference objects (ie, sub-objects) hanging off the passed-in BO
894      * object. It will do it down to the specified depth. An IllegalArgumentException will be thrown if the bo object passed in is
895      * itself a non-materialized proxy object. If the bo passed in has no proxied sub-objects, then the object will not be modified,
896      * and no errors will be thrown. WARNING: Be careful using depth any greater than 2. The number of DB hits, time, and memory
897      * consumed grows exponentially with each additional increment to depth. Make sure you really need that depth before doing so.
898      *
899      * @param bo    A valid, populated BusinessObject containing (possibly) proxied sub-objects. This object will be modified in place.
900      * @param depth int Value 0-5 indicating how deep to recurse the materialization. If a zero (0) is passed in, then no work will
901      *              be done.
902      */
903     public static void materializeSubObjectsToDepth(PersistableBusinessObject bo, int depth) {
904         if (bo == null) {
905             throw new IllegalArgumentException("The bo passed in was null.");
906         }
907         if (depth < 0 || depth > 5) {
908             throw new IllegalArgumentException("The depth passed in was out of bounds.  Only values " + "between 0 and 5, inclusively, are allowed.");
909         }
910 
911         // if depth is zero, then we're done recursing and can just exit
912         if (depth == 0) {
913             return;
914         }
915 
916         // deal with the possibility that the bo passed in (ie, the parent object) is an un-materialized proxy
917         if (ProxyHelper.isProxy(bo)) {
918             if (!ProxyHelper.isMaterialized(bo)) {
919                 throw new IllegalArgumentException("The bo passed in is an un-materialized proxy, and cannot be used.");
920             }
921         }
922 
923         // get the list of reference objects hanging off the parent BO
924         if (KRADServiceLocator.getPersistenceStructureService().isPersistable(bo.getClass())) {
925             Map<String, Class> references = KRADServiceLocator.getPersistenceStructureService().listReferenceObjectFields(bo);
926 
927             // initialize our in-loop objects
928             String referenceName = "";
929             Class referenceClass = null;
930             Object referenceValue = null;
931             Object realReferenceValue = null;
932 
933             // for each reference object on the parent bo
934             for (Iterator iter = references.keySet().iterator(); iter.hasNext();) {
935                 referenceName = (String) iter.next();
936                 referenceClass = references.get(referenceName);
937 
938                 // if its a proxy, replace it with a non-proxy
939                 referenceValue = getPropertyValue(bo, referenceName);
940                 if (referenceValue != null) {
941                     if (ProxyHelper.isProxy(referenceValue)) {
942                         realReferenceValue = ProxyHelper.getRealObject(referenceValue);
943                         if (realReferenceValue != null) {
944                             try {
945                                 setObjectProperty(bo, referenceName, referenceClass, realReferenceValue);
946                             } catch (FormatException e) {
947                                 throw new RuntimeException("FormatException: could not set the property '" + referenceName + "'.", e);
948                             } catch (IllegalAccessException e) {
949                                 throw new RuntimeException("IllegalAccessException: could not set the property '" + referenceName + "'.", e);
950                             } catch (InvocationTargetException e) {
951                                 throw new RuntimeException("InvocationTargetException: could not set the property '" + referenceName + "'.", e);
952                             } catch (NoSuchMethodException e) {
953                                 throw new RuntimeException("NoSuchMethodException: could not set the property '" + referenceName + "'.", e);
954                             }
955                         }
956                     }
957 
958                     // recurse down through this reference object
959                     if (realReferenceValue instanceof PersistableBusinessObject && depth > 1) {
960                         materializeSubObjectsToDepth((PersistableBusinessObject) realReferenceValue, depth - 1);
961                     }
962                 }
963 
964             }
965         }
966     }
967 
968     /**
969      * This method attempts to materialize all of the proxied reference objects (ie, sub-objects) hanging off the passed-in BO
970      * object. It will do it just three levels down. In other words, it will only materialize the objects that are direct members of
971      * the bo, objects that are direct members of those bos, that one more time, and no further down. An IllegalArgumentException
972      * will be thrown if the bo object passed in is itself a non-materialized proxy object. If the bo passed in has no proxied
973      * sub-objects, then the object will not be modified, and no errors will be thrown.
974      *
975      * @param bo A valid, populated BusinessObject containing (possibly) proxied sub-objects. This object will be modified in place.
976      */
977     public static void materializeAllSubObjects(PersistableBusinessObject bo) {
978         materializeSubObjectsToDepth(bo, 3);
979     }
980 
981     /**
982      * This method safely extracts either simple values OR nested values. For example, if the bo is SubAccount, and the fieldName is
983      * a21SubAccount.subAccountTypeCode, this thing makes sure it gets the value off the very end attribute, no matter how deeply
984      * nested it is. The code would be slightly simpler if this was done recursively, but this is safer, and consumes a constant
985      * amount of memory, no matter how deeply nested it goes.
986      *
987      * @param bo
988      * @param fieldName
989      * @return The field value if it exists. If it doesnt, and the name is invalid, and
990      */
991     public static Object getNestedValue(Object bo, String fieldName) {
992 
993         if (bo == null) {
994             throw new IllegalArgumentException("The bo passed in was null.");
995         }
996         if (StringUtils.isBlank(fieldName)) {
997             throw new IllegalArgumentException("The fieldName passed in was blank.");
998         }
999 
1000         // okay, this section of code is to handle sub-object values, like
1001         // SubAccount.a21SubAccount.subAccountTypeCode. it basically walks
1002         // through the period-delimited list of names, and ends up with the
1003         // final value.
1004         String[] fieldNameParts = fieldName.split("\\.");
1005         Object currentObject = null;
1006         Object priorObject = bo;
1007         for (int i = 0; i < fieldNameParts.length; i++) {
1008             String fieldNamePart = fieldNameParts[i];
1009 
1010             try {
1011                 if (fieldNamePart.indexOf("]") > 0) {
1012                     currentObject = PropertyUtils.getIndexedProperty(priorObject, fieldNamePart);
1013                 } else {
1014                     currentObject = PropertyUtils.getSimpleProperty(priorObject, fieldNamePart);
1015                 }
1016             } catch (IllegalAccessException e) {
1017                 throw new RuntimeException("Caller does not have access to the property accessor method.", e);
1018             } catch (InvocationTargetException e) {
1019                 throw new RuntimeException("Property accessor method threw an exception.", e);
1020             } catch (NoSuchMethodException e) {
1021                 throw new RuntimeException("The accessor method requested for this property cannot be found.", e);
1022             }
1023 
1024             // materialize the proxy, if it is a proxy
1025             if (ProxyHelper.isProxy(currentObject)) {
1026                 currentObject = ProxyHelper.getRealObject(currentObject);
1027             }
1028 
1029             // if a node or the leaf is null, then we're done, there's no need to
1030             // continue accessing null things
1031             if (currentObject == null) {
1032                 return currentObject;
1033             }
1034 
1035             priorObject = currentObject;
1036         }
1037         return currentObject;
1038     }
1039 
1040     /**
1041      * This method safely creates a object from a class
1042      * Convenience method to create new object and throw a runtime exception if it cannot
1043      * If the class is an {@link ExternalizableBusinessObject}, this method will determine the interface for the EBO and query the
1044      * appropriate module service to create a new instance.
1045      *
1046      * @param clazz
1047      * @return a newInstance() of clazz
1048      */
1049     public static Object createNewObjectFromClass(Class clazz) {
1050         if (clazz == null) {
1051             throw new RuntimeException("BO class was passed in as null");
1052         }
1053         try {
1054             if (ExternalizableBusinessObject.class.isAssignableFrom(clazz)) {
1055                 Class eboInterface = ExternalizableBusinessObjectUtils.determineExternalizableBusinessObjectSubInterface(clazz);
1056                 ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(eboInterface);
1057                 return moduleService.createNewObjectFromExternalizableClass(eboInterface);
1058             } else {
1059                 return clazz.newInstance();
1060             }
1061         } catch (Exception e) {
1062             throw new RuntimeException("Error occured while trying to create a new instance for class " + clazz, e);
1063         }
1064     }
1065 
1066     /**
1067      * Return whether or not an attribute is writeable. This method is aware that that Collections may be involved and handles them
1068      * consistently with the way in which OJB handles specifying the attributes of elements of a Collection.
1069      *
1070      * @param o
1071      * @param p
1072      * @return
1073      * @throws IllegalArgumentException
1074      */
1075     public static boolean isWriteable(Object o, String p, PersistenceStructureService persistenceStructureService)
1076             throws IllegalArgumentException {
1077         if (null == o || null == p) {
1078             throw new IllegalArgumentException("Cannot check writeable status with null arguments.");
1079         }
1080 
1081         boolean b = false;
1082 
1083         // Try the easy way.
1084         if (!(PropertyUtils.isWriteable(o, p))) {
1085             // If that fails lets try to be a bit smarter, understanding that Collections may be involved.
1086             if (-1 != p.indexOf('.')) {
1087                 String[] parts = p.split("\\.");
1088 
1089                 // Get the type of the attribute.
1090                 Class c = ObjectUtils.getPropertyType(o, parts[0], persistenceStructureService);
1091 
1092                 if (c != null) {
1093                     Object i = null;
1094 
1095                     // If the next level is a Collection, look into the collection, to find out what type its elements are.
1096                     if (Collection.class.isAssignableFrom(c)) {
1097                         Map<String, Class> m = persistenceStructureService.listCollectionObjectTypes(o.getClass());
1098                         c = m.get(parts[0]);
1099                     }
1100 
1101                     // Look into the attribute class to see if it is writeable.
1102                     try {
1103                         i = c.newInstance();
1104 
1105                         StringBuffer sb = new StringBuffer();
1106                         for (int x = 1; x < parts.length; x++) {
1107                             sb.append(1 == x ? "" : ".").append(parts[x]);
1108                         }
1109                         b = isWriteable(i, sb.toString(), persistenceStructureService);
1110 
1111                     } catch (Exception ex) {
1112                         LOG.error("Skipping Criteria: " + p + " - Unable to instantiate class : " + c.getName(), ex);
1113                     }
1114                 } else {
1115                     LOG.error("Skipping Criteria: " + p + " - Unable to determine class for object: "
1116                             + o.getClass().getName() + " - " + parts[0]);
1117                 }
1118             }
1119 
1120         } else {
1121             b = true;
1122         }
1123 
1124         return b;
1125     }
1126 
1127     /**
1128      * Helper method for creating a new instance of the given class
1129      * 
1130      * @param clazz
1131      *            - class of object to create
1132      * @return T object of type given by the clazz parameter
1133      */
1134     public static <T> T newInstance(Class<T> clazz) {
1135         T object = null;
1136         try {
1137             object = clazz.newInstance();
1138         }
1139         catch (InstantiationException e) {
1140             LOG.error("Unable to create new instance of class: " + clazz.getName());
1141             throw new RuntimeException(e);
1142         }
1143         catch (IllegalAccessException e) {
1144             LOG.error("Unable to create new instance of class: " + clazz.getName());
1145             throw new RuntimeException(e);
1146         }
1147 
1148         return object;
1149     }
1150 }