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