View Javadoc

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