View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.maintenance;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.encryption.EncryptionService;
22  import org.kuali.rice.core.web.format.FormatException;
23  import org.kuali.rice.kim.api.identity.PersonService;
24  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
25  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
26  import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
27  import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
28  import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
29  import org.kuali.rice.kns.document.MaintenanceDocument;
30  import org.kuali.rice.kns.document.authorization.FieldRestriction;
31  import org.kuali.rice.kns.document.authorization.MaintenanceDocumentPresentationController;
32  import org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions;
33  import org.kuali.rice.kns.lookup.LookupUtils;
34  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
35  import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
36  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
37  import org.kuali.rice.kns.service.DocumentHelperService;
38  import org.kuali.rice.kns.service.KNSServiceLocator;
39  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
40  import org.kuali.rice.kns.util.FieldUtils;
41  import org.kuali.rice.kns.util.InactiveRecordsHidingUtils;
42  import org.kuali.rice.kns.util.MaintenanceUtils;
43  import org.kuali.rice.kns.web.ui.Section;
44  import org.kuali.rice.kns.web.ui.SectionBridge;
45  import org.kuali.rice.krad.bo.BusinessObject;
46  import org.kuali.rice.krad.bo.DataObjectRelationship;
47  import org.kuali.rice.krad.bo.PersistableBusinessObject;
48  import org.kuali.rice.krad.datadictionary.AttributeSecurity;
49  import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
50  import org.kuali.rice.krad.maintenance.MaintainableImpl;
51  import org.kuali.rice.krad.service.DataDictionaryService;
52  import org.kuali.rice.krad.service.KRADServiceLocator;
53  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
54  import org.kuali.rice.krad.service.ModuleService;
55  import org.kuali.rice.krad.service.PersistenceStructureService;
56  import org.kuali.rice.krad.util.GlobalVariables;
57  import org.kuali.rice.krad.util.KRADConstants;
58  import org.kuali.rice.krad.util.KRADPropertyConstants;
59  import org.kuali.rice.krad.util.MessageMap;
60  import org.kuali.rice.krad.util.ObjectUtils;
61  import org.kuali.rice.krad.valuefinder.ValueFinder;
62  
63  import java.beans.PropertyDescriptor;
64  import java.lang.reflect.InvocationTargetException;
65  import java.security.GeneralSecurityException;
66  import java.util.ArrayList;
67  import java.util.Collection;
68  import java.util.HashMap;
69  import java.util.HashSet;
70  import java.util.Iterator;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.Set;
74  
75  /**
76   * Base Maintainable class to hold things common to all maintainables.
77   */
78  @Deprecated
79  public class KualiMaintainableImpl extends MaintainableImpl implements Maintainable {
80  	private static final long serialVersionUID = 4814145799502207182L;
81  
82  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiMaintainableImpl.class);
83  
84  	protected PersistableBusinessObject businessObject;
85  
86  	protected Map<String, PersistableBusinessObject> newCollectionLines = new HashMap<String, PersistableBusinessObject>();
87  	protected Map<String, Boolean> inactiveRecordDisplay = new HashMap<String, Boolean>();
88  
89      // TODO: rename once 'newCollectionLines' is removed
90      protected Set<String> newCollectionLineNames = new HashSet<String>();
91  
92  	protected transient BusinessObjectDictionaryService businessObjectDictionaryService;
93  	protected transient PersonService personService;
94  	protected transient BusinessObjectMetaDataService businessObjectMetaDataService;
95  	protected transient BusinessObjectAuthorizationService businessObjectAuthorizationService;
96  	protected transient DocumentHelperService documentHelperService;
97      protected transient MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
98  
99  	/**
100 	 * Default empty constructor
101 	 */
102 	public KualiMaintainableImpl() {
103         super();
104 	}
105 
106 	/**
107 	 * Constructor which initializes the business object to be maintained.
108 	 *
109 	 * @param businessObject
110 	 */
111 	public KualiMaintainableImpl(PersistableBusinessObject businessObject) {
112 		super();
113 		this.businessObject = businessObject;
114 		super.setDataObject(businessObject);
115 	}
116 
117 	/**
118 	 * @see Maintainable#populateBusinessObject(java.util.Map,
119 	 *      org.kuali.rice.krad.maintenance.MaintenanceDocument, String)
120 	 */
121 	@SuppressWarnings("unchecked")
122 	public Map populateBusinessObject(Map<String, String> fieldValues, MaintenanceDocument maintenanceDocument,
123 			String methodToCall) {
124 		fieldValues = decryptEncryptedData(fieldValues, maintenanceDocument, methodToCall);
125 		Map newFieldValues = null;
126 		newFieldValues = getPersonService().resolvePrincipalNamesToPrincipalIds(getBusinessObject(), fieldValues);
127 
128 		Map cachedValues = FieldUtils.populateBusinessObjectFromMap(getBusinessObject(), newFieldValues);
129 		performForceUpperCase(newFieldValues);
130 
131 		return cachedValues;
132 	}
133 
134 	/**
135 	 * Special hidden parameters are set on the maintenance jsp starting with a
136 	 * prefix that tells us which fields have been encrypted. This field finds
137 	 * the those parameters in the map, whose value gives us the property name
138 	 * that has an encrypted value. We then need to decrypt the value in the Map
139 	 * before the business object is populated.
140 	 * 
141 	 * @param fieldValues
142 	 *            - possibly with encrypted values
143 	 * @return Map fieldValues - with no encrypted values
144 	 */
145 	protected Map<String, String> decryptEncryptedData(Map<String, String> fieldValues,
146 			MaintenanceDocument maintenanceDocument, String methodToCall) {
147 		try {
148 			MaintenanceDocumentRestrictions auths = KNSServiceLocator.getBusinessObjectAuthorizationService()
149 					.getMaintenanceDocumentRestrictions(maintenanceDocument,
150 							GlobalVariables.getUserSession().getPerson());
151 			for (Iterator<String> iter = fieldValues.keySet().iterator(); iter.hasNext();) {
152 				String fieldName = iter.next();
153 				String fieldValue = (String) fieldValues.get(fieldName);
154 
155 				if (fieldValue != null && !"".equals(fieldValue)
156 						&& fieldValue.endsWith(EncryptionService.ENCRYPTION_POST_PREFIX)) {
157 					if (shouldFieldBeEncrypted(maintenanceDocument, fieldName, auths, methodToCall)) {
158 						String encryptedValue = fieldValue;
159 
160 						// take off the postfix
161 						encryptedValue = StringUtils.stripEnd(encryptedValue, EncryptionService.ENCRYPTION_POST_PREFIX);
162                         if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
163                             String decryptedValue = getEncryptionService().decrypt(encryptedValue);
164 
165 						    fieldValues.put(fieldName, decryptedValue);
166                         }
167 					}
168 					else
169 						throw new RuntimeException("The field value for field name " + fieldName
170 								+ " should not be encrypted.");
171 				}
172 				else if (fieldValue != null && !"".equals(fieldValue)
173 				    && auths.hasRestriction(fieldName)
174 				    && shouldFieldBeEncrypted(maintenanceDocument, fieldName, auths, methodToCall))
175 					    throw new RuntimeException("The field value for field name " + fieldName + " should be encrypted.");
176 			}
177 		}
178 		catch (GeneralSecurityException e) {
179 			throw new RuntimeException("Unable to decrypt secure data: " + e.getMessage());
180 		}
181 
182 		return fieldValues;
183 	}
184 
185 	/**
186 	 * Determines whether the field in a request should be encrypted. This base
187 	 * implementation does not work for properties of collection elements.
188 	 * 
189 	 * This base implementation will only return true if the maintenance
190 	 * document is being refreshed after a lookup (i.e. methodToCall is
191 	 * "refresh") and the data dictionary-based attribute security definition
192 	 * has any restriction defined, whether the user would be authorized to view
193 	 * the field. This assumes that only fields returned from a lookup should be
194 	 * encrypted in a request. If the user otherwise has no permissions to
195 	 * view/edit the field, then a request parameter will not be sent back to
196 	 * the server for population.
197 	 * 
198 	 * @param maintenanceDocument
199 	 * @param fieldName
200 	 * @param auths
201 	 * @param methodToCall
202 	 * @return
203 	 */
204 	protected boolean shouldFieldBeEncrypted(MaintenanceDocument maintenanceDocument, String fieldName,
205 			MaintenanceDocumentRestrictions auths, String methodToCall) {
206 		if ("refresh".equals(methodToCall) && fieldName != null) {
207 			fieldName = fieldName.replaceAll("\\[[0-9]*+\\]", "");
208 			fieldName = fieldName.replaceAll("^add\\.", "");
209 			Map<String, AttributeSecurity> fieldNameToAttributeSecurityMap = MaintenanceUtils
210 					.retrievePropertyPathToAttributeSecurityMappings(getDocumentTypeName());
211 			AttributeSecurity attributeSecurity = fieldNameToAttributeSecurityMap.get(fieldName);
212 			return attributeSecurity != null && attributeSecurity.hasRestrictionThatRemovesValueFromUI();
213 		}
214 		else {
215 			return false;
216 		}
217 	}
218 
219 	/**
220 	 * Calls method to get all the core sections for the business object defined
221 	 * in the data dictionary. Then determines if the bo has custom attributes,
222 	 * if so builds a custom attribute section and adds to the section list.
223 	 * 
224 	 * @return List of org.kuali.ui.Section objects
225 	 */
226 	public List getSections(MaintenanceDocument document, Maintainable oldMaintainable) {
227 		List<Section> sections = new ArrayList<Section>();
228 		sections.addAll(getCoreSections(document, oldMaintainable));
229 
230 		return sections;
231 	}
232 
233 	/**
234 	 * Gets list of maintenance sections built from the data dictionary. If the
235 	 * section contains maintenance fields, construct Row/Field UI objects and
236 	 * place under Section UI. If section contains a maintenance collection,
237 	 * call method to build a Section UI which contains rows of Container
238 	 * Fields.
239 	 * 
240 	 * @return List of org.kuali.ui.Section objects
241 	 */
242 	public List<Section> getCoreSections(MaintenanceDocument document, Maintainable oldMaintainable) {
243 		List<Section> sections = new ArrayList<Section>();
244 		MaintenanceDocumentRestrictions maintenanceRestrictions = KNSServiceLocator
245 				.getBusinessObjectAuthorizationService().getMaintenanceDocumentRestrictions(document,
246 						GlobalVariables.getUserSession().getPerson());
247 
248 		MaintenanceDocumentPresentationController maintenanceDocumentPresentationController = (MaintenanceDocumentPresentationController) getDocumentHelperService()
249 				.getDocumentPresentationController(document);
250 		Set<String> conditionallyRequiredFields = maintenanceDocumentPresentationController
251 				.getConditionallyRequiredPropertyNames(document);
252 
253 		List<MaintainableSectionDefinition> sectionDefinitions = getMaintenanceDocumentDictionaryService()
254 				.getMaintainableSections(getDocumentTypeName());
255 		try {
256 			// iterate through section definitions and create Section UI object
257 			for (Iterator iter = sectionDefinitions.iterator(); iter.hasNext();) {
258 				MaintainableSectionDefinition maintSectionDef = (MaintainableSectionDefinition) iter.next();
259 
260 				List<String> displayedFieldNames = new ArrayList<String>();
261 				if (!maintenanceRestrictions.isHiddenSectionId(maintSectionDef.getId())) {
262 
263 					for (Iterator iter2 = maintSectionDef.getMaintainableItems().iterator(); iter2.hasNext();) {
264 						MaintainableItemDefinition item = (MaintainableItemDefinition) iter2.next();
265 						if (item instanceof MaintainableFieldDefinition) {
266 							displayedFieldNames.add(((MaintainableFieldDefinition) item).getName());
267 						}
268 					}
269 
270 					Section section = SectionBridge
271                             .toSection(maintSectionDef, getBusinessObject(), this, oldMaintainable,
272                                     getMaintenanceAction(), displayedFieldNames, conditionallyRequiredFields);
273 					if (maintenanceRestrictions.isReadOnlySectionId(maintSectionDef.getId())) {
274 						section.setReadOnly(true);
275 					}
276 
277 					// add to section list
278 					sections.add(section);
279 				}
280 
281 			}
282 
283 		}
284 		catch (InstantiationException e) {
285 			LOG.error("Unable to create instance of object class" + e.getMessage());
286 			throw new RuntimeException("Unable to create instance of object class" + e.getMessage());
287 		}
288 		catch (IllegalAccessException e) {
289 			LOG.error("Unable to create instance of object class" + e.getMessage());
290 			throw new RuntimeException("Unable to create instance of object class" + e.getMessage());
291 		}
292 
293 		return sections;
294 	}
295 
296 
297     /**
298 	 * 
299 	 * @see Maintainable#saveBusinessObject()
300 	 */
301 	public void saveBusinessObject() {
302 		getBusinessObjectService().linkAndSave(businessObject);
303 	}
304 
305     /**
306      * delegate this call to KNS' {@link org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject()} in order
307      * to support KNS maintainables.
308      */
309     @Override
310     public void saveDataObject() {
311         saveBusinessObject();
312     }
313 
314     /**
315 	 * Retrieves title for maintenance document from data dictionary
316 	 */
317 	public String getMaintainableTitle() {
318 		return getMaintenanceDocumentDictionaryService().getMaintenanceLabel(getDocumentTypeName());
319 	}
320 
321     @Override
322     public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
323     }
324 
325 	public boolean isBoNotesEnabled() {
326         return getDataObjectMetaDataService().areNotesSupported(getDataObjectClass());
327 	}
328 
329     /**
330      * Overriding to call old (KNS) name of the method
331      */
332     @Override
333     public boolean isNotesEnabled() {
334         return isBoNotesEnabled();
335     }
336 
337     /**
338 	 * @see Maintainable#refresh(java.lang.String,
339 	 *      java.util.Map) Impls will be needed if custom action is needed on
340 	 *      refresh.
341 	 */
342 	public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
343 		String referencesToRefresh = (String) fieldValues.get(KRADConstants.REFERENCES_TO_REFRESH);
344 		refreshReferences(referencesToRefresh);
345 	}
346 
347 	protected void refreshReferences(String referencesToRefresh) {
348 		PersistenceStructureService persistenceStructureService = getPersistenceStructureService();
349 		if (StringUtils.isNotBlank(referencesToRefresh)) {
350 			String[] references = StringUtils.split(referencesToRefresh, KRADConstants.REFERENCES_TO_REFRESH_SEPARATOR);
351 			for (String reference : references) {
352 				if (StringUtils.isNotBlank(reference)) {
353 					if (reference.startsWith(KRADConstants.ADD_PREFIX + ".")) {
354 						// add one for the period
355 						reference = reference.substring(KRADConstants.ADD_PREFIX.length() + 1);
356 
357 						String boToRefreshName = StringUtils.substringBeforeLast(reference, ".");
358 						String propertyToRefresh = StringUtils.substringAfterLast(reference, ".");
359 						if (StringUtils.isNotBlank(propertyToRefresh)) {
360 							PersistableBusinessObject addlineBO = getNewCollectionLine(boToRefreshName);
361 							Class addlineBOClass = addlineBO.getClass();
362 							if (LOG.isDebugEnabled()) {
363 								LOG.debug("Refresh this \"new\"/add object for the collections:  " + reference);
364 							}
365 							if (persistenceStructureService.hasReference(addlineBOClass, propertyToRefresh)
366 									|| persistenceStructureService.hasCollection(addlineBOClass, propertyToRefresh)) {
367 								addlineBO.refreshReferenceObject(propertyToRefresh);
368 							}
369 							else {
370 								if (getDataDictionaryService().hasRelationship(addlineBOClass.getName(),
371 										propertyToRefresh)) {
372 									// a DD mapping, try to go straight to the
373 									// object and refresh it there
374 									Object possibleBO = ObjectUtils.getPropertyValue(addlineBO, propertyToRefresh);
375 									if (possibleBO != null && possibleBO instanceof PersistableBusinessObject) {
376 										((PersistableBusinessObject) possibleBO).refresh();
377 									}
378 								}
379 							}
380 						}
381 						else {
382 							LOG.error("Error: unable to refresh this \"new\"/add object for the collections:  "
383 									+ reference);
384 						}
385 					}
386 					else if (ObjectUtils.isNestedAttribute(reference)) {
387 						Object nestedObject = ObjectUtils.getNestedValue(getBusinessObject(),
388 								ObjectUtils.getNestedAttributePrefix(reference));
389                         if (nestedObject == null) {
390                             LOG.warn("Unable to refresh ReferenceToRefresh (" + reference + ")  was found to be null");
391                         }
392                         else {
393                             if (nestedObject instanceof Collection) {
394                                 // do nothing, probably because it's not really a
395                                 // collection reference but a relationship defined
396                                 // in the DD for a collections lookup
397                                 // this part will need to be rewritten when the DD
398                                 // supports true collection references
399                             }
400                             else if (nestedObject instanceof PersistableBusinessObject) {
401                                 String propertyToRefresh = ObjectUtils.getNestedAttributePrimitive(reference);
402                                 if (persistenceStructureService.hasReference(nestedObject.getClass(), propertyToRefresh)
403                                         || persistenceStructureService.hasCollection(nestedObject.getClass(),
404                                                 propertyToRefresh)) {
405                                     if (LOG.isDebugEnabled()) {
406                                         LOG.debug("Refeshing " + ObjectUtils.getNestedAttributePrefix(reference) + " "
407                                                 + ObjectUtils.getNestedAttributePrimitive(reference));
408                                     }
409                                     ((PersistableBusinessObject) nestedObject).refreshReferenceObject(propertyToRefresh);
410                                 }
411                                 else {
412                                     // a DD mapping, try to go straight to the
413                                     // object and refresh it there
414                                     Object possibleBO = ObjectUtils.getPropertyValue(nestedObject, propertyToRefresh);
415                                     if (possibleBO != null && possibleBO instanceof PersistableBusinessObject) {
416                                         if (getDataDictionaryService().hasRelationship(possibleBO.getClass().getName(),
417                                                 propertyToRefresh)) {
418                                             ((PersistableBusinessObject) possibleBO).refresh();
419                                         }
420                                     }
421                                 }
422                             }
423                             else {
424                                 LOG.warn("Expected that a referenceToRefresh ("
425                                         + reference
426                                         + ")  would be a PersistableBusinessObject or Collection, but instead, it was of class "
427                                         + nestedObject.getClass().getName());
428                             }
429                         }
430 					}
431 					else {
432 						if (LOG.isDebugEnabled()) {
433 							LOG.debug("Refreshing " + reference);
434 						}
435 						if (persistenceStructureService.hasReference(getDataObjectClass(), reference)
436 								|| persistenceStructureService.hasCollection(getDataObjectClass(), reference)) {
437 							getBusinessObject().refreshReferenceObject(reference);
438 						}
439 						else {
440 							if (getDataDictionaryService().hasRelationship(getBusinessObject().getClass().getName(),
441 									reference)) {
442 								// a DD mapping, try to go straight to the
443 								// object and refresh it there
444 								Object possibleRelationship = ObjectUtils.getPropertyValue(getBusinessObject(),
445 										reference);
446 								if (possibleRelationship != null) {
447 									if (possibleRelationship instanceof PersistableBusinessObject) {
448 										((PersistableBusinessObject) possibleRelationship).refresh();
449 									}
450 									else if (possibleRelationship instanceof Collection) {
451 										// do nothing, probably because it's not
452 										// really a collection reference but a
453 										// relationship defined in the DD for a
454 										// collections lookup
455 										// this part will need to be rewritten
456 										// when the DD supports true collection
457 										// references
458 									}
459 									else {
460 										LOG.warn("Expected that a referenceToRefresh ("
461 												+ reference
462 												+ ")  would be a PersistableBusinessObject or Collection, but instead, it was of class "
463 												+ possibleRelationship.getClass().getName());
464 									}
465 								}
466 							}
467 						}
468 					}
469 				}
470 			}
471 		}
472 	}
473 
474 	public void addMultipleValueLookupResults(MaintenanceDocument document, String collectionName,
475 			Collection<PersistableBusinessObject> rawValues, boolean needsBlank, PersistableBusinessObject bo) {
476 		Collection maintCollection = (Collection) ObjectUtils.getPropertyValue(bo, collectionName);
477 		String docTypeName = document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
478 
479 		List<String> duplicateIdentifierFieldsFromDataDictionary = getDuplicateIdentifierFieldsFromDataDictionary(
480 				docTypeName, collectionName);
481 
482 		List<String> existingIdentifierList = getMultiValueIdentifierList(maintCollection,
483 				duplicateIdentifierFieldsFromDataDictionary);
484 
485 		Class collectionClass = getMaintenanceDocumentDictionaryService().getCollectionBusinessObjectClass(docTypeName,
486 				collectionName);
487 
488 		List<MaintainableSectionDefinition> sections = getMaintenanceDocumentDictionaryService()
489 				.getMaintainableSections(docTypeName);
490 		Map<String, String> template = MaintenanceUtils.generateMultipleValueLookupBOTemplate(sections, collectionName);
491 		try {
492 			for (PersistableBusinessObject nextBo : rawValues) {
493 				PersistableBusinessObject templatedBo;
494 				if (needsBlank) {
495 					templatedBo = (PersistableBusinessObject) collectionClass.newInstance();
496 				}
497 				else {
498 					// templatedBo = (PersistableBusinessObject)
499 					// ObjectUtils.createHybridBusinessObject(collectionClass,
500 					// nextBo, template);
501 					try {
502 						ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService()
503 								.getResponsibleModuleService(collectionClass);
504 						if (moduleService != null && moduleService.isExternalizable(collectionClass))
505 							templatedBo = (PersistableBusinessObject) moduleService
506 									.createNewObjectFromExternalizableClass(collectionClass);
507 						else
508 							templatedBo = (PersistableBusinessObject) collectionClass.newInstance();
509 					}
510 					catch (Exception e) {
511 						throw new RuntimeException("Cannot instantiate " + collectionClass.getName(), e);
512 					}
513 					// first set the default values specified in the DD
514 					setNewCollectionLineDefaultValues(collectionName, templatedBo);
515 					// then set the values from the multiple value lookup result
516 					ObjectUtils.createHybridBusinessObject(templatedBo, nextBo, template);
517 
518 					prepareBusinessObjectForAdditionFromMultipleValueLookup(collectionName, templatedBo);
519 				}
520 				templatedBo.setNewCollectionRecord(true);
521 
522 				if (!hasBusinessObjectExisted(templatedBo, existingIdentifierList,
523 						duplicateIdentifierFieldsFromDataDictionary)) {
524 					maintCollection.add(templatedBo);
525 
526 				}
527 			}
528 		}
529 		catch (Exception e) {
530 			LOG.error("Unable to add multiple value lookup results " + e.getMessage());
531 			throw new RuntimeException("Unable to add multiple value lookup results " + e.getMessage());
532 		}
533 	}
534 
535 	/**
536 	 * This method is to retrieve a List of fields which are specified in the
537 	 * maintenance document data dictionary as the
538 	 * duplicateIdentificationFields. This List is used to determine whether the
539 	 * new entry being added to the collection is a duplicate entry and if so,
540 	 * we should not add the new entry to the existing collection
541 	 * 
542 	 * @param docTypeName
543 	 * @param collectionName
544 	 */
545 	public List<String> getDuplicateIdentifierFieldsFromDataDictionary(String docTypeName, String collectionName) {
546 		List<String> duplicateIdentifierFieldNames = new ArrayList<String>();
547 		MaintainableCollectionDefinition collDef = getMaintenanceDocumentDictionaryService().getMaintainableCollection(
548 				docTypeName, collectionName);
549 		Collection<MaintainableFieldDefinition> fieldDef = collDef.getDuplicateIdentificationFields();
550 		for (MaintainableFieldDefinition eachFieldDef : fieldDef) {
551 			duplicateIdentifierFieldNames.add(eachFieldDef.getName());
552 		}
553 		return duplicateIdentifierFieldNames;
554 	}
555 
556 	public List<String> getMultiValueIdentifierList(Collection maintCollection, List<String> duplicateIdentifierFields) {
557 		List<String> identifierList = new ArrayList<String>();
558 		for (PersistableBusinessObject bo : (Collection<PersistableBusinessObject>) maintCollection) {
559 			String uniqueIdentifier = new String();
560 			for (String identifierField : duplicateIdentifierFields) {
561 				uniqueIdentifier = uniqueIdentifier + identifierField + "-"
562 						+ ObjectUtils.getPropertyValue(bo, identifierField);
563 			}
564 			if (StringUtils.isNotEmpty(uniqueIdentifier)) {
565 				identifierList.add(uniqueIdentifier);
566 			}
567 		}
568 		return identifierList;
569 	}
570 
571 	public boolean hasBusinessObjectExisted(BusinessObject bo, List<String> existingIdentifierList,
572 			List<String> duplicateIdentifierFields) {
573 		String uniqueIdentifier = new String();
574 		for (String identifierField : duplicateIdentifierFields) {
575 			uniqueIdentifier = uniqueIdentifier + identifierField + "-"
576 					+ ObjectUtils.getPropertyValue(bo, identifierField);
577 		}
578 		if (existingIdentifierList.contains(uniqueIdentifier)) {
579 			return true;
580 		}
581 		else {
582 			return false;
583 		}
584 	}
585 
586 	public void prepareBusinessObjectForAdditionFromMultipleValueLookup(String collectionName, BusinessObject bo) {
587 		// default implementation does nothing
588 	}
589 
590 	/**
591 	 * Set the new collection records back to true so they can be deleted (copy
592 	 * should act like new)
593 	 * 
594 	 * @see KualiMaintainableImpl#processAfterCopy()
595 	 */
596 	public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
597 		try {
598 			ObjectUtils.setObjectPropertyDeep(businessObject, KRADPropertyConstants.NEW_COLLECTION_RECORD,
599 					boolean.class, true, 2);
600 		} catch (Exception e) {
601 			LOG.error("unable to set newCollectionRecord property: " + e.getMessage(), e);
602 			throw new RuntimeException("unable to set newCollectionRecord property: " + e.getMessage(), e);
603 		}
604 	}
605 
606     @Override
607     public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
608 
609     }
610 
611     @Override
612     public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
613 
614     }
615 
616     @Override
617     public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
618 
619     }
620 
621     @Override
622     public void setDataObject(Object object) {
623         super.setDataObject(object);
624         
625         if(object instanceof PersistableBusinessObject) {
626             this.businessObject = (PersistableBusinessObject)object;
627         }
628     }
629 
630     @Override
631     public String getDocumentTitle(MaintenanceDocument document) {
632         return super.getDocumentTitle((org.kuali.rice.krad.maintenance.MaintenanceDocument) document);
633     }
634 
635     /**
636      * @return Returns the instance of the business object being maintained.
637      */
638     public PersistableBusinessObject getBusinessObject() {
639         return businessObject;
640     }
641 
642     /**
643      * @param businessObject
644      *            Sets the instance of a business object that will be
645      *            maintained.
646      */
647     public void setBusinessObject(PersistableBusinessObject businessObject) {
648         this.businessObject = businessObject;
649         setDataObject(businessObject);
650     }
651 
652 	/**
653 	 * @return Returns the boClass.
654 	 */
655 	public Class getBoClass() {
656 		return super.getDataObjectClass();
657 	}
658 
659 	/**
660 	 * @param boClass
661 	 *            The boClass to set.
662 	 */
663 	public void setBoClass(Class boClass) {
664 		setDataObjectClass(boClass);
665 	}
666 
667 	/**
668 	 * 
669 	 * @see Maintainable#setGenerateDefaultValues()
670 	 */
671 	public void setGenerateDefaultValues(String docTypeName) {
672 		List<MaintainableSectionDefinition> sectionDefinitions = getMaintenanceDocumentDictionaryService()
673 				.getMaintainableSections(docTypeName);
674 		Map defaultValues = new HashMap();
675 
676 		try {
677 			// iterate through section definitions
678 			for (Iterator iter = sectionDefinitions.iterator(); iter.hasNext();) {
679 
680 				MaintainableSectionDefinition maintSectionDef = (MaintainableSectionDefinition) iter.next();
681 				Collection maintItems = maintSectionDef.getMaintainableItems();
682 				for (Iterator iterator = maintItems.iterator(); iterator.hasNext();) {
683 					MaintainableItemDefinition item = (MaintainableItemDefinition) iterator.next();
684 
685 					if (item instanceof MaintainableFieldDefinition) {
686 						MaintainableFieldDefinition maintainableFieldDefinition = (MaintainableFieldDefinition) item;
687 
688 						String defaultValue = maintainableFieldDefinition.getDefaultValue();
689 						if (defaultValue != null) {
690 							if (defaultValue.equals("true")) {
691 								defaultValue = "Yes";
692 							}
693 							else if (defaultValue.equals("false")) {
694 								defaultValue = "No";
695 							}
696 						}
697 
698 						Class defaultValueFinderClass = maintainableFieldDefinition.getDefaultValueFinderClass();
699 						if (defaultValueFinderClass != null) {
700 							defaultValue = ((ValueFinder) defaultValueFinderClass.newInstance()).getValue();
701 
702 						}
703 						if (defaultValue != null) {
704 							defaultValues.put(item.getName(), defaultValue);
705 						}
706 					}
707 				}
708 			}
709 			Map cachedValues = FieldUtils.populateBusinessObjectFromMap(getBusinessObject(), defaultValues);
710 		}
711 		catch (Exception e) {
712 			LOG.error("Unable to set default value " + e.getMessage(), e);
713 			throw new RuntimeException("Unable to set default value" + e.getMessage(), e);
714 		}
715 
716 	}
717 
718 	/**
719 	 * 
720 	 * @see Maintainable#setGenerateBlankRequiredValues()
721 	 */
722 	public void setGenerateBlankRequiredValues(String docTypeName) {
723 		try {
724 			List<MaintainableSectionDefinition> sectionDefinitions = getMaintenanceDocumentDictionaryService()
725 					.getMaintainableSections(docTypeName);
726 			Map<String, String> defaultValues = new HashMap<String, String>();
727 
728 			for (MaintainableSectionDefinition maintSectionDef : sectionDefinitions) {
729 				for (MaintainableItemDefinition item : maintSectionDef.getMaintainableItems()) {
730 					if (item instanceof MaintainableFieldDefinition) {
731 						MaintainableFieldDefinition maintainableFieldDefinition = (MaintainableFieldDefinition) item;
732 						if (maintainableFieldDefinition.isRequired()
733 								&& maintainableFieldDefinition.isUnconditionallyReadOnly()) {
734 							Object currPropVal = ObjectUtils.getPropertyValue(this.getBusinessObject(), item.getName());
735 							if (currPropVal == null
736 									|| (currPropVal instanceof String && StringUtils.isBlank((String) currPropVal))) {
737 								Class<? extends ValueFinder> defaultValueFinderClass = maintainableFieldDefinition
738 										.getDefaultValueFinderClass();
739 								if (defaultValueFinderClass != null) {
740 									String defaultValue = defaultValueFinderClass.newInstance().getValue();
741 									if (defaultValue != null) {
742 										defaultValues.put(item.getName(), defaultValue);
743 									}
744 								}
745 							}
746 						}
747 					}
748 				}
749 			}
750 			FieldUtils.populateBusinessObjectFromMap(getBusinessObject(), defaultValues);
751 		}
752 		catch (Exception e) {
753 			LOG.error("Unable to set blank required value " + e.getMessage(), e);
754 			throw new RuntimeException("Unable to set blank required value" + e.getMessage(), e);
755 		}
756 	}
757 
758 	@Deprecated
759 	public void processAfterAddLine(String colName, Class colClass) {
760 	}
761 
762 	/**
763 	 * @see Maintainable#processBeforeAddLine(java.lang.String,
764 	 *      java.lang.Class, org.kuali.rice.krad.bo.BusinessObject)
765 	 */
766 	public void processBeforeAddLine(String colName, Class colClass, BusinessObject addBO) {
767 	}
768 
769 	/**
770 	 * @see Maintainable#getShowInactiveRecords(java.lang.String)
771 	 */
772 	public boolean getShowInactiveRecords(String collectionName) {
773 		return InactiveRecordsHidingUtils.getShowInactiveRecords(inactiveRecordDisplay, collectionName);
774 	}
775 
776 	/**
777 	 * @see Maintainable#setShowInactiveRecords(java.lang.String,
778 	 *      boolean)
779 	 */
780 	public void setShowInactiveRecords(String collectionName, boolean showInactive) {
781 		InactiveRecordsHidingUtils.setShowInactiveRecords(inactiveRecordDisplay, collectionName, showInactive);
782 	}
783 
784 	/**
785 	 * @return the inactiveRecordDisplay
786 	 */
787 	public Map<String, Boolean> getInactiveRecordDisplay() {
788 		return inactiveRecordDisplay;
789 	}
790 
791 	public void addNewLineToCollection(String collectionName) {
792 
793 		if (LOG.isDebugEnabled()) {
794 			LOG.debug("addNewLineToCollection( " + collectionName + " )");
795 		}
796 		// get the new line from the map
797 		PersistableBusinessObject addLine = newCollectionLines.get(collectionName);
798 		if (addLine != null) {
799 			// mark the isNewCollectionRecord so the option to delete this line
800 			// will be presented
801 			addLine.setNewCollectionRecord(true);
802 
803 			// if we add back add button on sub collection of an "add line" we
804 			// may need extra logic here
805 
806 			// get the collection from the business object
807 			Collection maintCollection = (Collection) ObjectUtils.getPropertyValue(getBusinessObject(), collectionName);
808 			// add the line to the collection
809 			maintCollection.add(addLine);
810 			// refresh parent object since attributes could of changed prior to
811 			// user clicking add
812 
813 			String referencesToRefresh = LookupUtils
814 					.convertReferencesToSelectCollectionToString(getAllRefreshableReferences(getBusinessObject()
815 							.getClass()));
816 			if (LOG.isInfoEnabled()) {
817 				LOG.info("References to refresh for adding line to collection " + collectionName + ": "
818 						+ referencesToRefresh);
819 			}
820 			refreshReferences(referencesToRefresh);
821 		}
822 
823 		initNewCollectionLine(collectionName);
824 
825 	}
826 
827 	public PersistableBusinessObject getNewCollectionLine(String collectionName) {
828 		if (LOG.isDebugEnabled()) {
829 			// LOG.debug( this + ") getNewCollectionLine( " + collectionName +
830 			// ")", new Exception( "tracing exception") );
831 			LOG.debug("newCollectionLines: " + newCollectionLines);
832 		}
833 		PersistableBusinessObject addLine = newCollectionLines.get(collectionName);
834 		if (addLine == null) {
835 			addLine = initNewCollectionLine(collectionName);
836 		}
837 		return addLine;
838 	}
839 
840 	public PersistableBusinessObject initNewCollectionLine(String collectionName) {
841 		if (LOG.isDebugEnabled()) {
842 			LOG.debug("initNewCollectionLine( " + collectionName + " )");
843 		}
844 		// try to get the object from the map
845 		// BusinessObject addLine = newCollectionLines.get( collectionName );
846 		// if ( addLine == null ) {
847 		// if not there, instantiate a new one
848 		PersistableBusinessObject addLine;
849 		try {
850 			addLine = (PersistableBusinessObject) getMaintenanceDocumentDictionaryService()
851 					.getCollectionBusinessObjectClass(getDocumentTypeName(), collectionName).newInstance();
852 		}
853 		catch (Exception ex) {
854 			LOG.error("unable to instantiate new collection line", ex);
855 			throw new RuntimeException("unable to instantiate new collection line", ex);
856 		}
857 		// and add it to the map
858 		newCollectionLines.put(collectionName, addLine);
859 		// }
860 		// set its values to the defaults
861 		setNewCollectionLineDefaultValues(collectionName, addLine);
862 		return addLine;
863 	}
864 
865 	/**
866 	 * 
867 	 * @see Maintainable#populateNewCollectionLines(java.util.Map)
868 	 */
869 	public Map<String, String> populateNewCollectionLines(Map<String, String> fieldValues,
870 			MaintenanceDocument maintenanceDocument, String methodToCall) {
871 		if (LOG.isDebugEnabled()) {
872 			LOG.debug("populateNewCollectionLines: " + fieldValues);
873 		}
874 		fieldValues = decryptEncryptedData(fieldValues, maintenanceDocument, methodToCall);
875 
876 		Map<String, String> cachedValues = new HashMap<String, String>();
877 
878 		// loop over all collections with an enabled add line
879 		List<MaintainableCollectionDefinition> collections = getMaintenanceDocumentDictionaryService()
880 				.getMaintainableCollections(getDocumentTypeName());
881 
882 		for (MaintainableCollectionDefinition coll : collections) {
883 			// get the collection name
884 			String collName = coll.getName();
885 			if (LOG.isDebugEnabled()) {
886 				LOG.debug("checking for collection: " + collName);
887 			}
888 			// build a map for that collection
889 			Map<String, String> collectionValues = new HashMap<String, String>();
890 			Map<String, String> subCollectionValues = new HashMap<String, String>();
891 			// loop over the collection, extracting entries with a matching
892 			// prefix
893 			for (Map.Entry<String, String> entry : fieldValues.entrySet()) {
894 				String key = entry.getKey();
895 				if (key.startsWith(collName)) {
896 					String subStrKey = key.substring(collName.length() + 1);
897 					// check for subcoll w/ '[', set collName to propername and
898 					// put in correct name for collection values (i.e. strip
899 					// '*[x].')
900 					if (key.contains("[")) {
901 
902 						// collName = StringUtils.substringBeforeLast(key,"[");
903 
904 						// need the whole thing if subcollection
905 						subCollectionValues.put(key, entry.getValue());
906 					}
907 					else {
908 						collectionValues.put(subStrKey, entry.getValue());
909 					}
910 				}
911 			}
912 			// send those values to the business object
913 			if (LOG.isDebugEnabled()) {
914 				LOG.debug("values for collection: " + collectionValues);
915 			}
916 			cachedValues.putAll(FieldUtils.populateBusinessObjectFromMap(getNewCollectionLine(collName),
917 					collectionValues, KRADConstants.MAINTENANCE_ADD_PREFIX + collName + "."));
918 			performFieldForceUpperCase(getNewCollectionLine(collName), collectionValues);
919 
920 			cachedValues.putAll(populateNewSubCollectionLines(coll, subCollectionValues));
921 		}
922 
923 		// cachedValues.putAll( FieldUtils.populateBusinessObjectFromMap( ))
924 		return cachedValues;
925 	}
926 
927 	/*
928 	 * Yes, I think this could be merged with the above code - I'm leaving it
929 	 * separate until I figure out of there are any issues which would reqire
930 	 * that it be separated.
931 	 */
932 	protected Map populateNewSubCollectionLines(MaintainableCollectionDefinition parentCollection, Map fieldValues) {
933 		if (LOG.isDebugEnabled()) {
934 			LOG.debug("populateNewSubCollectionLines: " + fieldValues);
935 		}
936 		Map cachedValues = new HashMap();
937 
938 		for (MaintainableCollectionDefinition coll : parentCollection.getMaintainableCollections()) {
939 			// get the collection name
940 			String collName = coll.getName();
941 
942 			if (LOG.isDebugEnabled()) {
943 				LOG.debug("checking for sub collection: " + collName);
944 			}
945 			Map<String, String> parents = new HashMap<String, String>();
946 			// get parents from list
947 			for (Object entry : fieldValues.entrySet()) {
948 				String key = (String) ((Map.Entry) entry).getKey();
949 				if (key.contains(collName)) {
950 					parents.put(StringUtils.substringBefore(key, "."), "");
951 				}
952 			}
953 
954 			for (String parent : parents.keySet()) {
955 				// build a map for that collection
956 				Map<String, Object> collectionValues = new HashMap<String, Object>();
957 				// loop over the collection, extracting entries with a matching
958 				// prefix
959 				for (Object entry : fieldValues.entrySet()) {
960 					String key = (String) ((Map.Entry) entry).getKey();
961 					if (key.contains(parent)) {
962 						String substr = StringUtils.substringAfterLast(key, ".");
963 						collectionValues.put(substr, ((Map.Entry) entry).getValue());
964 					}
965 				}
966 				// send those values to the business object
967 				if (LOG.isDebugEnabled()) {
968 					LOG.debug("values for sub collection: " + collectionValues);
969 				}
970 				GlobalVariables.getMessageMap().addToErrorPath(
971 						KRADConstants.MAINTENANCE_ADD_PREFIX + parent + "." + collName);
972 				cachedValues.putAll(FieldUtils.populateBusinessObjectFromMap(getNewCollectionLine(parent + "."
973 						+ collName), collectionValues, KRADConstants.MAINTENANCE_ADD_PREFIX + parent + "." + collName
974 						+ "."));
975 				performFieldForceUpperCase(getNewCollectionLine(parent + "." + collName), collectionValues);
976 				GlobalVariables.getMessageMap().removeFromErrorPath(
977 						KRADConstants.MAINTENANCE_ADD_PREFIX + parent + "." + collName);
978 			}
979 
980 			cachedValues.putAll(populateNewSubCollectionLines(coll, fieldValues));
981 		}
982 
983 		return cachedValues;
984 	}
985 
986 	public Collection<String> getAffectedReferencesFromLookup(BusinessObject baseBO, String attributeName,
987 			String collectionPrefix) {
988 		PersistenceStructureService pss = getPersistenceStructureService();
989 		String nestedBOPrefix = "";
990 		if (ObjectUtils.isNestedAttribute(attributeName)) {
991 			// if we're performing a lookup on a nested attribute, we need to
992 			// use the nested BO all the way down the chain
993 			nestedBOPrefix = ObjectUtils.getNestedAttributePrefix(attributeName);
994 
995 			// renormalize the base BO so that the attribute name is not nested
996 			// anymore
997 			Class reference = ObjectUtils.getPropertyType(baseBO, nestedBOPrefix, pss);
998 			if (!(PersistableBusinessObject.class.isAssignableFrom(reference))) {
999 				return new ArrayList<String>();
1000 			}
1001 
1002 			try {
1003 				baseBO = (PersistableBusinessObject) reference.newInstance();
1004 			}
1005 			catch (InstantiationException e) {
1006 				LOG.error(e);
1007 			}
1008 			catch (IllegalAccessException e) {
1009 				LOG.error(e);
1010 			}
1011 			attributeName = ObjectUtils.getNestedAttributePrimitive(attributeName);
1012 		}
1013 
1014 		if (baseBO == null) {
1015 			return new ArrayList<String>();
1016 		}
1017 
1018 		Map<String, Class> referenceNameToClassFromPSS = LookupUtils.getPrimitiveReference(baseBO, attributeName);
1019 		if (referenceNameToClassFromPSS.size() > 1) {
1020 			LOG.error("LookupUtils.getPrimitiveReference return results should only have at most one element");
1021 		}
1022 
1023 		BusinessObjectMetaDataService businessObjectMetaDataService = getBusinessObjectMetaDataService();
1024 		DataObjectRelationship relationship = businessObjectMetaDataService.getBusinessObjectRelationship(baseBO,
1025 				attributeName);
1026 		if (relationship == null) {
1027 			return new ArrayList<String>();
1028 		}
1029 
1030 		Map<String, String> fkToPkMappings = relationship.getParentToChildReferences();
1031 
1032 		Collection<String> affectedReferences = generateAllAffectedReferences(baseBO.getClass(), fkToPkMappings,
1033 				nestedBOPrefix, collectionPrefix);
1034 		if (LOG.isDebugEnabled()) {
1035 			LOG.debug("References affected by a lookup on BO attribute \"" + collectionPrefix + nestedBOPrefix + "."
1036 					+ attributeName + ": " + affectedReferences);
1037 		}
1038 
1039 		return affectedReferences;
1040 	}
1041 
1042 	protected boolean isRelationshipRefreshable(Class boClass, String relationshipName) {
1043 		if (getPersistenceStructureService().isPersistable(boClass)) {
1044 			if (getPersistenceStructureService().hasCollection(boClass, relationshipName)) {
1045 				return !getPersistenceStructureService().isCollectionUpdatable(boClass, relationshipName);
1046 			}
1047 			else if (getPersistenceStructureService().hasReference(boClass, relationshipName)) {
1048 				return !getPersistenceStructureService().isReferenceUpdatable(boClass, relationshipName);
1049 			}
1050 			// else, assume that the relationship is defined in the DD
1051 		}
1052 
1053 		return true;
1054 	}
1055 
1056 	protected Collection<String> generateAllAffectedReferences(Class boClass, Map<String, String> fkToPkMappings,
1057 			String nestedBOPrefix, String collectionPrefix) {
1058 		Set<String> allAffectedReferences = new HashSet<String>();
1059 		DataDictionaryService dataDictionaryService = getDataDictionaryService();
1060 		PersistenceStructureService pss = getPersistenceStructureService();
1061 
1062 		collectionPrefix = StringUtils.isBlank(collectionPrefix) ? "" : collectionPrefix;
1063 
1064 		// retrieve the attributes that are affected by a lookup on
1065 		// attributeName.
1066 		Collection<String> attributeReferenceFKAttributes = fkToPkMappings.keySet();
1067 
1068 		// a lookup on an attribute may cause other attributes to be updated
1069 		// (e.g. account code lookup would also affect chart code)
1070 		// build a list of all affected FK values via mapKeyFields above, and
1071 		// for each FK, see if there are any non-updatable references with that
1072 		// FK
1073 
1074 		// deal with regular simple references (<reference-descriptor>s in OJB)
1075 		for (String fkAttribute : attributeReferenceFKAttributes) {
1076 			for (String affectedReference : pss.getReferencesForForeignKey(boClass, fkAttribute).keySet()) {
1077 				if (isRelationshipRefreshable(boClass, affectedReference)) {
1078 					if (StringUtils.isBlank(nestedBOPrefix)) {
1079 						allAffectedReferences.add(collectionPrefix + affectedReference);
1080 					}
1081 					else {
1082 						allAffectedReferences.add(collectionPrefix + nestedBOPrefix + "." + affectedReference);
1083 					}
1084 				}
1085 			}
1086 		}
1087 
1088 		// now with collection references (<collection-descriptor>s in OJB)
1089 		for (String collectionName : pss.listCollectionObjectTypes(boClass).keySet()) {
1090 			if (isRelationshipRefreshable(boClass, collectionName)) {
1091 				Map<String, String> keyMappingsForCollection = pss.getInverseForeignKeysForCollection(boClass,
1092 						collectionName);
1093 				for (String collectionForeignKey : keyMappingsForCollection.keySet()) {
1094 					if (attributeReferenceFKAttributes.contains(collectionForeignKey)) {
1095 						if (StringUtils.isBlank(nestedBOPrefix)) {
1096 							allAffectedReferences.add(collectionPrefix + collectionName);
1097 						}
1098 						else {
1099 							allAffectedReferences.add(collectionPrefix + nestedBOPrefix + "." + collectionName);
1100 						}
1101 					}
1102 				}
1103 			}
1104 		}
1105 
1106 		// now use the DD to compute more affected references
1107 		List<String> ddDefinedRelationships = dataDictionaryService.getRelationshipNames(boClass.getName());
1108 		for (String ddRelationship : ddDefinedRelationships) {
1109 			// note that this map is PK (key/target) => FK (value/source)
1110 			Map<String, String> referencePKtoFKmappings = dataDictionaryService.getRelationshipAttributeMap(
1111 					boClass.getName(), ddRelationship);
1112 			for (String sourceAttribute : referencePKtoFKmappings.values()) {
1113 				// the sourceAttribute is the FK pointing to the target
1114 				// attribute (PK)
1115 				if (attributeReferenceFKAttributes.contains(sourceAttribute)) {
1116 					for (String affectedReference : dataDictionaryService.getRelationshipEntriesForSourceAttribute(
1117 							boClass.getName(), sourceAttribute)) {
1118 						if (isRelationshipRefreshable(boClass, ddRelationship)) {
1119 							if (StringUtils.isBlank(nestedBOPrefix)) {
1120 								allAffectedReferences.add(affectedReference);
1121 							}
1122 							else {
1123 								allAffectedReferences.add(nestedBOPrefix + "." + affectedReference);
1124 							}
1125 						}
1126 					}
1127 				}
1128 			}
1129 		}
1130 		return allAffectedReferences;
1131 	}
1132 
1133 	protected Collection<String> getAllRefreshableReferences(Class boClass) {
1134 		HashSet<String> references = new HashSet<String>();
1135 		for (String referenceName : getPersistenceStructureService().listReferenceObjectFields(boClass).keySet()) {
1136 			if (isRelationshipRefreshable(boClass, referenceName)) {
1137 				references.add(referenceName);
1138 			}
1139 		}
1140 		for (String collectionName : getPersistenceStructureService().listCollectionObjectTypes(boClass).keySet()) {
1141 			if (isRelationshipRefreshable(boClass, collectionName)) {
1142 				references.add(collectionName);
1143 			}
1144 		}
1145 		for (String relationshipName : getDataDictionaryService().getRelationshipNames(boClass.getName())) {
1146 			if (isRelationshipRefreshable(boClass, relationshipName)) {
1147 				references.add(relationshipName);
1148 			}
1149 		}
1150 		return references;
1151 	}
1152 
1153 	protected void setNewCollectionLineDefaultValues(String collectionName, PersistableBusinessObject addLine) {
1154 		PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(addLine);
1155 		for (int i = 0; i < descriptors.length; ++i) {
1156 			PropertyDescriptor propertyDescriptor = descriptors[i];
1157 
1158 			String fieldName = propertyDescriptor.getName();
1159 			Class propertyType = propertyDescriptor.getPropertyType();
1160 			String value = getMaintenanceDocumentDictionaryService().getCollectionFieldDefaultValue(getDocumentTypeName(),
1161 					collectionName, fieldName);
1162 
1163 			if (value != null) {
1164 				try {
1165 					ObjectUtils.setObjectProperty(addLine, fieldName, propertyType, value);
1166 				}
1167 				catch (Exception ex) {
1168 					LOG.error("Unable to set default property of collection object: " + "\nobject: " + addLine
1169 							+ "\nfieldName=" + fieldName + "\npropertyType=" + propertyType + "\nvalue=" + value, ex);
1170 				}
1171 			}
1172 
1173 		}
1174 	}
1175 
1176 	/**
1177 	 * @see Maintainable#clearBusinessObjectOfRestrictedValues(org.kuali.rice.kns.document.authorization.MaintenanceDocumentRestrictions)
1178 	 */
1179 	public void clearBusinessObjectOfRestrictedValues(MaintenanceDocumentRestrictions maintenanceDocumentRestrictions) {
1180 		List<MaintainableSectionDefinition> sections = getMaintenanceDocumentDictionaryService()
1181 				.getMaintainableSections(getDocumentTypeName());
1182 		for (MaintainableSectionDefinition sectionDefinition : sections) {
1183 			for (MaintainableItemDefinition itemDefinition : sectionDefinition.getMaintainableItems()) {
1184 				if (itemDefinition instanceof MaintainableFieldDefinition) {
1185 					clearFieldRestrictedValues("", businessObject, (MaintainableFieldDefinition) itemDefinition,
1186 							maintenanceDocumentRestrictions);
1187 				}
1188 				else if (itemDefinition instanceof MaintainableCollectionDefinition) {
1189 					clearCollectionRestrictedValues("", businessObject,
1190 							(MaintainableCollectionDefinition) itemDefinition, maintenanceDocumentRestrictions);
1191 				}
1192 			}
1193 		}
1194 	}
1195 
1196 	protected void clearCollectionRestrictedValues(String fieldNamePrefix, BusinessObject businessObject,
1197 			MaintainableCollectionDefinition collectionDefinition,
1198 			MaintenanceDocumentRestrictions maintenanceDocumentRestrictions) {
1199 		String collectionName = fieldNamePrefix + collectionDefinition.getName();
1200 		Collection<BusinessObject> collection = (Collection<BusinessObject>) ObjectUtils.getPropertyValue(
1201 				businessObject, collectionDefinition.getName());
1202 
1203 		if (collection != null) {
1204 			int i = 0;
1205 			// even though it's technically a Collection, we're going to index
1206 			// it like a list
1207 			for (BusinessObject collectionItem : collection) {
1208 				String collectionItemNamePrefix = collectionName + "[" + i + "].";
1209 				for (MaintainableCollectionDefinition subCollectionDefinition : collectionDefinition
1210 						.getMaintainableCollections()) {
1211 					clearCollectionRestrictedValues(collectionItemNamePrefix, collectionItem, subCollectionDefinition,
1212 							maintenanceDocumentRestrictions);
1213 				}
1214 				for (MaintainableFieldDefinition fieldDefinition : collectionDefinition.getMaintainableFields()) {
1215 					clearFieldRestrictedValues(collectionItemNamePrefix, collectionItem, fieldDefinition,
1216 							maintenanceDocumentRestrictions);
1217 				}
1218 				i++;
1219 			}
1220 		}
1221 	}
1222 
1223 	protected void clearFieldRestrictedValues(String fieldNamePrefix, BusinessObject businessObject,
1224 			MaintainableFieldDefinition fieldDefinition, MaintenanceDocumentRestrictions maintenanceDocumentRestrictions) {
1225 		String fieldName = fieldNamePrefix + fieldDefinition.getName();
1226 
1227 		FieldRestriction fieldRestriction = maintenanceDocumentRestrictions.getFieldRestriction(fieldName);
1228 		if (fieldRestriction.isRestricted()) {
1229 			String defaultValue = null;
1230 			if (StringUtils.isNotBlank(fieldDefinition.getDefaultValue())) {
1231 				defaultValue = fieldDefinition.getDefaultValue();
1232 			}
1233 			else if (fieldDefinition.getDefaultValueFinderClass() != null) {
1234 				try {
1235 					defaultValue = ((ValueFinder) fieldDefinition.getDefaultValueFinderClass().newInstance())
1236 							.getValue();
1237 				}
1238 				catch (Exception e) {
1239 					defaultValue = null;
1240 					LOG.error("Error trying to instantiate ValueFinder or to determine ValueFinder for doc type: "
1241 							+ getDocumentTypeName() + " field name " + fieldDefinition.getName() + " with field prefix: "
1242 							+ fieldNamePrefix, e);
1243 				}
1244 			}
1245 			try {
1246 				ObjectUtils.setObjectProperty(businessObject, fieldDefinition.getName(), defaultValue);
1247 			}
1248 			catch (Exception e) {
1249 				// throw an exception, because we don't want users to be able to
1250 				// see the restricted value
1251 				LOG.error("Unable to clear maintenance document values for field name: " + fieldName
1252 						+ " default value: " + defaultValue, e);
1253 				throw new RuntimeException("Unable to clear maintenance document values for field name: " + fieldName,
1254 						e);
1255 			}
1256 		}
1257 	}
1258 
1259 	protected void performForceUpperCase(Map fieldValues) {
1260 		List<MaintainableSectionDefinition> sections = getMaintenanceDocumentDictionaryService()
1261 				.getMaintainableSections(getDocumentTypeName());
1262 		for (MaintainableSectionDefinition sectionDefinition : sections) {
1263 			for (MaintainableItemDefinition itemDefinition : sectionDefinition.getMaintainableItems()) {
1264 				if (itemDefinition instanceof MaintainableFieldDefinition) {
1265 					performFieldForceUpperCase("", businessObject, (MaintainableFieldDefinition) itemDefinition,
1266 							fieldValues);
1267 				}
1268 				else if (itemDefinition instanceof MaintainableCollectionDefinition) {
1269 					performCollectionForceUpperCase("", businessObject,
1270 							(MaintainableCollectionDefinition) itemDefinition, fieldValues);
1271 
1272 				}
1273 			}
1274 		}
1275 	}
1276 
1277 	protected void performFieldForceUpperCase(String fieldNamePrefix, BusinessObject bo,
1278 			MaintainableFieldDefinition fieldDefinition, Map fieldValues) {
1279 		MessageMap errorMap = GlobalVariables.getMessageMap();
1280 		String fieldName = fieldDefinition.getName();
1281 		String mapKey = fieldNamePrefix + fieldName;
1282 		if (fieldValues != null && fieldValues.get(mapKey) != null) {
1283 			if (PropertyUtils.isWriteable(bo, fieldName) && ObjectUtils.getNestedValue(bo, fieldName) != null) {
1284 
1285 				try {
1286 					Class type = ObjectUtils.easyGetPropertyType(bo, fieldName);
1287 					// convert to upperCase based on data dictionary
1288 					Class businessObjectClass = bo.getClass();
1289 					boolean upperCase = false;
1290 					try {
1291 						upperCase = getDataDictionaryService().getAttributeForceUppercase(businessObjectClass,
1292 								fieldName);
1293 					}
1294 					catch (UnknownBusinessClassAttributeException t) {
1295 						boolean catchme = true;
1296 						// throw t;
1297 					}
1298 
1299 					Object fieldValue = ObjectUtils.getNestedValue(bo, fieldName);
1300 
1301 					if (upperCase && fieldValue instanceof String) {
1302 						fieldValue = ((String) fieldValue).toUpperCase();
1303 					}
1304 					ObjectUtils.setObjectProperty(bo, fieldName, type, fieldValue);
1305 				}
1306 				catch (FormatException e) {
1307 					errorMap.putError(fieldName, e.getErrorKey(), e.getErrorArgs());
1308 				}
1309 				catch (IllegalAccessException e) {
1310 					LOG.error("unable to populate business object" + e.getMessage());
1311 					throw new RuntimeException(e.getMessage(), e);
1312 				}
1313 				catch (InvocationTargetException e) {
1314 					LOG.error("unable to populate business object" + e.getMessage());
1315 					throw new RuntimeException(e.getMessage(), e);
1316 				}
1317 				catch (NoSuchMethodException e) {
1318 					LOG.error("unable to populate business object" + e.getMessage());
1319 					throw new RuntimeException(e.getMessage(), e);
1320 				}
1321 			}
1322 		}
1323 	}
1324 
1325 	protected void performCollectionForceUpperCase(String fieldNamePrefix, BusinessObject bo,
1326 			MaintainableCollectionDefinition collectionDefinition, Map fieldValues) {
1327 		String collectionName = fieldNamePrefix + collectionDefinition.getName();
1328 		Collection<BusinessObject> collection = (Collection<BusinessObject>) ObjectUtils.getPropertyValue(bo,
1329 				collectionDefinition.getName());
1330 		if (collection != null) {
1331 			int i = 0;
1332 			// even though it's technically a Collection, we're going to index
1333 			// it like a list
1334 			for (BusinessObject collectionItem : collection) {
1335 				String collectionItemNamePrefix = collectionName + "[" + i + "].";
1336 				// String collectionItemNamePrefix = "";
1337 				for (MaintainableFieldDefinition fieldDefinition : collectionDefinition.getMaintainableFields()) {
1338 					performFieldForceUpperCase(collectionItemNamePrefix, collectionItem, fieldDefinition, fieldValues);
1339 				}
1340 				for (MaintainableCollectionDefinition subCollectionDefinition : collectionDefinition
1341 						.getMaintainableCollections()) {
1342 					performCollectionForceUpperCase(collectionItemNamePrefix, collectionItem, subCollectionDefinition,
1343 							fieldValues);
1344 				}
1345 				i++;
1346 			}
1347 		}
1348 	}
1349 
1350 	protected void performFieldForceUpperCase(BusinessObject bo, Map fieldValues) {
1351 		MessageMap errorMap = GlobalVariables.getMessageMap();
1352 
1353 		try {
1354 			for (Iterator iter = fieldValues.keySet().iterator(); iter.hasNext();) {
1355 				String propertyName = (String) iter.next();
1356 
1357 				if (PropertyUtils.isWriteable(bo, propertyName) && fieldValues.get(propertyName) != null) {
1358 					// if the field propertyName is a valid property on the bo
1359 					// class
1360 					Class type = ObjectUtils.easyGetPropertyType(bo, propertyName);
1361 					try {
1362 						// Keep the convert to upperCase logic here. It will be
1363 						// used in populateNewCollectionLines,
1364 						// populateNewSubCollectionLines
1365 						// convert to upperCase based on data dictionary
1366 						Class businessObjectClass = bo.getClass();
1367 						boolean upperCase = false;
1368 						try {
1369 							upperCase = getDataDictionaryService().getAttributeForceUppercase(businessObjectClass,
1370 									propertyName);
1371 						}
1372 						catch (UnknownBusinessClassAttributeException t) {
1373 							boolean catchme = true;
1374 							// throw t;
1375 						}
1376 
1377 						Object fieldValue = fieldValues.get(propertyName);
1378 
1379 						if (upperCase && fieldValue instanceof String) {
1380 							fieldValue = ((String) fieldValue).toUpperCase();
1381 						}
1382 						ObjectUtils.setObjectProperty(bo, propertyName, type, fieldValue);
1383 					}
1384 					catch (FormatException e) {
1385 						errorMap.putError(propertyName, e.getErrorKey(), e.getErrorArgs());
1386 					}
1387 				}
1388 			}
1389 		}
1390 		catch (IllegalAccessException e) {
1391 			LOG.error("unable to populate business object" + e.getMessage());
1392 			throw new RuntimeException(e.getMessage(), e);
1393 		}
1394 		catch (InvocationTargetException e) {
1395 			LOG.error("unable to populate business object" + e.getMessage());
1396 			throw new RuntimeException(e.getMessage(), e);
1397 		}
1398 		catch (NoSuchMethodException e) {
1399 			LOG.error("unable to populate business object" + e.getMessage());
1400 			throw new RuntimeException(e.getMessage(), e);
1401 		}
1402 
1403 	}
1404 
1405 	/**
1406 	 * By default a maintainable is not external
1407 	 * 
1408 	 * @see Maintainable#isExternalBusinessObject()
1409 	 */
1410 	public boolean isExternalBusinessObject() {
1411 		return false;
1412 	}
1413 
1414 	/**
1415 	 * @see Maintainable#getExternalBusinessObject()
1416 	 */
1417 	public void prepareBusinessObject(BusinessObject businessObject) {
1418 		// Do nothing by default
1419 	}
1420 
1421 	// 3070
1422 	public void deleteBusinessObject() {
1423 		if (businessObject == null)
1424 			return;
1425 
1426 		KRADServiceLocator.getBusinessObjectService().delete(businessObject);
1427 		businessObject = null;
1428 	}
1429 
1430 	public boolean isOldBusinessObjectInDocument() {
1431 		return super.isOldDataObjectInDocument();
1432 	}
1433 
1434 	protected BusinessObjectDictionaryService getBusinessObjectDictionaryService() {
1435 		if (businessObjectDictionaryService == null) {
1436 			businessObjectDictionaryService = KNSServiceLocator.getBusinessObjectDictionaryService();
1437 		}
1438 		return businessObjectDictionaryService;
1439 	}
1440 
1441 	protected PersonService getPersonService() {
1442 		if (personService == null) {
1443 			personService = KimApiServiceLocator.getPersonService();
1444 		}
1445 		return personService;
1446 	}
1447 
1448 	protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
1449 		if (businessObjectMetaDataService == null) {
1450 			businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
1451 		}
1452 		return businessObjectMetaDataService;
1453 	}
1454 
1455 	protected BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
1456 		if (businessObjectAuthorizationService == null) {
1457 			businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
1458 		}
1459 		return businessObjectAuthorizationService;
1460 	}
1461 
1462 	protected DocumentHelperService getDocumentHelperService() {
1463 		if (documentHelperService == null) {
1464 			documentHelperService = KNSServiceLocator.getDocumentHelperService();
1465 		}
1466 		return documentHelperService;
1467 	}
1468 
1469 	public void setBusinessObjectDictionaryService(BusinessObjectDictionaryService businessObjectDictionaryService) {
1470 		this.businessObjectDictionaryService = businessObjectDictionaryService;
1471 	}
1472 
1473 	public void setPersonService(PersonService personService) {
1474 		this.personService = personService;
1475 	}
1476 
1477 	public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) {
1478 		this.businessObjectMetaDataService = businessObjectMetaDataService;
1479 	}
1480 
1481 	public void setBusinessObjectAuthorizationService(
1482 			BusinessObjectAuthorizationService businessObjectAuthorizationService) {
1483 		this.businessObjectAuthorizationService = businessObjectAuthorizationService;
1484 	}
1485 
1486 	public void setDocumentHelperService(DocumentHelperService documentHelperService) {
1487 		this.documentHelperService = documentHelperService;
1488 	}
1489 
1490     public MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
1491         if (maintenanceDocumentDictionaryService == null) {
1492             this.maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
1493         }
1494         return maintenanceDocumentDictionaryService;
1495     }
1496 
1497     public void setMaintenanceDocumentDictionaryService(
1498             MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
1499         this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
1500     }
1501 }