View Javadoc

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