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