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