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