View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.container;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.kim.api.identity.Person;
20  import org.kuali.rice.krad.uif.CssConstants;
21  import org.kuali.rice.krad.uif.UifConstants;
22  import org.kuali.rice.krad.uif.UifPropertyPaths;
23  import org.kuali.rice.krad.uif.component.BindingInfo;
24  import org.kuali.rice.krad.uif.component.Component;
25  import org.kuali.rice.krad.uif.component.ComponentSecurity;
26  import org.kuali.rice.krad.uif.component.DataBinding;
27  import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
28  import org.kuali.rice.krad.uif.control.Control;
29  import org.kuali.rice.krad.uif.control.ControlBase;
30  import org.kuali.rice.krad.uif.element.Action;
31  import org.kuali.rice.krad.uif.field.Field;
32  import org.kuali.rice.krad.uif.field.FieldGroup;
33  import org.kuali.rice.krad.uif.field.InputField;
34  import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
35  import org.kuali.rice.krad.uif.layout.CollectionLayoutManager;
36  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
37  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
38  import org.kuali.rice.krad.uif.util.ComponentUtils;
39  import org.kuali.rice.krad.uif.util.ScriptUtils;
40  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
41  import org.kuali.rice.krad.uif.view.View;
42  import org.kuali.rice.krad.uif.view.ViewAuthorizer;
43  import org.kuali.rice.krad.uif.view.ViewModel;
44  import org.kuali.rice.krad.uif.view.ViewPresentationController;
45  import org.kuali.rice.krad.util.GlobalVariables;
46  import org.kuali.rice.krad.web.form.UifFormBase;
47  
48  import java.io.Serializable;
49  import java.util.ArrayList;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Map;
53  
54  /**
55   * Process configuration from the collection group to prepare components for a new line and invokes the associated
56   * layout manager to add the line.
57   *
58   * @author Kuali Rice Team (rice.collab@kuali.org)
59   */
60  public class CollectionGroupLineBuilder implements Serializable {
61      private static final long serialVersionUID = 981187437246864378L;
62  
63      private LineBuilderContext lineBuilderContext;
64  
65      public CollectionGroupLineBuilder(LineBuilderContext lineBuilderContext) {
66          this.lineBuilderContext = lineBuilderContext;
67      }
68  
69      /**
70       * Invoked to build a line in the collection.
71       *
72       * <p>First the context for the line is preprocessed in {@link CollectionGroupLineBuilder#preprocessLine()}.
73       * After preprocessing the configured layout manager is invoked to place the line into the layout.</p>
74       */
75      public void buildLine() {
76          preprocessLine();
77  
78          boolean hasLineFields =
79                  (lineBuilderContext.getLineFields() != null) && (!lineBuilderContext.getLineFields().isEmpty());
80          boolean hasSubCollections = (lineBuilderContext.getSubCollectionFields() != null) &&
81                  (!lineBuilderContext.getSubCollectionFields().isEmpty());
82  
83          // invoke layout manager to build the complete line
84          if (hasLineFields || hasSubCollections) {
85              lineBuilderContext.getLayoutManager().buildLine(lineBuilderContext);
86          }
87  
88          // After the lines have been processed and correct value set for readOnly and other properties
89          // set the script for enabling/disabling save button
90          applyOnChangeForSave(lineBuilderContext.getLineFields());
91      }
92  
93      /**
94       * Performs various preprocessing of the line components and configuration.
95       *
96       * <p>Preprocessing includes:
97       * <ul>
98       * <li>Make a copy of the collection groups items and adjust binding and id</li>
99       * <li>Process any remotable fields within the line</li>
100      * <li>Check line and field authorization</li>
101      * <li>Remove fields that should not be rendered</li>
102      * <li>Configure client side functionality such as save enable and add line validation</li>
103      * <li>Build field groups to hold any configured sub-collections</li>
104      * </ul></p>
105      */
106     public void preprocessLine() {
107         List<? extends Component> lineItems = initializeLineItems();
108 
109         List<Field> lineFields = processAnyRemoteFieldsHolder(lineBuilderContext.getCollectionGroup(), lineItems);
110 
111         adjustFieldBindingAndId(lineFields);
112 
113         // update contexts before add line fields are added to the index below
114         ComponentUtils.updateContextsForLine(lineFields, lineBuilderContext.getCollectionGroup(),
115                 lineBuilderContext.getCurrentLine(), lineBuilderContext.getLineIndex(),
116                 lineBuilderContext.getIdSuffix());
117 
118         boolean canViewLine = checkViewLineAuthorization();
119         if (!canViewLine) {
120             return;
121         }
122 
123         List<Action> actions = ViewLifecycleUtils.getElementsOfTypeDeep(lineBuilderContext.getLineActions(),
124                 Action.class);
125         setFocusOnIdForActions(actions, lineFields);
126 
127         boolean canEditLine = checkEditLineAuthorization(lineFields);
128         ComponentUtils.pushObjectToContext(lineFields, UifConstants.ContextVariableNames.READONLY_LINE, !canEditLine);
129         ComponentUtils.pushObjectToContext(actions, UifConstants.ContextVariableNames.READONLY_LINE, !canEditLine);
130 
131         // check authorization for line fields
132         applyLineFieldAuthorizationAndPresentationLogic(!canEditLine, lineFields, actions);
133 
134         // remove fields from the line that have render false
135         lineFields = removeNonRenderLineFields(lineFields);
136 
137         buildSubCollectionFieldGroups();
138 
139         // update action parameters for any actions that were added in the line items (as opposed to the line actions)
140         List<Action> lineFieldActions = ViewLifecycleUtils.getElementsOfTypeDeep(lineFields, Action.class);
141         if (lineFieldActions != null) {
142             lineBuilderContext.getCollectionGroup().getCollectionGroupBuilder().initializeActions(lineFieldActions,
143                     lineBuilderContext.getCollectionGroup(), lineBuilderContext.getLineIndex());
144         }
145 
146         setupAddLineControlValidation(lineFields);
147 
148         lineBuilderContext.setLineFields(lineFields);
149     }
150 
151     /**
152      * Copies either the collections groups items or add line items to a list of components that will be used
153      * for the collection line.
154      *
155      * @return list of component instance for the collection line
156      */
157     protected List<? extends Component> initializeLineItems() {
158         List<? extends Component> lineItems;
159 
160         if (lineBuilderContext.isAddLine()) {
161             lineItems = ComponentUtils.copyComponentList(lineBuilderContext.getCollectionGroup().getAddLineItems(),
162                     null);
163         } else {
164             lineItems = ComponentUtils.copyComponentList(lineBuilderContext.getCollectionGroup().getItems(), null);
165         }
166 
167         return lineItems;
168     }
169 
170     /**
171      * Iterates through the given items checking for {@code RemotableFieldsHolder}, if found
172      * the holder is invoked to retrieved the remotable fields and translate to attribute fields.
173      *
174      * <p>The translated list is then inserted into the returned list at the position of the holder</p>
175      *
176      * @param group collection group instance to check for any remotable fields holder
177      * @param items list of items to process
178      */
179     public List<Field> processAnyRemoteFieldsHolder(CollectionGroup group, List<? extends Component> items) {
180         List<Field> processedItems = new ArrayList<Field>();
181 
182         // check for holders and invoke to retrieve the remotable fields and translate
183         // translated fields are placed into the processed items list at the position of the holder
184         for (Component item : items) {
185             if (item instanceof RemoteFieldsHolder) {
186                 List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(group);
187                 processedItems.addAll(translatedFields);
188             } else {
189                 processedItems.add((Field) item);
190             }
191         }
192 
193         return processedItems;
194     }
195 
196     /**
197      * Adjusts the binding path for the given fields to match the collections path, and updates the field ids to
198      * contain a suffix for the line.
199      *
200      * @param lineFields list of fields to update
201      */
202     protected void adjustFieldBindingAndId(List<Field> lineFields) {
203         // adjust binding path and id to match collection line path
204         ComponentUtils.bindAndIdFieldList(lineFields, lineBuilderContext.getBindingPath(),
205                 lineBuilderContext.getIdSuffix());
206 
207         for (Field field : lineFields) {
208             List<CollectionGroup> components = ViewLifecycleUtils.getElementsOfTypeDeep(field, CollectionGroup.class);
209             for (CollectionGroup fieldCollectionGroup : components) {
210                 fieldCollectionGroup.setSubCollectionSuffix(lineBuilderContext.getIdSuffix());
211             }
212         }
213 
214         if (lineBuilderContext.isBindToForm()) {
215             ComponentUtils.setComponentsPropertyDeep(lineFields, UifPropertyPaths.BIND_TO_FORM, Boolean.valueOf(true));
216         }
217     }
218 
219     /**
220      * For any actions with focus id {@link org.kuali.rice.krad.uif.UifConstants.Order#LINE_FIRST}, the focus id
221      * is replaced to match to id of the first control in the line.
222      *
223      * @param actions list of actions to potientially update
224      * @param lineFields list of line fields, the control for the first field in the list
225      * will be used for the focus id
226      */
227     protected void setFocusOnIdForActions(List<Action> actions, List<Field> lineFields) {
228         for (Action action : actions) {
229             if (action == null) {
230                 continue;
231             }
232 
233             boolean focusLineFirst = StringUtils.isNotBlank(action.getFocusOnIdAfterSubmit()) &&
234                     action.getFocusOnIdAfterSubmit().equalsIgnoreCase(UifConstants.Order.LINE_FIRST.toString());
235             boolean lineHasFields = !lineFields.isEmpty();
236             if (focusLineFirst && lineHasFields) {
237                 action.setFocusOnIdAfterSubmit(lineFields.get(0).getId() + UifConstants.IdSuffixes.CONTROL);
238             }
239         }
240     }
241 
242     /**
243      * If {@link CollectionGroup#isRenderSaveLineActions()} is true and the line has been added by the user, on change
244      * script is added to any controls in the line to enable the save action.
245      *
246      * @param lineFields list of line fields
247      */
248     protected void applyOnChangeForSave(List<Field> lineFields) {
249         boolean isLineNewlyAdded = ((UifFormBase) lineBuilderContext.getModel()).isAddedCollectionItem(
250                 lineBuilderContext.getCurrentLine());
251         boolean saveLineEnabled = lineBuilderContext.getCollectionGroup().isRenderSaveLineActions();
252 
253         if (!isLineNewlyAdded && !saveLineEnabled) {
254             return;
255         }
256 
257         for (Field field : lineFields) {
258             boolean isInputField = (field instanceof InputField);
259             if (field.isHidden() || field.isReadOnly() || !isInputField) {
260                 continue;
261             }
262 
263             ControlBase control = (ControlBase) ((InputField) field).getControl();
264 
265             String onBlurScript = UifConstants.JsFunctions.COLLECTION_LINE_CHANGED + "(this, '" +
266                     CssConstants.Classes.NEW_COLLECTION_ITEM + "');";
267             onBlurScript = ScriptUtils.appendScript(control.getOnBlurScript(), onBlurScript);
268 
269             control.setOnBlurScript(onBlurScript);
270         }
271     }
272 
273     /**
274      * Evaluates the render property for the given list of field instances for the line and removes any fields
275      * from the returned list that have render false.
276      *
277      * <p>The conditional render string is also taken into account. This needs to be done here as opposed
278      * to during the normal condition evaluation so the the fields are not used while building the
279      * collection lines</p>
280      *
281      * @param lineFields list of fields configured for the line
282      * @return list of field instances that should be rendered
283      */
284     protected List<Field> removeNonRenderLineFields(List<Field> lineFields) {
285         List<Field> fields = new ArrayList<Field>();
286 
287         ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
288 
289         for (Field lineField : lineFields) {
290             String conditionalRender = lineField.getPropertyExpression(UifPropertyPaths.RENDER);
291 
292             // evaluate conditional render string if set
293             if (StringUtils.isNotBlank(conditionalRender)) {
294                 Map<String, Object> context = getContextForField(ViewLifecycle.getView(),
295                         lineBuilderContext.getCollectionGroup(), lineField);
296 
297                 // Adjust the condition as ExpressionUtils.adjustPropertyExpressions will only be
298                 // executed after the collection is built.
299                 conditionalRender = expressionEvaluator.replaceBindingPrefixes(ViewLifecycle.getView(), lineField,
300                         conditionalRender);
301 
302                 Boolean render = (Boolean) expressionEvaluator.evaluateExpression(context, conditionalRender);
303                 lineField.setRender(render);
304             }
305 
306             // only add line field if set to render or if it is hidden by progressive render
307             if (lineField.isRender() || StringUtils.isNotBlank(lineField.getProgressiveRender())) {
308                 fields.add(lineField);
309             }
310         }
311 
312         return fields;
313     }
314 
315     /**
316      * Determines whether the user is authorized to the view the line.
317      *
318      * @return boolean true if the user can view the line, false if not
319      */
320     protected boolean checkViewLineAuthorization() {
321         boolean canViewLine = !lineBuilderContext.getCollectionGroup().isHidden();
322 
323         // check view line authorization if collection is not hidden
324         if (canViewLine && !lineBuilderContext.isAddLine()) {
325             canViewLine = checkViewLineAuthorizationAndPresentationLogic();
326         }
327 
328         if (!canViewLine) {
329             addUnauthorizedBindingInfo();
330         }
331 
332         return canViewLine;
333     }
334 
335     /**
336      * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
337      * to view the line (if a permission has been established).
338      *
339      * @return true if the user can view the line, false if not
340      */
341     protected boolean checkViewLineAuthorizationAndPresentationLogic() {
342         ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
343         ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
344 
345         Person user = GlobalVariables.getUserSession().getPerson();
346 
347         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
348 
349         boolean canViewLine = authorizer.canViewLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
350                 collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine(), user);
351         if (canViewLine) {
352             canViewLine = presentationController.canViewLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
353                     collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine());
354         }
355 
356         return canViewLine;
357     }
358 
359     /**
360      * Determines whether the user is authorized to the edit the line.
361      *
362      * @param lineFields list of fields configured for the line
363      * @return boolean true if the user can edit the line, false if not
364      */
365     protected boolean checkEditLineAuthorization(List<Field> lineFields) {
366         boolean canEditLine = !lineBuilderContext.getCollectionGroup().isReadOnly();
367 
368         if (!canEditLine) {
369             ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
370             View view = ViewLifecycle.getView();
371 
372             for (Field field : lineFields) {
373                 field.pushObjectToContext(UifConstants.ContextVariableNames.PARENT,
374                         lineBuilderContext.getCollectionGroup());
375                 field.pushAllToContext(view.getContext());
376                 field.pushObjectToContext(UifConstants.ContextVariableNames.COMPONENT, field);
377 
378                 expressionEvaluator.evaluatePropertyExpression(view, field.getContext(), field,
379                         UifPropertyPaths.READ_ONLY, true);
380 
381                 if (!field.isReadOnly()) {
382                     canEditLine = true;
383                     break;
384                 }
385             }
386         }
387 
388         if (canEditLine && !lineBuilderContext.isAddLine()) {
389             canEditLine = checkEditLineAuthorizationAndPresentationLogic();
390         }
391 
392         if (!canEditLine) {
393             addUnauthorizedBindingInfo();
394         }
395 
396         return canEditLine;
397     }
398 
399     /**
400      * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
401      * to edit the line (if a permission has been established).
402      *
403      * @return true if the user can edit the line, false if not
404      */
405     protected boolean checkEditLineAuthorizationAndPresentationLogic() {
406         ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
407         ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
408 
409         Person user = GlobalVariables.getUserSession().getPerson();
410 
411         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
412 
413         boolean canEditLine = authorizer.canEditLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
414                 collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine(), user);
415         if (canEditLine) {
416             canEditLine = presentationController.canEditLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
417                     collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine());
418         }
419 
420         return canEditLine;
421     }
422 
423     /**
424      * Adds a {@link org.kuali.rice.krad.uif.component.BindingInfo} instance for the given binding
425      * path to the collection groups unauthorized list.
426      */
427     protected void addUnauthorizedBindingInfo() {
428         if (lineBuilderContext.getCollectionGroup().getUnauthorizedLineBindingInfos() == null) {
429             lineBuilderContext.getCollectionGroup().setUnauthorizedLineBindingInfos(new ArrayList<BindingInfo>());
430         }
431 
432         BindingInfo bindingInfo = new BindingInfo();
433         bindingInfo.setDefaults(ViewLifecycle.getView(), lineBuilderContext.getBindingPath());
434         lineBuilderContext.getCollectionGroup().getUnauthorizedLineBindingInfos().add(bindingInfo);
435     }
436 
437     /**
438      * Iterates through the line fields and checks the view field authorization using the view's configured authorizer
439      * and presentation controller.
440      *
441      * <p>If the field is viewable, then sets the edit field authorization. Finally iterates
442      * through the line actions invoking the authorizer and presentation controller to authorizer the action</p>
443      *
444      * @param readOnlyLine flag indicating whether the line has been marked as read only (which will force the fields
445      * to be read only)
446      * @param lineFields list of fields instances for the line
447      * @param actionList list of action field instances for the line
448      */
449     protected void applyLineFieldAuthorizationAndPresentationLogic(boolean readOnlyLine, List<Field> lineFields,
450             List<? extends Component> actionList) {
451         ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
452         ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
453 
454         Person user = GlobalVariables.getUserSession().getPerson();
455         ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
456 
457         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
458         View view = ViewLifecycle.getView();
459         ViewModel model = lineBuilderContext.getModel();
460         Object currentLine = lineBuilderContext.getCurrentLine();
461 
462         for (Field lineField : lineFields) {
463             String propertyName = null;
464             if (lineField instanceof DataBinding) {
465                 propertyName = ((DataBinding) lineField).getPropertyName();
466             }
467 
468             // evaluate expression on fields component security (since apply model phase has not been invoked on
469             // them yet
470             ComponentSecurity componentSecurity = lineField.getComponentSecurity();
471 
472             Map<String, Object> context = getContextForField(ViewLifecycle.getView(), collectionGroup, lineField);
473             expressionEvaluator.evaluateExpressionsOnConfigurable(ViewLifecycle.getView(), componentSecurity, context);
474 
475             // check view field auth
476             if (!lineField.isRender() || lineField.isHidden()) {
477                 continue;
478             }
479 
480             boolean canViewField = authorizer.canViewLineField(view, model, collectionGroup,
481                     collectionGroup.getPropertyName(), currentLine, lineField, propertyName, user);
482             if (canViewField) {
483                 canViewField = presentationController.canViewLineField(view, model, collectionGroup,
484                         collectionGroup.getPropertyName(), currentLine, lineField, propertyName);
485             }
486 
487             if (!canViewField) {
488                 // since removing can impact layout, set to hidden
489                 // TODO: check into encryption setting
490                 lineField.setHidden(true);
491 
492                 if (lineField.getPropertyExpressions().containsKey(UifPropertyPaths.HIDDEN)) {
493                     lineField.getPropertyExpressions().remove(UifPropertyPaths.HIDDEN);
494                 }
495 
496                 continue;
497             }
498 
499             // check edit field auth
500             boolean canEditField = !readOnlyLine;
501             if (!readOnlyLine) {
502                 canEditField = authorizer.canEditLineField(view, model, collectionGroup,
503                         collectionGroup.getPropertyName(), currentLine, lineField, propertyName, user);
504                 if (canEditField) {
505                     canEditField = presentationController.canEditLineField(view, model, collectionGroup,
506                             collectionGroup.getPropertyName(), currentLine, lineField, propertyName);
507                 }
508             }
509 
510             if (readOnlyLine || !canEditField) {
511                 lineField.setReadOnly(true);
512 
513                 if (lineField.getPropertyExpressions().containsKey(UifPropertyPaths.READ_ONLY)) {
514                     lineField.getPropertyExpressions().remove(UifPropertyPaths.READ_ONLY);
515                 }
516             }
517         }
518 
519         // check auth on line actions
520         List<Action> actions = ViewLifecycleUtils.getElementsOfTypeDeep(actionList, Action.class);
521         for (Action action : actions) {
522             if (!action.isRender()) {
523                 continue;
524             }
525 
526             boolean canPerformAction = authorizer.canPerformLineAction(view, model, collectionGroup,
527                     collectionGroup.getPropertyName(), currentLine, action, action.getActionEvent(), action.getId(),
528                     user);
529             if (canPerformAction) {
530                 canPerformAction = presentationController.canPerformLineAction(view, model, collectionGroup,
531                         collectionGroup.getPropertyName(), currentLine, action, action.getActionEvent(),
532                         action.getId());
533             }
534 
535             if (!canPerformAction) {
536                 action.setRender(false);
537 
538                 if (action.getPropertyExpressions().containsKey(UifPropertyPaths.RENDER)) {
539                     action.getPropertyExpressions().remove(UifPropertyPaths.RENDER);
540                 }
541             }
542         }
543     }
544 
545     /**
546      * For each configured sub collection of the collection group, creates a field group by copying
547      * {@link org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype()} and adds
548      * to a list which is stored in the line context.
549      */
550     protected void buildSubCollectionFieldGroups() {
551         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
552         String idSuffix = lineBuilderContext.getIdSuffix();
553 
554         // sub collections are not created for the add line
555         if (lineBuilderContext.isAddLine() || (collectionGroup.getSubCollections() == null)) {
556             return;
557         }
558 
559         List<FieldGroup> subCollectionFields = new ArrayList<FieldGroup>();
560         for (int subLineIndex = 0; subLineIndex < collectionGroup.getSubCollections().size(); subLineIndex++) {
561             CollectionGroup subCollectionPrototype = collectionGroup.getSubCollections().get(subLineIndex);
562             CollectionGroup subCollectionGroup = ComponentUtils.copy(subCollectionPrototype, idSuffix);
563 
564             // verify the sub-collection should be rendered
565             boolean renderSubCollection = checkSubCollectionRender(subCollectionGroup);
566             if (!renderSubCollection) {
567                 continue;
568             }
569 
570             subCollectionGroup.getBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath());
571             if (subCollectionGroup.isRenderAddLine()) {
572                 subCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath());
573             }
574 
575             // set sub-collection suffix on group so it can be used for generated groups
576             String subCollectionSuffix = idSuffix;
577             if (StringUtils.isNotBlank(subCollectionGroup.getSubCollectionSuffix())) {
578                 subCollectionSuffix = subCollectionGroup.getSubCollectionSuffix() + idSuffix;
579             }
580             subCollectionGroup.setSubCollectionSuffix(subCollectionSuffix);
581 
582             FieldGroup fieldGroupPrototype =
583                     lineBuilderContext.getLayoutManager().getSubCollectionFieldGroupPrototype();
584 
585             FieldGroup subCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype,
586                     idSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
587             subCollectionFieldGroup.setGroup(subCollectionGroup);
588 
589             ComponentUtils.updateContextForLine(subCollectionFieldGroup, collectionGroup,
590                     lineBuilderContext.getCurrentLine(), lineBuilderContext.getLineIndex(),
591                     idSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
592             ComponentUtils.pushObjectToContext(subCollectionGroup, UifConstants.ContextVariableNames.PARENT_LINE,
593                     lineBuilderContext.getCurrentLine());
594 
595             subCollectionFields.add(subCollectionFieldGroup);
596         }
597 
598         ComponentUtils.pushObjectToContext(subCollectionFields, UifConstants.ContextVariableNames.PARENT_LINE,
599                 lineBuilderContext.getCurrentLine());
600 
601         lineBuilderContext.setSubCollectionFields(subCollectionFields);
602     }
603 
604     /**
605      * Checks whether the given sub-collection should be rendered, any conditional render string is evaluated.
606      *
607      * @param subCollectionGroup sub collection group to check render status for
608      * @return true if sub collection should be rendered, false if it
609      * should not be rendered
610      */
611     protected boolean checkSubCollectionRender(CollectionGroup subCollectionGroup) {
612         String conditionalRender = subCollectionGroup.getPropertyExpression(UifPropertyPaths.RENDER);
613 
614         // TODO: check authorizer
615 
616         // evaluate conditional render string if set
617         if (StringUtils.isNotBlank(conditionalRender)) {
618             Map<String, Object> context = new HashMap<String, Object>();
619             Map<String, Object> viewContext = ViewLifecycle.getView().getContext();
620 
621             if (viewContext != null) {
622                 context.putAll(viewContext);
623             }
624 
625             context.put(UifConstants.ContextVariableNames.PARENT, lineBuilderContext.getCollectionGroup());
626             context.put(UifConstants.ContextVariableNames.COMPONENT, subCollectionGroup);
627 
628             Boolean render = (Boolean) ViewLifecycle.getExpressionEvaluator().evaluateExpression(context,
629                     conditionalRender);
630             subCollectionGroup.setRender(render);
631         }
632 
633         return subCollectionGroup.isRender();
634     }
635 
636     /**
637      * Add additional information to the group and fields in the add line to allow for correct
638      * add control selection.
639      */
640     protected void setupAddLineControlValidation(List<Field> lineFields) {
641         if (!lineBuilderContext.isAddLine()) {
642             return;
643         }
644 
645         String selector = "";
646         for (Field field : lineFields) {
647             if (field instanceof InputField) {
648                 // sets up - skipping these fields in add area during standard form validation calls
649                 // custom addLineToCollection js call will validate these fields manually on an add
650                 Control control = ((InputField) field).getControl();
651 
652                 if (control != null) {
653                     control.addStyleClass(CssConstants.Classes.IGNORE_VALID);
654                     selector = selector + ",#" + field.getId() + UifConstants.IdSuffixes.CONTROL;
655                 }
656             } else if (field instanceof FieldGroup) {
657                 List<InputField> fields = ViewLifecycleUtils.getElementsOfTypeDeep(((FieldGroup) field).getGroup(),
658                         InputField.class);
659 
660                 for (InputField nestedField : fields) {
661                     Control control = nestedField.getControl();
662 
663                     if (control != null) {
664                         control.addStyleClass(CssConstants.Classes.IGNORE_VALID);
665                         selector = selector + ",#" + nestedField.getId() + UifConstants.IdSuffixes.CONTROL;
666                     }
667                 }
668             }
669         }
670 
671         lineBuilderContext.getCollectionGroup().addDataAttribute(UifConstants.DataAttributes.ADD_CONTROLS,
672                 selector.replaceFirst(",", ""));
673     }
674 
675     /**
676      * Helper method to build the context for a field (needed because the apply model phase for line fields has
677      * not been applied yet and their full context not set)
678      *
679      * @param view view instance the field belongs to
680      * @param collectionGroup collection group instance the field belongs to
681      * @param field field instance to build context for
682      * @return Map<String, Object> context for field
683      */
684     protected Map<String, Object> getContextForField(View view, CollectionGroup collectionGroup, Field field) {
685         Map<String, Object> context = new HashMap<String, Object>();
686 
687         Map<String, Object> viewContext = view.getContext();
688         if (viewContext != null) {
689             context.putAll(viewContext);
690         }
691 
692         Map<String, Object> fieldContext = field.getContext();
693         if (fieldContext != null) {
694             context.putAll(fieldContext);
695         }
696 
697         context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup);
698         context.put(UifConstants.ContextVariableNames.COMPONENT, field);
699 
700         return context;
701     }
702 }