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