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.maintenance;
0017:
0018: import java.beans.PropertyDescriptor;
0019: import java.io.Serializable;
0020: import java.security.GeneralSecurityException;
0021: import java.util.ArrayList;
0022: import java.util.Collection;
0023: import java.util.HashMap;
0024: import java.util.HashSet;
0025: import java.util.Iterator;
0026: import java.util.List;
0027: import java.util.Map;
0028: import java.util.Set;
0029:
0030: import org.apache.commons.beanutils.PropertyUtils;
0031: import org.apache.commons.lang.StringUtils;
0032: import org.kuali.RiceConstants;
0033: import org.kuali.RicePropertyConstants;
0034: import org.kuali.core.bo.BusinessObject;
0035: import org.kuali.core.bo.BusinessObjectRelationship;
0036: import org.kuali.core.bo.DocumentHeader;
0037: import org.kuali.core.bo.PersistableBusinessObject;
0038: import org.kuali.core.datadictionary.DataDictionaryDefinitionBase;
0039: import org.kuali.core.datadictionary.MaintainableCollectionDefinition;
0040: import org.kuali.core.datadictionary.MaintainableFieldDefinition;
0041: import org.kuali.core.datadictionary.MaintainableItemDefinition;
0042: import org.kuali.core.datadictionary.MaintainableSectionDefinition;
0043: import org.kuali.core.document.MaintenanceDocument;
0044: import org.kuali.core.document.MaintenanceLock;
0045: import org.kuali.core.lookup.LookupUtils;
0046: import org.kuali.core.service.BusinessObjectMetaDataService;
0047: import org.kuali.core.service.DataDictionaryService;
0048: import org.kuali.core.service.EncryptionService;
0049: import org.kuali.core.service.PersistenceStructureService;
0050: import org.kuali.core.util.FieldUtils;
0051: import org.kuali.core.util.GlobalVariables;
0052: import org.kuali.core.util.InactiveRecordsHidingUtils;
0053: import org.kuali.core.util.MaintenanceUtils;
0054: import org.kuali.core.util.ObjectUtils;
0055: import org.kuali.core.web.ui.Section;
0056: import org.kuali.core.web.ui.SectionBridge;
0057: import org.kuali.rice.KNSServiceLocator;
0058:
0059: /**
0060: * Base Maintainable class to hold things common to all maintainables.
0061: */
0062: public class KualiMaintainableImpl implements Maintainable,
0063: Serializable {
0064: private static final long serialVersionUID = 4814145799502207182L;
0065:
0066: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0067: .getLogger(KualiMaintainableImpl.class);
0068:
0069: protected String documentNumber;
0070: protected PersistableBusinessObject businessObject;
0071: protected Class boClass;
0072: protected String maintenanceAction;
0073: protected boolean generateDefaultValues;
0074: protected boolean generateBlankRequiredValues;
0075:
0076: protected Map<String, PersistableBusinessObject> newCollectionLines = new HashMap<String, PersistableBusinessObject>();
0077: protected Map<String, Boolean> inactiveRecordDisplay = new HashMap<String, Boolean>();
0078:
0079: private String docTypeName;
0080:
0081: /**
0082: * Default empty constructor
0083: */
0084: public KualiMaintainableImpl() {
0085: }
0086:
0087: /**
0088: * Constructor which initializes the business object to be maintained.
0089: *
0090: * @param businessObject
0091: */
0092: public KualiMaintainableImpl(
0093: PersistableBusinessObject businessObject) {
0094: this ();
0095: this .businessObject = businessObject;
0096: }
0097:
0098: public void setupNewFromExisting() {
0099:
0100: }
0101:
0102: /**
0103: * This is a hook to allow the document to override the generic document title.
0104: *
0105: * @return String document title
0106: */
0107: public String getDocumentTitle(MaintenanceDocument document) {
0108: //default implementation is to allow MaintenanceDocumentBase to generate the doc title
0109: return "";
0110: }
0111:
0112: /**
0113: * Note: as currently implemented, every key field for a given BusinessObject subclass must have a visible getter.
0114: *
0115: * @return String containing the business object class and key value pairs of the current instance that can be used as a unique
0116: * locking representation.
0117: */
0118: public List<MaintenanceLock> generateMaintenanceLocks() {
0119:
0120: //NOTE: KualiGlobalMaintainableImpl overrides this method and forces all globals to override that, so they each do their own thing
0121:
0122: List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
0123: StringBuffer lockRepresentation = new StringBuffer(boClass
0124: .getName());
0125: lockRepresentation
0126: .append(RiceConstants.Maintenance.AFTER_CLASS_DELIM);
0127:
0128: PersistableBusinessObject bo = getBusinessObject();
0129: List keyFieldNames = KNSServiceLocator
0130: .getMaintenanceDocumentDictionaryService()
0131: .getLockingKeys(getDocumentTypeName());
0132:
0133: for (Iterator i = keyFieldNames.iterator(); i.hasNext();) {
0134: String fieldName = (String) i.next();
0135: Object fieldValue = ObjectUtils.getPropertyValue(bo,
0136: fieldName);
0137: if (fieldValue == null) {
0138: fieldValue = "";
0139: }
0140:
0141: // check if field is a secure
0142: String displayWorkgroup = KNSServiceLocator
0143: .getDataDictionaryService()
0144: .getAttributeDisplayWorkgroup(getBoClass(),
0145: fieldName);
0146: if (StringUtils.isNotBlank(displayWorkgroup)) {
0147: try {
0148: fieldValue = KNSServiceLocator
0149: .getEncryptionService().encrypt(fieldValue);
0150: } catch (GeneralSecurityException e) {
0151: LOG
0152: .error("Unable to encrypt secure field for locking representation "
0153: + e.getMessage());
0154: throw new RuntimeException(
0155: "Unable to encrypt secure field for locking representation "
0156: + e.getMessage());
0157: }
0158: }
0159:
0160: lockRepresentation.append(fieldName);
0161: lockRepresentation
0162: .append(RiceConstants.Maintenance.AFTER_FIELDNAME_DELIM);
0163: lockRepresentation.append(String.valueOf(fieldValue));
0164: if (i.hasNext()) {
0165: lockRepresentation
0166: .append(RiceConstants.Maintenance.AFTER_VALUE_DELIM);
0167: }
0168: }
0169:
0170: MaintenanceLock maintenanceLock = new MaintenanceLock();
0171: maintenanceLock.setDocumentNumber(documentNumber);
0172: maintenanceLock.setLockingRepresentation(lockRepresentation
0173: .toString());
0174: maintenanceLocks.add(maintenanceLock);
0175: return maintenanceLocks;
0176: }
0177:
0178: /**
0179: * @see org.kuali.core.maintenance.Maintainable#populateBusinessObject(java.util.Map)
0180: */
0181: public Map populateBusinessObject(Map fieldValues) {
0182: fieldValues = decryptEncryptedData(fieldValues);
0183: Map newFieldValues = null;
0184: newFieldValues = KNSServiceLocator.getUniversalUserService()
0185: .resolveUserIdentifiersToUniversalIdentifiers(
0186: getBusinessObject(), fieldValues);
0187:
0188: Map cachedValues = FieldUtils.populateBusinessObjectFromMap(
0189: getBusinessObject(), newFieldValues);
0190: KNSServiceLocator.getBusinessObjectDictionaryService()
0191: .performForceUppercase(getBusinessObject());
0192: return cachedValues;
0193: }
0194:
0195: /**
0196: * Special hidden parameters are set on the maintenance jsp starting with a prefix that tells us which fields have been
0197: * encrypted. This field finds the those parameters in the map, whose value gives us the property name that has an encrypted
0198: * value. We then need to decrypt the value in the Map before the business object is populated.
0199: *
0200: * @param fieldValues - possibly with encrypted values
0201: * @return Map fieldValues - with no encrypted values
0202: */
0203: private Map decryptEncryptedData(Map fieldValues) {
0204: try {
0205: for (Iterator iter = fieldValues.keySet().iterator(); iter
0206: .hasNext();) {
0207: String fieldName = (String) iter.next();
0208: String fieldValue = (String) fieldValues.get(fieldName);
0209: if (fieldValue != null
0210: && fieldValue
0211: .endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
0212: String encryptedValue = fieldValue;
0213:
0214: // take of the postfix
0215: encryptedValue = StringUtils.stripEnd(
0216: encryptedValue,
0217: EncryptionService.ENCRYPTION_POST_PREFIX);
0218: String decryptedValue = KNSServiceLocator
0219: .getEncryptionService().decrypt(
0220: encryptedValue);
0221:
0222: fieldValues.put(fieldName, decryptedValue);
0223: }
0224: }
0225: } catch (GeneralSecurityException e) {
0226: throw new RuntimeException(
0227: "Unable to decrypt secure data: " + e.getMessage());
0228: }
0229:
0230: return fieldValues;
0231: }
0232:
0233: /**
0234: * Calls method to get all the core sections for the business object defined in the data dictionary. Then determines if the bo
0235: * has custom attributes, if so builds a custom attribute section and adds to the section list.
0236: *
0237: * @return List of org.kuali.ui.Section objects
0238: */
0239: public List getSections(Maintainable oldMaintainable) {
0240: List<Section> sections = new ArrayList<Section>();
0241: sections.addAll(getCoreSections(oldMaintainable));
0242:
0243: return sections;
0244: }
0245:
0246: /**
0247: * Gets list of maintenance sections built from the data dictionary. If the section contains maintenance fields, construct
0248: * Row/Field UI objects and place under Section UI. If section contains a maintenance collection, call method to build a Section
0249: * UI which contains rows of Container Fields.
0250: *
0251: * @return List of org.kuali.ui.Section objects
0252: */
0253: public List<Section> getCoreSections(Maintainable oldMaintainable) {
0254:
0255: List<Section> sections = new ArrayList<Section>();
0256:
0257: List<MaintainableSectionDefinition> sectionDefinitions = KNSServiceLocator
0258: .getMaintenanceDocumentDictionaryService()
0259: .getMaintainableSections(docTypeName);
0260:
0261: try {
0262: // iterate through section definitions and create Section UI object
0263: for (Iterator iter = sectionDefinitions.iterator(); iter
0264: .hasNext();) {
0265:
0266: MaintainableSectionDefinition maintSectionDef = (MaintainableSectionDefinition) iter
0267: .next();
0268:
0269: List<String> displayedFieldNames = new ArrayList<String>();
0270:
0271: for (Iterator iter2 = maintSectionDef
0272: .getMaintainableItems().iterator(); iter2
0273: .hasNext();) {
0274:
0275: MaintainableItemDefinition item = (MaintainableItemDefinition) iter2
0276: .next();
0277: if (item instanceof MaintainableFieldDefinition) {
0278: displayedFieldNames
0279: .add(((MaintainableFieldDefinition) item)
0280: .getName());
0281: }
0282: }
0283:
0284: Section section = SectionBridge.toSection(
0285: maintSectionDef, getBusinessObject(), this ,
0286: oldMaintainable, getMaintenanceAction(),
0287: isGenerateDefaultValues(),
0288: isGenerateBlankRequiredValues(),
0289: displayedFieldNames);
0290:
0291: // add to section list
0292: sections.add(section);
0293:
0294: }
0295:
0296: } catch (InstantiationException e) {
0297: LOG.error("Unable to create instance of object class"
0298: + e.getMessage());
0299: throw new RuntimeException(
0300: "Unable to create instance of object class"
0301: + e.getMessage());
0302: } catch (IllegalAccessException e) {
0303: LOG.error("Unable to create instance of object class"
0304: + e.getMessage());
0305: throw new RuntimeException(
0306: "Unable to create instance of object class"
0307: + e.getMessage());
0308: }
0309:
0310: return sections;
0311: }
0312:
0313: /**
0314: *
0315: * @see org.kuali.core.maintenance.Maintainable#saveBusinessObject()
0316: */
0317: public void saveBusinessObject() {
0318: KNSServiceLocator.getBusinessObjectService().linkAndSave(
0319: businessObject);
0320: }
0321:
0322: /**
0323: * Retrieves title for maintenance document from data dictionary
0324: */
0325: public String getMaintainableTitle() {
0326: return KNSServiceLocator
0327: .getMaintenanceDocumentDictionaryService()
0328: .getMaintenanceLabel(getDocumentTypeName());
0329: }
0330:
0331: /**
0332: * Retrieves the status of the boNotesEnabled
0333: */
0334: public boolean isBoNotesEnabled() {
0335: return KNSServiceLocator.getBusinessObjectDictionaryService()
0336: .areNotesSupported(this .boClass);
0337: }
0338:
0339: /**
0340: * @see org.kuali.core.maintenance.Maintainable#refresh(java.lang.String, java.util.Map) Impls will be needed if custom action
0341: * is needed on refresh.
0342: */
0343: public void refresh(String refreshCaller, Map fieldValues,
0344: MaintenanceDocument document) {
0345: String referencesToRefresh = (String) fieldValues
0346: .get(RiceConstants.REFERENCES_TO_REFRESH);
0347: refreshReferences(referencesToRefresh);
0348: }
0349:
0350: protected void refreshReferences(String referencesToRefresh) {
0351: PersistenceStructureService persistenceStructureService = KNSServiceLocator
0352: .getPersistenceStructureService();
0353: if (StringUtils.isNotBlank(referencesToRefresh)) {
0354: String[] references = StringUtils.split(
0355: referencesToRefresh,
0356: RiceConstants.REFERENCES_TO_REFRESH_SEPARATOR);
0357: for (String reference : references) {
0358: if (StringUtils.isNotBlank(reference)) {
0359: if (reference.startsWith(RiceConstants.ADD_PREFIX
0360: + ".")) {
0361: // add one for the period
0362: reference = reference
0363: .substring(RiceConstants.ADD_PREFIX
0364: .length() + 1);
0365:
0366: String boToRefreshName = StringUtils
0367: .substringBeforeLast(reference, ".");
0368: String propertyToRefresh = StringUtils
0369: .substringAfterLast(reference, ".");
0370: if (StringUtils.isNotBlank(propertyToRefresh)) {
0371: PersistableBusinessObject addlineBO = getNewCollectionLine(boToRefreshName);
0372: Class addlineBOClass = addlineBO.getClass();
0373: if (LOG.isDebugEnabled()) {
0374: LOG
0375: .debug("Refresh this \"new\"/add object for the collections: "
0376: + reference);
0377: }
0378: if (persistenceStructureService
0379: .hasReference(addlineBOClass,
0380: propertyToRefresh)
0381: || persistenceStructureService
0382: .hasCollection(
0383: addlineBOClass,
0384: propertyToRefresh)) {
0385: addlineBO
0386: .refreshReferenceObject(propertyToRefresh);
0387: } else {
0388: if (KNSServiceLocator
0389: .getDataDictionaryService()
0390: .hasRelationship(
0391: addlineBOClass
0392: .getName(),
0393: propertyToRefresh)) {
0394: // a DD mapping, try to go straight to the object and refresh it there
0395: Object possibleBO = ObjectUtils
0396: .getPropertyValue(
0397: addlineBO,
0398: propertyToRefresh);
0399: if (possibleBO != null
0400: && possibleBO instanceof PersistableBusinessObject) {
0401: ((PersistableBusinessObject) possibleBO)
0402: .refresh();
0403: }
0404: }
0405: }
0406: } else {
0407: LOG
0408: .error("Error: unable to refresh this \"new\"/add object for the collections: "
0409: + reference);
0410: }
0411: } else if (ObjectUtils.isNestedAttribute(reference)) {
0412: Object nestedObject = ObjectUtils
0413: .getNestedValue(
0414: getBusinessObject(),
0415: ObjectUtils
0416: .getNestedAttributePrefix(reference));
0417: if (nestedObject instanceof Collection) {
0418: // do nothing, probably because it's not really a collection reference but a relationship defined in the DD for a collections lookup
0419: // this part will need to be rewritten when the DD supports true collection references
0420: } else if (nestedObject instanceof PersistableBusinessObject) {
0421: String propertyToRefresh = ObjectUtils
0422: .getNestedAttributePrimitive(reference);
0423: if (persistenceStructureService
0424: .hasReference(nestedObject
0425: .getClass(),
0426: propertyToRefresh)
0427: || persistenceStructureService
0428: .hasCollection(nestedObject
0429: .getClass(),
0430: propertyToRefresh)) {
0431: if (LOG.isDebugEnabled()) {
0432: LOG
0433: .debug("Refeshing "
0434: + ObjectUtils
0435: .getNestedAttributePrefix(reference)
0436: + " "
0437: + ObjectUtils
0438: .getNestedAttributePrimitive(reference));
0439: }
0440: ((PersistableBusinessObject) nestedObject)
0441: .refreshReferenceObject(propertyToRefresh);
0442: } else {
0443: // a DD mapping, try to go straight to the object and refresh it there
0444: Object possibleBO = ObjectUtils
0445: .getPropertyValue(nestedObject,
0446: propertyToRefresh);
0447: if (possibleBO != null
0448: && possibleBO instanceof PersistableBusinessObject) {
0449: if (KNSServiceLocator
0450: .getDataDictionaryService()
0451: .hasRelationship(
0452: possibleBO
0453: .getClass()
0454: .getName(),
0455: propertyToRefresh)) {
0456: ((PersistableBusinessObject) possibleBO)
0457: .refresh();
0458: }
0459: }
0460: }
0461: } else {
0462: LOG
0463: .warn("Expected that a referenceToRefresh ("
0464: + reference
0465: + ") would be a PersistableBusinessObject or Collection, but instead, it was of class "
0466: + nestedObject.getClass()
0467: .getName());
0468: }
0469: } else {
0470: if (LOG.isDebugEnabled()) {
0471: LOG.debug("Refreshing " + reference);
0472: }
0473: if (persistenceStructureService.hasReference(
0474: boClass, reference)
0475: || persistenceStructureService
0476: .hasCollection(boClass,
0477: reference)) {
0478: getBusinessObject().refreshReferenceObject(
0479: reference);
0480: } else {
0481: if (KNSServiceLocator
0482: .getDataDictionaryService()
0483: .hasRelationship(
0484: getBusinessObject()
0485: .getClass()
0486: .getName(),
0487: reference)) {
0488: // a DD mapping, try to go straight to the object and refresh it there
0489: Object possibleRelationship = ObjectUtils
0490: .getPropertyValue(
0491: getBusinessObject(),
0492: reference);
0493: if (possibleRelationship != null) {
0494: if (possibleRelationship instanceof PersistableBusinessObject) {
0495: ((PersistableBusinessObject) possibleRelationship)
0496: .refresh();
0497: } else if (possibleRelationship instanceof Collection) {
0498: // do nothing, probably because it's not really a collection reference but a relationship defined in the DD for a collections lookup
0499: // this part will need to be rewritten when the DD supports true collection references
0500: } else {
0501: LOG
0502: .warn("Expected that a referenceToRefresh ("
0503: + reference
0504: + ") would be a PersistableBusinessObject or Collection, but instead, it was of class "
0505: + possibleRelationship
0506: .getClass()
0507: .getName());
0508: }
0509: }
0510: }
0511: }
0512: }
0513: }
0514: }
0515: }
0516: }
0517:
0518: public void addMultipleValueLookupResults(
0519: MaintenanceDocument document, String collectionName,
0520: Collection<PersistableBusinessObject> rawValues) {
0521: PersistableBusinessObject bo = document
0522: .getNewMaintainableObject().getBusinessObject();
0523: Collection maintCollection = (Collection) ObjectUtils
0524: .getPropertyValue(bo, collectionName);
0525: String docTypeName = document.getDocumentHeader()
0526: .getWorkflowDocument().getDocumentType();
0527:
0528: List<String> duplicateIdentifierFieldsFromDataDictionary = getDuplicateIdentifierFieldsFromDataDictionary(
0529: docTypeName, collectionName);
0530:
0531: List<String> existingIdentifierList = getMultiValueIdentifierList(
0532: maintCollection,
0533: duplicateIdentifierFieldsFromDataDictionary);
0534:
0535: Class collectionClass = KNSServiceLocator
0536: .getMaintenanceDocumentDictionaryService()
0537: .getCollectionBusinessObjectClass(docTypeName,
0538: collectionName);
0539:
0540: List<MaintainableSectionDefinition> sections = KNSServiceLocator
0541: .getMaintenanceDocumentDictionaryService()
0542: .getMaintainableSections(docTypeName);
0543: Map<String, String> template = MaintenanceUtils
0544: .generateMultipleValueLookupBOTemplate(sections,
0545: collectionName);
0546: try {
0547: for (PersistableBusinessObject nextBo : rawValues) {
0548: PersistableBusinessObject templatedBo = (PersistableBusinessObject) ObjectUtils
0549: .createHybridBusinessObject(collectionClass,
0550: nextBo, template);
0551: templatedBo.setNewCollectionRecord(true);
0552: prepareBusinessObjectForAdditionFromMultipleValueLookup(
0553: collectionName, templatedBo);
0554: if (!hasBusinessObjectExisted(templatedBo,
0555: existingIdentifierList,
0556: duplicateIdentifierFieldsFromDataDictionary)) {
0557: maintCollection.add(templatedBo);
0558: }
0559: }
0560: } catch (Exception e) {
0561: LOG.error("Unable to add multiple value lookup results "
0562: + e.getMessage());
0563: throw new RuntimeException(
0564: "Unable to add multiple value lookup results "
0565: + e.getMessage());
0566: }
0567: }
0568:
0569: /**
0570: * This method is to retrieve a List of fields which are specified in the maintenance document
0571: * data dictionary as the duplicateIdentificationFields. This List is used to determine whether
0572: * the new entry being added to the collection is a duplicate entry and if so, we should not
0573: * add the new entry to the existing collection
0574: *
0575: * @param docTypeName
0576: * @param collectionName
0577: */
0578: public List<String> getDuplicateIdentifierFieldsFromDataDictionary(
0579: String docTypeName, String collectionName) {
0580: List<String> duplicateIdentifierFieldNames = new ArrayList<String>();
0581: MaintainableCollectionDefinition collDef = KNSServiceLocator
0582: .getMaintenanceDocumentDictionaryService()
0583: .getMaintainableCollection(docTypeName, collectionName);
0584: Collection<MaintainableFieldDefinition> fieldDef = collDef
0585: .getDuplicateIdentificationFields();
0586: for (MaintainableFieldDefinition eachFieldDef : fieldDef) {
0587: duplicateIdentifierFieldNames.add(eachFieldDef.getName());
0588: }
0589: return duplicateIdentifierFieldNames;
0590: }
0591:
0592: public List<String> getMultiValueIdentifierList(
0593: Collection maintCollection,
0594: List<String> duplicateIdentifierFields) {
0595: List<String> identifierList = new ArrayList<String>();
0596: for (PersistableBusinessObject bo : (Collection<PersistableBusinessObject>) maintCollection) {
0597: String uniqueIdentifier = new String();
0598: for (String identifierField : duplicateIdentifierFields) {
0599: uniqueIdentifier = uniqueIdentifier
0600: + identifierField
0601: + "-"
0602: + ObjectUtils.getPropertyValue(bo,
0603: identifierField);
0604: }
0605: if (StringUtils.isNotEmpty(uniqueIdentifier)) {
0606: identifierList.add(uniqueIdentifier);
0607: }
0608: }
0609: return identifierList;
0610: }
0611:
0612: public boolean hasBusinessObjectExisted(BusinessObject bo,
0613: List<String> existingIdentifierList,
0614: List<String> duplicateIdentifierFields) {
0615: String uniqueIdentifier = new String();
0616: for (String identifierField : duplicateIdentifierFields) {
0617: uniqueIdentifier = uniqueIdentifier + identifierField + "-"
0618: + ObjectUtils.getPropertyValue(bo, identifierField);
0619: }
0620: if (existingIdentifierList.contains(uniqueIdentifier)) {
0621: return true;
0622: } else {
0623: return false;
0624: }
0625: }
0626:
0627: public void prepareBusinessObjectForAdditionFromMultipleValueLookup(
0628: String collectionName, BusinessObject bo) {
0629: // default implementation does nothing
0630: }
0631:
0632: /**
0633: *
0634: * @see org.kuali.core.maintenance.Maintainable#prepareForSave()
0635: */
0636: public void prepareForSave() {
0637: }
0638:
0639: /**
0640: *
0641: * @see org.kuali.core.maintenance.Maintainable#processAfterRetrieve()
0642: */
0643: public void processAfterRetrieve() {
0644: }
0645:
0646: /**
0647: * Set the new collection records back to true so they can be deleted (copy should act like new)
0648: *
0649: * @see org.kuali.core.maintenance.KualiMaintainableImpl#processAfterCopy()
0650: */
0651: public void processAfterCopy() {
0652: try {
0653: ObjectUtils.setObjectPropertyDeep(businessObject,
0654: RicePropertyConstants.NEW_COLLECTION_RECORD,
0655: boolean.class, true, 2);
0656: } catch (Exception e) {
0657: LOG.error("unable to set newCollectionRecord property: "
0658: + e.getMessage(), e);
0659: throw new RuntimeException(
0660: "unable to set newCollectionRecord property: "
0661: + e.getMessage(), e);
0662: }
0663: }
0664:
0665: /**
0666: * @see org.kuali.core.maintenance.Maintainable#processAfterEdit()
0667: */
0668: public void processAfterEdit() {
0669: }
0670:
0671: /**
0672: * Retrieves the document type name from the data dictionary based on business object class
0673: */
0674: private String getDocumentTypeName() {
0675: return KNSServiceLocator
0676: .getMaintenanceDocumentDictionaryService()
0677: .getDocumentTypeName(boClass);
0678: }
0679:
0680: /**
0681: * @return Returns the instance of the business object being maintained.
0682: */
0683: public PersistableBusinessObject getBusinessObject() {
0684: return businessObject;
0685: }
0686:
0687: /**
0688: * @param businessObject Sets the instance of a business object that will be maintained.
0689: */
0690: public void setBusinessObject(
0691: PersistableBusinessObject businessObject) {
0692: this .businessObject = businessObject;
0693: }
0694:
0695: /**
0696: * @return Returns the boClass.
0697: */
0698: public Class getBoClass() {
0699: return boClass;
0700: }
0701:
0702: /**
0703: * @param boClass The boClass to set.
0704: */
0705: public void setBoClass(Class boClass) {
0706: this .boClass = boClass;
0707: this .docTypeName = getDocumentTypeName();
0708: }
0709:
0710: /**
0711: * @return Returns the maintenanceAction.
0712: */
0713: public String getMaintenanceAction() {
0714: return maintenanceAction;
0715: }
0716:
0717: /**
0718: * @param maintenanceAction The maintenanceAction to set.
0719: */
0720: public void setMaintenanceAction(String maintenanceAction) {
0721: this .maintenanceAction = maintenanceAction;
0722: }
0723:
0724: /**
0725: * @return Returns the generateDefaultValues.
0726: */
0727: public boolean isGenerateDefaultValues() {
0728: return generateDefaultValues;
0729: }
0730:
0731: /**
0732: * @param generateDefaultValues The generateDefaultValues to set.
0733: */
0734: public void setGenerateDefaultValues(boolean generateDefaultValues) {
0735: this .generateDefaultValues = generateDefaultValues;
0736: }
0737:
0738: /**
0739: * @return Returns the generateDefaultValues.
0740: */
0741: public boolean isGenerateBlankRequiredValues() {
0742: return generateBlankRequiredValues;
0743: }
0744:
0745: /**
0746: * @param generateDefaultValues The generateDefaultValues to set.
0747: */
0748: public void setGenerateBlankRequiredValues(
0749: boolean generateBlankRequiredValues) {
0750: this .generateBlankRequiredValues = generateBlankRequiredValues;
0751: }
0752:
0753: /**
0754: * Sets the documentNumber attribute value.
0755: *
0756: * @param documentNumber The documentNumber to set.
0757: */
0758: public final void setDocumentNumber(String documentNumber) {
0759: this .documentNumber = documentNumber;
0760: }
0761:
0762: public void processAfterAddLine(String colName, Class colClass) {
0763: }
0764:
0765: /**
0766: * @see org.kuali.core.maintenance.Maintainable#getShowInactiveRecords(java.lang.String)
0767: */
0768: public boolean getShowInactiveRecords(String collectionName) {
0769: return InactiveRecordsHidingUtils.getShowInactiveRecords(
0770: inactiveRecordDisplay, collectionName);
0771: }
0772:
0773: /**
0774: * @see org.kuali.core.maintenance.Maintainable#setShowInactiveRecords(java.lang.String, boolean)
0775: */
0776: public void setShowInactiveRecords(String collectionName,
0777: boolean showInactive) {
0778: InactiveRecordsHidingUtils.setShowInactiveRecords(
0779: inactiveRecordDisplay, collectionName, showInactive);
0780: }
0781:
0782: /**
0783: * @return the inactiveRecordDisplay
0784: */
0785: public Map<String, Boolean> getInactiveRecordDisplay() {
0786: return inactiveRecordDisplay;
0787: }
0788:
0789: public void addNewLineToCollection(String collectionName) {
0790:
0791: if (LOG.isDebugEnabled()) {
0792: LOG.debug("addNewLineToCollection( " + collectionName
0793: + " )");
0794: }
0795: // get the new line from the map
0796: PersistableBusinessObject addLine = newCollectionLines
0797: .get(collectionName);
0798: if (addLine != null) {
0799: // mark the isNewCollectionRecord so the option to delete this line will be presented
0800: addLine.setNewCollectionRecord(true);
0801:
0802: //if we add back add button on sub collection of an "add line" we may need extra logic here
0803:
0804: // get the collection from the business object
0805: Collection maintCollection = (Collection) ObjectUtils
0806: .getPropertyValue(getBusinessObject(),
0807: collectionName);
0808: // add the line to the collection
0809: maintCollection.add(addLine);
0810: //refresh parent object since attributes could of changed prior to user clicking add
0811:
0812: String referencesToRefresh = LookupUtils
0813: .convertReferencesToSelectCollectionToString(getAllRefreshableReferences(getBusinessObject()
0814: .getClass()));
0815: if (LOG.isInfoEnabled()) {
0816: LOG
0817: .info("References to refresh for adding line to collection "
0818: + collectionName
0819: + ": "
0820: + referencesToRefresh);
0821: }
0822: refreshReferences(referencesToRefresh);
0823: }
0824:
0825: initNewCollectionLine(collectionName);
0826:
0827: }
0828:
0829: public PersistableBusinessObject getNewCollectionLine(
0830: String collectionName) {
0831: if (LOG.isDebugEnabled()) {
0832: //LOG.debug( this + ") getNewCollectionLine( " + collectionName + ")", new Exception( "tracing exception") );
0833: LOG.debug("newCollectionLines: " + newCollectionLines);
0834: }
0835: PersistableBusinessObject addLine = newCollectionLines
0836: .get(collectionName);
0837: if (addLine == null) {
0838: addLine = initNewCollectionLine(collectionName);
0839: }
0840: return addLine;
0841: }
0842:
0843: public PersistableBusinessObject initNewCollectionLine(
0844: String collectionName) {
0845: if (LOG.isDebugEnabled()) {
0846: LOG
0847: .debug("initNewCollectionLine( " + collectionName
0848: + " )");
0849: }
0850: // try to get the object from the map
0851: //BusinessObject addLine = newCollectionLines.get( collectionName );
0852: //if ( addLine == null ) {
0853: // if not there, instantiate a new one
0854: PersistableBusinessObject addLine;
0855: try {
0856: addLine = (PersistableBusinessObject) KNSServiceLocator
0857: .getMaintenanceDocumentDictionaryService()
0858: .getCollectionBusinessObjectClass(docTypeName,
0859: collectionName).newInstance();
0860: } catch (Exception ex) {
0861: LOG.error("unable to instantiate new collection line", ex);
0862: throw new RuntimeException(
0863: "unable to instantiate new collection line", ex);
0864: }
0865: // and add it to the map
0866: newCollectionLines.put(collectionName, addLine);
0867: //}
0868: // set its values to the defaults
0869: PropertyDescriptor[] descriptors = PropertyUtils
0870: .getPropertyDescriptors(addLine);
0871: for (int i = 0; i < descriptors.length; ++i) {
0872: PropertyDescriptor propertyDescriptor = descriptors[i];
0873:
0874: String fieldName = propertyDescriptor.getName();
0875: Class propertyType = propertyDescriptor.getPropertyType();
0876: String value = KNSServiceLocator
0877: .getMaintenanceDocumentDictionaryService()
0878: .getCollectionFieldDefaultValue(docTypeName,
0879: collectionName, fieldName);
0880: if (value != null) {
0881: try {
0882: ObjectUtils.setObjectProperty(addLine, fieldName,
0883: propertyType, value);
0884: } catch (Exception ex) {
0885: LOG.error(
0886: "Unable to set default property of collection object: "
0887: + "\nobject: " + addLine
0888: + "\nfieldName=" + fieldName
0889: + "\npropertyType=" + propertyType
0890: + "\nvalue=" + value, ex);
0891: }
0892: }
0893:
0894: }
0895: return addLine;
0896: }
0897:
0898: /**
0899: *
0900: * @see org.kuali.core.maintenance.Maintainable#populateNewCollectionLines(java.util.Map)
0901: */
0902: public Map populateNewCollectionLines(Map fieldValues) {
0903: if (LOG.isDebugEnabled()) {
0904: LOG.debug("populateNewCollectionLines: " + fieldValues);
0905: }
0906: Map cachedValues = new HashMap();
0907:
0908: // loop over all collections with an enabled add line
0909: List<MaintainableCollectionDefinition> collections = KNSServiceLocator
0910: .getMaintenanceDocumentDictionaryService()
0911: .getMaintainableCollections(docTypeName);
0912:
0913: for (MaintainableCollectionDefinition coll : collections) {
0914: // get the collection name
0915: String collName = coll.getName();
0916: if (LOG.isDebugEnabled()) {
0917: LOG.debug("checking for collection: " + collName);
0918: }
0919: // build a map for that collection
0920: Map<String, Object> collectionValues = new HashMap<String, Object>();
0921: Map<String, Object> subCollectionValues = new HashMap<String, Object>();
0922: // loop over the collection, extracting entries with a matching prefix
0923: for (Object entry : fieldValues.entrySet()) {
0924: String key = (String) ((Map.Entry) entry).getKey();
0925: if (key.startsWith(collName)) {
0926: String subStrKey = key
0927: .substring(collName.length() + 1);
0928: //check for subcoll w/ '[', set collName to propername and put in correct name for collection values (i.e. strip '*[x].')
0929: if (key.contains("[")) {
0930:
0931: //collName = StringUtils.substringBeforeLast(key,"[");
0932:
0933: //need the whole thing if subcollection
0934: subCollectionValues.put(key,
0935: ((Map.Entry) entry).getValue());
0936: } else {
0937: collectionValues.put(subStrKey,
0938: ((Map.Entry) entry).getValue());
0939: }
0940: }
0941: }
0942: // send those values to the business object
0943: if (LOG.isDebugEnabled()) {
0944: LOG.debug("values for collection: " + collectionValues);
0945: }
0946: GlobalVariables.getErrorMap().addToErrorPath(
0947: RiceConstants.MAINTENANCE_ADD_PREFIX + collName);
0948: cachedValues.putAll(FieldUtils
0949: .populateBusinessObjectFromMap(
0950: getNewCollectionLine(collName),
0951: collectionValues,
0952: RiceConstants.MAINTENANCE_ADD_PREFIX
0953: + collName + "."));
0954: GlobalVariables.getErrorMap().removeFromErrorPath(
0955: RiceConstants.MAINTENANCE_ADD_PREFIX + collName);
0956: cachedValues.putAll(populateNewSubCollectionLines(coll,
0957: subCollectionValues));
0958: }
0959:
0960: //cachedValues.putAll( FieldUtils.populateBusinessObjectFromMap( ))
0961: return cachedValues;
0962: }
0963:
0964: /* Yes, I think this could be merged with the above code - I'm
0965: * leaving it separate until I figure out of there are any issues which would reqire
0966: * that it be separated.
0967: */
0968: protected Map populateNewSubCollectionLines(
0969: MaintainableCollectionDefinition parentCollection,
0970: Map fieldValues) {
0971: if (LOG.isDebugEnabled()) {
0972: LOG.debug("populateNewSubCollectionLines: " + fieldValues);
0973: }
0974: Map cachedValues = new HashMap();
0975:
0976: for (MaintainableCollectionDefinition coll : parentCollection
0977: .getMaintainableCollections()) {
0978: // get the collection name
0979: String collName = coll.getName();
0980:
0981: if (LOG.isDebugEnabled()) {
0982: LOG.debug("checking for sub collection: " + collName);
0983: }
0984: Map<String, String> parents = new HashMap<String, String>();
0985: //get parents from list
0986: for (Object entry : fieldValues.entrySet()) {
0987: String key = (String) ((Map.Entry) entry).getKey();
0988: if (key.contains(collName)) {
0989: parents.put(StringUtils.substringBefore(key, "."),
0990: "");
0991: }
0992: }
0993:
0994: for (String parent : parents.keySet()) {
0995: // build a map for that collection
0996: Map<String, Object> collectionValues = new HashMap<String, Object>();
0997: // loop over the collection, extracting entries with a matching prefix
0998: for (Object entry : fieldValues.entrySet()) {
0999: String key = (String) ((Map.Entry) entry).getKey();
1000: if (key.contains(parent)) {
1001: String substr = StringUtils.substringAfterLast(
1002: key, ".");
1003: collectionValues.put(substr,
1004: ((Map.Entry) entry).getValue());
1005: }
1006: }
1007: // send those values to the business object
1008: if (LOG.isDebugEnabled()) {
1009: LOG.debug("values for sub collection: "
1010: + collectionValues);
1011: }
1012: GlobalVariables.getErrorMap().addToErrorPath(
1013: RiceConstants.MAINTENANCE_ADD_PREFIX + parent
1014: + "." + collName);
1015: cachedValues
1016: .putAll(FieldUtils
1017: .populateBusinessObjectFromMap(
1018: getNewCollectionLine(parent
1019: + "." + collName),
1020: collectionValues,
1021: RiceConstants.MAINTENANCE_ADD_PREFIX
1022: + parent
1023: + "."
1024: + collName + "."));
1025: GlobalVariables.getErrorMap().removeFromErrorPath(
1026: RiceConstants.MAINTENANCE_ADD_PREFIX + parent
1027: + "." + collName);
1028: }
1029:
1030: cachedValues.putAll(populateNewSubCollectionLines(coll,
1031: fieldValues));
1032: }
1033:
1034: return cachedValues;
1035: }
1036:
1037: public Collection<String> getAffectedReferencesFromLookup(
1038: BusinessObject baseBO, String attributeName,
1039: String collectionPrefix) {
1040: PersistenceStructureService pss = KNSServiceLocator
1041: .getPersistenceStructureService();
1042: String nestedBOPrefix = "";
1043: if (ObjectUtils.isNestedAttribute(attributeName)) {
1044: // if we're performing a lookup on a nested attribute, we need to use the nested BO all the way down the chain
1045: nestedBOPrefix = ObjectUtils
1046: .getNestedAttributePrefix(attributeName);
1047:
1048: // renormalize the base BO so that the attribute name is not nested anymore
1049: Class reference = ObjectUtils.getPropertyType(baseBO,
1050: nestedBOPrefix, pss);
1051: if (!(PersistableBusinessObject.class
1052: .isAssignableFrom(reference))) {
1053: return new ArrayList<String>();
1054: }
1055:
1056: try {
1057: baseBO = (PersistableBusinessObject) reference
1058: .newInstance();
1059: } catch (InstantiationException e) {
1060: // TODO Auto-generated catch block
1061: e.printStackTrace();
1062: } catch (IllegalAccessException e) {
1063: // TODO Auto-generated catch block
1064: e.printStackTrace();
1065: }
1066: attributeName = ObjectUtils
1067: .getNestedAttributePrimitive(attributeName);
1068: }
1069:
1070: if (baseBO == null) {
1071: return new ArrayList<String>();
1072: }
1073:
1074: Map<String, Class> referenceNameToClassFromPSS = LookupUtils
1075: .getPrimitiveReference(baseBO, attributeName);
1076: if (referenceNameToClassFromPSS.size() > 1) {
1077: LOG
1078: .error("LookupUtils.getPrimitiveReference return results should only have at most one element");
1079: }
1080:
1081: BusinessObjectMetaDataService businessObjectMetaDataService = KNSServiceLocator
1082: .getBusinessObjectMetaDataService();
1083: BusinessObjectRelationship relationship = businessObjectMetaDataService
1084: .getBusinessObjectRelationship(baseBO, attributeName);
1085: if (relationship == null) {
1086: return new ArrayList<String>();
1087: }
1088:
1089: Map<String, String> fkToPkMappings = relationship
1090: .getParentToChildReferences();
1091:
1092: Collection<String> affectedReferences = generateAllAffectedReferences(
1093: baseBO.getClass(), fkToPkMappings, nestedBOPrefix,
1094: collectionPrefix);
1095: if (LOG.isDebugEnabled()) {
1096: LOG
1097: .debug("References affected by a lookup on BO attribute \""
1098: + collectionPrefix
1099: + nestedBOPrefix
1100: + "."
1101: + attributeName + ": " + affectedReferences);
1102: }
1103:
1104: return affectedReferences;
1105: }
1106:
1107: protected boolean isRelationshipRefreshable(Class boClass,
1108: String relationshipName) {
1109: if (KNSServiceLocator.getPersistenceStructureService()
1110: .isPersistable(boClass)) {
1111: if (KNSServiceLocator.getPersistenceStructureService()
1112: .hasCollection(boClass, relationshipName)) {
1113: return !KNSServiceLocator
1114: .getPersistenceStructureService()
1115: .isCollectionUpdatable(boClass,
1116: relationshipName);
1117: } else if (KNSServiceLocator
1118: .getPersistenceStructureService().hasReference(
1119: boClass, relationshipName)) {
1120: return !KNSServiceLocator
1121: .getPersistenceStructureService()
1122: .isReferenceUpdatable(boClass, relationshipName);
1123: }
1124: // else, assume that the relationship is defined in the DD
1125: }
1126:
1127: return true;
1128: }
1129:
1130: protected Collection<String> generateAllAffectedReferences(
1131: Class boClass, Map<String, String> fkToPkMappings,
1132: String nestedBOPrefix, String collectionPrefix) {
1133: Set<String> allAffectedReferences = new HashSet<String>();
1134: DataDictionaryService dataDictionaryService = KNSServiceLocator
1135: .getDataDictionaryService();
1136: PersistenceStructureService pss = KNSServiceLocator
1137: .getPersistenceStructureService();
1138:
1139: collectionPrefix = StringUtils.isBlank(collectionPrefix) ? ""
1140: : collectionPrefix;
1141:
1142: // retrieve the attributes that are affected by a lookup on attributeName.
1143: Collection<String> attributeReferenceFKAttributes = fkToPkMappings
1144: .keySet();
1145:
1146: // a lookup on an attribute may cause other attributes to be updated (e.g. account code lookup would also affect chart code)
1147: // build a list of all affected FK values via mapKeyFields above, and for each FK, see if there are any non-updatable references with that FK
1148:
1149: // deal with regular simple references (<reference-descriptor>s in OJB)
1150: for (String fkAttribute : attributeReferenceFKAttributes) {
1151: for (String affectedReference : pss
1152: .getReferencesForForeignKey(boClass, fkAttribute)
1153: .keySet()) {
1154: if (isRelationshipRefreshable(boClass,
1155: affectedReference)) {
1156: if (StringUtils.isBlank(nestedBOPrefix)) {
1157: allAffectedReferences.add(collectionPrefix
1158: + affectedReference);
1159: } else {
1160: allAffectedReferences.add(collectionPrefix
1161: + nestedBOPrefix + "."
1162: + affectedReference);
1163: }
1164: }
1165: }
1166: }
1167:
1168: // now with collection references (<collection-descriptor>s in OJB)
1169: for (String collectionName : pss.listCollectionObjectTypes(
1170: boClass).keySet()) {
1171: if (isRelationshipRefreshable(boClass, collectionName)) {
1172: Map<String, String> keyMappingsForCollection = pss
1173: .getInverseForeignKeysForCollection(boClass,
1174: collectionName);
1175: for (String collectionForeignKey : keyMappingsForCollection
1176: .keySet()) {
1177: if (attributeReferenceFKAttributes
1178: .contains(collectionForeignKey)) {
1179: if (StringUtils.isBlank(nestedBOPrefix)) {
1180: allAffectedReferences.add(collectionPrefix
1181: + collectionName);
1182: } else {
1183: allAffectedReferences.add(collectionPrefix
1184: + nestedBOPrefix + "."
1185: + collectionName);
1186: }
1187: }
1188: }
1189: }
1190: }
1191:
1192: // now use the DD to compute more affected references
1193: List<String> ddDefinedRelationships = dataDictionaryService
1194: .getRelationshipNames(boClass.getName());
1195: for (String ddRelationship : ddDefinedRelationships) {
1196: // note that this map is PK (key/target) => FK (value/source)
1197: Map<String, String> referencePKtoFKmappings = dataDictionaryService
1198: .getRelationshipAttributeMap(boClass.getName(),
1199: ddRelationship);
1200: for (String sourceAttribute : referencePKtoFKmappings
1201: .values()) {
1202: // the sourceAttribute is the FK pointing to the target attribute (PK)
1203: if (attributeReferenceFKAttributes
1204: .contains(sourceAttribute)) {
1205: for (String affectedReference : dataDictionaryService
1206: .getRelationshipEntriesForSourceAttribute(
1207: boClass.getName(), sourceAttribute)) {
1208: if (isRelationshipRefreshable(boClass,
1209: ddRelationship)) {
1210: if (StringUtils.isBlank(nestedBOPrefix)) {
1211: allAffectedReferences
1212: .add(affectedReference);
1213: } else {
1214: allAffectedReferences
1215: .add(nestedBOPrefix + "."
1216: + affectedReference);
1217: }
1218: }
1219: }
1220: }
1221: }
1222: }
1223: return allAffectedReferences;
1224: }
1225:
1226: protected Collection<String> getAllRefreshableReferences(
1227: Class boClass) {
1228: HashSet<String> references = new HashSet<String>();
1229: for (String referenceName : KNSServiceLocator
1230: .getPersistenceStructureService()
1231: .listReferenceObjectFields(boClass).keySet()) {
1232: if (isRelationshipRefreshable(boClass, referenceName)) {
1233: references.add(referenceName);
1234: }
1235: }
1236: for (String collectionName : KNSServiceLocator
1237: .getPersistenceStructureService()
1238: .listCollectionObjectTypes(boClass).keySet()) {
1239: if (isRelationshipRefreshable(boClass, collectionName)) {
1240: references.add(collectionName);
1241: }
1242: }
1243: for (String relationshipName : KNSServiceLocator
1244: .getDataDictionaryService().getRelationshipNames(
1245: boClass.getName())) {
1246: if (isRelationshipRefreshable(boClass, relationshipName)) {
1247: references.add(relationshipName);
1248: }
1249: }
1250: return references;
1251: }
1252:
1253: public void handleRouteStatusChange(DocumentHeader documentHeader) {
1254: }
1255: }
|