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