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