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