001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.container;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.kim.api.identity.Person;
020import org.kuali.rice.krad.uif.CssConstants;
021import org.kuali.rice.krad.uif.UifConstants;
022import org.kuali.rice.krad.uif.UifParameters;
023import org.kuali.rice.krad.uif.UifPropertyPaths;
024import org.kuali.rice.krad.uif.component.BindingInfo;
025import org.kuali.rice.krad.uif.component.Component;
026import org.kuali.rice.krad.uif.component.ComponentSecurity;
027import org.kuali.rice.krad.uif.component.DataBinding;
028import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
029import org.kuali.rice.krad.uif.control.Control;
030import org.kuali.rice.krad.uif.control.ControlBase;
031import org.kuali.rice.krad.uif.element.Action;
032import org.kuali.rice.krad.uif.field.DataField;
033import org.kuali.rice.krad.uif.field.Field;
034import org.kuali.rice.krad.uif.field.FieldGroup;
035import org.kuali.rice.krad.uif.field.InputField;
036import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
037import org.kuali.rice.krad.uif.layout.CollectionLayoutManager;
038import org.kuali.rice.krad.uif.layout.TableLayoutManagerBase;
039import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
040import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
041import org.kuali.rice.krad.uif.util.ComponentFactory;
042import org.kuali.rice.krad.uif.util.ComponentUtils;
043import org.kuali.rice.krad.uif.util.ContextUtils;
044import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
045import org.kuali.rice.krad.uif.util.ScriptUtils;
046import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
047import org.kuali.rice.krad.uif.view.View;
048import org.kuali.rice.krad.uif.view.ViewAuthorizer;
049import org.kuali.rice.krad.uif.view.ViewModel;
050import org.kuali.rice.krad.uif.view.ViewPresentationController;
051import org.kuali.rice.krad.util.GlobalVariables;
052import org.kuali.rice.krad.web.form.UifFormBase;
053
054import java.io.Serializable;
055import java.util.ArrayList;
056import java.util.HashMap;
057import java.util.Iterator;
058import java.util.List;
059import java.util.Map;
060
061/**
062 * Process configuration from the collection group to prepare components for a new line and invokes the associated
063 * layout manager to add the line.
064 *
065 * @author Kuali Rice Team (rice.collab@kuali.org)
066 */
067public class CollectionGroupLineBuilder implements Serializable {
068
069    private static final long serialVersionUID = 981187437246864378L;
070
071    private LineBuilderContext lineBuilderContext;
072    private List<Field> unauthorizedFields = new ArrayList<Field>();
073
074    public CollectionGroupLineBuilder(LineBuilderContext lineBuilderContext) {
075        this.lineBuilderContext = lineBuilderContext;
076    }
077
078    /**
079     * Invoked to build a line in the collection.
080     *
081     * <p>First the context for the line is preprocessed in {@link CollectionGroupLineBuilder#preprocessLine()}.
082     * After preprocessing the configured layout manager is invoked to place the line into the layout.</p>
083     */
084    public void buildLine() {
085        preprocessLine();
086
087        boolean hasLineFields =
088                (lineBuilderContext.getLineFields() != null) && (!lineBuilderContext.getLineFields().isEmpty());
089        boolean hasSubCollections =
090                (lineBuilderContext.getSubCollectionFields() != null) && (!lineBuilderContext.getSubCollectionFields()
091                        .isEmpty());
092
093        // invoke layout manager and setup edit line dialog to build the complete line
094        if (hasLineFields || hasSubCollections) {
095            // setup the edit dialog if its not an add line
096            if (!lineBuilderContext.isAddLine()) {
097                setupEditLineDetails();
098            }
099
100            // add the lineDialogs to the lineActions
101            List<Component> actions = new ArrayList<Component>();
102            actions.addAll(lineBuilderContext.getLineActions());
103            List<DialogGroup> dialogGroups = lineBuilderContext.getCollectionGroup().getLineDialogs();
104            if (Boolean.TRUE.equals(lineBuilderContext.getCollectionGroup().getReadOnly())) {
105                for (DialogGroup group : dialogGroups) {
106                    group.setReadOnly(true);
107                }
108            }
109            actions.addAll(dialogGroups);
110            lineBuilderContext.setLineActions(actions);
111            lineBuilderContext.getCollectionGroup().getLineDialogs().clear();
112
113            // invoke the layout manager
114            lineBuilderContext.getLayoutManager().buildLine(lineBuilderContext);
115        }
116
117        // After the lines have been processed and correct value set for readOnly and other properties
118        // set the script for enabling/disabling save button
119        applyOnChangeForSave(lineBuilderContext.getLineFields());
120    }
121
122    /**
123     * Performs various preprocessing of the line components and configuration.
124     *
125     * <p>Preprocessing includes:
126     * <ul>
127     * <li>Make a copy of the collection groups items and adjust binding and id</li>
128     * <li>Process any remotable fields within the line</li>
129     * <li>Check line and field authorization</li>
130     * <li>Remove fields that should not be rendered</li>
131     * <li>Configure client side functionality such as save enable and add line validation</li>
132     * <li>Build field groups to hold any configured sub-collections</li>
133     * </ul></p>
134     */
135    public void preprocessLine() {
136        List<? extends Component> lineItems = initializeLineItems();
137
138        List<Field> lineFields = processAnyRemoteFieldsHolder(lineBuilderContext.getCollectionGroup(), lineItems);
139
140        adjustFieldBindingAndId(lineFields, lineBuilderContext.getBindingPath());
141
142        ContextUtils.updateContextsForLine(lineFields, lineBuilderContext.getCollectionGroup(),
143                lineBuilderContext.getCurrentLine(), lineBuilderContext.getLineIndex(),
144                lineBuilderContext.getIdSuffix());
145
146        boolean canViewLine = checkViewLineAuthorization();
147        if (!canViewLine) {
148            return;
149        }
150
151        List<Action> actions = ViewLifecycleUtils.getElementsOfTypeDeep(lineBuilderContext.getLineActions(),
152                Action.class);
153        setFocusOnIdForActions(actions, lineFields);
154
155        boolean canEditLine = checkEditLineAuthorization(lineFields);
156        ContextUtils.pushObjectToContextDeep(lineFields, UifConstants.ContextVariableNames.READONLY_LINE, !canEditLine);
157        ContextUtils.pushObjectToContextDeep(actions, UifConstants.ContextVariableNames.READONLY_LINE, !canEditLine);
158
159        if (!canEditLine) {
160            Iterator<Action> actionsIterator = actions.iterator();
161            while (actionsIterator.hasNext()) {
162                Action action = actionsIterator.next();
163                if (action.getId().startsWith(ComponentFactory.EDIT_LINE_IN_DIALOG_ACTION + "_" +
164                        lineBuilderContext.getCollectionGroup().getId())) {
165                    actionsIterator.remove();
166                    break;
167                }
168            }
169            lineBuilderContext.setLineActions(actions);
170        }
171
172        // check authorization for line fields
173        applyLineFieldAuthorizationAndPresentationLogic(!canEditLine, lineFields, actions);
174
175        // remove fields from the line that have render false
176        lineFields = removeNonRenderLineFields(lineFields);
177
178        buildSubCollectionFieldGroups();
179
180        // update action parameters for any actions that were added in the line items (as opposed to the line actions)
181        List<Action> lineFieldActions = ViewLifecycleUtils.getElementsOfTypeDeep(lineFields, Action.class);
182        if (lineFieldActions != null) {
183            lineBuilderContext.getCollectionGroup().getCollectionGroupBuilder().initializeActions(lineFieldActions,
184                    lineBuilderContext.getCollectionGroup(), lineBuilderContext.getLineIndex());
185        }
186
187        setupAddLineControlValidation(lineFields);
188
189        lineBuilderContext.setLineFields(lineFields);
190    }
191
192    /**
193     * Copies either the collections groups items or add line items to a list of components that will be used
194     * for the collection line.
195     *
196     * @return list of component instance for the collection line
197     */
198    protected List<? extends Component> initializeLineItems() {
199        List<? extends Component> lineItems;
200
201        if (lineBuilderContext.isAddLine()) {
202            lineItems = ComponentUtils.copyComponentList(lineBuilderContext.getCollectionGroup().getAddLineItems(),
203                    null);
204        } else {
205            lineItems = ComponentUtils.copyComponentList(lineBuilderContext.getCollectionGroup().getItems(), null);
206        }
207
208        return lineItems;
209    }
210
211    /**
212     * Iterates through the given items checking for {@code RemotableFieldsHolder}, if found
213     * the holder is invoked to retrieved the remotable fields and translate to attribute fields.
214     *
215     * <p>The translated list is then inserted into the returned list at the position of the holder</p>
216     *
217     * @param group collection group instance to check for any remotable fields holder
218     * @param items list of items to process
219     */
220    public List<Field> processAnyRemoteFieldsHolder(CollectionGroup group, List<? extends Component> items) {
221        List<Field> processedItems = new ArrayList<Field>();
222
223        // check for holders and invoke to retrieve the remotable fields and translate
224        // translated fields are placed into the processed items list at the position of the holder
225        for (Component item : items) {
226            if (item instanceof RemoteFieldsHolder) {
227                List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(group);
228                processedItems.addAll(translatedFields);
229            } else if (item instanceof Field) {
230                processedItems.add((Field) item);
231            }
232        }
233
234        return processedItems;
235    }
236
237    /**
238     * Adjusts the binding path for the given fields to match the collections path, and sets the container id
239     * suffix for the fields so all nested components will get their ids adjusted.
240     *
241     * @param lineFields list of fields to update
242     * @param bindingPath binding path to add
243     */
244    protected void adjustFieldBindingAndId(List<Field> lineFields, String bindingPath) {
245        for (Field lineField : lineFields) {
246            adjustFieldBinding(lineField, bindingPath);
247            adjustFieldId(lineField);
248        }
249
250        if (lineBuilderContext.isBindToForm()) {
251            ComponentUtils.setComponentsPropertyDeep(lineFields, UifPropertyPaths.BIND_TO_FORM, Boolean.valueOf(true));
252        }
253    }
254
255    /**
256     * Adjusts the binding path for the given field to match the collections path.
257     *
258     * @param lineField field to update
259     * @param bindingPath binding path to add
260     */
261    protected void adjustFieldBinding(Field lineField, String bindingPath) {
262        if (lineField instanceof DataBinding && ((DataBinding) lineField).getBindingInfo().isBindToForm()) {
263            BindingInfo bindingInfo = ((DataBinding) lineField).getBindingInfo();
264            bindingInfo.setCollectionPath(null);
265            bindingInfo.setBindingName(bindingInfo.getBindingName() + "[" + lineBuilderContext.getLineIndex() + "]");
266        } else {
267            ComponentUtils.prefixBindingPath(lineField, bindingPath);
268        }
269    }
270
271    /**
272     * Adjusts the id suffix for the given field.
273     *
274     * @param lineField field to update
275     */
276    protected void adjustFieldId(Field lineField) {
277        ComponentUtils.updateIdWithSuffix(lineField, lineBuilderContext.getIdSuffix());
278
279        lineField.setContainerIdSuffix(lineBuilderContext.getIdSuffix());
280    }
281
282    /**
283     * For any actions with focus id {@link org.kuali.rice.krad.uif.UifConstants.Order#LINE_FIRST}, the focus id
284     * is replaced to match to id of the first control in the line.
285     *
286     * @param actions list of actions to potientially update
287     * @param lineFields list of line fields, the control for the first field in the list
288     * will be used for the focus id
289     */
290    protected void setFocusOnIdForActions(List<Action> actions, List<Field> lineFields) {
291        for (Action action : actions) {
292            if (action == null) {
293                continue;
294            }
295
296            boolean focusLineFirst = StringUtils.isNotBlank(action.getFocusOnIdAfterSubmit()) && action
297                    .getFocusOnIdAfterSubmit().equalsIgnoreCase(UifConstants.Order.LINE_FIRST.toString());
298            boolean lineHasFields = !lineFields.isEmpty();
299            if (focusLineFirst && lineHasFields) {
300                action.setFocusOnIdAfterSubmit(lineFields.get(0).getId() + UifConstants.IdSuffixes.CONTROL);
301            }
302        }
303    }
304
305    /**
306     * If {@link CollectionGroup#isRenderSaveLineActions()} is true and the line has been added by the user, on change
307     * script is added to any controls in the line to enable the save action.
308     *
309     * @param lineFields list of line fields
310     */
311    protected void applyOnChangeForSave(List<Field> lineFields) {
312        boolean isLineNewlyAdded = ((UifFormBase) lineBuilderContext.getModel()).isAddedCollectionItem(
313                lineBuilderContext.getCurrentLine());
314        boolean saveLineEnabled = lineBuilderContext.getCollectionGroup().isRenderSaveLineActions();
315
316        if (!isLineNewlyAdded && !saveLineEnabled) {
317            return;
318        }
319
320        for (Field field : lineFields) {
321            boolean isInputField = (field instanceof InputField);
322            if (field.isHidden() || Boolean.TRUE.equals(field.getReadOnly()) || !isInputField) {
323                continue;
324            }
325
326            // if control null, assign default
327            InputField inputField = (InputField) field;
328            if (inputField.getControl() == null) {
329                inputField.setControl(ComponentFactory.getTextControl());
330            }
331
332            ControlBase control = (ControlBase) ((InputField) field).getControl();
333
334            String onBlurScript = UifConstants.JsFunctions.COLLECTION_LINE_CHANGED + "(this, '" +
335                    CssConstants.Classes.NEW_COLLECTION_ITEM + "');";
336            onBlurScript = ScriptUtils.appendScript(control.getOnBlurScript(), onBlurScript);
337
338            control.setOnBlurScript(onBlurScript);
339        }
340    }
341
342    /**
343     * Evaluates the render property for the given list of field instances for the line and removes any fields
344     * from the returned list that have render false.
345     *
346     * <p>The conditional render string is also taken into account. This needs to be done here as opposed
347     * to during the normal condition evaluation so the the fields are not used while building the
348     * collection lines</p>
349     *
350     * @param lineFields list of fields configured for the line
351     * @return list of field instances that should be rendered
352     */
353    protected List<Field> removeNonRenderLineFields(List<Field> lineFields) {
354        List<Field> fields = new ArrayList<Field>();
355
356        ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
357
358        for (Field lineField : lineFields) {
359            String conditionalRender = lineField.getPropertyExpression(UifPropertyPaths.RENDER);
360
361            // evaluate conditional render string if set
362            if (StringUtils.isNotBlank(conditionalRender)) {
363                Map<String, Object> context = getContextForField(ViewLifecycle.getView(),
364                        lineBuilderContext.getCollectionGroup(), lineField);
365
366                // Adjust the condition as ExpressionUtils.adjustPropertyExpressions will only be
367                // executed after the collection is built.
368                conditionalRender = expressionEvaluator.replaceBindingPrefixes(ViewLifecycle.getView(), lineField,
369                        conditionalRender);
370
371                Boolean render = (Boolean) expressionEvaluator.evaluateExpression(context, conditionalRender);
372                lineField.setRender(render);
373            }
374
375            // only add line field if set to render or if it is hidden by progressive render
376            if (lineField.isRender() || StringUtils.isNotBlank(lineField.getProgressiveRender())) {
377                fields.add(lineField);
378            }
379        }
380
381        return fields;
382    }
383
384    /**
385     * Determines whether the user is authorized to the view the line.
386     *
387     * @return boolean true if the user can view the line, false if not
388     */
389    protected boolean checkViewLineAuthorization() {
390        boolean canViewLine = true;
391
392        // check view line authorization if collection is not hidden
393        if (!lineBuilderContext.isAddLine()) {
394            canViewLine = checkViewLineAuthorizationAndPresentationLogic();
395        }
396
397        if (!canViewLine) {
398            addUnauthorizedBindingInfo();
399        }
400
401        return canViewLine;
402    }
403
404    /**
405     * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
406     * to view the line (if a permission has been established).
407     *
408     * @return true if the user can view the line, false if not
409     */
410    protected boolean checkViewLineAuthorizationAndPresentationLogic() {
411        ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
412        ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
413
414        Person user = GlobalVariables.getUserSession().getPerson();
415
416        CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
417
418        boolean canViewLine = authorizer.canViewLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
419                collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine(), user);
420        if (canViewLine) {
421            canViewLine = presentationController.canViewLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
422                    collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine());
423        }
424
425        return canViewLine;
426    }
427
428    /**
429     * Determines whether the user is authorized to the edit the line.
430     *
431     * @param lineFields list of fields configured for the line
432     * @return boolean true if the user can edit the line, false if not
433     */
434    protected boolean checkEditLineAuthorization(List<Field> lineFields) {
435        boolean canEditLine = !Boolean.TRUE.equals(lineBuilderContext.getCollectionGroup().getReadOnly());
436
437        if (!canEditLine) {
438            ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
439            View view = ViewLifecycle.getView();
440
441            for (Field field : lineFields) {
442                field.pushObjectToContext(UifConstants.ContextVariableNames.PARENT,
443                        lineBuilderContext.getCollectionGroup());
444                field.pushAllToContext(view.getContext());
445                field.pushObjectToContext(UifConstants.ContextVariableNames.COMPONENT, field);
446
447                expressionEvaluator.evaluatePropertyExpression(view, field.getContext(), field,
448                        UifPropertyPaths.READ_ONLY, true);
449
450                if (!Boolean.TRUE.equals(field.getReadOnly())) {
451                    canEditLine = true;
452                    break;
453                }
454            }
455        }
456
457        if (canEditLine && !lineBuilderContext.isAddLine()) {
458            canEditLine = checkEditLineAuthorizationAndPresentationLogic(lineBuilderContext.getCollectionGroup(),
459                    lineBuilderContext.getModel(), lineBuilderContext.getCurrentLine());
460        }
461
462        if (!canEditLine) {
463            addUnauthorizedBindingInfo();
464        }
465
466        return canEditLine;
467    }
468
469    /**
470     * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
471     * to edit the line (if a permission has been established).
472     *
473     * @return true if the user can edit the line, false if not
474     */
475    protected boolean checkEditLineAuthorizationAndPresentationLogic(CollectionGroup collectionGroup, ViewModel model,
476            Object currentLine) {
477        ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
478        ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
479
480        Person user = GlobalVariables.getUserSession().getPerson();
481
482        boolean canEditLine = authorizer.canEditLine(ViewLifecycle.getView(), model, collectionGroup,
483                collectionGroup.getPropertyName(), currentLine, user);
484        if (canEditLine) {
485            canEditLine = presentationController.canEditLine(ViewLifecycle.getView(), model, collectionGroup,
486                    collectionGroup.getPropertyName(), currentLine);
487        }
488
489        return canEditLine;
490    }
491
492    /**
493     * Adds a {@link org.kuali.rice.krad.uif.component.BindingInfo} instance for the given binding
494     * path to the collection groups unauthorized list.
495     */
496    protected void addUnauthorizedBindingInfo() {
497        if (lineBuilderContext.getCollectionGroup().getUnauthorizedLineBindingInfos() == null) {
498            lineBuilderContext.getCollectionGroup().setUnauthorizedLineBindingInfos(new ArrayList<BindingInfo>());
499        }
500
501        BindingInfo bindingInfo = new BindingInfo();
502        bindingInfo.setDefaults(ViewLifecycle.getView(), lineBuilderContext.getBindingPath());
503        lineBuilderContext.getCollectionGroup().getUnauthorizedLineBindingInfos().add(bindingInfo);
504    }
505
506    /**
507     * Iterates through the line fields and checks the view field authorization using the view's configured authorizer
508     * and presentation controller.
509     *
510     * <p>If the field is viewable, then sets the edit field authorization. Finally iterates
511     * through the line actions invoking the authorizer and presentation controller to authorizer the action</p>
512     *
513     * @param readOnlyLine flag indicating whether the line has been marked as read only (which will force the fields
514     * to be read only)
515     * @param lineFields list of fields instances for the line
516     * @param actionList list of action field instances for the line
517     */
518    protected void applyLineFieldAuthorizationAndPresentationLogic(boolean readOnlyLine, List<Field> lineFields,
519            List<? extends Component> actionList) {
520        ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
521        ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
522
523        Person user = GlobalVariables.getUserSession().getPerson();
524        ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
525
526        CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
527        View view = ViewLifecycle.getView();
528        ViewModel model = lineBuilderContext.getModel();
529        Object currentLine = lineBuilderContext.getCurrentLine();
530
531        for (Field lineField : lineFields) {
532            String propertyName = null;
533            if (lineField instanceof DataBinding) {
534                propertyName = ((DataBinding) lineField).getPropertyName();
535            }
536
537            // evaluate expression on fields component security (since apply model phase has not been invoked on
538            // them yet
539            ComponentSecurity componentSecurity = lineField.getComponentSecurity();
540
541            Map<String, Object> context = getContextForField(ViewLifecycle.getView(), collectionGroup, lineField);
542            expressionEvaluator.evaluateExpressionsOnConfigurable(ViewLifecycle.getView(), componentSecurity, context);
543
544            // check view field auth
545            if (!lineField.isRender() || lineField.isHidden()) {
546                continue;
547            }
548
549            boolean canViewField = authorizer.canViewLineField(view, model, collectionGroup,
550                    collectionGroup.getPropertyName(), currentLine, lineField, propertyName, user);
551            if (canViewField) {
552                canViewField = presentationController.canViewLineField(view, model, collectionGroup,
553                        collectionGroup.getPropertyName(), currentLine, lineField, propertyName);
554            }
555
556            if (!canViewField) {
557                // since removing can impact layout, set to hidden
558                // TODO: check into encryption setting
559                lineField.setHidden(true);
560
561                if (lineField.getPropertyExpressions().containsKey(UifPropertyPaths.HIDDEN)) {
562                    lineField.getPropertyExpressions().remove(UifPropertyPaths.HIDDEN);
563                }
564
565                continue;
566            }
567
568            // check edit field auth
569            boolean canEditField = !readOnlyLine;
570            if (!readOnlyLine) {
571                canEditField = authorizer.canEditLineField(view, model, collectionGroup,
572                        collectionGroup.getPropertyName(), currentLine, lineField, propertyName, user);
573                if (canEditField) {
574                    canEditField = presentationController.canEditLineField(view, model, collectionGroup,
575                            collectionGroup.getPropertyName(), currentLine, lineField, propertyName);
576                }
577            }
578
579            if (readOnlyLine || !canEditField) {
580                lineField.setReadOnly(true);
581
582                if (lineField.getPropertyExpressions().containsKey(UifPropertyPaths.READ_ONLY)) {
583                    lineField.getPropertyExpressions().remove(UifPropertyPaths.READ_ONLY);
584                }
585            }
586        }
587
588        // check auth on line actions
589        List<Action> actions = ViewLifecycleUtils.getElementsOfTypeDeep(actionList, Action.class);
590        for (Action action : actions) {
591            if (!action.isRender()) {
592                continue;
593            }
594
595            boolean canPerformAction = authorizer.canPerformLineAction(view, model, collectionGroup,
596                    collectionGroup.getPropertyName(), currentLine, action, action.getActionEvent(), action.getId(),
597                    user);
598            if (canPerformAction) {
599                canPerformAction = presentationController.canPerformLineAction(view, model, collectionGroup,
600                        collectionGroup.getPropertyName(), currentLine, action, action.getActionEvent(),
601                        action.getId());
602            }
603
604            if (!canPerformAction) {
605                action.setRender(false);
606
607                if (action.getPropertyExpressions().containsKey(UifPropertyPaths.RENDER)) {
608                    action.getPropertyExpressions().remove(UifPropertyPaths.RENDER);
609                }
610            }
611        }
612    }
613
614    /**
615     * For each configured sub collection of the collection group, creates a field group by copying
616     * {@link org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype()} and adds
617     * to a list which is stored in the line context.
618     */
619    protected void buildSubCollectionFieldGroups() {
620        CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
621
622        String idSuffix = lineBuilderContext.getIdSuffix();
623
624        // sub collections are not created for the add line
625        if (lineBuilderContext.isAddLine() || (collectionGroup.getSubCollections() == null)) {
626            return;
627        }
628
629        List<FieldGroup> subCollectionFields = new ArrayList<FieldGroup>();
630        for (int subLineIndex = 0; subLineIndex < collectionGroup.getSubCollections().size(); subLineIndex++) {
631            CollectionGroup subCollectionPrototype = collectionGroup.getSubCollections().get(subLineIndex);
632            CollectionGroup subCollectionGroup = ComponentUtils.copy(subCollectionPrototype);
633
634            // verify the sub-collection should be rendered
635            boolean renderSubCollection = checkSubCollectionRender(subCollectionGroup);
636            if (!renderSubCollection) {
637                continue;
638            }
639
640            subCollectionGroup.getBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath());
641            if (subCollectionGroup.isRenderAddLine()) {
642                subCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath());
643            }
644
645            FieldGroup fieldGroupPrototype =
646                    lineBuilderContext.getLayoutManager().getSubCollectionFieldGroupPrototype();
647
648            FieldGroup subCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype,
649                    idSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
650            subCollectionFieldGroup.setGroup(subCollectionGroup);
651
652            subCollectionFieldGroup.setContainerIdSuffix(idSuffix);
653
654            ContextUtils.updateContextForLine(subCollectionFieldGroup, collectionGroup,
655                    lineBuilderContext.getCurrentLine(), lineBuilderContext.getLineIndex(),
656                    idSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
657            ContextUtils.pushObjectToContextDeep(subCollectionGroup, UifConstants.ContextVariableNames.PARENT_LINE,
658                    lineBuilderContext.getCurrentLine());
659
660            subCollectionFields.add(subCollectionFieldGroup);
661        }
662
663        ContextUtils.pushObjectToContextDeep(subCollectionFields, UifConstants.ContextVariableNames.PARENT_LINE,
664                lineBuilderContext.getCurrentLine());
665
666        // set the parent line on the context of every sub-collection field
667        for (FieldGroup subCollectionField : subCollectionFields) {
668            Group group = subCollectionField.getGroup();
669            if (group != null && group instanceof CollectionGroup) {
670                CollectionGroup collectionGroup1 = (CollectionGroup) group;
671                ContextUtils.pushObjectToContextDeep(collectionGroup1.getItems(),
672                        UifConstants.ContextVariableNames.PARENT_LINE, lineBuilderContext.getCurrentLine());
673            }
674        }
675
676        lineBuilderContext.setSubCollectionFields(subCollectionFields);
677    }
678
679    /**
680     * Checks whether the given sub-collection should be rendered, any conditional render string is evaluated.
681     *
682     * @param subCollectionGroup sub collection group to check render status for
683     * @return true if sub collection should be rendered, false if it
684     * should not be rendered
685     */
686    protected boolean checkSubCollectionRender(CollectionGroup subCollectionGroup) {
687        String conditionalRender = subCollectionGroup.getPropertyExpression(UifPropertyPaths.RENDER);
688
689        // TODO: check authorizer
690
691        // evaluate conditional render string if set
692        if (StringUtils.isNotBlank(conditionalRender)) {
693            Map<String, Object> context = new HashMap<String, Object>();
694            Map<String, Object> viewContext = ViewLifecycle.getView().getContext();
695
696            if (viewContext != null) {
697                context.putAll(viewContext);
698            }
699
700            context.put(UifConstants.ContextVariableNames.PARENT, lineBuilderContext.getCollectionGroup());
701            context.put(UifConstants.ContextVariableNames.COMPONENT, subCollectionGroup);
702
703            Boolean render = (Boolean) ViewLifecycle.getExpressionEvaluator().evaluateExpression(context,
704                    conditionalRender);
705            subCollectionGroup.setRender(render);
706        }
707
708        return subCollectionGroup.isRender();
709    }
710
711    /**
712     * Add additional information to the fields in the add line to allow for correct add control selection.
713     *
714     * @param lineFields list of fields instances for the line
715     */
716    protected void setupAddLineControlValidation(List<Field> lineFields) {
717        // don't process for anything but an add line
718        if (!lineBuilderContext.isAddLine()) {
719            return;
720        }
721
722        // set up skipping fields with the given selectors in add area during standard form validation calls
723        // custom addLineToCollection js call will validate these fields manually on an add
724        List<String> selectors = new ArrayList<String>();
725        String lineFieldSelector = UifConstants.IdSuffixes.CONTROL;
726        String nestedLineFieldSelector = UifConstants.IdSuffixes.ADD_LINE + UifConstants.IdSuffixes.CONTROL;
727
728        // apply changes to and collect selectors from all fields and field groups
729        for (Field lineField : lineFields) {
730            if (lineField instanceof InputField) {
731                setupAddLineControlValidation((InputField) lineField, selectors, lineFieldSelector);
732            } else if (lineField instanceof FieldGroup) {
733                Group group = ((FieldGroup) lineField).getGroup();
734                List<InputField> nestedLineFields = ViewLifecycleUtils.getElementsOfTypeDeep(group, InputField.class);
735
736                for (InputField nestedLineField : nestedLineFields) {
737                    setupAddLineControlValidation(nestedLineField, selectors, nestedLineFieldSelector);
738                }
739            }
740        }
741
742        // add collected selectors to data attributes
743        lineBuilderContext.getCollectionGroup().addDataAttribute(
744                UifConstants.DataAttributes.ADD_CONTROLS, StringUtils.join(selectors, ","));
745    }
746
747    /**
748     * Add additional information to a field in the add line to allow for correct add control selection.
749     *
750     * @param lineField field instance for the line
751     * @param selectors list of selectors
752     * @param suffix id suffix to add
753     */
754    protected void setupAddLineControlValidation(InputField lineField, List<String> selectors, String suffix) {
755        Control control = lineField.getControl();
756
757        // ignore automatic validation and grab the selector for manual validation
758        if (control != null) {
759            control.addStyleClass(CssConstants.Classes.IGNORE_VALID);
760            selectors.add("#" + lineField.getId() + suffix);
761        }
762    }
763
764    /**
765     * Setup edit line dialog group with the line fields
766     *
767     * <p>The items for a dialog are created from line fields and added if not provided by the user, but
768     * if they are then each item is processed.</p>
769     */
770    protected void setupEditLineDetails() {
771        CollectionGroup group = lineBuilderContext.getCollectionGroup();
772
773        if (!group.isEditWithDialog()) {
774            return;
775        }
776
777        for (DialogGroup lineDialog : group.getLineDialogs()) {
778            String dialogId = lineDialog.getId();
779
780            UifFormBase form = (UifFormBase) lineBuilderContext.getModel();
781            if (group.getCollectionGroupBuilder().refreshEditLineDialogContents(lineDialog, form, group,
782                    lineBuilderContext.getLineIndex()) && lineDialog.getId().contains(
783                    ComponentFactory.EDIT_LINE_DIALOG)) {
784                form.setUpdateComponent(lineDialog);
785                // if there are no custom user items or if there are no items, then use the line fields as items
786                if (lineDialog.getItems() == null || lineDialog.getItems().isEmpty() || !group
787                        .isCustomEditLineDialog()) {
788                    List<Field> lineFields = lineBuilderContext.getLineFields();
789
790                    // process and set items
791                    lineDialog.setItems(processDialogFieldsFromLineFields(lineFields, dialogId));
792
793                    // process the sub-collection items
794                    List<Component> items = new ArrayList<Component>(lineDialog.getItems());
795                    items.addAll(processDialogSubFieldsFromLineSubFields(lineDialog));
796                    items.addAll(processDialogSubFieldsFromRowDetails(lineDialog));
797                    lineDialog.setItems(items);
798                } else { // user provided dialog items
799                    List<Component> dialogFields = new ArrayList<>(lineDialog.getItems());
800                    List<Component> dialogComponents = new ArrayList<>();
801                    int fieldIndex = 0;
802                    int subIndex = 0;
803
804                    // for every user provided dialog item, find its corresponding line field and set the binding info
805                    for (Component dialogField : dialogFields) {
806                        if (dialogField instanceof DataField) {
807                            DataField dataField = (DataField) dialogField;
808                            DataField lineField = findItemInLineFields(dataField);
809
810                            if (lineField != null) {
811                                dataField.getBindingInfo().setCollectionPath(
812                                        lineField.getBindingInfo().getCollectionPath());
813                                // set the line field to read-only
814                                lineField.setReadOnly(true);
815                            } else {
816                                // update the binding info on the custom field
817                                dataField.getBindingInfo().setCollectionPath(group.getBindingInfo().getBindingName());
818                            }
819
820                            dataField.getBindingInfo().setBindByNamePrefix(UifPropertyPaths.DIALOG_DATA_OBJECT);
821                            dialogComponents.add(dataField);
822                        } else if (dialogField instanceof FieldGroup) {
823                            FieldGroup fieldGroup = (FieldGroup) dialogField;
824
825                            if (fieldGroup.getGroup() instanceof CollectionGroup) {
826                                dialogComponents.add(getNewFieldGroup(fieldGroup, (CollectionGroup) fieldGroup.
827                                        getGroup(), lineDialog, fieldIndex, subIndex, null));
828                                subIndex++;
829                            }
830                        } else if (dialogField instanceof CollectionGroup) {
831                            CollectionGroup collectionGroup = (CollectionGroup) dialogField;
832                            FieldGroup fieldGroupPrototype =
833                                    lineBuilderContext.getLayoutManager().getSubCollectionFieldGroupPrototype();
834
835                            dialogComponents.add(getNewFieldGroup(fieldGroupPrototype, collectionGroup, lineDialog,
836                                    fieldIndex, subIndex, UifPropertyPaths.DIALOG_DATA_OBJECT));
837                            subIndex++;
838                        } else {
839                            ComponentUtils.prefixBindingPath(dialogField, UifPropertyPaths.DIALOG_DATA_OBJECT);
840                            dialogComponents.add(dialogField);
841                        }
842                        fieldIndex++;
843                    }
844                    lineDialog.setItems(dialogComponents);
845                }
846            }
847
848        }
849
850        // set all collection fields and sub-collection fields to readOnly
851        if (lineBuilderContext.getCollectionGroup().isEditWithDialog()) {
852            for (Field lineField : lineBuilderContext.getLineFields()) {
853                if (lineField instanceof InputField) {
854                    lineField.setReadOnly(Boolean.TRUE);
855                }
856            }
857            List<FieldGroup> subLineFields = lineBuilderContext.getSubCollectionFields();
858            if (subLineFields != null) {
859                for (FieldGroup subLineField : subLineFields) {
860                    subLineField.setReadOnly(Boolean.TRUE);
861                }
862            }
863        }
864    }
865
866    /**
867     * Helper method to build sub-collection fields for the given dialog using the line's sub-collection fields
868     *
869     * @param lineDialog the line dialog to build the sub-collection for
870     * @return the list of created sub-collection fields
871     */
872    private List<FieldGroup> processDialogSubFieldsFromLineSubFields(DialogGroup lineDialog) {
873        // process the subcollections
874        List<FieldGroup> subCollectionFields = lineBuilderContext.getSubCollectionFields();
875        List<FieldGroup> newSubCollectionFields = new ArrayList<FieldGroup>();
876        int fieldIndex = lineDialog.getItems().size();
877        int subIndex = 0;
878
879        for (FieldGroup subCollectionFieldGroup : subCollectionFields) {
880            CollectionGroup subCollectionGroup = (CollectionGroup) subCollectionFieldGroup.getGroup();
881
882            // make a copy of the sub-group for the dialog
883            newSubCollectionFields.add(getNewFieldGroup(subCollectionFieldGroup, subCollectionGroup, lineDialog,
884                    fieldIndex, subIndex, UifPropertyPaths.DIALOG_DATA_OBJECT));
885            fieldIndex++;
886            subIndex++;
887
888            List<Component> components = ViewLifecycleUtils.getElementsOfTypeDeep(subCollectionGroup.getItems(),
889                    Component.class);
890            for (Component component : components) {
891                component.setReadOnly(Boolean.TRUE);
892            }
893        }
894
895        return newSubCollectionFields;
896    }
897
898    /**
899     * Helper method to build sub-collection fields for the given dialog using the row details group
900     *
901     * @param lineDialog the line dialog to build the sub-collection for
902     * @return the list of created sub-collection fields
903     */
904    private List<Field> processDialogSubFieldsFromRowDetails(DialogGroup lineDialog) {
905        List<Field> newSubCollectionFields = new ArrayList<Field>();
906
907        // process the row details group
908        CollectionLayoutManager layoutManager = (CollectionLayoutManager) lineBuilderContext.
909                getCollectionGroup().getLayoutManager();
910
911        // only the table layout manager has row details
912        if (layoutManager instanceof TableLayoutManagerBase) {
913            TableLayoutManagerBase tableLayoutManagerBase = (TableLayoutManagerBase) layoutManager;
914            Group rowDetailsGroup = tableLayoutManagerBase.getRowDetailsGroup();
915
916            if (rowDetailsGroup != null) {
917                List<Component> subCollectionComponents = new ArrayList<Component>(rowDetailsGroup.getItems());
918
919                int fieldIndex = lineDialog.getItems().size();
920                int subIndex = 0;
921
922                // for each item in the row details group create a field group to add the the line dialog's items
923                for (Component component : subCollectionComponents) {
924                    if (component instanceof CollectionGroup) {
925                        CollectionGroup subCollectionGroup = (CollectionGroup) component;
926
927                        boolean renderSubCollection = checkSubCollectionRender(subCollectionGroup);
928                        if (!renderSubCollection) {
929                            continue;
930                        }
931
932                        FieldGroup fieldGroupPrototype = lineBuilderContext.
933                                getLayoutManager().getSubCollectionFieldGroupPrototype();
934                        newSubCollectionFields.add(getNewFieldGroup(fieldGroupPrototype, subCollectionGroup, lineDialog,
935                                fieldIndex, subIndex, UifPropertyPaths.DIALOG_DATA_OBJECT));
936                        subIndex++;
937                    } else if (component instanceof Field) {
938                        Field subCollectionField = (Field) component;
939                        Field newSubCollectionField = getNewFieldForEditLineDialog(subCollectionField,
940                                lineDialog.getId() + UifConstants.IdSuffixes.FIELDSET + Integer.toString(fieldIndex++));
941
942                        newSubCollectionFields.add(newSubCollectionField);
943                    }
944
945                    ContextUtils.pushObjectToContextDeep(newSubCollectionFields,
946                            UifConstants.ContextVariableNames.PARENT_LINE,
947                            ((UifFormBase) lineBuilderContext.getModel()).getDialogDataObject());
948                }
949            }
950        }
951
952        return newSubCollectionFields;
953    }
954
955    /**
956     * Helper method that creates a new field group for a given sub-collection.
957     *
958     * @param fieldGroupPrototype the field group prototype to use
959     * @param subCollectionGroup the sub-collection to create field group for
960     * @param lineDialog the line dialog that the field group should be in
961     * @param fieldIndex the index to apply for the field group
962     * @param subIndex the index to apply for the sub-collection
963     * @return the created field group
964     */
965    private FieldGroup getNewFieldGroup(FieldGroup fieldGroupPrototype, CollectionGroup subCollectionGroup,
966            DialogGroup lineDialog, int fieldIndex, int subIndex, String bindingPrefix) {
967        FieldGroup newSubCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype);
968        newSubCollectionFieldGroup.setId(lineDialog.getId() +
969                UifConstants.IdSuffixes.FIELDSET + Integer.toString(fieldIndex));
970        newSubCollectionFieldGroup.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, lineDialog);
971
972        CollectionGroup newSubCollectionGroup = ComponentUtils.copy(subCollectionGroup);
973        newSubCollectionGroup.setId(newSubCollectionFieldGroup.getId() + UifConstants.IdSuffixes.SUB +
974                Integer.toString(subIndex));
975        if (bindingPrefix != null) {
976            newSubCollectionGroup.getBindingInfo().setBindByNamePrefix(bindingPrefix);
977        }
978
979        if (newSubCollectionGroup.getBindingInfo().getBindingName() == null) {
980            newSubCollectionGroup.getBindingInfo().setBindingName(newSubCollectionGroup.getPropertyName());
981        }
982
983        newSubCollectionGroup.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, lineDialog);
984        newSubCollectionGroup.addDataAttribute(UifConstants.
985                ContextVariableNames.PARENT, lineDialog.getId());
986
987        if (newSubCollectionGroup.isRenderAddLine()) {
988            newSubCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(newSubCollectionGroup.
989                    getBindingInfo().getBindByNamePrefix());
990            newSubCollectionGroup.getAddLineBindingInfo().setBindingName(newSubCollectionGroup.
991                    getBindingInfo().getBindingName());
992            String addBindingPath = UifPropertyPaths.NEW_COLLECTION_LINES + "['" +
993                    newSubCollectionGroup.getBindingInfo().getBindByNamePrefix() + "." +
994                    newSubCollectionGroup.getBindingInfo().getBindingName() + "']";
995            Object addLine = ObjectPropertyUtils.getPropertyValue(lineBuilderContext.getModel(), addBindingPath);
996            if (addLine != null) {
997                ObjectPropertyUtils.setPropertyValue(lineBuilderContext.getModel(), addBindingPath, null);
998            }
999        }
1000
1001        // set the new collection group's line actions to refresh the entire
1002        // dialog not just the sub-collection
1003        List<Action> subLineActions = ViewLifecycleUtils.getElementsOfTypeDeep(newSubCollectionGroup.getLineActions(),
1004                Action.class);
1005        setupSubCollectionActions(subLineActions, lineDialog.getId(),
1006                lineBuilderContext.getCollectionGroup().getBindingInfo().getBindingName(),
1007                lineBuilderContext.getLineIndex());
1008
1009        // initialize the new sub-collections line actions
1010        lineBuilderContext.getCollectionGroup().getCollectionGroupBuilder().initializeLineActions(subLineActions,
1011                ViewLifecycle.getView(), newSubCollectionGroup, lineBuilderContext.getCurrentLine(),
1012                lineBuilderContext.getLineIndex());
1013
1014        // get the add line actions for this group
1015            List<Component> subAddLineComponents = new ArrayList<Component>(newSubCollectionGroup.getAddLineActions());
1016            if (newSubCollectionGroup.getAddBlankLineAction() != null) {
1017                subAddLineComponents.add(newSubCollectionGroup.getAddBlankLineAction());
1018            }
1019            List<Action> subAddLineActions = ViewLifecycleUtils.getElementsOfTypeDeep(subAddLineComponents,
1020                    Action.class);
1021
1022            // initialize the new sub-collections add line actions
1023            setupSubCollectionActions(subAddLineActions, lineDialog.getId(), lineBuilderContext.
1024                            getCollectionGroup().getBindingInfo().getBindingName(), lineBuilderContext.getLineIndex()
1025            );
1026
1027        newSubCollectionFieldGroup.setGroup(newSubCollectionGroup);
1028
1029        ContextUtils.updateContextForLine(newSubCollectionFieldGroup, lineBuilderContext.
1030                        getCollectionGroup(), ((UifFormBase) lineBuilderContext.getModel()).
1031                        getDialogDataObject(), lineBuilderContext.getLineIndex(),
1032                lineBuilderContext.getIdSuffix() + UifConstants.IdSuffixes.SUB + subIndex
1033        );
1034        ContextUtils.pushObjectToContextDeep(newSubCollectionGroup, UifConstants.ContextVariableNames.PARENT_LINE,
1035                ((UifFormBase) lineBuilderContext.getModel()).getDialogDataObject());
1036        return newSubCollectionFieldGroup;
1037    }
1038
1039    /**
1040     * Helper method that builds line dialog fields from the line fields
1041     *
1042     * @param lineFields the fields in the component
1043     * @param prefix the prefix to use in the id for the new fields
1044     * @return the list of created fields
1045     */
1046    private List<Field> processDialogFieldsFromLineFields(List<Field> lineFields, String prefix) {
1047        List<Field> newLineFields = new ArrayList<Field>();
1048
1049        // for each line field create and add a corresponding dialog field
1050        int fieldIndex = 0;
1051        for (Field lineField : lineFields) {
1052            if (!(lineField instanceof FieldGroup)) {
1053                Field newLineField = getNewFieldForEditLineDialog(lineField, prefix +
1054                        UifConstants.IdSuffixes.FIELDSET + Integer.toString(fieldIndex));
1055                newLineFields.add(newLineField);
1056                fieldIndex++;
1057            }
1058        }
1059        return newLineFields;
1060    }
1061
1062    /**
1063     * Helper method to create a new field that is a copy of a given field for the edit line dialog.
1064     *
1065     * @param field the field to copy
1066     * @param id the id of the new field
1067     * @return the new field
1068     */
1069    private Field getNewFieldForEditLineDialog(Field field, String id) {
1070        Field newLineField = ComponentUtils.copy(field, id);
1071
1072        // set the line field to point to the dialog's data object
1073        if (newLineField instanceof DataField) {
1074            ((DataField) newLineField).getBindingInfo().setBindByNamePrefix(UifPropertyPaths.DIALOG_DATA_OBJECT);
1075        }
1076        return newLineField;
1077    }
1078
1079    /**
1080     * Helper method to setup edit line dialog's sub-collection's line actions.
1081     *
1082     * @param actions the actions to setup
1083     * @param dialogId the id of the dialog the sub-collection is in
1084     * @param bindingName the binding name of the dialog's sub-collection
1085     * @param lineIndex the index of the line being edited in the dialog
1086     */
1087    private void setupSubCollectionActions(List<Action> actions, String dialogId, String bindingName, int lineIndex) {
1088        for (Action action : actions) {
1089            action.setDialogDismissOption("REQUEST");
1090            action.setRefreshId(StringUtils.substring(dialogId, dialogId.indexOf("_") + 1, dialogId.lastIndexOf("_")));
1091            String actionScript = UifConstants.JsFunctions.SHOW_EDIT_LINE_DIALOG + "('" +
1092                    dialogId + "', '" + bindingName + "', " + lineIndex + ");";
1093            action.setRefreshedByAction(false);
1094            action.setSuccessCallback("jQuery.unblockUI();" + actionScript);
1095            action.setOnClickScript("jQuery('#" + dialogId +
1096                "').one('hide.bs.modal', function(e) { jQuery.blockUI({ message: '<h1>Editing line ...</h1>' }); });");
1097            action.addActionParameter(UifParameters.DIALOG_ID, dialogId);
1098        }
1099    }
1100
1101    /**
1102     * Helper method that gets a line item that corresponds to the given field
1103     *
1104     * <p>In this case, the corresponding line item for a field is one where the field's property names
1105     * are the same.</p>
1106     *
1107     * @param dataItem the data field to get the line item for
1108     */
1109    private DataField findItemInLineFields(DataField dataItem) {
1110        for (Field field : lineBuilderContext.getLineFields()) {
1111            if (field instanceof DataField) {
1112                if (dataItem.getPropertyName().equals(((DataField) field).getPropertyName())) {
1113                    return (DataField) field;
1114                }
1115            }
1116        }
1117        return null;
1118    }
1119
1120    /**
1121     * Helper method to build the context for a field (needed because the apply model phase for line fields has
1122     * not been applied yet and their full context not set)
1123     *
1124     * @param view view instance the field belongs to
1125     * @param collectionGroup collection group instance the field belongs to
1126     * @param field field instance to build context for
1127     * @return Map<String, Object> context for field
1128     */
1129    protected Map<String, Object> getContextForField(View view, CollectionGroup collectionGroup, Field field) {
1130        Map<String, Object> context = new HashMap<String, Object>();
1131
1132        Map<String, Object> viewContext = view.getContext();
1133        if (viewContext != null) {
1134            context.putAll(viewContext);
1135        }
1136
1137        Map<String, Object> fieldContext = field.getContext();
1138        if (fieldContext != null) {
1139            context.putAll(fieldContext);
1140        }
1141
1142        context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup);
1143        context.put(UifConstants.ContextVariableNames.COMPONENT, field);
1144
1145        return context;
1146    }
1147}