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.web.ui;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.mo.common.active.Inactivatable;
21  import org.kuali.rice.kns.datadictionary.CollectionDefinitionI;
22  import org.kuali.rice.kns.datadictionary.FieldDefinition;
23  import org.kuali.rice.kns.datadictionary.FieldDefinitionI;
24  import org.kuali.rice.kns.datadictionary.InquiryCollectionDefinition;
25  import org.kuali.rice.kns.datadictionary.InquirySectionDefinition;
26  import org.kuali.rice.kns.datadictionary.InquirySubSectionHeaderDefinition;
27  import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
28  import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
29  import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
30  import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
31  import org.kuali.rice.kns.datadictionary.MaintainableSubSectionHeaderDefinition;
32  import org.kuali.rice.kns.datadictionary.SubSectionHeaderDefinitionI;
33  import org.kuali.rice.kns.document.authorization.FieldRestriction;
34  import org.kuali.rice.kns.inquiry.Inquirable;
35  import org.kuali.rice.kns.inquiry.InquiryRestrictions;
36  import org.kuali.rice.kns.lookup.LookupUtils;
37  import org.kuali.rice.kns.maintenance.Maintainable;
38  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
39  import org.kuali.rice.kns.service.KNSServiceLocator;
40  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
41  import org.kuali.rice.kns.util.FieldUtils;
42  import org.kuali.rice.kns.util.KNSConstants;
43  import org.kuali.rice.kns.util.MaintenanceUtils;
44  import org.kuali.rice.kns.util.WebUtils;
45  import org.kuali.rice.krad.bo.BusinessObject;
46  import org.kuali.rice.krad.bo.PersistableBusinessObject;
47  import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
48  import org.kuali.rice.krad.exception.ClassNotPersistableException;
49  import org.kuali.rice.krad.service.DataDictionaryService;
50  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
51  import org.kuali.rice.krad.util.KRADConstants;
52  import org.kuali.rice.krad.util.ObjectUtils;
53  
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.HashMap;
57  import java.util.HashSet;
58  import java.util.Iterator;
59  import java.util.List;
60  import java.util.Map;
61  import java.util.Set;
62  
63  @Deprecated
64  public class SectionBridge {
65      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SectionBridge.class);
66      private static BusinessObjectAuthorizationService businessObjectAuthorizationService;
67      private static BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
68      	if (businessObjectAuthorizationService == null) {
69      		businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
70      	}
71      	return businessObjectAuthorizationService;
72      }
73      private static DataDictionaryService dataDictionaryService;
74      private static DataDictionaryService getDataDictionaryService() {
75      	if (dataDictionaryService == null) {
76      		dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
77      	}
78      	return dataDictionaryService;
79      }
80      private static MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
81  
82      /**
83       * This method creates a Section for display on an Inquiry Screen.
84       * 
85       * @param sd The DD definition from which to construct the Section.
86       * @param o The BusinessObject from which to populate the Section values.
87       * @return A populated Section.
88       */
89      public static final Section toSection(Inquirable inquirable, InquirySectionDefinition sd, BusinessObject o, InquiryRestrictions auths) {
90          Section section = new Section();
91          section.setSectionId( sd.getId() );
92          section.setSectionTitle(sd.getTitle());
93          section.setRows(new ArrayList());
94          section.setDefaultOpen(sd.isDefaultOpen());
95          
96          if (sd.getNumberOfColumns() != null) {
97              section.setNumberOfColumns(sd.getNumberOfColumns());
98          }
99          else {
100             section.setNumberOfColumns(KRADConstants.DEFAULT_NUM_OF_COLUMNS);
101         }
102 
103         List<Field> sectionFields = new ArrayList();
104         for (FieldDefinition fieldDefinition : sd.getInquiryFields()) {
105             List row = new ArrayList();
106 
107             Field f = null;
108             if (fieldDefinition instanceof InquiryCollectionDefinition) {
109                 InquiryCollectionDefinition inquiryCollectionDefinition = (InquiryCollectionDefinition) fieldDefinition;
110 
111                 List<Row> sectionRows = new ArrayList();
112                 sectionRows = getContainerRows(section, inquiryCollectionDefinition, o, null, null, new ArrayList(), new HashSet<String>(), new StringBuffer(section.getErrorKey()), inquiryCollectionDefinition.getNumberOfColumns(), inquirable);
113                 section.setRows(sectionRows);
114             }
115             else if (fieldDefinition instanceof InquirySubSectionHeaderDefinition) {
116                 f = createMaintainableSubSectionHeader((InquirySubSectionHeaderDefinition) fieldDefinition);
117             }
118             else {
119                 f = FieldBridge.toField(fieldDefinition, o, section);
120             }
121 
122             if (null != f) {
123                 sectionFields.add(f);
124             }
125 
126         }
127 
128         if (!sectionFields.isEmpty()) {
129             section.setRows(FieldUtils.wrapFields(sectionFields, section.getNumberOfColumns()));
130         }
131 
132         applyInquirySectionAuthorizations(section, auths);
133         
134         section.setRows(reArrangeRows(section.getRows(), section.getNumberOfColumns()));
135         
136         return section;
137     }
138     
139    
140     private static final void applyInquirySectionAuthorizations(Section section, InquiryRestrictions inquiryRestrictions) {
141     	applyInquiryRowsAuthorizations(section.getRows(), inquiryRestrictions);
142     }
143     
144     private static final void applyInquiryRowsAuthorizations(List<Row> rows, InquiryRestrictions inquiryRestrictions) {
145     	for (Row row : rows) {
146     		List<Field> rowFields = row.getFields();
147     		for (Field field : rowFields) {
148     			applyInquiryFieldAuthorizations(field, inquiryRestrictions);
149     		}
150     	}
151     }
152     
153     protected static final void applyInquiryFieldAuthorizations(Field field, InquiryRestrictions inquiryRestrictions) {
154     	if (Field.CONTAINER.equals(field.getFieldType())) {
155     		applyInquiryRowsAuthorizations(field.getContainerRows(), inquiryRestrictions);
156     		field.setContainerRows(reArrangeRows(field.getContainerRows(), field.getNumberOfColumnsForCollection()));
157     	}
158     	else if (!Field.IMAGE_SUBMIT.equals(field.getFieldType())) {
159     		FieldRestriction fieldRestriction = inquiryRestrictions.getFieldRestriction(field.getPropertyName());
160     		if (fieldRestriction.isHidden()) {
161     			field.setFieldType(Field.HIDDEN);
162     			field.setPropertyValue(null);
163     		}
164     		// KULRICE-8271: partially masked field can't be masked properly 
165     		else if (fieldRestriction.isMasked() || fieldRestriction.isPartiallyMasked()) {
166             	field.setSecure(true);
167             	MaskFormatter maskFormatter = fieldRestriction.getMaskFormatter();
168             	String displayMaskValue = maskFormatter.maskValue(field.getPropertyValue());
169             	field.setDisplayMaskValue(displayMaskValue);
170             	// since it's an inquiry, let's wipe out the encrypted field value since we don't need to post it back
171             	field.setEncryptedValue("");
172     		}
173        	}
174     }
175     
176     //This method is used to remove hidden fields (To fix JIRA KFSMI-2449)
177     private static final List<Row> reArrangeRows(List<Row> rows, int numberOfColumns){
178     	List<Row> rearrangedRows = new ArrayList<Row>();
179     	
180     	for (Row row : rows) {
181     		List<Field> fields = new ArrayList<Field>();
182     		List<Field> rowFields = row.getFields();
183     		for (Field field : rowFields) {
184     			if(!Field.HIDDEN.equals(field.getFieldType()) && !Field.BLANK_SPACE.equals(field.getFieldType())){
185     				fields.add(field);
186     			}
187     		}
188     		List<Row> rewrappedFieldRows = FieldUtils.wrapFields(fields, numberOfColumns);
189     		if (row.isHidden()) {
190     			for (Row rewrappedRow : rewrappedFieldRows) {
191     				rewrappedRow.setHidden(true);
192     			}
193     		}
194     		rearrangedRows.addAll(rewrappedFieldRows);
195     	}
196     	
197     	return rearrangedRows;
198     }
199 
200     
201     /**
202      * This method creates a Section for a MaintenanceDocument.
203      * 
204      * @param sd The DD definition of the Section.
205      * @param o The BusinessObject from which the Section will be populated.
206      * @param maintainable
207      * @param maintenanceAction The action (new, newwithexisting, copy, edit, etc) requested from the UI.
208      * @param autoFillDefaultValues Should default values be auto-filled?
209      * @param autoFillBlankRequiredValues Should required values left blank on the UI be auto-filled?
210      * @param displayedFieldNames What fields are displayed on the UI?
211      * @return A populated Section.
212      * @throws InstantiationException
213      * @throws IllegalAccessException
214      */
215     public static final Section toSection(MaintainableSectionDefinition sd, BusinessObject o, Maintainable maintainable, Maintainable oldMaintainable, String maintenanceAction,  List<String> displayedFieldNames, Set<String> conditionallyRequiredMaintenanceFields) throws InstantiationException, IllegalAccessException {
216     	Section section = new Section();
217 
218         section.setSectionId( sd.getId() );
219         section.setSectionTitle(sd.getTitle());
220         section.setSectionClass(o.getClass());
221         section.setHidden( sd.isHidden() );
222         section.setDefaultOpen(sd.isDefaultOpen());
223         section.setHelpUrl(sd.getHelpUrl());
224 
225         // iterate through section maint items and contruct Field UI objects
226         Collection<MaintainableItemDefinition> maintItems = sd.getMaintainableItems();
227         List<Row> sectionRows = new ArrayList<Row>();
228         List<Field> sectionFields = new ArrayList<Field>();
229 
230         for (MaintainableItemDefinition maintItem : maintItems) {
231             Field field = FieldBridge.toField(maintItem, sd, o, maintainable, section, displayedFieldNames, conditionallyRequiredMaintenanceFields);
232             boolean skipAdd = false;
233 
234             // if CollectionDefiniton, then have a many section
235             if (maintItem instanceof MaintainableCollectionDefinition) {
236                 MaintainableCollectionDefinition definition = (MaintainableCollectionDefinition) maintItem;
237                 section.getContainedCollectionNames().add(maintItem.getName());
238 
239                 StringBuffer containerRowErrorKey = new StringBuffer();
240                 sectionRows = getContainerRows(section, definition, o, maintainable, oldMaintainable, displayedFieldNames, conditionallyRequiredMaintenanceFields, containerRowErrorKey, KRADConstants.DEFAULT_NUM_OF_COLUMNS, null);
241             } else if (maintItem instanceof MaintainableSubSectionHeaderDefinition) {
242                 MaintainableSubSectionHeaderDefinition definition = (MaintainableSubSectionHeaderDefinition) maintItem;
243                 field = createMaintainableSubSectionHeader(definition);
244             }
245 
246             if (!skipAdd) {
247                 sectionFields.add(field);
248             }
249         }
250 
251         // populate field values from business object
252         //if (o != null && !autoFillDefaultValues) {
253         if (o != null) {
254             sectionFields = FieldUtils.populateFieldsFromBusinessObject(sectionFields, o);
255 
256             /* if maintenance action is copy, clear out secure fields */
257             if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) {
258                 for (Iterator iterator = sectionFields.iterator(); iterator.hasNext();) {
259                     Field element = (Field) iterator.next();
260                     if (element.isSecure()) {
261                         element.setPropertyValue("");
262                     }
263                 }
264             }
265         }
266 
267         sectionRows.addAll(FieldUtils.wrapFields(sectionFields));
268         section.setRows(sectionRows);
269 
270         return section;
271 
272     }
273     
274     
275     /**
276      * @see #getContainerRows(Section, CollectionDefinitionI, BusinessObject, Maintainable, List<String>, StringBuffer, String,
277      *      boolean, int)
278      */
279     public static final List<Row> getContainerRows(Section s, CollectionDefinitionI collectionDefinition, BusinessObject o, Maintainable m, Maintainable oldMaintainable, List<String> displayedFieldNames, Set<String> conditionallyRequiredMaintenanceFields, StringBuffer containerRowErrorKey, int numberOfColumns, Inquirable inquirable) {
280         return getContainerRows(s, collectionDefinition, o, m, oldMaintainable, displayedFieldNames, conditionallyRequiredMaintenanceFields, containerRowErrorKey, "", false, numberOfColumns, inquirable);
281     }
282 
283     /**
284      * Builds a list of Rows with Fields of type containers for a many section.
285      * 
286      * @param s The Section containing the Collection/Container.
287      * @param collectionDefinition The DD definition of the Collection.
288      * @param o The BusinessObject from which the Container/Collection will be populated.
289      * @param m The Maintainable for the BO (needed by some methods called on FieldBridge, FieldUtils etc.)
290      * @param displayedFieldNames
291      * @param containerRowErrorKey The error key for the Container/Collection.
292      * @param parents
293      * @param hideAdd Should the add line be added to the Container/Collection?
294      * @param numberOfColumns In how many columns in the UI will the fields in the Container/Collection be shown?
295      * @return
296      */
297      public static final List<Row> getContainerRows(Section s, CollectionDefinitionI collectionDefinition, BusinessObject o, Maintainable m, Maintainable oldMaintainable, List<String> displayedFieldNames, Set<String> conditionallyRequiredMaintenanceFields, StringBuffer containerRowErrorKey, String parents, boolean hideAdd, int numberOfColumns, Inquirable inquirable) {
298         List<Row> containerRows = new ArrayList<Row>();
299         List<Field> collFields = new ArrayList<Field>();
300         
301         String collectionName = collectionDefinition.getName();
302         
303         // add the toggle inactive record display button for the collection
304         if (m != null && Inactivatable.class.isAssignableFrom(collectionDefinition.getBusinessObjectClass()) && StringUtils.isBlank(parents)) {
305             addShowInactiveButtonField(s, collectionName, !m.getShowInactiveRecords(collectionName));
306         }
307         if (inquirable != null && Inactivatable.class.isAssignableFrom(collectionDefinition.getBusinessObjectClass()) && StringUtils.isBlank(parents)) {
308             addShowInactiveButtonField(s, collectionName, !inquirable.getShowInactiveRecords(collectionName));
309         }
310         
311         // first need to populate the containerRows with the "new" form if available
312         if (!hideAdd) {
313             List<Field> newFormFields = new ArrayList<Field>();
314             if (collectionDefinition.getIncludeAddLine()) {
315 
316 
317                 newFormFields = FieldBridge.getNewFormFields(collectionDefinition, o, m, displayedFieldNames, conditionallyRequiredMaintenanceFields, containerRowErrorKey, parents, hideAdd, numberOfColumns);
318 
319 
320             } else if(collectionDefinition instanceof MaintainableCollectionDefinition) {
321                 MaintainableCollectionDefinition mcd = (MaintainableCollectionDefinition)collectionDefinition;
322                 if(FieldUtils.isCollectionMultipleLookupEnabled(mcd)) {
323                     //do just the top line for collection if add is not allowed
324                   newFormFields = FieldBridge.constructContainerField(collectionDefinition, parents, o, hideAdd, numberOfColumns, mcd.getName(), new ArrayList<Field>());
325                 }
326             }
327             if (null != newFormFields) {
328                 containerRows.add(new Row(newFormFields));
329             }
330         }
331 
332         Collection<? extends CollectionDefinitionI> collections = collectionDefinition.getCollections();
333         for (CollectionDefinitionI collection : collections) {
334             int subCollectionNumberOfColumn = numberOfColumns;
335             if (collectionDefinition instanceof InquiryCollectionDefinition) {
336                 InquiryCollectionDefinition icd = (InquiryCollectionDefinition) collection;
337                 if (icd.getNumberOfColumns() != null) {
338                     subCollectionNumberOfColumn = icd.getNumberOfColumns();
339                 }
340             }
341             // no colNum for add rows
342              containerRows.addAll(getContainerRows(s, collection, o, m, oldMaintainable, displayedFieldNames, conditionallyRequiredMaintenanceFields, containerRowErrorKey, parents + collectionDefinition.getName() + ".", true, subCollectionNumberOfColumn, inquirable));
343         }
344 
345         // then we need to loop through the existing collection and add those fields
346         Collection<? extends FieldDefinitionI> collectionFields = collectionDefinition.getFields();
347         // get label for collection
348         String collectionLabel = getDataDictionaryService().getCollectionLabel(o.getClass(), collectionDefinition.getName());
349         
350         // retrieve the summary label either from the override or from the DD
351         String collectionElementLabel = collectionDefinition.getSummaryTitle();
352         if (StringUtils.isEmpty(collectionElementLabel)) {
353             collectionElementLabel = getDataDictionaryService().getCollectionElementLabel(o.getClass().getName(), collectionDefinition.getName(), collectionDefinition.getBusinessObjectClass());
354         }
355 
356         boolean translateCodes = getMaintenanceDocumentDictionaryService().translateCodes(o.getClass());
357 
358         if (o != null) {
359             if (PropertyUtils.isWriteable(o, collectionDefinition.getName()) && ObjectUtils.getPropertyValue(o, collectionDefinition.getName()) != null) {
360                 Object obj = ObjectUtils.getPropertyValue(o, collectionName);
361                 
362                 Object oldObj = null;
363                 if (oldMaintainable != null && oldMaintainable.getBusinessObject() != null) {
364                     oldObj = ObjectUtils.getPropertyValue(oldMaintainable.getBusinessObject(), collectionName);
365                 }
366 
367                 if (obj instanceof List) {
368                     Map summaryFields = new HashMap();
369                     boolean hidableRowsPresent = false;
370                     for (int i = 0; i < ((List) obj).size(); i++) {
371                         BusinessObject lineBusinessObject = (BusinessObject) ((List) obj).get(i);
372                         
373                         if (lineBusinessObject instanceof PersistableBusinessObject) {
374                         	((PersistableBusinessObject) lineBusinessObject).refreshNonUpdateableReferences();
375                         }
376                         
377                         /*
378                          * Handle display of inactive records. The old maintainable is used to compare the old side (if it exists). If the row should not be displayed, it is set as
379                          * hidden and will be handled in the maintenance rowDisplay.tag.   
380                          */  
381                         boolean setRowHidden = false;
382                         BusinessObject oldLineBusinessObject = null;
383                         if (oldObj != null && ((List) oldObj).size() > i) {
384                             oldLineBusinessObject = (BusinessObject) ((List) oldObj).get(i);
385                         }
386                         
387                         if (lineBusinessObject instanceof Inactivatable && !((Inactivatable) lineBusinessObject).isActive()) {
388                             if (m != null) {
389                                 // rendering a maint doc
390                                 if (!hidableRowsPresent) {
391                                     hidableRowsPresent = isRowHideableForMaintenanceDocument(lineBusinessObject, oldLineBusinessObject);
392                                     }
393                                 setRowHidden = isRowHiddenForMaintenanceDocument(lineBusinessObject, oldLineBusinessObject, m, collectionName);
394                                     }
395                             if (inquirable != null) {
396                                 // rendering an inquiry screen
397                                 if (!hidableRowsPresent) {
398                                     hidableRowsPresent = isRowHideableForInquiry(lineBusinessObject);
399                                 }
400                                 setRowHidden = isRowHiddenForInquiry(lineBusinessObject, inquirable, collectionName);
401                             }
402                         }
403 
404                         collFields = new ArrayList<Field>();
405                         List<String> duplicateIdentificationFieldNames = new ArrayList<String>(); 
406                         //We only need to do this if the collection definition is a maintainable collection definition, 
407                         //don't need it for inquiry collection definition.
408                         if (collectionDefinition instanceof MaintainableCollectionDefinition) {
409                             Collection<MaintainableFieldDefinition> duplicateFieldDefs = ((MaintainableCollectionDefinition)collectionDefinition).getDuplicateIdentificationFields();
410                             for (MaintainableFieldDefinition eachFieldDef : duplicateFieldDefs) {
411                                 duplicateIdentificationFieldNames.add(eachFieldDef.getName());
412                             }
413                         }
414                         
415                         for (FieldDefinitionI collectionField : collectionFields) {
416 
417                             // construct Field UI object from definition
418                             Field collField = FieldUtils.getPropertyField(collectionDefinition.getBusinessObjectClass(), collectionField.getName(), false);
419                             
420             				if (translateCodes) {
421             					FieldUtils.setAdditionalDisplayPropertyForCodes(lineBusinessObject.getClass(), collField.getPropertyName(), collField);
422             				}
423                             
424                             FieldBridge.setupField(collField, collectionField, conditionallyRequiredMaintenanceFields);
425                             setPrimaryKeyFieldsReadOnly(collectionDefinition.getBusinessObjectClass(), collField);
426 
427                             //If the duplicateIdentificationFields were specified in the maint. doc. DD, we'll need
428                             //to set the fields to be read only as well, in addition to the primary key fields.
429                             if (duplicateIdentificationFieldNames.size() > 0) {
430                                 setDuplicateIdentificationFieldsReadOnly(collField, duplicateIdentificationFieldNames);
431                             }
432                             
433                             FieldUtils.setInquiryURL(collField, lineBusinessObject, collectionField.getName());
434                             // save the simple property name
435                             String name = collField.getPropertyName();
436 
437                             // prefix name for multi line (indexed)
438                             collField.setPropertyName(collectionDefinition.getName() + "[" + (new Integer(i)).toString() + "]." + collField.getPropertyName());
439 
440                             // commenting out codes for sub-collections show/hide for now
441                             // subCollField.setContainerName(collectionDefinition.getName() + "["+i+"]" +"." +
442                             // subCollectionDefinition.getName() + "[" + j + "]");
443 
444                             if (collectionField instanceof MaintainableFieldDefinition) {
445                                 MaintenanceUtils.setFieldQuickfinder(lineBusinessObject, collectionDefinition.getName(), false, i, name, collField, displayedFieldNames, m, (MaintainableFieldDefinition) collectionField);
446                                 MaintenanceUtils.setFieldDirectInquiry(lineBusinessObject, name, (MaintainableFieldDefinition) collectionField, collField, displayedFieldNames);
447                             } else {
448                                 LookupUtils
449                                         .setFieldQuickfinder(lineBusinessObject, collectionDefinition.getName(), false,
450                                                 i, name, collField, displayedFieldNames, m);
451                                 LookupUtils.setFieldDirectInquiry(lineBusinessObject, name, collField);
452                             }
453 
454                             String propertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(lineBusinessObject, collectionField.getName());
455                             // For files the FormFile is not being persisted instead the file data is stored in
456                             // individual fields as defined by PersistableAttachment.  However, newly added rows contain all data
457                             // in the FormFile, so check if it's empty.
458                             if (StringUtils.isBlank(propertyValue) && Field.FILE.equals(collField.getFieldType())) {
459                                 Object fileName = ObjectUtils.getNestedValue(lineBusinessObject, KRADConstants.BO_ATTACHMENT_FILE_NAME);
460                                 collField.setPropertyValue(fileName);
461                             } else {
462                                 collField.setPropertyValue(propertyValue);
463 
464                             }
465 
466                             if (Field.FILE.equals(collField.getFieldType())) {
467                                 Object fileType = ObjectUtils.getNestedValue(lineBusinessObject, KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE);
468                                 if (fileType == null
469                                         && collField.getPropertyName().contains(".")) {
470                                     // fileType not found on bo, so check in the attachment field on bo
471                                     String tempName = collField.getPropertyName().substring(collField.getPropertyName().lastIndexOf('.')+1);
472                                     fileType =  ObjectUtils.getNestedValue(lineBusinessObject, (tempName + "." + KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE));
473                                 }
474                                 collField.setImageSrc(WebUtils.getAttachmentImageForUrl((String) fileType));
475                             }
476                             
477 							if (StringUtils.isNotBlank(collField.getAlternateDisplayPropertyName())) {
478 								String alternateDisplayPropertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(lineBusinessObject,
479 										collField.getAlternateDisplayPropertyName());
480 								collField.setAlternateDisplayPropertyValue(alternateDisplayPropertyValue);
481 							}
482 							
483 							if (StringUtils.isNotBlank(collField.getAdditionalDisplayPropertyName())) {
484 								String additionalDisplayPropertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(lineBusinessObject,
485 										collField.getAdditionalDisplayPropertyName());
486 								collField.setAdditionalDisplayPropertyValue(additionalDisplayPropertyValue);
487 							}
488                                 
489 							//update user fields with universal id and/or name
490 							updateUserFields(collField, lineBusinessObject);
491                                 
492                             // the the field as read only (if appropriate)
493                             if (collectionField.isReadOnlyAfterAdd()) {
494                                 collField.setReadOnly(true);
495                             }
496 
497                             // check if this is a summary field
498                             if (collectionDefinition.hasSummaryField(collectionField.getName())) {
499                                 summaryFields.put(collectionField.getName(), collField);
500                             }
501 
502                             collFields.add(collField);
503                         }
504 
505                         Field containerField;
506                         containerField = FieldUtils.constructContainerField(
507                                 KRADConstants.EDIT_PREFIX + "[" + (new Integer(i)).toString() + "]", collectionLabel + " " + (i + 1), collFields, numberOfColumns);
508                         // why is this only on collections and not subcollections any significance or just oversight?
509                         containerField.setContainerName(collectionDefinition.getName() + "[" + (new Integer(i)).toString() + "].");
510 
511                         /* If the collection line is pending (meaning added by this document) the isNewCollectionRecord will be set to true. In this
512                            case we give an option to delete the line. The parameters for the delete action method are embedded into the button name. */
513                         if (lineBusinessObject instanceof PersistableBusinessObject && 
514                         		(((PersistableBusinessObject) lineBusinessObject).isNewCollectionRecord() 
515                         				|| collectionDefinition.isAlwaysAllowCollectionDeletion())) {
516                             containerField.getContainerRows().add(new Row(getDeleteRowButtonField(parents + collectionDefinition.getName(), (new Integer(i)).toString())));
517                         }
518 
519                         if (StringUtils.isNotEmpty(collectionElementLabel)) {
520                             //We don't want to associate any indexes to the containerElementName anymore so that
521                             //when the element is deleted, the currentTabIndex won't be associated with the
522                             //wrong tab for the remaining tab.
523                             //containerField.setContainerElementName(collectionElementLabel + " " + (i + 1));
524                             containerField.setContainerElementName(collectionElementLabel);
525                             // reorder summaryFields to make sure they are in the order specified in the summary section
526                             List orderedSummaryFields = getSummaryFields(summaryFields, collectionDefinition);
527                             containerField.setContainerDisplayFields(orderedSummaryFields);
528                         }
529                         
530                         Row containerRow = new Row(containerField);
531                         if (setRowHidden) {
532                             containerRow.setHidden(true);
533                         }
534                         containerRows.add(containerRow);
535                         
536                         
537 
538                         Collection<? extends CollectionDefinitionI> subCollections = collectionDefinition.getCollections();
539                         List<Field> subCollFields = new ArrayList<Field>();
540 
541                         summaryFields = new HashMap();
542                         // iterate over the subCollections directly on this collection
543                         for (CollectionDefinitionI subCollection : subCollections) {
544                             Collection<? extends FieldDefinitionI> subCollectionFields = subCollection.getFields();
545                             int subCollectionNumberOfColumns = numberOfColumns;
546 
547                             if (!s.getContainedCollectionNames().contains(collectionDefinition.getName() + "." + subCollection.getName())) {
548                                 s.getContainedCollectionNames().add(collectionDefinition.getName() + "." + subCollection.getName());
549                             }
550 
551                             if (subCollection instanceof InquiryCollectionDefinition) {
552                                 InquiryCollectionDefinition icd = (InquiryCollectionDefinition) subCollection;
553                                 if (icd.getNumberOfColumns() != null) {
554                                     subCollectionNumberOfColumns = icd.getNumberOfColumns();
555                                 }
556                             }
557                             // get label for collection
558                             String subCollectionLabel = getDataDictionaryService().getCollectionLabel(o.getClass(), subCollection.getName());
559 
560                             // retrieve the summary label either from the override or from the DD
561                             String subCollectionElementLabel = subCollection.getSummaryTitle();
562                             if (StringUtils.isEmpty(subCollectionElementLabel)) {
563                                 subCollectionElementLabel = getDataDictionaryService().getCollectionElementLabel(o.getClass().getName(), subCollection.getName(), subCollection.getBusinessObjectClass());
564                             }
565                             // make sure it's really a collection (i.e. list)
566 
567                             String subCollectionName = subCollection.getName();
568                             Object subObj = ObjectUtils.getPropertyValue(lineBusinessObject, subCollectionName);
569                             
570                             Object oldSubObj = null;
571                             if (oldLineBusinessObject != null) {
572                                 oldSubObj = ObjectUtils.getPropertyValue(oldLineBusinessObject, subCollectionName);
573                             }
574                             
575                             if (subObj instanceof List) {
576                                 /* recursively call this method to get the add row and exisiting members of the subCollections subcollections containerRows.addAll(getContainerRows(subCollectionDefinition,
577                                    displayedFieldNames,containerRowErrorKey, parents+collectionDefinition.getName()+"["+i+"]"+".","[0]",false, subCollectionNumberOfColumn)); */
578                                 containerField.getContainerRows().addAll(getContainerRows(s, subCollection, o, m, oldMaintainable, displayedFieldNames, conditionallyRequiredMaintenanceFields, containerRowErrorKey, parents + collectionDefinition.getName() + "[" + i + "]" + ".", false, subCollectionNumberOfColumns, inquirable));
579                              
580                                 // iterate over the fields
581                                 for (int j = 0; j < ((List) subObj).size(); j++) {
582                                     BusinessObject lineSubBusinessObject = (BusinessObject) ((List) subObj).get(j);
583                                     
584                                     if (lineSubBusinessObject instanceof PersistableBusinessObject) {
585                                     	((PersistableBusinessObject) lineSubBusinessObject).refreshNonUpdateableReferences();
586                                     }
587                                     
588                                     // determine if sub collection line is inactive and should be hidden
589                                     boolean setSubRowHidden = false;
590                                     if (lineSubBusinessObject instanceof Inactivatable && !((Inactivatable) lineSubBusinessObject).isActive()) {
591                                         if (oldSubObj != null) { 
592                                             // get corresponding elements in both the new list and the old list
593                                             BusinessObject oldLineSubBusinessObject = (BusinessObject) ((List) oldSubObj).get(j);
594                                             if (m != null) {
595                                                     if (!hidableRowsPresent) {
596                                                         hidableRowsPresent = isRowHideableForMaintenanceDocument(lineSubBusinessObject, oldLineSubBusinessObject);
597                                                 }
598                                                     setSubRowHidden = isRowHiddenForMaintenanceDocument(lineSubBusinessObject, oldLineSubBusinessObject, m, collectionName);
599                                                 }
600                                             }
601                                         if (inquirable != null) {
602                                             if (!hidableRowsPresent) {
603                                                 hidableRowsPresent = isRowHideableForInquiry(lineSubBusinessObject);
604                                         }
605                                             setSubRowHidden = isRowHiddenForInquiry(lineSubBusinessObject, inquirable, collectionName);
606                                     }
607                                     }
608 
609                                     
610                                     subCollFields = new ArrayList<Field>();
611                                     // construct field objects based on fields
612                                     for (FieldDefinitionI subCollectionField : subCollectionFields) {
613 
614                                         // construct Field UI object from definition
615                                         Field subCollField = FieldUtils.getPropertyField(subCollection.getBusinessObjectClass(), subCollectionField.getName(), false);
616 
617                                         String subCollectionFullName = collectionDefinition.getName() + "[" + i + "]" + "." + subCollection.getName();
618                                         
619                         				if (translateCodes) {
620                         					FieldUtils.setAdditionalDisplayPropertyForCodes(lineSubBusinessObject.getClass(), subCollField.getPropertyName(), subCollField);
621                         				}
622 
623                                         FieldBridge.setupField(subCollField, subCollectionField, conditionallyRequiredMaintenanceFields);
624                                         setPrimaryKeyFieldsReadOnly(subCollection.getBusinessObjectClass(), subCollField);
625                                        
626                                         // save the simple property name
627                                         String name = subCollField.getPropertyName();
628 
629                                         // prefix name for multi line (indexed)
630                                         subCollField.setPropertyName(subCollectionFullName + "[" + j + "]." + subCollField.getPropertyName());
631 
632                                         // commenting out codes for sub-collections show/hide for now
633                                         if (subCollectionField instanceof MaintainableFieldDefinition) {
634                                             MaintenanceUtils.setFieldQuickfinder(lineSubBusinessObject, subCollectionFullName, false, j, name, subCollField, displayedFieldNames, m, (MaintainableFieldDefinition) subCollectionField);
635                                             MaintenanceUtils
636                                                     .setFieldDirectInquiry(lineSubBusinessObject, subCollectionFullName,
637                                                             false, j, name, subCollField, displayedFieldNames, m,
638                                                             (MaintainableFieldDefinition) subCollectionField);
639                                         } else {
640                                             LookupUtils.setFieldQuickfinder(lineSubBusinessObject, subCollectionFullName, false, j, name, subCollField, displayedFieldNames);
641                                             LookupUtils.setFieldDirectInquiry(lineBusinessObject, name, subCollField);
642                                         }
643 
644                                         String propertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(lineSubBusinessObject, subCollectionField.getName());
645                                         subCollField.setPropertyValue(propertyValue);
646                                         
647             							if (StringUtils.isNotBlank(subCollField.getAlternateDisplayPropertyName())) {
648             								String alternateDisplayPropertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(lineSubBusinessObject,
649             										subCollField.getAlternateDisplayPropertyName());
650             								subCollField.setAlternateDisplayPropertyValue(alternateDisplayPropertyValue);
651             							}
652                                         
653             							if (StringUtils.isNotBlank(subCollField.getAdditionalDisplayPropertyName())) {
654             								String additionalDisplayPropertyValue = ObjectUtils.getFormattedPropertyValueUsingDataDictionary(lineSubBusinessObject,
655             										subCollField.getAdditionalDisplayPropertyName());
656             								subCollField.setAdditionalDisplayPropertyValue(additionalDisplayPropertyValue);
657             							}
658                                      
659                                         // check if this is a summary field
660                                         if (subCollection.hasSummaryField(subCollectionField.getName())) {
661                                             summaryFields.put(subCollectionField.getName(), subCollField);
662                                         }
663 
664                                         if (subCollectionField.isReadOnlyAfterAdd()) {
665                                             subCollField.setReadOnly(true);
666                                         }
667 
668                                         subCollFields.add(subCollField);
669                                     }
670 
671                                     Field subContainerField = FieldUtils.constructContainerField(
672                                             KRADConstants.EDIT_PREFIX + "[" + (new Integer(j)).toString() + "]", subCollectionLabel, subCollFields);
673                                     if (lineSubBusinessObject instanceof PersistableBusinessObject && (((PersistableBusinessObject) lineSubBusinessObject).isNewCollectionRecord() || subCollection.isAlwaysAllowCollectionDeletion())) {
674                                         subContainerField.getContainerRows().add(new Row(getDeleteRowButtonField(parents + collectionDefinition.getName() + "[" + i + "]" + "." + subCollectionName, (new Integer(j)).toString())));
675                                     }
676 
677                                     // summary line code
678                                     if (StringUtils.isNotEmpty(subCollectionElementLabel)) {
679                                         //We don't want to associate any indexes to the containerElementName anymore so that
680                                         //when the element is deleted, the currentTabIndex won't be associated with the
681                                         //wrong tab for the remaining tab.
682                                         //subContainerField.setContainerElementName(subCollectionElementLabel + " " + (j + 1));
683                                         subContainerField.setContainerElementName(collectionElementLabel + "-" + subCollectionElementLabel);
684                                     }
685                                     subContainerField.setContainerName(collectionDefinition.getName() + "." + subCollectionName);
686                                     if (!summaryFields.isEmpty()) {
687                                         // reorder summaryFields to make sure they are in the order specified in the summary section
688                                         List orderedSummaryFields = getSummaryFields(summaryFields, subCollection);
689                                         subContainerField.setContainerDisplayFields(orderedSummaryFields);
690                                     }
691                                     
692                                     Row subContainerRow = new Row(subContainerField);
693                                     if (setRowHidden || setSubRowHidden) {
694                                         subContainerRow.setHidden(true);
695                                     }
696                                     containerField.getContainerRows().add(subContainerRow);
697                                 }
698                             }
699                         }
700                     }
701                     if ( !hidableRowsPresent ) {
702                         s.setExtraButtonSource( "" );
703                     }
704                 }
705             }
706         }
707         
708         return containerRows;
709     }
710 
711     /**
712       * Updates fields of type kualiuser sets the universal user id and/or name if required. 
713       * 
714       * @param field
715       * @param businessObject
716       */
717      private static final void updateUserFields(Field field, BusinessObject businessObject){
718          // for user fields, attempt to pull the principal ID and person's name from the source object
719          if ( field.getFieldType().equals(Field.KUALIUSER) ) {
720              // this is supplemental, so catch and log any errors
721              try {
722                  if ( StringUtils.isNotBlank(field.getUniversalIdAttributeName()) ) {
723                      Object principalId = ObjectUtils.getNestedValue(businessObject, field.getUniversalIdAttributeName());
724                      if ( principalId != null ) {
725                          field.setUniversalIdValue(principalId.toString());
726                      }
727                  }
728                  if ( StringUtils.isNotBlank(field.getPersonNameAttributeName()) ) {
729                      Object personName = ObjectUtils.getNestedValue(businessObject, field.getPersonNameAttributeName());
730                      if ( personName != null ) {
731                          field.setPersonNameValue( personName.toString() );
732                      }
733                  }
734              } catch ( Exception ex ) {
735                  LOG.warn( "Unable to get principal ID or person name property in SectionBridge.", ex );
736              }
737          }
738      }
739      
740     /**
741      * Helper method to build up a Field containing a delete button mapped up to remove the collection record identified by the
742      * given collection name and index.
743      * 
744      * @param collectionName - name of the collection
745      * @param rowIndex - index of the record to associate delete button
746      * @return Field - of type IMAGE_SUBMIT
747      */
748     private static final Field getDeleteRowButtonField(String collectionName, String rowIndex) {
749         Field deleteButtonField = new Field();
750 
751         String deleteButtonName = KRADConstants.DISPATCH_REQUEST_PARAMETER + "." + KRADConstants.DELETE_LINE_METHOD + "." + collectionName + "." + KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL + ".line" + rowIndex;
752         deleteButtonField.setPropertyName(deleteButtonName);
753         deleteButtonField.setFieldType(Field.IMAGE_SUBMIT);
754         deleteButtonField.setPropertyValue("images/tinybutton-delete1.gif");
755 
756         return deleteButtonField;
757     }
758     
759     /**
760      * Helper method to build up the show inactive button source and place in the section.
761      * 
762      * @param section - section that will display the button
763      * @param collectionName - name of the collection to toggle setting
764      * @param showInactive - boolean indicating whether inactive rows should be displayed
765      * @return Field - of type IMAGE_SUBMIT
766      */
767     private static final void addShowInactiveButtonField(Section section, String collectionName, boolean showInactive) {
768     	String methodName = KRADConstants.DISPATCH_REQUEST_PARAMETER + "." + KRADConstants.TOGGLE_INACTIVE_METHOD + "." + collectionName.replace( '.', '_' );
769         methodName += "." + KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL + showInactive + ".anchorshowInactive." + collectionName + KRADConstants.METHOD_TO_CALL_BOPARM_RIGHT_DEL;
770            
771         String imageSource = showInactive ? "tinybutton-showinact.gif" : "tinybutton-hideinact.gif";
772 
773         String showInactiveButton = "property=" + methodName + ";src=" + imageSource + ";alt=show(hide) inactive" + ";title=show(hide) inactive";
774 
775         section.setExtraButtonSource(showInactiveButton);
776     }
777     
778     /**
779      * Retrieves the primary key property names for the given class. If the field's property is one of those keys, makes the field
780      * read-only. This is called for collection lines. Since deletion is not allowed for existing lines, the pk fields must be
781      * read-only, otherwise a user could change the pk value which would be equivalent to deleting the line and adding a new line.
782      */
783     private static final void setPrimaryKeyFieldsReadOnly(Class businessObjectClass, Field field) {
784     	try{
785     		//TODO: Revisit this. Changing since getPrimaryKeys and listPrimaryKeyFieldNames are apparently same.
786     		//May be we might want to replace listPrimaryKeyFieldNames with getPrimaryKeys... Not sure.
787 	    	List primaryKeyPropertyNames = 
788 	    		KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
789 	        if (primaryKeyPropertyNames.contains(field.getPropertyName())) {
790 	            field.setReadOnly(true);
791 	        }
792     	} catch(ClassNotPersistableException ex){
793     		//Not all classes will be persistable in a collection. For e.g. externalizable business objects.
794     		LOG.info("Not persistable dataObjectClass: "+businessObjectClass+", field: "+field);
795     	}
796     }
797     
798     private static void setDuplicateIdentificationFieldsReadOnly(Field field, List<String>duplicateIdentificationFieldNames) {
799         if (duplicateIdentificationFieldNames.contains(field.getPropertyName())) {
800             field.setReadOnly(true);
801         }
802     }
803 
804     /**
805      * This method returns an ordered list of fields.
806      * 
807      * @param collSummaryFields
808      * @param collectionDefinition
809      * @return
810      */
811     private static final List<Field> getSummaryFields(Map collSummaryFields, CollectionDefinitionI collectionDefinition) {
812         List<Field> orderedSummaryFields = new ArrayList<Field>();
813         for (FieldDefinitionI summaryField : collectionDefinition.getSummaryFields()) {
814             String name = summaryField.getName();
815             boolean found = false;
816             Field addField = (Field) collSummaryFields.get(name);
817             if (!(addField == null)) {
818                 orderedSummaryFields.add(addField);
819                 found = true;
820             }
821 
822             if (!found) {
823                 // should we throw a real error here?
824                 LOG.error("summaryField " + summaryField + " not present in the list");
825             }
826 
827         }
828         return orderedSummaryFields;
829     }
830 
831     /**
832      * This is a helper method to create a sub section header
833      * 
834      * @param definition the MaintainableSubSectionHeaderDefinition that we'll use to create the sub section header
835      * @return the Field, which is the sub section header
836      */
837     private static final Field createMaintainableSubSectionHeader(SubSectionHeaderDefinitionI definition) {
838         Field separatorField = new Field();
839         separatorField.setFieldLabel(definition.getName());
840         separatorField.setFieldType(Field.SUB_SECTION_SEPARATOR);
841         separatorField.setReadOnly(true);
842 
843         return separatorField;
844     }
845     
846     /**
847      * Determines whether a business object is hidable on a maintenance document.  Hidable means that if the user chose to hide the inactive
848      * elements in the collection in which the passed in BOs reside, then the BOs would be hidden
849      * 
850      * @param lineBusinessObject the BO in the new maintainable, should be of type {@link PersistableBusinessObject} and {@link Inquirable}
851      * @param oldLineBusinessObject the corresponding BO in the old maintainable, should be of type {@link PersistableBusinessObject} and 
852      * {@link Inquirable}
853      * @return whether the BOs are eligible to be hidden if the user decides to hide them
854      */
855     protected static boolean isRowHideableForMaintenanceDocument(BusinessObject lineBusinessObject, BusinessObject oldLineBusinessObject) {
856         if (oldLineBusinessObject != null) {
857             if (((PersistableBusinessObject) lineBusinessObject).isNewCollectionRecord()) {
858                 // new records are never hidden, regardless of active status
859                 return false;
860 }
861             if (!((Inactivatable) lineBusinessObject).isActive() && !((Inactivatable) oldLineBusinessObject).isActive()) {
862                 // records with an old and new collection elements of NOT active are eligible to be hidden
863                 return true;
864             }
865         }
866         return false;
867     }
868     /**
869      * Determines whether a business object is hidden on a maintenance document.
870      * 
871      * @param lineBusinessObject the BO in the new maintainable, should be of type {@link PersistableBusinessObject}
872      * @param oldLineBusinessObject the corresponding BO in the old maintainable
873      * @param newMaintainable the new maintainable from the maintenace document
874      * @param collectionName the name of the collection from which these BOs come
875      * @return
876      */
877     protected static boolean isRowHiddenForMaintenanceDocument(BusinessObject lineBusinessObject, BusinessObject oldLineBusinessObject,
878             Maintainable newMaintainable, String collectionName) {
879         return isRowHideableForMaintenanceDocument(lineBusinessObject, oldLineBusinessObject) && !newMaintainable.getShowInactiveRecords(collectionName);
880     }
881     
882     /**
883      * Determines whether a business object is hidable on an inquiry screen.  Hidable means that if the user chose to hide the inactive
884      * elements in the collection in which the passed in BO resides, then the BO would be hidden
885      * 
886      * @param lineBusinessObject the collection element BO, should be of type {@link PersistableBusinessObject} and {@link Inquirable}
887      * @return whether the BO is eligible to be hidden if the user decides to hide them
888      */
889     protected static boolean isRowHideableForInquiry(BusinessObject lineBusinessObject) {
890         return !((Inactivatable) lineBusinessObject).isActive();
891     }
892     
893     /**
894      * Determines whether a business object is hidden on an inquiry screen.
895      * 
896      * @param lineBusinessObject the BO in the collection, should be of type {@link PersistableBusinessObject} and {@link Inquirable}
897      * @param inquirable the inquirable
898      * @param collectionName the name of the collection from which the BO comes
899      * @return true if the business object is to be hidden; false otherwise
900      */
901     protected static boolean isRowHiddenForInquiry(BusinessObject lineBusinessObject, Inquirable inquirable, String collectionName) {
902         return isRowHideableForInquiry(lineBusinessObject) && !inquirable.getShowInactiveRecords(collectionName);
903     }
904     
905 	public static MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
906     	if (maintenanceDocumentDictionaryService == null) {
907     		maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
908     	}
909 		return maintenanceDocumentDictionaryService; 
910 	}
911 }
912