View Javadoc
1   /*
2    * Copyright 2007 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.ole.module.purap.util;
17  
18  import org.kuali.ole.module.purap.PurapConstants;
19  import org.kuali.ole.sys.context.SpringContext;
20  import org.kuali.rice.core.web.format.FormatException;
21  import org.kuali.rice.krad.bo.BusinessObject;
22  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
23  import org.kuali.rice.krad.service.KualiModuleService;
24  import org.kuali.rice.krad.service.ModuleService;
25  import org.kuali.rice.krad.service.PersistenceService;
26  import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
27  import org.kuali.rice.krad.util.ObjectUtils;
28  
29  import java.lang.reflect.Field;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Modifier;
32  import java.util.*;
33  
34  /**
35   * Purap Object Utils.
36   * Similar to the nervous system ObjectUtils this class contains methods to reflectively set and get values on
37   * BusinessObjects that are passed in.
38   */
39  public class PurApObjectUtils {
40      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurApObjectUtils.class);
41  
42      /**
43       * Populates a class using a base class to determine fields
44       *
45       * @param base                   the class to determine what fields to copy
46       * @param src                    the source class
47       * @param target                 the target class
48       * @param supplementalUncopyable a list of fields to never copy
49       */
50      public static void populateFromBaseClass(Class base, BusinessObject src, BusinessObject target, Map supplementalUncopyable) {
51          List<String> fieldNames = new ArrayList<String>();
52          Field[] fields = base.getDeclaredFields();
53  
54  
55          for (Field field : fields) {
56              if (!Modifier.isTransient(field.getModifiers())) {
57                  fieldNames.add(field.getName());
58              } else {
59                  if (LOG.isDebugEnabled()) {
60                      LOG.debug("field " + field.getName() + " is transient, skipping ");
61                  }
62              }
63          }
64          int counter = 0;
65          for (String fieldName : fieldNames) {
66              if ((isProcessableField(base, fieldName, PurapConstants.KNOWN_UNCOPYABLE_FIELDS)) && (isProcessableField(base, fieldName, supplementalUncopyable))) {
67                  attemptCopyOfFieldName(base.getName(), fieldName, src, target, supplementalUncopyable);
68                  counter++;
69              }
70          }
71          if (LOG.isDebugEnabled()) {
72              LOG.debug("Population complete for " + counter + " fields out of a total of " + fieldNames.size() + " potential fields in object with base class '" + base + "'");
73          }
74      }
75  
76      /**
77       * True if a field is processable
78       *
79       * @param baseClass          the base class
80       * @param fieldName          the field name to detrmine if processable
81       * @param excludedFieldNames field names to exclude
82       * @return true if a field is processable
83       */
84      protected static boolean isProcessableField(Class baseClass, String fieldName, Map excludedFieldNames) {
85          if (excludedFieldNames.containsKey(fieldName)) {
86              Class potentialClassName = (Class) excludedFieldNames.get(fieldName);
87              if ((ObjectUtils.isNull(potentialClassName)) || (potentialClassName.equals(baseClass))) {
88                  return false;
89              }
90          }
91          return true;
92      }
93  
94      /**
95       * Attempts to copy a field
96       *
97       * @param baseClass              the base class
98       * @param fieldName              the field name to determine if processable
99       * @param sourceObject           source object
100      * @param targetObject           target object
101      * @param supplementalUncopyable
102      */
103     protected static void attemptCopyOfFieldName(String baseClassName, String fieldName, BusinessObject sourceObject, BusinessObject targetObject, Map supplementalUncopyable) {
104         try {
105 
106             Object propertyValue = ObjectUtils.getPropertyValue(sourceObject, fieldName);
107             if ((ObjectUtils.isNotNull(propertyValue)) && (Collection.class.isAssignableFrom(propertyValue.getClass()))) {
108                 if (LOG.isDebugEnabled()) {
109                     LOG.debug("attempting to copy collection field '" + fieldName + "' using base class '" + baseClassName + "' and property value class '" + propertyValue.getClass() + "'");
110                 }
111                 copyCollection(fieldName, targetObject, (Collection) propertyValue, supplementalUncopyable);
112             } else {
113                 String propertyValueClass = (ObjectUtils.isNotNull(propertyValue)) ? propertyValue.getClass().toString() : "(null)";
114                 if (LOG.isDebugEnabled()) {
115                     LOG.debug("attempting to set field '" + fieldName + "' using base class '" + baseClassName + "' and property value class '" + propertyValueClass + "'");
116                 }
117                 ObjectUtils.setObjectProperty(targetObject, fieldName, propertyValue);
118             }
119         } catch (Exception e) {
120             // purposefully skip for now
121             // (I wish objectUtils getPropertyValue threw named errors instead of runtime) so I could
122             // selectively skip
123             if (LOG.isDebugEnabled()) {
124                 LOG.debug("couldn't set field '" + fieldName + "' using base class '" + baseClassName + "' due to exception with class name '" + e.getClass().getName() + "'", e);
125             }
126         }
127     }
128 
129     /**
130      * Copies a collection
131      *
132      * @param fieldName              field to copy
133      * @param targetObject           the object of the collection
134      * @param propertyValue          value to copy
135      * @param supplementalUncopyable uncopyable fields
136      * @throws FormatException
137      * @throws IllegalAccessException
138      * @throws InvocationTargetException
139      * @throws NoSuchMethodException
140      */
141     protected static <T extends BusinessObject> void copyCollection(String fieldName, BusinessObject targetObject, Collection<T> sourceList, Map supplementalUncopyable) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
142         Collection listToSet = null;
143 
144         // materialize collections
145         if (ObjectUtils.isNotNull(sourceList)) {
146             ObjectUtils.materializeObjects(sourceList);
147         }
148 
149         // ArrayList requires argument so handle differently than below
150 
151 
152         try {
153             listToSet = sourceList.getClass().newInstance();
154         } catch (Exception e) {
155             if (LOG.isDebugEnabled()) {
156                 LOG.debug("couldn't set class '" + sourceList.getClass() + "' on collection..." + fieldName + " using " + sourceList.getClass());
157             }
158             listToSet = new ArrayList<T>();
159         }
160 
161 
162         for (Iterator iterator = sourceList.iterator(); iterator.hasNext(); ) {
163             BusinessObject sourceCollectionObject = (BusinessObject) iterator.next();
164             if (LOG.isDebugEnabled()) {
165                 LOG.debug("attempting to copy collection member with class '" + sourceCollectionObject.getClass() + "'");
166             }
167             BusinessObject targetCollectionObject = (BusinessObject) createNewObjectFromClass(sourceCollectionObject.getClass());
168             populateFromBaseWithSuper(sourceCollectionObject, targetCollectionObject, supplementalUncopyable, new HashSet<Class>());
169             // BusinessObject targetCollectionObject = (BusinessObject)ObjectUtils.deepCopy((Serializable)sourceCollectionObject);
170             Map pkMap = SpringContext.getBean(PersistenceService.class).getPrimaryKeyFieldValues(targetCollectionObject);
171             Set<String> pkFields = pkMap.keySet();
172             for (String field : pkFields) {
173                 ObjectUtils.setObjectProperty(targetCollectionObject, field, null);
174             }
175             listToSet.add(targetCollectionObject);
176         }
177         ObjectUtils.setObjectProperty(targetObject, fieldName, listToSet);
178     }
179 
180     /**
181      * This method safely creates a object from a class
182      * Convenience method to create new object and throw a runtime exception if it cannot
183      * If the class is an {@link ExternalizableBusinessObject}, this method will determine the interface for the EBO and query the
184      * appropriate module service to create a new instance.
185      *
186      * @param boClass
187      * @return a newInstance() of clazz
188      */
189     protected static Object createNewObjectFromClass(Class clazz) {
190         if (clazz == null) {
191             throw new RuntimeException("BO class was passed in as null");
192         }
193         try {
194             if (clazz.getSuperclass().equals(ExternalizableBusinessObject.class)) {
195                 Class eboInterface = ExternalizableBusinessObjectUtils.determineExternalizableBusinessObjectSubInterface(clazz);
196                 ModuleService moduleService = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(eboInterface);
197                 return moduleService.createNewObjectFromExternalizableClass(eboInterface);
198             } else {
199                 return clazz.newInstance();
200             }
201         } catch (Exception e) {
202             throw new RuntimeException("Error occured while trying to create a new instance for class " + clazz);
203         }
204     }
205 
206     /**
207      * Copies based on a class template it does not copy fields in Known Uncopyable Fields
208      *
209      * @param base   the base class
210      * @param src    source
211      * @param target target
212      */
213     public static void populateFromBaseClass(Class base, BusinessObject src, BusinessObject target) {
214         populateFromBaseClass(base, src, target, new HashMap());
215     }
216 
217     /**
218      * Populates from a base class traversing up the object hierarchy.
219      *
220      * @param sourceObject     object to copy from
221      * @param targetObject     object to copy to
222      * @param supplementalUncopyableFieldNames
223      *                         fields to exclude
224      * @param classesToExclude classes to exclude
225      */
226     public static void populateFromBaseWithSuper(BusinessObject sourceObject, BusinessObject targetObject, Map supplementalUncopyableFieldNames, Set<Class> classesToExclude) {
227         List<Class> classesToCopy = new ArrayList<Class>();
228         Class sourceObjectClass = sourceObject.getClass();
229         classesToCopy.add(sourceObjectClass);
230         while (sourceObjectClass.getSuperclass() != null) {
231             sourceObjectClass = sourceObjectClass.getSuperclass();
232             if (!classesToExclude.contains(sourceObjectClass)) {
233                 classesToCopy.add(sourceObjectClass);
234             }
235         }
236         for (int i = (classesToCopy.size() - 1); i >= 0; i--) {
237             Class temp = classesToCopy.get(i);
238             populateFromBaseClass(temp, sourceObject, targetObject, supplementalUncopyableFieldNames);
239         }
240     }
241 
242     // ***** following changes are to work around an ObjectUtils bug and are copied from ObjectUtils.java
243 
244     /**
245      * Compares a business object with a List of BOs to determine if an object with the same key as the BO exists in the list. If it
246      * does, the item is returned.
247      *
248      * @param controlList - The list of items to check
249      * @param bo          - The BO whose keys we are looking for in the controlList
250      */
251     public static BusinessObject retrieveObjectWithIdentitcalKey(Collection controlList, BusinessObject bo) {
252         BusinessObject returnBo = null;
253 
254         for (Iterator i = controlList.iterator(); i.hasNext(); ) {
255             BusinessObject listBo = (BusinessObject) i.next();
256             if (equalByKeys(listBo, bo)) {
257                 returnBo = listBo;
258             }
259         }
260 
261         return returnBo;
262     }
263 
264     /**
265      * Compares two business objects for equality of type and key values.
266      *
267      * @param bo1
268      * @param bo2
269      * @return boolean indicating whether the two objects are equal.
270      */
271     public static boolean equalByKeys(BusinessObject bo1, BusinessObject bo2) {
272         boolean equal = true;
273 
274         if (bo1 == null && bo2 == null) {
275             equal = true;
276         } else if (bo1 == null || bo2 == null) {
277             equal = false;
278         } else if (!bo1.getClass().getName().equals(bo2.getClass().getName())) {
279             equal = false;
280         } else {
281             Map bo1Keys = SpringContext.getBean(PersistenceService.class).getPrimaryKeyFieldValues(bo1);
282             Map bo2Keys = SpringContext.getBean(PersistenceService.class).getPrimaryKeyFieldValues(bo2);
283             for (Iterator iter = bo1Keys.keySet().iterator(); iter.hasNext(); ) {
284                 String keyName = (String) iter.next();
285                 if (bo1Keys.get(keyName) != null && bo2Keys.get(keyName) != null) {
286                     if (!bo1Keys.get(keyName).toString().equals(bo2Keys.get(keyName).toString())) {
287                         equal = false;
288                     }
289                 } else {
290                     // CHANGE FOR PurapOjbCollectionHelper change if one is null we are likely looking at a new object (sequence) which is definitely
291                     // not equal
292                     equal = false;
293                 }
294             }
295         }
296 
297 
298         return equal;
299     }
300     // ***** END copied from ObjectUtils.java changes
301 }