001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kns.web.ui;
017    
018    import org.apache.commons.beanutils.PropertyUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.kuali.rice.core.api.mo.common.active.Inactivatable;
021    import org.kuali.rice.kns.datadictionary.CollectionDefinitionI;
022    import org.kuali.rice.kns.datadictionary.FieldDefinition;
023    import org.kuali.rice.kns.datadictionary.FieldDefinitionI;
024    import org.kuali.rice.kns.datadictionary.InquiryCollectionDefinition;
025    import org.kuali.rice.kns.datadictionary.InquirySectionDefinition;
026    import org.kuali.rice.kns.datadictionary.InquirySubSectionHeaderDefinition;
027    import org.kuali.rice.kns.datadictionary.MaintainableCollectionDefinition;
028    import org.kuali.rice.kns.datadictionary.MaintainableFieldDefinition;
029    import org.kuali.rice.kns.datadictionary.MaintainableItemDefinition;
030    import org.kuali.rice.kns.datadictionary.MaintainableSectionDefinition;
031    import org.kuali.rice.kns.datadictionary.MaintainableSubSectionHeaderDefinition;
032    import org.kuali.rice.kns.datadictionary.SubSectionHeaderDefinitionI;
033    import org.kuali.rice.kns.document.authorization.FieldRestriction;
034    import org.kuali.rice.kns.inquiry.Inquirable;
035    import org.kuali.rice.kns.inquiry.InquiryRestrictions;
036    import org.kuali.rice.kns.lookup.LookupUtils;
037    import org.kuali.rice.kns.maintenance.Maintainable;
038    import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
039    import org.kuali.rice.kns.service.KNSServiceLocator;
040    import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
041    import org.kuali.rice.kns.util.FieldUtils;
042    import org.kuali.rice.kns.util.MaintenanceUtils;
043    import org.kuali.rice.krad.bo.BusinessObject;
044    import org.kuali.rice.krad.bo.PersistableBusinessObject;
045    import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
046    import org.kuali.rice.krad.exception.ClassNotPersistableException;
047    import org.kuali.rice.krad.service.DataDictionaryService;
048    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
049    import org.kuali.rice.krad.util.KRADConstants;
050    import org.kuali.rice.krad.util.ObjectUtils;
051    
052    import java.util.ArrayList;
053    import java.util.Collection;
054    import java.util.HashMap;
055    import java.util.HashSet;
056    import java.util.Iterator;
057    import java.util.List;
058    import java.util.Map;
059    import java.util.Set;
060    
061    @Deprecated
062    public class SectionBridge {
063        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SectionBridge.class);
064        private static BusinessObjectAuthorizationService businessObjectAuthorizationService;
065        private static BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
066            if (businessObjectAuthorizationService == null) {
067                    businessObjectAuthorizationService = KNSServiceLocator.getBusinessObjectAuthorizationService();
068            }
069            return businessObjectAuthorizationService;
070        }
071        private static DataDictionaryService dataDictionaryService;
072        private static DataDictionaryService getDataDictionaryService() {
073            if (dataDictionaryService == null) {
074                    dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
075            }
076            return dataDictionaryService;
077        }
078        private static MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
079    
080        /**
081         * This method creates a Section for display on an Inquiry Screen.
082         * 
083         * @param sd The DD definition from which to construct the Section.
084         * @param o The BusinessObject from which to populate the Section values.
085         * @return A populated Section.
086         */
087        public static final Section toSection(Inquirable inquirable, InquirySectionDefinition sd, BusinessObject o, InquiryRestrictions auths) {
088            Section section = new Section();
089            section.setSectionId( sd.getId() );
090            section.setSectionTitle(sd.getTitle());
091            section.setRows(new ArrayList());
092            section.setDefaultOpen(sd.isDefaultOpen());
093            
094            if (sd.getNumberOfColumns() != null) {
095                section.setNumberOfColumns(sd.getNumberOfColumns());
096            }
097            else {
098                section.setNumberOfColumns(KRADConstants.DEFAULT_NUM_OF_COLUMNS);
099            }
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