View Javadoc

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