Java tutorial
/* * Kuali Coeus, a comprehensive research administration system for higher education. * * Copyright 2005-2015 Kuali, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kra.award.awardhierarchy.sync.helpers; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kuali.kra.award.awardhierarchy.sync.*; import org.kuali.kra.award.awardhierarchy.sync.service.AwardSyncServiceImpl; import org.kuali.kra.award.awardhierarchy.sync.service.AwardSyncUtilityService; import org.kuali.kra.award.home.Award; import org.kuali.rice.krad.bo.PersistableBusinessObject; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; /** * Base class for award hierarchy sync data access objects. */ public abstract class AwardSyncHelperBase implements AwardSyncHelper { protected final Log LOG = LogFactory.getLog(AwardSyncServiceImpl.class); /** * Delimiter used between fields for the object data description */ protected final String DELIMITER = " : "; private AwardSyncUtilityService awardSyncUtilityService; @Override public AwardSyncChange createAwardSyncChange(AwardSyncType syncType, PersistableBusinessObject syncableObject, String awardAttrName, String boAttrName) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException { AwardSyncChange syncChange = new AwardSyncChange(); syncChange.setClassName(syncableObject.getClass().getCanonicalName()); syncChange.setAttrName(awardAttrName); syncChange.setSyncType(syncType.getSyncValue()); syncChange.setObjectDesc(getObjectDesc(syncableObject, boAttrName)); syncChange.setDataDesc(getDataDesc(syncableObject, boAttrName)); syncChange.setXmlExport(buildXmlExport(syncableObject, boAttrName)); return syncChange; } /** * Returns the object description for this object type. * @param syncableObject * @param attrName * @return */ protected abstract String getObjectDesc(PersistableBusinessObject syncableObject, String attrName); /** * Returns the data description for this object type. * @param syncableObject * @param attrName * @return */ protected abstract String getDataDesc(PersistableBusinessObject syncableObject, String attrName); @Override public AwardSyncXmlExport buildXmlExport(PersistableBusinessObject syncable, String attrName) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException { return buildXmlExport(syncable, attrName, null, null, true); } /** * Using annotations set on the class and reflection creates a map based hierarchy of objects * from the award attribute down to the BO this is initially called on. * * @param syncable * @param attrName * @param childAttr * @param childExport * @param walkParents * @return * @throws NoSuchFieldException * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") protected AwardSyncXmlExport buildXmlExport(PersistableBusinessObject syncable, String attrName, String childAttr, AwardSyncXmlExport childExport, boolean walkParents) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException { Class clazz = syncable.getClass(); AwardSyncXmlExport parentExport = null; AwardSyncXmlExport xmlExport = new AwardSyncXmlExport(); xmlExport.setClassName(syncable.getClass().getName()); xmlExport.setKeys(new HashMap<String, Object>()); xmlExport.setValues(new HashMap<String, Object>()); //if this is the parent of another BO we will get the childAttr and the childExport //and to make the export tree complete we need to add the attr and the export to our values if (childExport != null) { xmlExport.getValues().put(childAttr, childExport); } //if we should walk to parent nodes then the syncable BO is part of the //key to the original object because we are either on the actual synced BO or //walking up the object graph. if (walkParents) { xmlExport.setPartOfObjectKey(true); } //loop through all properties on the bean and if the property is annotated as a syncable property //then export that property for (PropertyDescriptor propDescriptor : PropertyUtils.getPropertyDescriptors(clazz)) { Field propertyField = findField(clazz, propDescriptor.getName()); if (propertyField != null) { AwardSyncableProperty annotation = propertyField.getAnnotation(AwardSyncableProperty.class); if (annotation != null) { Object propertyValue = propDescriptor.getReadMethod().invoke(syncable); if (walkParents && annotation.parent()) { parentExport = buildXmlExport((PersistableBusinessObject) propertyValue, null, annotation.parentProperty(), xmlExport, true); parentExport.setAddIfNotFound(false); } else if (!annotation.parent()) { Object mapValue = getValueForExport(propertyValue); if (annotation.key()) { xmlExport.getKeys().put(propDescriptor.getName(), mapValue); } else if ((attrName == null || StringUtils.equals(attrName, propDescriptor.getName())) && childAttr == null) { //if we don't have a specific attribute or this is the attribute we want to save //and this is not a parent as we do not want to save parent values xmlExport.getValues().put(propDescriptor.getName(), mapValue); } } } } } if (parentExport != null) { return parentExport; } else { return xmlExport; } } /** * Returns a value for export based on the property type. If it is a collection, the * return will be a list of {@link AwardSyncXmlExport}. If the value is a simple * business object it will return an AwardSyncXmlExport. Otherwise the return will * be the value itself. * @param propertyValue * @return * @throws NoSuchFieldException * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") protected Object getValueForExport(Object propertyValue) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException { Object mapValue = null; if (propertyValue instanceof Collection) { mapValue = buildXmlExport((Collection) propertyValue); } else if (propertyValue instanceof PersistableBusinessObject) { mapValue = buildXmlExport((PersistableBusinessObject) propertyValue, null, null, null, false); } else { mapValue = propertyValue; } return mapValue; } /** * Loops through a collection of business objects to return a list of {@link AwardSyncXmlExport}. * @param syncables * @return * @throws NoSuchFieldException * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") protected List<AwardSyncXmlExport> buildXmlExport(Collection syncables) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException { List<AwardSyncXmlExport> xmls = new ArrayList<AwardSyncXmlExport>(); for (Object object : (Collection) syncables) { if (object instanceof PersistableBusinessObject) { xmls.add(buildXmlExport((PersistableBusinessObject) object, null, null, null, false)); } } return xmls; } /** * Recursively ascends the class tree to find the property field. * This is done because you cannot get the field for a private * property unless called on the defining class itself. * @param clazz * @param propertyName * @return */ @SuppressWarnings("unchecked") protected Field findField(Class clazz, String propertyName) { if (clazz != null) { Field retVal = null; try { retVal = clazz.getDeclaredField(propertyName); } catch (SecurityException e) { //exceptions are likely as we walk the class tree to find the //property. As the child class won't necessarily have the property. LOG.info("Security exception when trying to find " + propertyName + " for class" + clazz.getSimpleName(), e); } catch (NoSuchFieldException e) { //this is so likely given we are walking a tree of objects to find the property, do nothing } if (retVal != null) { return retVal; } else { return findField(clazz.getSuperclass(), propertyName); } } else { return null; } } @Override public void applySyncChange(Award award, AwardSyncChange change) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException, AwardSyncException { applySyncChange(award, change, change.getAttrName(), change.getXmlExport()); } /** * Applies the xmlExport keys and values to the object. This will recursively descend the tree of objects defined in the * xmlExport by calling {@link #setValuesOnSyncable}. * @param object * @param change * @param attrName * @param xmlExport * @throws NoSuchFieldException * @throws IllegalAccessException * @throws InvocationTargetException * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InstantiationException */ @SuppressWarnings("unchecked") protected void applySyncChange(PersistableBusinessObject object, AwardSyncChange change, String attrName, AwardSyncXmlExport xmlExport) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException { Object propertyValue = getPropertyValue(object, attrName); if (propertyValue instanceof Collection) { Collection items = (Collection) propertyValue; PersistableBusinessObject matchedBo = getAwardSyncUtilityService().findMatchingBo(items, xmlExport.getKeys()); Map<String, Object> values = xmlExport.getValues(); if (StringUtils.equals(change.getSyncType(), AwardSyncType.ADD_SYNC.getSyncValue())) { if (matchedBo == null) { if (xmlExport.isAddIfNotFound()) { matchedBo = createNewItem(change, xmlExport); items.add(matchedBo); } } else { setValuesOnSyncable(matchedBo, values, change); } } else if (StringUtils.equals(change.getSyncType(), AwardSyncType.DELETE_SYNC.getSyncValue())) { items.remove(matchedBo); } } else if (StringUtils.equals(change.getSyncType(), AwardSyncType.ADD_SYNC.getSyncValue())) { if (propertyValue != null && !getAwardSyncUtilityService() .doKeyValuesMatch((PersistableBusinessObject) propertyValue, xmlExport.getKeys())) { if (xmlExport.isAddIfNotFound()) { Object newObject = createNewItem(change, xmlExport); Method setter = getPropertyDescriptor(object, attrName).getWriteMethod(); setter.invoke(object, newObject); } } else { setValuesOnSyncable((PersistableBusinessObject) propertyValue, xmlExport.getValues(), change); } } else if (propertyValue != null && getAwardSyncUtilityService() .doKeyValuesMatch((PersistableBusinessObject) propertyValue, xmlExport.getKeys())) { Method setter = getPropertyDescriptor(object, attrName).getWriteMethod(); setter.invoke(object, (Object[]) null); } } /** * Creates all items defined by xmlExports and adds them to the attribute. * @param object * @param change * @param attrName * @param xmlExports * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException * @throws NoSuchFieldException */ protected void applySyncChange(PersistableBusinessObject object, AwardSyncChange change, String attrName, Collection<AwardSyncXmlExport> xmlExports) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { Collection<Object> newCollection = new ArrayList<Object>(); for (AwardSyncXmlExport xmlExport : xmlExports) { if (xmlExport.isAddIfNotFound()) { Object newObject = createNewItem(change, xmlExport); newCollection.add(newObject); } } Method setter = getPropertyDescriptor(object, attrName).getWriteMethod(); setter.invoke(object, newCollection); } /** * Creates a new item based on the key values and values passed in. * @param change * @param xmlExport * @return * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException * @throws NoSuchFieldException */ @SuppressWarnings("unchecked") protected PersistableBusinessObject createNewItem(AwardSyncChange change, AwardSyncXmlExport xmlExport) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { String className = xmlExport.getClassName(); Map<String, Object> keyValues = xmlExport.getKeys(); Map<String, Object> values = xmlExport.getValues(); Class subClazz = Class.forName(className); Constructor constructor = subClazz.getConstructor((Class[]) null); PersistableBusinessObject newObject = (PersistableBusinessObject) constructor.newInstance((Object[]) null); setValuesOnSyncable(newObject, keyValues, change); setValuesOnSyncable(newObject, values, change); return newObject; } /** * Recursively sets values on this syncable working way down in the object tree found in the values map. * @param syncable * @param values * @param change * @throws NoSuchFieldException * @throws IllegalAccessException * @throws InvocationTargetException * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InstantiationException */ @SuppressWarnings("unchecked") protected void setValuesOnSyncable(PersistableBusinessObject syncable, Map<String, Object> values, AwardSyncChange change) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException { Class clazz = syncable.getClass(); boolean setEntry = false; for (Map.Entry<String, Object> entry : values.entrySet()) { setEntry = false; for (PropertyDescriptor propDescriptor : PropertyUtils.getPropertyDescriptors(clazz)) { if (StringUtils.equals(propDescriptor.getName(), entry.getKey())) { if (entry.getValue() instanceof AwardSyncXmlExport) { AwardSyncXmlExport xmlExport = (AwardSyncXmlExport) entry.getValue(); applySyncChange(syncable, change, entry.getKey(), xmlExport); setEntry = true; } else if (Collection.class.isAssignableFrom(propDescriptor.getPropertyType()) && entry.getValue() instanceof List) { applySyncChange(syncable, change, entry.getKey(), (Collection<AwardSyncXmlExport>) entry.getValue()); setEntry = true; } else { Method setter = propDescriptor.getWriteMethod(); setter.invoke(syncable, entry.getValue()); setEntry = true; } } } if (!setEntry) { throw new NoSuchFieldException(); } } } /** * Finds the bean property descriptor for the names attribute. * @param object * @param attributeName * @return */ @SuppressWarnings("unchecked") protected PropertyDescriptor getPropertyDescriptor(PersistableBusinessObject object, String attributeName) { Class clazz = object.getClass(); for (PropertyDescriptor propDescriptor : PropertyUtils.getPropertyDescriptors(clazz)) { if (StringUtils.equals(propDescriptor.getName(), attributeName)) { return propDescriptor; } } return null; } /** * Finds the property descriptor for the named attribute and returns the value retrieved using the read method * @param object * @param attributeName * @return * @throws IllegalAccessException * @throws InvocationTargetException */ protected Object getPropertyValue(PersistableBusinessObject object, String attributeName) throws IllegalAccessException, InvocationTargetException { for (PropertyDescriptor propDesc : PropertyUtils.getPropertyDescriptors(object.getClass())) { if (StringUtils.equals(propDesc.getName(), attributeName)) { Method getter = propDesc.getReadMethod(); return getter.invoke(object); } } return null; } public AwardSyncUtilityService getAwardSyncUtilityService() { return awardSyncUtilityService; } public void setAwardSyncUtilityService(AwardSyncUtilityService awardSyncUtilityService) { this.awardSyncUtilityService = awardSyncUtilityService; } }