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