001/**
002 * Copyright 2005-2018 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 */
016package org.kuali.rice.krad.util;
017
018import org.apache.commons.beanutils.NestedNullException;
019import org.apache.commons.beanutils.PropertyUtils;
020import org.apache.commons.lang.StringUtils;
021import org.apache.log4j.Logger;
022import org.apache.ojb.broker.core.proxy.ProxyHelper;
023import org.kuali.rice.core.api.CoreApiServiceLocator;
024import org.kuali.rice.core.api.encryption.EncryptionService;
025import org.kuali.rice.core.api.search.SearchOperator;
026import org.kuali.rice.core.api.util.cache.CopiedObject;
027import org.kuali.rice.core.web.format.CollectionFormatter;
028import org.kuali.rice.core.web.format.FormatException;
029import org.kuali.rice.core.web.format.Formatter;
030import org.kuali.rice.kns.service.KNSServiceLocator;
031import org.kuali.rice.krad.bo.BusinessObject;
032import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
033import org.kuali.rice.krad.bo.PersistableBusinessObject;
034import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
035import org.kuali.rice.krad.data.DataObjectWrapper;
036import org.kuali.rice.krad.exception.ClassNotPersistableException;
037import org.kuali.rice.krad.maintenance.MaintenanceUtils;
038import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039import org.kuali.rice.krad.service.ModuleService;
040import org.kuali.rice.krad.service.PersistenceStructureService;
041
042import javax.persistence.EntityNotFoundException;
043
044import java.beans.PropertyDescriptor;
045import java.io.ByteArrayInputStream;
046import java.io.ByteArrayOutputStream;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import java.lang.reflect.Field;
051import java.lang.reflect.InvocationTargetException;
052import java.security.GeneralSecurityException;
053import java.security.MessageDigest;
054import java.util.Collection;
055import java.util.Iterator;
056import java.util.List;
057import java.util.Map;
058
059/**
060 * Contains various Object, Proxy, and serialization utilities
061 *
062 * @author Kuali Rice Team (rice.collab@kuali.org)
063 *
064 * @deprecated use new KRAD data framework instead
065 */
066@Deprecated
067public final class ObjectUtils {
068    private static final Logger LOG = Logger.getLogger(ObjectUtils.class);
069
070    private ObjectUtils() {
071        throw new UnsupportedOperationException("do not call");
072    }
073
074    /**
075     * Uses Serialization mechanism to create a deep copy of the given Object. As a special case, deepCopy of null
076     * returns null,
077     * just to make using this method simpler. For a detailed discussion see:
078     * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
079     *
080     * @param src
081     * @return deep copy of the given Serializable
082     */
083    public static Serializable deepCopy(Serializable src) {
084        CopiedObject co = deepCopyForCaching(src);
085        return co.getContent();
086    }
087
088    /**
089     * Uses Serialization mechanism to create a deep copy of the given Object, and returns a CacheableObject instance
090     * containing the
091     * deepCopy and its size in bytes. As a special case, deepCopy of null returns a cacheableObject containing null and
092     * a size of
093     * 0, to make using this method simpler. For a detailed discussion see:
094     * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
095     *
096     * @param src
097     * @return CopiedObject containing a deep copy of the given Serializable and its size in bytes
098     */
099    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}