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