001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.util;
017    
018    import org.apache.commons.beanutils.NestedNullException;
019    import org.apache.commons.beanutils.PropertyUtils;
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.log4j.Logger;
022    import org.apache.ojb.broker.core.proxy.ProxyHelper;
023    import org.hibernate.collection.PersistentBag;
024    import org.hibernate.proxy.HibernateProxy;
025    import org.kuali.rice.core.api.search.SearchOperator;
026    import org.kuali.rice.core.api.util.cache.CopiedObject;
027    import org.kuali.rice.core.web.format.CollectionFormatter;
028    import org.kuali.rice.core.web.format.FormatException;
029    import org.kuali.rice.core.web.format.Formatter;
030    import org.kuali.rice.krad.bo.BusinessObject;
031    import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
032    import org.kuali.rice.krad.bo.PersistableBusinessObject;
033    import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
034    import org.kuali.rice.krad.service.KRADServiceLocator;
035    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
036    import org.kuali.rice.krad.service.ModuleService;
037    import org.kuali.rice.krad.service.PersistenceStructureService;
038    
039    import javax.persistence.EntityNotFoundException;
040    import java.beans.PropertyDescriptor;
041    import java.io.ByteArrayInputStream;
042    import java.io.ByteArrayOutputStream;
043    import java.io.ObjectInputStream;
044    import java.io.ObjectOutputStream;
045    import java.io.Serializable;
046    import java.lang.reflect.Field;
047    import java.lang.reflect.InvocationTargetException;
048    import java.security.MessageDigest;
049    import java.util.Collection;
050    import java.util.Iterator;
051    import java.util.List;
052    import java.util.Map;
053    
054    /**
055     * This class contains various Object, Proxy, and serialization utilities.
056     */
057    public final class ObjectUtils {
058        private static final Logger LOG = Logger.getLogger(ObjectUtils.class);
059    
060        private ObjectUtils() {
061            throw new UnsupportedOperationException("do not call");
062        }
063    
064        /**
065         * Uses Serialization mechanism to create a deep copy of the given Object. As a special case, deepCopy of null returns null,
066         * just to make using this method simpler. For a detailed discussion see:
067         * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
068         *
069         * @param src
070         * @return deep copy of the given Serializable
071         */
072        public static Serializable deepCopy(Serializable src) {
073            CopiedObject co = deepCopyForCaching(src);
074            return co.getContent();
075        }
076    
077    
078        /**
079         * Uses Serialization mechanism to create a deep copy of the given Object, and returns a CacheableObject instance containing the
080         * deepCopy and its size in bytes. As a special case, deepCopy of null returns a cacheableObject containing null and a size of
081         * 0, to make using this method simpler. For a detailed discussion see:
082         * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
083         *
084         * @param src
085         * @return CopiedObject containing a deep copy of the given Serializable and its size in bytes
086         */
087        public static CopiedObject deepCopyForCaching(Serializable src) {
088            CopiedObject co = new CopiedObject();
089    
090            co.setContent(src);
091    
092            return co;
093        }
094    
095    
096        /**
097         * Converts the object to a byte array using the output stream.
098         *
099         * @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    }