0001: /*
0002: * Copyright 2005-2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.core.util;
0017:
0018: import java.beans.PropertyDescriptor;
0019: import java.io.ByteArrayInputStream;
0020: import java.io.ByteArrayOutputStream;
0021: import java.io.IOException;
0022: import java.io.ObjectInputStream;
0023: import java.io.ObjectOutputStream;
0024: import java.io.Serializable;
0025: import java.lang.reflect.InvocationTargetException;
0026: import java.security.MessageDigest;
0027: import java.util.Collection;
0028: import java.util.Iterator;
0029: import java.util.List;
0030: import java.util.Map;
0031:
0032: import org.apache.commons.beanutils.NestedNullException;
0033: import org.apache.commons.beanutils.PropertyUtils;
0034: import org.apache.commons.lang.StringUtils;
0035: import org.apache.log4j.Logger;
0036: import org.apache.ojb.broker.core.proxy.ProxyHelper;
0037: import org.kuali.RiceConstants;
0038: import org.kuali.core.bo.BusinessObject;
0039: import org.kuali.core.bo.PersistableBusinessObject;
0040: import org.kuali.core.bo.PersistableBusinessObjectExtension;
0041: import org.kuali.core.service.PersistenceStructureService;
0042: import org.kuali.core.util.cache.CacheException;
0043: import org.kuali.core.web.format.FormatException;
0044: import org.kuali.core.web.format.Formatter;
0045: import org.kuali.core.web.struts.pojo.PojoPropertyUtilsBean;
0046: import org.kuali.rice.KNSServiceLocator;
0047:
0048: /**
0049: * This class contains various Object, Proxy, and serialization utilities.
0050: */
0051: public class ObjectUtils {
0052: private static final Logger LOG = Logger
0053: .getLogger(ObjectUtils.class);
0054:
0055: private ObjectUtils() {
0056: }
0057:
0058: /**
0059: * Uses Serialization mechanism to create a deep copy of the given Object. As a special case, deepCopy of null returns null,
0060: * just to make using this method simpler. For a detailed discussion see:
0061: * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
0062: *
0063: * @param src
0064: * @return deep copy of the given Serializable
0065: */
0066: public static Serializable deepCopy(Serializable src) {
0067: CopiedObject co = deepCopyForCaching(src);
0068: return co.getContent();
0069: }
0070:
0071: /**
0072: * Uses Serialization mechanism to create a deep copy of the given Object, and returns a CacheableObject instance containing the
0073: * deepCopy and its size in bytes. As a special case, deepCopy of null returns a cacheableObject containing null and a size of
0074: * 0, to make using this method simpler. For a detailed discussion see:
0075: * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html
0076: *
0077: * @param src
0078: * @return CopiedObject containing a deep copy of the given Serializable and its size in bytes
0079: */
0080: public static CopiedObject deepCopyForCaching(Serializable src) {
0081: Timer t0 = new Timer("deepCopy");
0082: CopiedObject co = new CopiedObject();
0083:
0084: int copySize = 0;
0085: if (src != null) {
0086: ObjectOutputStream oos = null;
0087: ObjectInputStream ois = null;
0088: try {
0089: ByteArrayOutputStream serializer = new ByteArrayOutputStream();
0090: oos = new ObjectOutputStream(serializer);
0091: oos.writeObject(src);
0092:
0093: co.setSize(serializer.size());
0094:
0095: ByteArrayInputStream deserializer = new ByteArrayInputStream(
0096: serializer.toByteArray());
0097: ois = new ObjectInputStream(deserializer);
0098: Serializable copy = (Serializable) ois.readObject();
0099:
0100: co.setContent(copy);
0101: } catch (IOException e) {
0102: throw new CacheException(
0103: "unable to complete deepCopy from src '"
0104: + src.toString() + "'", e);
0105: } catch (ClassNotFoundException e) {
0106: throw new CacheException(
0107: "unable to complete deepCopy from src '"
0108: + src.toString() + "'", e);
0109: } finally {
0110: try {
0111: if (oos != null) {
0112: oos.close();
0113: }
0114: if (ois != null) {
0115: ois.close();
0116: }
0117: } catch (IOException e) {
0118: // ignoring this IOException, since the streams are going to be abandoned now anyway
0119: }
0120: }
0121: }
0122: t0.log();
0123: return co;
0124: }
0125:
0126: /**
0127: * Converts the object to a byte array using the output stream.
0128: *
0129: * @param object
0130: * @return byte array of the object
0131: */
0132: public static byte[] toByteArray(Object object) throws Exception {
0133: ObjectOutputStream oos = null;
0134: try {
0135: ByteArrayOutputStream bos = new ByteArrayOutputStream(); // A
0136: oos = new ObjectOutputStream(bos); // B
0137: // serialize and pass the object
0138: oos.writeObject(object); // C
0139: // oos.flush(); // D
0140: return bos.toByteArray();
0141: } catch (Exception e) {
0142: LOG.warn("Exception in ObjectUtil = " + e);
0143: throw (e);
0144: } finally {
0145: if (oos != null) {
0146: oos.close();
0147: }
0148: }
0149: }
0150:
0151: /**
0152: * reconsitiutes the object that was converted into a byte array by toByteArray
0153: * @param bytes
0154: * @return
0155: * @throws Exception
0156: */
0157: public static Object fromByteArray(byte[] bytes) throws Exception {
0158: ObjectInputStream ois = null;
0159: try {
0160: ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
0161: ois = new ObjectInputStream(bis);
0162: Object obj = ois.readObject();
0163: return obj;
0164: } catch (Exception e) {
0165: LOG.warn("Exception in ObjectUtil = " + e);
0166: throw (e);
0167: } finally {
0168: if (ois != null) {
0169: ois.close();
0170: }
0171: }
0172: }
0173:
0174: /**
0175: * use MD5 to create a one way hash of an object
0176: *
0177: * @param object
0178: * @return
0179: */
0180: public static String getMD5Hash(Object object) throws Exception {
0181: try {
0182: MessageDigest md = MessageDigest.getInstance("MD5");
0183: md.update(toByteArray(object));
0184: return new String(md.digest());
0185: } catch (Exception e) {
0186: LOG.warn(e);
0187: throw e;
0188: }
0189: }
0190:
0191: /**
0192: * Creates a new instance of a given BusinessObject, copying fields specified in template from the given source BO. For example,
0193: * this can be used to create an AccountChangeDetail based on a particular Account.
0194: *
0195: * @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)
0196: * entry, the value of property V on the source BO will be assigned to the K property of the newly created BO
0197: *
0198: * @see org.kuali.core.maintenance.util.MaintenanceUtils
0199: *
0200: * @throws NoSuchMethodException
0201: * @throws InvocationTargetException
0202: * @throws IllegalAccessException
0203: * @throws FormatException
0204: */
0205:
0206: public static BusinessObject createHybridBusinessObject(
0207: Class businessObjectClass, BusinessObject source,
0208: Map<String, String> template) throws FormatException,
0209: IllegalAccessException, InvocationTargetException,
0210: NoSuchMethodException {
0211: BusinessObject obj = null;
0212: try {
0213: obj = (BusinessObject) businessObjectClass.newInstance();
0214: } catch (InstantiationException e) {
0215: throw new RuntimeException("Cannot instantiate "
0216: + businessObjectClass.getName(), e);
0217: }
0218:
0219: for (String name : template.keySet()) {
0220: String sourcePropertyName = template.get(name);
0221: setObjectProperty(obj, name, easyGetPropertyType(source,
0222: sourcePropertyName), getPropertyValue(source,
0223: sourcePropertyName));
0224: }
0225:
0226: return obj;
0227: }
0228:
0229: /**
0230: * This method simply uses PojoPropertyUtilsBean logic to get the Class of a Class property.
0231: * This method does not have any of the logic needed to obtain the Class of an element of a Collection specified in the DataDictionary.
0232: *
0233: * @param object An instance of the Class of which we're trying to get the property Class.
0234: * @param propertyName The name of the property.
0235:
0236: * @return
0237: *
0238: * @throws IllegalAccessException
0239: * @throws NoSuchMethodException
0240: * @throws InvocationTargetException
0241: */
0242: static public Class easyGetPropertyType(Object object,
0243: String propertyName) throws IllegalAccessException,
0244: NoSuchMethodException, InvocationTargetException {
0245:
0246: // FIXME (laran) This dependence should be inverted. Instead of having a core class
0247: // depend on PojoPropertyUtilsBean, which is in the web layer, the web layer
0248: // should depend downward to the core.
0249: return (new PojoPropertyUtilsBean()).getPropertyType(object,
0250: propertyName);
0251:
0252: }
0253:
0254: /**
0255: * 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
0256: * of an attribute of an element in the collection.
0257: *
0258: * 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
0259: * to look through Collections. This patch is currently under review.
0260: *
0261: * @param object An instance of the Class for which we're trying to get the property type.
0262: * @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.
0263: * TODO: The rules about this dot notation needs to be explained in Confluence using examples.
0264: * @param persistenceStructureService Needed to get the type of elements in a Collection from OJB.
0265: *
0266: * @return Object will be null if any parent property for the given property is null.
0267: */
0268: public static Class getPropertyType(Object object,
0269: String propertyName,
0270: PersistenceStructureService persistenceStructureService) {
0271:
0272: if (object == null || propertyName == null) {
0273: throw new RuntimeException(
0274: "Business object and property name can not be null");
0275: }
0276:
0277: Class propertyType = null;
0278:
0279: try {
0280:
0281: try {
0282:
0283: // Try to simply use the default or simple way of getting the property type.
0284: propertyType = PropertyUtils.getPropertyType(object,
0285: propertyName);
0286:
0287: } catch (IllegalArgumentException ex) {
0288: // swallow the exception, propertyType stays null
0289: } catch (NoSuchMethodException nsme) {
0290: // swallow the exception, propertyType stays null
0291: }
0292:
0293: // if the property type as determined from the object is PersistableBusinessObject,
0294: // then this must be an extension attribute -- attempt to get the property type from the
0295: // persistence structure service
0296: if (propertyType != null
0297: && propertyType
0298: .equals(PersistableBusinessObjectExtension.class)) {
0299: propertyType = persistenceStructureService
0300: .getBusinessObjectAttributeClass(ProxyHelper
0301: .getRealClass(object), propertyName);
0302: }
0303:
0304: // If the easy way didn't work ...
0305: if (null == propertyType && -1 != propertyName.indexOf('.')) {
0306:
0307: if (null == persistenceStructureService) {
0308:
0309: LOG
0310: .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.");
0311:
0312: } else {
0313:
0314: String prePeriod = StringUtils.substringBefore(
0315: propertyName, ".");
0316: String postPeriod = StringUtils.substringAfter(
0317: propertyName, ".");
0318:
0319: Class prePeriodClass = getPropertyType(object,
0320: prePeriod, persistenceStructureService);
0321: Object prePeriodClassInstance = prePeriodClass
0322: .newInstance();
0323: propertyType = getPropertyType(
0324: prePeriodClassInstance, postPeriod,
0325: persistenceStructureService);
0326:
0327: }
0328:
0329: } else if (Collection.class.isAssignableFrom(propertyType)) {
0330:
0331: Map<String, Class> map = persistenceStructureService
0332: .listCollectionObjectTypes(object.getClass());
0333: propertyType = map.get(propertyName);
0334:
0335: }
0336:
0337: } catch (Exception e) {
0338:
0339: LOG.debug("unable to get property type for " + propertyName
0340: + " " + e.getMessage());
0341: // continue and return null for propertyType
0342:
0343: }
0344:
0345: return propertyType;
0346: }
0347:
0348: /**
0349: * Returns the value of the property in the object.
0350: *
0351: * @param businessObject
0352: * @param propertyName
0353: *
0354: * @return Object will be null if any parent property for the given property is null.
0355: */
0356: public static Object getPropertyValue(Object businessObject,
0357: String propertyName) {
0358: if (businessObject == null || propertyName == null) {
0359: throw new RuntimeException(
0360: "Business object and property name can not be null");
0361: }
0362:
0363: Object propertyValue = null;
0364: try {
0365: propertyValue = PropertyUtils.getProperty(businessObject,
0366: propertyName);
0367: } catch (NestedNullException e) {
0368: // continue and return null for propertyValue
0369: } catch (IllegalAccessException e1) {
0370: LOG.error("error getting property value for "
0371: + propertyName + " " + e1.getMessage());
0372: throw new RuntimeException(
0373: "error getting property value for " + propertyName
0374: + " " + e1.getMessage());
0375: } catch (InvocationTargetException e1) {
0376: // continue and return null for propertyValue
0377: } catch (NoSuchMethodException e1) {
0378: LOG.error("error getting property value for "
0379: + propertyName + " " + e1.getMessage());
0380: throw new RuntimeException(
0381: "error getting property value for " + propertyName
0382: + " " + e1.getMessage());
0383: }
0384:
0385: return propertyValue;
0386: }
0387:
0388: /**
0389: * Sets the property of an object with the given value. Converts using the formatter of the type for the property.
0390: * Note: propertyType does not need passed, is found by util method.
0391: */
0392: public static void setObjectProperty(Object bo,
0393: String propertyName, Object propertyValue)
0394: throws FormatException, IllegalAccessException,
0395: InvocationTargetException, NoSuchMethodException {
0396: Class propertyType = easyGetPropertyType(bo, propertyName);
0397: setObjectProperty(bo, propertyName, propertyType, propertyValue);
0398: }
0399:
0400: /**
0401: * Sets the property of an object with the given value. Converts using the formatter of the given type if one is found.
0402: *
0403: * @param bo
0404: * @param propertyName
0405: * @param propertyType
0406: * @param propertyValue
0407: *
0408: * @throws NoSuchMethodException
0409: * @throws InvocationTargetException
0410: * @throws IllegalAccessException
0411: */
0412: public static void setObjectProperty(Object bo,
0413: String propertyName, Class propertyType,
0414: Object propertyValue) throws FormatException,
0415: IllegalAccessException, InvocationTargetException,
0416: NoSuchMethodException {
0417: // reformat propertyValue, if necessary
0418: boolean reformat = false;
0419: if (propertyValue != null
0420: && propertyType.isAssignableFrom(String.class)) {
0421: // always reformat if the destination is a String
0422: reformat = true;
0423: } else if (propertyValue != null
0424: && !propertyType.isAssignableFrom(propertyValue
0425: .getClass())) {
0426: // otherwise, only reformat if the propertyValue can't be assigned into the property
0427: reformat = true;
0428: }
0429:
0430: // attempting to set boolean fields to null throws an exception, set to false instead
0431: if (boolean.class.isAssignableFrom(propertyType)
0432: && propertyValue == null) {
0433: propertyValue = false;
0434: }
0435:
0436: if (reformat && Formatter.findFormatter(propertyType) != null) {
0437: Formatter formatter = Formatter.getFormatter(propertyType);
0438: LOG.debug("reformatting propertyValue using Formatter "
0439: + formatter.getClass().getName());
0440: propertyValue = formatter
0441: .convertFromPresentationFormat(propertyValue);
0442: }
0443:
0444: // set property in the object
0445: (new PojoPropertyUtilsBean()).setNestedProperty(bo,
0446: propertyName, propertyValue);
0447: }
0448:
0449: /**
0450: * Sets the property of an object with the given value. Converts using the given formatter, if it isn't null.
0451: *
0452: * @param formatter
0453: * @param bo
0454: * @param propertyName
0455: * @param type
0456: * @param propertyValue
0457: *
0458: * @throws NoSuchMethodException
0459: * @throws InvocationTargetException
0460: * @throws IllegalAccessException
0461: */
0462: public static void setObjectProperty(Formatter formatter,
0463: Object bo, String propertyName, Class type,
0464: Object propertyValue) throws FormatException,
0465: IllegalAccessException, InvocationTargetException,
0466: NoSuchMethodException {
0467:
0468: // convert value using formatter for type
0469: if (formatter != null) {
0470: propertyValue = formatter
0471: .convertFromPresentationFormat(propertyValue);
0472: }
0473:
0474: // set property in the object
0475: PropertyUtils
0476: .setNestedProperty(bo, propertyName, propertyValue);
0477: }
0478:
0479: /**
0480: * Recursive; sets all occurences of the property in the object, its nested objects and its object lists with the given value.
0481: *
0482: * @param bo
0483: * @param propertyName
0484: * @param type
0485: * @param propertyValue
0486: *
0487: * @throws NoSuchMethodException
0488: * @throws InvocationTargetException
0489: * @throws IllegalAccessException
0490: */
0491: public static void setObjectPropertyDeep(Object bo,
0492: String propertyName, Class type, Object propertyValue)
0493: throws FormatException, IllegalAccessException,
0494: InvocationTargetException, NoSuchMethodException {
0495:
0496: // Base return cases to avoid null pointers & infinite loops
0497: if (isNull(bo)
0498: || !PropertyUtils.isReadable(bo, propertyName)
0499: || (propertyValue != null && propertyValue
0500: .equals(getPropertyValue(bo, propertyName)))
0501: || (type != null && !type.equals(easyGetPropertyType(
0502: bo, propertyName)))) {
0503: return;
0504: }
0505:
0506: // need to materialize the updateable collections before resetting the property, because it may be used in the retrieval
0507: materializeUpdateableCollections(bo);
0508:
0509: // Set the property in the BO
0510: setObjectProperty(bo, propertyName, type, propertyValue);
0511:
0512: // Now drill down and check nested BOs and BO lists
0513: PropertyDescriptor[] propertyDescriptors = PropertyUtils
0514: .getPropertyDescriptors(bo.getClass());
0515: for (int i = 0; i < propertyDescriptors.length; i++) {
0516:
0517: PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
0518:
0519: // Business Objects
0520: if (propertyDescriptor.getPropertyType() != null
0521: && (BusinessObject.class)
0522: .isAssignableFrom(propertyDescriptor
0523: .getPropertyType())
0524: && PropertyUtils.isReadable(bo, propertyDescriptor
0525: .getName())) {
0526: BusinessObject nestedBo = (BusinessObject) getPropertyValue(
0527: bo, propertyDescriptor.getName());
0528: setObjectPropertyDeep(nestedBo, propertyName, type,
0529: propertyValue);
0530: }
0531:
0532: // Lists
0533: else if (propertyDescriptor.getPropertyType() != null
0534: && (List.class).isAssignableFrom(propertyDescriptor
0535: .getPropertyType())
0536: && getPropertyValue(bo, propertyDescriptor
0537: .getName()) != null) {
0538:
0539: List propertyList = (List) getPropertyValue(bo,
0540: propertyDescriptor.getName());
0541: for (Object listedBo : propertyList) {
0542: if (listedBo != null
0543: && listedBo instanceof BusinessObject) {
0544: setObjectPropertyDeep(listedBo, propertyName,
0545: type, propertyValue);
0546: }
0547: } // end for
0548: }
0549: } // end for
0550: }
0551:
0552: /*
0553: * 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.
0554: */
0555: public static void setObjectPropertyDeep(Object bo,
0556: String propertyName, Class type, Object propertyValue,
0557: int depth) throws FormatException, IllegalAccessException,
0558: InvocationTargetException, NoSuchMethodException {
0559: // Base return cases to avoid null pointers & infinite loops
0560: if (depth == 0 || isNull(bo)
0561: || !PropertyUtils.isReadable(bo, propertyName)) {
0562: return;
0563: }
0564:
0565: // need to materialize the updateable collections before resetting the property, because it may be used in the retrieval
0566: materializeUpdateableCollections(bo);
0567:
0568: // Set the property in the BO
0569: setObjectProperty(bo, propertyName, type, propertyValue);
0570:
0571: // Now drill down and check nested BOs and BO lists
0572: PropertyDescriptor[] propertyDescriptors = PropertyUtils
0573: .getPropertyDescriptors(bo.getClass());
0574: for (int i = 0; i < propertyDescriptors.length; i++) {
0575: PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
0576:
0577: // Business Objects
0578: if (propertyDescriptor.getPropertyType() != null
0579: && (BusinessObject.class)
0580: .isAssignableFrom(propertyDescriptor
0581: .getPropertyType())
0582: && PropertyUtils.isReadable(bo, propertyDescriptor
0583: .getName())) {
0584: BusinessObject nestedBo = (BusinessObject) getPropertyValue(
0585: bo, propertyDescriptor.getName());
0586: setObjectPropertyDeep(nestedBo, propertyName, type,
0587: propertyValue, depth - 1);
0588: }
0589:
0590: // Lists
0591: else if (propertyDescriptor.getPropertyType() != null
0592: && (List.class).isAssignableFrom(propertyDescriptor
0593: .getPropertyType())
0594: && getPropertyValue(bo, propertyDescriptor
0595: .getName()) != null) {
0596:
0597: List propertyList = (List) getPropertyValue(bo,
0598: propertyDescriptor.getName());
0599: for (Object listedBo : propertyList) {
0600: if (listedBo != null
0601: && listedBo instanceof BusinessObject) {
0602: setObjectPropertyDeep(listedBo, propertyName,
0603: type, propertyValue, depth - 1);
0604: }
0605: } // end for
0606: }
0607: } // end for
0608: }
0609:
0610: /**
0611: *
0612: * This method checks for updateable collections on the business object provided and materializes the corresponding collection proxies
0613: *
0614: * @param bo The business object for which you want unpdateable, proxied collections materialized
0615: * @throws FormatException
0616: * @throws IllegalAccessException
0617: * @throws InvocationTargetException
0618: * @throws NoSuchMethodException
0619: */
0620: public static void materializeUpdateableCollections(Object bo)
0621: throws FormatException, IllegalAccessException,
0622: InvocationTargetException, NoSuchMethodException {
0623: if (isNotNull(bo)) {
0624: PropertyDescriptor[] propertyDescriptors = PropertyUtils
0625: .getPropertyDescriptors(bo.getClass());
0626: for (int i = 0; i < propertyDescriptors.length; i++) {
0627: if (KNSServiceLocator.getPersistenceStructureService()
0628: .hasCollection(bo.getClass(),
0629: propertyDescriptors[i].getName())
0630: && KNSServiceLocator
0631: .getPersistenceStructureService()
0632: .isCollectionUpdatable(
0633: bo.getClass(),
0634: propertyDescriptors[i]
0635: .getName())) {
0636: Collection updateableCollection = (Collection) getPropertyValue(
0637: bo, propertyDescriptors[i].getName());
0638: if ((updateableCollection != null)
0639: && ProxyHelper
0640: .isCollectionProxy(updateableCollection)) {
0641: materializeObjects(updateableCollection);
0642: }
0643: }
0644: }
0645: }
0646: }
0647:
0648: /**
0649: * Removes all query characters from a string.
0650: *
0651: * @param string
0652: *
0653: * @return Cleaned string
0654: */
0655: public static String clean(String string) {
0656: for (int i = 0; i < RiceConstants.QUERY_CHARACTERS.length; i++) {
0657: string = StringUtils.replace(string,
0658: RiceConstants.QUERY_CHARACTERS[i],
0659: RiceConstants.EMPTY_STRING);
0660: }
0661: return string;
0662: }
0663:
0664: /**
0665: * Compares two business objects for equality of type and key values.
0666: *
0667: * @param bo1
0668: * @param bo2
0669: *
0670: * @return boolean indicating whether the two objects are equal.
0671: */
0672: public static boolean equalByKeys(BusinessObject bo1,
0673: BusinessObject bo2) {
0674: boolean equal = true;
0675:
0676: if (bo1 == null && bo2 == null) {
0677: equal = true;
0678: } else if (bo1 == null || bo2 == null) {
0679: equal = false;
0680: } else if (!bo1.getClass().getName().equals(
0681: bo2.getClass().getName())) {
0682: equal = false;
0683: } else {
0684: Map bo1Keys = KNSServiceLocator.getPersistenceService()
0685: .getPrimaryKeyFieldValues(bo1);
0686: Map bo2Keys = KNSServiceLocator.getPersistenceService()
0687: .getPrimaryKeyFieldValues(bo2);
0688: for (Iterator iter = bo1Keys.keySet().iterator(); iter
0689: .hasNext();) {
0690: String keyName = (String) iter.next();
0691: if (bo1Keys.get(keyName) != null
0692: && bo2Keys.get(keyName) != null) {
0693: if (!bo1Keys.get(keyName).toString().equals(
0694: bo2Keys.get(keyName).toString())) {
0695: equal = false;
0696: }
0697: }
0698: }
0699: }
0700:
0701: return equal;
0702: }
0703:
0704: /**
0705: * 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.
0706: *
0707: * @param controlList - The list of items to check
0708: * @param bo - The BO whose keys we are looking for in the controlList
0709: *
0710: * @return boolean
0711: */
0712: public static boolean collectionContainsObjectWithIdentitcalKey(
0713: Collection controlList, BusinessObject bo) {
0714: boolean objectExistsInList = false;
0715:
0716: for (Iterator i = controlList.iterator(); i.hasNext();) {
0717: if (equalByKeys((BusinessObject) i.next(), bo)) {
0718: return true;
0719: }
0720: }
0721:
0722: return objectExistsInList;
0723: }
0724:
0725: /**
0726: * Compares a business object with a Collection of BOs to count how many have the same key as the BO.
0727: *
0728: * @param collection - The collection of items to check
0729: * @param bo - The BO whose keys we are looking for in the collection
0730: *
0731: * @return how many have the same keys
0732: */
0733: public static int countObjectsWithIdentitcalKey(
0734: Collection<? extends BusinessObject> collection,
0735: BusinessObject bo) {
0736: // todo: genericize collectionContainsObjectWithIdentitcalKey() to leverage this method?
0737: int n = 0;
0738: for (BusinessObject item : collection) {
0739: if (equalByKeys(item, bo)) {
0740: n++;
0741: }
0742: }
0743: return n;
0744: }
0745:
0746: /**
0747: * 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
0748: * does, the item is removed from the List. This is functionally similar to List.remove() that operates only on Key values.
0749: *
0750: * @param controlList - The list of items to check
0751: * @param bo - The BO whose keys we are looking for in the controlList
0752: */
0753:
0754: public static void removeObjectWithIdentitcalKey(
0755: Collection controlList, BusinessObject bo) {
0756: for (Iterator i = controlList.iterator(); i.hasNext();) {
0757: BusinessObject listBo = (BusinessObject) i.next();
0758: if (equalByKeys(listBo, bo)) {
0759: i.remove();
0760: }
0761: }
0762: }
0763:
0764: /**
0765: * 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
0766: * does, the item is returned.
0767: *
0768: * @param controlList - The list of items to check
0769: * @param bo - The BO whose keys we are looking for in the controlList
0770: */
0771:
0772: public static BusinessObject retrieveObjectWithIdentitcalKey(
0773: Collection controlList, BusinessObject bo) {
0774: BusinessObject returnBo = null;
0775:
0776: for (Iterator i = controlList.iterator(); i.hasNext();) {
0777: BusinessObject listBo = (BusinessObject) i.next();
0778: if (equalByKeys(listBo, bo)) {
0779: returnBo = listBo;
0780: }
0781: }
0782:
0783: return returnBo;
0784: }
0785:
0786: /**
0787: * Determines if a given string could represent a nested attribute of an object.
0788: *
0789: * @param attributeName
0790: *
0791: * @return true if the attribute is nested
0792: */
0793: public static boolean isNestedAttribute(String attributeName) {
0794: boolean isNested = false;
0795:
0796: if (StringUtils.contains(attributeName, ".")) {
0797: isNested = true;
0798: }
0799:
0800: return isNested;
0801: }
0802:
0803: /**
0804: * Returns the prefix of a nested attribute name, or the empty string if the attribute name is not nested.
0805: *
0806: * @param attributeName
0807: *
0808: * @return everything BEFORE the last "." character in attributeName
0809: */
0810: public static String getNestedAttributePrefix(String attributeName) {
0811: String prefix = "";
0812:
0813: if (StringUtils.contains(attributeName, ".")) {
0814: prefix = StringUtils
0815: .substringBeforeLast(attributeName, ".");
0816: }
0817:
0818: return prefix;
0819: }
0820:
0821: /**
0822: * Returns the primitive part of an attribute name string.
0823: *
0824: * @param attributeName
0825: *
0826: * @return everything AFTER the last "." character in attributeName
0827: */
0828: public static String getNestedAttributePrimitive(
0829: String attributeName) {
0830: String primitive = attributeName;
0831:
0832: if (StringUtils.contains(attributeName, ".")) {
0833: primitive = StringUtils.substringAfterLast(attributeName,
0834: ".");
0835: }
0836:
0837: return primitive;
0838: }
0839:
0840: /**
0841: * 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
0842: * to use on a proxy (materialized or non-materialized) or on a non-proxy (ie, regular object). Note that this will force a
0843: * materialization of the proxy if the object is a proxy and unmaterialized.
0844: *
0845: * @param object - any object, proxied or not, materialized or not
0846: *
0847: * @return true if the object (or underlying materialized object) is null, false otherwise
0848: */
0849: public static boolean isNull(Object object) {
0850:
0851: // regardless, if its null, then its null
0852: if (object == null) {
0853: return true;
0854: }
0855:
0856: // only try to materialize the object to see if its null if this is a
0857: // proxy object
0858: if (ProxyHelper.isProxy(object)
0859: || ProxyHelper.isCollectionProxy(object)) {
0860: if (ProxyHelper.getRealObject(object) == null) {
0861: return true;
0862: }
0863: }
0864:
0865: return false;
0866: }
0867:
0868: /**
0869: * 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
0870: * safe to use on a proxy (materialized or non-materialized) or on a non-proxy (ie, regular object). Note that this will force a
0871: * materialization of the proxy if the object is a proxy and unmaterialized.
0872: *
0873: * @param object - any object, proxied or not, materialized or not
0874: *
0875: * @return true if the object (or underlying materialized object) is not null, true if its null
0876: */
0877: public static boolean isNotNull(Object object) {
0878: return !ObjectUtils.isNull(object);
0879: }
0880:
0881: /**
0882: * This method runs the ObjectUtils.isNotNull() method for each item in a list of BOs. ObjectUtils.isNotNull() will materialize
0883: * the objects if they are currently OJB proxies.
0884: *
0885: * @param possiblyProxiedObjects - a Collection of objects that may be proxies
0886: */
0887: public static void materializeObjects(
0888: Collection possiblyProxiedObjects) {
0889: for (Iterator i = possiblyProxiedObjects.iterator(); i
0890: .hasNext();) {
0891: ObjectUtils.isNotNull(i.next());
0892: }
0893: }
0894:
0895: /**
0896: * This method attempts to materialize all of the proxied reference objects (ie, sub-objects) hanging off the passed-in BO
0897: * object. It will do it down to the specified depth. An IllegalArgumentException will be thrown if the bo object passed in is
0898: * itself a non-materialized proxy object. If the bo passed in has no proxied sub-objects, then the object will not be modified,
0899: * and no errors will be thrown. WARNING: Be careful using depth any greater than 2. The number of DB hits, time, and memory
0900: * consumed grows exponentially with each additional increment to depth. Make sure you really need that depth before doing so.
0901: *
0902: * @param bo A valid, populated BusinessObject containing (possibly) proxied sub-objects. This object will be modified in place.
0903: * @param depth int Value 0-5 indicating how deep to recurse the materialization. If a zero (0) is passed in, then no work will
0904: * be done.
0905: */
0906: public static void materializeSubObjectsToDepth(
0907: PersistableBusinessObject bo, int depth) {
0908: if (bo == null) {
0909: throw new IllegalArgumentException(
0910: "The bo passed in was null.");
0911: }
0912: if (depth < 0 || depth > 5) {
0913: throw new IllegalArgumentException(
0914: "The depth passed in was out of bounds. Only values "
0915: + "between 0 and 5, inclusively, are allowed.");
0916: }
0917:
0918: // if depth is zero, then we're done recursing and can just exit
0919: if (depth == 0) {
0920: return;
0921: }
0922:
0923: // deal with the possibility that the bo passed in (ie, the parent object) is an un-materialized proxy
0924: if (ProxyHelper.isProxy(bo)) {
0925: if (!ProxyHelper.isMaterialized(bo)) {
0926: throw new IllegalArgumentException(
0927: "The bo passed in is an un-materialized proxy, and cannot be used.");
0928: }
0929: }
0930:
0931: // get the list of reference objects hanging off the parent BO
0932: Map<String, Class> references = KNSServiceLocator
0933: .getPersistenceStructureService()
0934: .listReferenceObjectFields(bo);
0935:
0936: // initialize our in-loop objects
0937: String referenceName = "";
0938: Class referenceClass = null;
0939: Object referenceValue = null;
0940: Object realReferenceValue = null;
0941:
0942: // for each reference object on the parent bo
0943: for (Iterator iter = references.keySet().iterator(); iter
0944: .hasNext();) {
0945: referenceName = (String) iter.next();
0946: referenceClass = references.get(referenceName);
0947:
0948: // if its a proxy, replace it with a non-proxy
0949: referenceValue = getPropertyValue(bo, referenceName);
0950: if (referenceValue != null) {
0951: if (ProxyHelper.isProxy(referenceValue)) {
0952: realReferenceValue = ProxyHelper
0953: .getRealObject(referenceValue);
0954: if (realReferenceValue != null) {
0955: try {
0956: setObjectProperty(bo, referenceName,
0957: referenceClass, realReferenceValue);
0958: } catch (FormatException e) {
0959: throw new RuntimeException(
0960: "FormatException: could not set the property '"
0961: + referenceName + "'.", e);
0962: } catch (IllegalAccessException e) {
0963: throw new RuntimeException(
0964: "IllegalAccessException: could not set the property '"
0965: + referenceName + "'.", e);
0966: } catch (InvocationTargetException e) {
0967: throw new RuntimeException(
0968: "InvocationTargetException: could not set the property '"
0969: + referenceName + "'.", e);
0970: } catch (NoSuchMethodException e) {
0971: throw new RuntimeException(
0972: "NoSuchMethodException: could not set the property '"
0973: + referenceName + "'.", e);
0974: }
0975: }
0976: }
0977:
0978: // recurse down through this reference object
0979: if (realReferenceValue instanceof PersistableBusinessObject
0980: && depth > 1) {
0981: materializeSubObjectsToDepth(
0982: (PersistableBusinessObject) realReferenceValue,
0983: depth - 1);
0984: }
0985: }
0986:
0987: }
0988: }
0989:
0990: /**
0991: * This method attempts to materialize all of the proxied reference objects (ie, sub-objects) hanging off the passed-in BO
0992: * object. It will do it just three levels down. In other words, it will only materialize the objects that are direct members of
0993: * the bo, objects that are direct members of those bos, that one more time, and no further down. An IllegalArgumentException
0994: * will be thrown if the bo object passed in is itself a non-materialized proxy object. If the bo passed in has no proxied
0995: * sub-objects, then the object will not be modified, and no errors will be thrown.
0996: *
0997: * @param bo A valid, populated BusinessObject containing (possibly) proxied sub-objects. This object will be modified in place.
0998: */
0999: public static void materializeAllSubObjects(
1000: PersistableBusinessObject bo) {
1001: materializeSubObjectsToDepth(bo, 3);
1002: }
1003:
1004: /**
1005: * This method safely extracts either simple values OR nested values. For example, if the bo is SubAccount, and the fieldName is
1006: * a21SubAccount.subAccountTypeCode, this thing makes sure it gets the value off the very end attribute, no matter how deeply
1007: * nested it is. The code would be slightly simpler if this was done recursively, but this is safer, and consumes a constant
1008: * amount of memory, no matter how deeply nested it goes.
1009: *
1010: * @param bo
1011: * @param fieldName
1012: *
1013: * @return The field value if it exists. If it doesnt, and the name is invalid, and
1014: */
1015: public static Object getNestedValue(BusinessObject bo,
1016: String fieldName) {
1017:
1018: if (bo == null) {
1019: throw new IllegalArgumentException(
1020: "The bo passed in was null.");
1021: }
1022: if (StringUtils.isBlank(fieldName)) {
1023: throw new IllegalArgumentException(
1024: "The fieldName passed in was blank.");
1025: }
1026:
1027: // okay, this section of code is to handle sub-object values, like
1028: // SubAccount.a21SubAccount.subAccountTypeCode. it basically walks
1029: // through the period-delimited list of names, and ends up with the
1030: // final value.
1031: String[] fieldNameParts = fieldName.split("\\.");
1032: Object currentObject = null;
1033: Object priorObject = bo;
1034: for (int i = 0; i < fieldNameParts.length; i++) {
1035: String fieldNamePart = fieldNameParts[i];
1036:
1037: try {
1038: if (fieldNamePart.indexOf("]") > 0) {
1039: currentObject = PropertyUtils.getIndexedProperty(
1040: priorObject, fieldNamePart);
1041: } else {
1042: currentObject = PropertyUtils.getSimpleProperty(
1043: priorObject, fieldNamePart);
1044: }
1045: } catch (IllegalAccessException e) {
1046: throw new RuntimeException(
1047: "Caller does not have access to the property accessor method.",
1048: e);
1049: } catch (InvocationTargetException e) {
1050: throw new RuntimeException(
1051: "Property accessor method threw an exception.",
1052: e);
1053: } catch (NoSuchMethodException e) {
1054: throw new RuntimeException(
1055: "The accessor method requested for this property cannot be found.",
1056: e);
1057: }
1058:
1059: // materialize the proxy, if it is a proxy
1060: if (ProxyHelper.isProxy(currentObject)) {
1061: currentObject = ProxyHelper
1062: .getRealObject(currentObject);
1063: }
1064:
1065: // if a node or the leaf is null, then we're done, there's no need to
1066: // continue accessing null things
1067: if (currentObject == null) {
1068: return currentObject;
1069: }
1070:
1071: priorObject = currentObject;
1072: }
1073: return currentObject;
1074: }
1075:
1076: /**
1077: * Returns the equality of the two given objects, automatically handling when one or both of the objects is null.
1078: *
1079: * @param obj1
1080: * @param obj2
1081: *
1082: * @return true if both objects are null or both are equal
1083: */
1084: public static boolean nullSafeEquals(Object obj1, Object obj2) {
1085: if (obj1 != null && obj2 != null) {
1086: return obj1.equals(obj2);
1087: } else {
1088: return (obj1 == obj2);
1089: }
1090: }
1091:
1092: /**
1093: *
1094: * This method safely creates a object from a class
1095: * Convenience method to create new object and throw a runtime exception if it cannot
1096: *
1097: * @param boClass
1098: *
1099: * @return a newInstance() of clazz
1100: */
1101: public static Object createNewObjectFromClass(Class clazz) {
1102: Object obj = null;
1103: try {
1104: obj = clazz.newInstance();
1105: } catch (InstantiationException ite) {
1106: LOG.info(ite);
1107: throw new RuntimeException(
1108: "ObjectUtils had a problem creating a new object of "
1109: + clazz.getName(), ite);
1110: } catch (IllegalAccessException iae) {
1111: LOG.info(iae);
1112: throw new RuntimeException(
1113: "ObjectUtils had a problem creating a new object of "
1114: + clazz.getName(), iae);
1115: }
1116: return obj;
1117: }
1118:
1119: }
|