View Javadoc

1   /**
2    * Copyright 2005-2013 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.collections.ListUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.kuali.rice.core.api.mo.common.active.Inactivatable;
23  import org.kuali.rice.kim.api.identity.Person;
24  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.UifParameters;
27  import org.kuali.rice.krad.uif.UifPropertyPaths;
28  import org.kuali.rice.krad.uif.component.Component;
29  import org.kuali.rice.krad.uif.component.ComponentSecurity;
30  import org.kuali.rice.krad.uif.component.DataBinding;
31  import org.kuali.rice.krad.uif.control.Control;
32  import org.kuali.rice.krad.uif.control.ControlBase;
33  import org.kuali.rice.krad.uif.element.Action;
34  import org.kuali.rice.krad.uif.field.Field;
35  import org.kuali.rice.krad.uif.field.FieldGroup;
36  import org.kuali.rice.krad.uif.field.InputField;
37  import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
38  import org.kuali.rice.krad.uif.layout.CollectionLayoutManager;
39  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
40  import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
41  import org.kuali.rice.krad.uif.util.ComponentUtils;
42  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
43  import org.kuali.rice.krad.uif.util.ScriptUtils;
44  import org.kuali.rice.krad.uif.view.View;
45  import org.kuali.rice.krad.uif.view.ViewAuthorizer;
46  import org.kuali.rice.krad.uif.view.ViewModel;
47  import org.kuali.rice.krad.uif.view.ViewPresentationController;
48  import org.kuali.rice.krad.util.GlobalVariables;
49  import org.kuali.rice.krad.util.KRADUtils;
50  import org.kuali.rice.krad.util.ObjectUtils;
51  import org.kuali.rice.krad.web.form.UifFormBase;
52  
53  import java.io.Serializable;
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.HashMap;
57  import java.util.List;
58  import java.util.Map;
59  
60  /**
61   * Builds out the {@code Field} instances for a collection group with a
62   * series of steps that interact with the configured
63   * {@code CollectionLayoutManager} to assemble the fields as necessary for
64   * the layout
65   *
66   * @author Kuali Rice Team (rice.collab@kuali.org)
67   */
68  public class CollectionGroupBuilder implements Serializable {
69      private static final long serialVersionUID = -4762031957079895244L;
70      private static Log LOG = LogFactory.getLog(CollectionGroupBuilder.class);
71  
72      /**
73       * Creates the {@code Field} instances that make up the table
74       *
75       * <p>
76       * The corresponding collection is retrieved from the model and iterated
77       * over to create the necessary fields. The binding path for fields that
78       * implement {@code DataBinding} is adjusted to point to the collection
79       * line it is apart of. For example, field 'number' of collection 'accounts'
80       * for line 1 will be set to 'accounts[0].number', and for line 2
81       * 'accounts[1].number'. Finally parameters are set on the line's action
82       * fields to indicate what collection and line they apply to.
83       * </p>
84       *
85       * @param view - View instance the collection belongs to
86       * @param model - Top level object containing the data
87       * @param collectionGroup - CollectionGroup component for the collection
88       */
89      public void build(View view, Object model, CollectionGroup collectionGroup) {
90          // create add line
91          if (collectionGroup.isRenderAddLine() && !collectionGroup.isReadOnly() &&
92                  !collectionGroup.isRenderAddBlankLineButton()) {
93              buildAddLine(view, model, collectionGroup);
94          }
95  
96          // if add line button enabled setup to refresh the collection group
97          if (collectionGroup.isRenderAddBlankLineButton() && (collectionGroup.getAddBlankLineAction() != null)) {
98              collectionGroup.getAddBlankLineAction().setRefreshId(collectionGroup.getId());
99          }
100 
101         // get the collection for this group from the model
102         List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(model,
103                 ((DataBinding) collectionGroup).getBindingInfo().getBindingPath());
104 
105         if (modelCollection != null) {
106             // filter inactive model
107             List<Integer> showIndexes = performCollectionFiltering(view, model, collectionGroup, modelCollection);
108 
109             if (collectionGroup.getDisplayCollectionSize() != -1 && showIndexes.size() > collectionGroup
110                     .getDisplayCollectionSize()) {
111                 // remove all indexes in showIndexes beyond the collection's size limitation
112                 List<Integer> newShowIndexes = new ArrayList<Integer>();
113                 Integer counter = 0;
114 
115                 for (int index = 0; index < showIndexes.size(); index++) {
116                     newShowIndexes.add(showIndexes.get(index));
117 
118                     counter++;
119 
120                     if (counter == collectionGroup.getDisplayCollectionSize()) {
121                         break;
122                     }
123                 }
124 
125                 showIndexes = newShowIndexes;
126             }
127 
128             // for each collection row build the line fields
129             for (int index = 0; index < modelCollection.size(); index++) {
130                 // display only records that passed filtering
131                 if (showIndexes.contains(index)) {
132                     String bindingPathPrefix = collectionGroup.getBindingInfo().getBindingName() + "[" + index + "]";
133                     if (StringUtils.isNotBlank(collectionGroup.getBindingInfo().getBindByNamePrefix())) {
134                         bindingPathPrefix =
135                                 collectionGroup.getBindingInfo().getBindByNamePrefix() + "." + bindingPathPrefix;
136                     }
137 
138                     Object currentLine = modelCollection.get(index);
139 
140                     List<Action> lineActions = initializeLineActions(collectionGroup.getLineActions(), view, model,
141                             collectionGroup, currentLine, index);
142 
143                     buildLine(view, model, collectionGroup, bindingPathPrefix, lineActions, false, currentLine, index);
144                 }
145             }
146         }
147     }
148 
149     /**
150      * Performs any filtering necessary on the collection before building the collection fields
151      *
152      * <p>
153      * If showInactive is set to false and the collection line type implements {@code Inactivatable},
154      * invokes the active collection filter. Then any {@link CollectionFilter} instances configured for the collection
155      * group are invoked to filter the collection. Collections lines must pass all filters in order to be
156      * displayed
157      * </p>
158      *
159      * @param view - view instance that contains the collection
160      * @param model - object containing the views data
161      * @param collectionGroup - collection group component instance that will display the collection
162      * @param collection - collection instance that will be filtered
163      */
164     protected List<Integer> performCollectionFiltering(View view, Object model, CollectionGroup collectionGroup,
165             Collection<?> collection) {
166         List<Integer> filteredIndexes = new ArrayList<Integer>();
167         for (int i = 0; i < collection.size(); i++) {
168             filteredIndexes.add(Integer.valueOf(i));
169         }
170 
171         if (Inactivatable.class.isAssignableFrom(collectionGroup.getCollectionObjectClass()) && !collectionGroup
172                 .isShowInactiveLines()) {
173             List<Integer> activeIndexes = collectionGroup.getActiveCollectionFilter().filter(view, model,
174                     collectionGroup);
175             filteredIndexes = ListUtils.intersection(filteredIndexes, activeIndexes);
176         }
177 
178         for (CollectionFilter collectionFilter : collectionGroup.getFilters()) {
179             List<Integer> indexes = collectionFilter.filter(view, model, collectionGroup);
180             filteredIndexes = ListUtils.intersection(filteredIndexes, indexes);
181             if (filteredIndexes.isEmpty()) {
182                 break;
183             }
184         }
185 
186         return filteredIndexes;
187     }
188 
189     /**
190      * Builds the fields for holding the collection add line and if necessary
191      * makes call to setup the new line instance
192      *
193      * @param view - view instance the collection belongs to
194      * @param collectionGroup - collection group the layout manager applies to
195      * @param model - Object containing the view data, should extend UifFormBase
196      * if using framework managed new lines
197      */
198     protected void buildAddLine(View view, Object model, CollectionGroup collectionGroup) {
199         boolean addLineBindsToForm = false;
200 
201         // initialize new line if one does not already exist
202         initializeNewCollectionLine(view, model, collectionGroup, false);
203 
204         // determine whether the add line binds to the generic form map or a
205         // specified property
206         if (StringUtils.isBlank(collectionGroup.getAddLinePropertyName())) {
207             addLineBindsToForm = true;
208         }
209 
210         String addLineBindingPath = collectionGroup.getAddLineBindingInfo().getBindingPath();
211         List<Action> actions = getAddLineActions(view, model, collectionGroup);
212 
213         Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLineBindingPath);
214         buildLine(view, model, collectionGroup, addLineBindingPath, actions, addLineBindsToForm, addLine, -1);
215     }
216 
217     /**
218      * Builds the field instances for the collection line. A copy of the
219      * configured items on the {@code CollectionGroup} is made and adjusted
220      * for the line (id and binding). Then a call is made to the
221      * {@code CollectionLayoutManager} to assemble the line as necessary
222      * for the layout
223      *
224      * @param view - view instance the collection belongs to
225      * @param model - top level object containing the data
226      * @param collectionGroup - collection group component for the collection
227      * @param bindingPath - binding path for the line fields (if DataBinding)
228      * @param actions - List of actions to set in the lines action column
229      * @param bindToForm - whether the bindToForm property on the items bindingInfo
230      * should be set to true (needed for add line)
231      * @param currentLine - object instance for the current line, or null if add line
232      * @param lineIndex - index of the line in the collection, or -1 if we are
233      * building the add line
234      */
235     @SuppressWarnings("unchecked")
236     protected void buildLine(View view, Object model, CollectionGroup collectionGroup, String bindingPath,
237             List<Action> actions, boolean bindToForm, Object currentLine, int lineIndex) {
238         CollectionLayoutManager layoutManager = (CollectionLayoutManager) collectionGroup.getLayoutManager();
239 
240         // copy group items for new line
241         List<? extends Component> lineItems = null;
242         String lineSuffix = null;
243         if (lineIndex == -1) {
244             lineItems = ComponentUtils.copyComponentList(collectionGroup.getAddLineItems(), null);
245             lineSuffix = UifConstants.IdSuffixes.ADD_LINE;
246         } else {
247             lineItems = ComponentUtils.copyComponentList(collectionGroup.getItems(), null);
248             lineSuffix = UifConstants.IdSuffixes.LINE + Integer.toString(lineIndex);
249         }
250 
251         if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) {
252             lineSuffix = collectionGroup.getSubCollectionSuffix() + lineSuffix;
253         }
254 
255         // check for remote fields holder
256         List<Field> lineFields = processAnyRemoteFieldsHolder(view, model, collectionGroup, lineItems);
257 
258         // copy fields for line and adjust binding to match collection line path
259         lineFields = (List<Field>) ComponentUtils.copyFieldList(lineFields, bindingPath, lineSuffix);
260 
261         // If the fields contain any collections themselves (details case) adjust their binding path
262         // TODO: does the copyFieldList method above not take care of this?
263         for (Field field : lineFields) {
264             List<CollectionGroup> components = ComponentUtils.getComponentsOfTypeDeep(field, CollectionGroup.class);
265             for (CollectionGroup fieldCollectionGroup : components) {
266                 ComponentUtils.prefixBindingPath(fieldCollectionGroup, bindingPath);
267                 fieldCollectionGroup.setSubCollectionSuffix(lineSuffix);
268             }
269         }
270 
271         boolean readOnlyLine = collectionGroup.isReadOnly();
272 
273         // update contexts before add line fields are added to the index below
274         ComponentUtils.updateContextsForLine(lineFields, currentLine, lineIndex, lineSuffix);
275 
276         for (Action action : actions) {
277             if(action !=null && StringUtils.isNotBlank(action.getFocusOnIdAfterSubmit()) &&
278                     action.getFocusOnIdAfterSubmit().equalsIgnoreCase(UifConstants.Order.LINE_FIRST.toString())
279                     && (lineFields.size() > 0)){
280                 action.setFocusOnIdAfterSubmit(lineFields.get(0).getId() + UifConstants.IdSuffixes.CONTROL);
281             }
282         }
283 
284         // add special css styles to identify the add line client side
285         if (lineIndex == -1) {
286             // do nothing
287         } else {
288             // for existing lines, check view line auth
289             boolean canViewLine = checkViewLineAuthorizationAndPresentationLogic(view, (ViewModel) model,
290                     collectionGroup, currentLine);
291 
292             // if line is not viewable, just return without calling the layout manager to add the line
293             if (!canViewLine) {
294                 return;
295             }
296 
297             // check edit line authorization if collection is not read only
298             if (!collectionGroup.isReadOnly()) {
299                 readOnlyLine = !checkEditLineAuthorizationAndPresentationLogic(view, (ViewModel) model, collectionGroup,
300                         currentLine);
301 
302                 // Add script to fields to activate save button on any change
303                 if (!readOnlyLine && !((UifFormBase) model).isAddedCollectionItem(currentLine) &&
304                         collectionGroup.isRenderSaveLineActions()) {
305                     for (Field f : lineFields) {
306                         if (f instanceof InputField && f.isRender()) {
307                             ControlBase control = (ControlBase) ((InputField) f).getControl();
308                             control.setOnChangeScript(control.getOnChangeScript() == null ?
309                                     ";collectionLineChanged(this, 'uif-newCollectionItem');" :
310                                     control.getOnChangeScript() +
311                                     ";collectionLineChanged(this, 'uif-newCollectionItem');");
312                         }
313                     }
314                 }
315             }
316 
317             ComponentUtils.pushObjectToContext(lineFields, UifConstants.ContextVariableNames.READONLY_LINE,
318                     readOnlyLine);
319             ComponentUtils.pushObjectToContext(actions, UifConstants.ContextVariableNames.READONLY_LINE, readOnlyLine);
320         }
321 
322         // check authorization for line fields
323         applyLineFieldAuthorizationAndPresentationLogic(view, (ViewModel) model, collectionGroup, currentLine,
324                 readOnlyLine, lineFields, actions);
325 
326         if (bindToForm) {
327             ComponentUtils.setComponentsPropertyDeep(lineFields, UifPropertyPaths.BIND_TO_FORM, Boolean.valueOf(true));
328         }
329 
330         // remove fields from the line that have render false
331         lineFields = removeNonRenderLineFields(view, model, collectionGroup, lineFields, currentLine, lineIndex);
332 
333         // if not add line build sub-collection field groups
334         List<FieldGroup> subCollectionFields = new ArrayList<FieldGroup>();
335         if ((lineIndex != -1) && (collectionGroup.getSubCollections() != null)) {
336             for (int subLineIndex = 0; subLineIndex < collectionGroup.getSubCollections().size(); subLineIndex++) {
337                 CollectionGroup subCollectionPrototype = collectionGroup.getSubCollections().get(subLineIndex);
338                 CollectionGroup subCollectionGroup = ComponentUtils.copy(subCollectionPrototype, lineSuffix);
339 
340                 // verify the sub-collection should be rendered
341                 boolean renderSubCollection = checkSubCollectionRender(view, model, collectionGroup,
342                         subCollectionGroup);
343                 if (!renderSubCollection) {
344                     continue;
345                 }
346 
347                 subCollectionGroup.getBindingInfo().setBindByNamePrefix(bindingPath);
348                 if (subCollectionGroup.isRenderAddLine()) {
349                     subCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(bindingPath);
350                 }
351 
352                 // set sub-collection suffix on group so it can be used for generated groups
353                 String subCollectionSuffix = lineSuffix;
354                 if (StringUtils.isNotBlank(subCollectionGroup.getSubCollectionSuffix())) {
355                     subCollectionSuffix = subCollectionGroup.getSubCollectionSuffix() + lineSuffix;
356                 }
357                 subCollectionGroup.setSubCollectionSuffix(subCollectionSuffix);
358 
359                 FieldGroup fieldGroupPrototype = layoutManager.getSubCollectionFieldGroupPrototype();
360 
361                 FieldGroup subCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype,
362                         lineSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
363                 subCollectionFieldGroup.setGroup(subCollectionGroup);
364 
365                 ComponentUtils.updateContextForLine(subCollectionFieldGroup, currentLine, lineIndex,
366                         lineSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
367 
368                 subCollectionFields.add(subCollectionFieldGroup);
369             }
370             ComponentUtils.pushObjectToContext(subCollectionFields, UifConstants.ContextVariableNames.PARENT_LINE,
371                     currentLine);
372         }
373 
374         // invoke layout manager to build the complete line
375         layoutManager.buildLine(view, model, collectionGroup, lineFields, subCollectionFields, bindingPath, actions,
376                 lineSuffix, currentLine, lineIndex);
377 
378         //add additional information to the group and fields to allow for correct add control selection
379         String selector = "";
380         if (lineIndex == -1) {
381             List<String> addIds = new ArrayList<String>();
382             for (Field f : lineFields) {
383                 if (f instanceof InputField) {
384                     // sets up - skipping these fields in add area during standard form validation calls
385                     // custom addLineToCollection js call will validate these fields manually on an add
386                     Control control = ((InputField) f).getControl();
387                     if (control != null) {
388                         control.addStyleClass("ignoreValid");
389                         selector = selector + ",#" + f.getId() + UifConstants.IdSuffixes.CONTROL;
390                     }
391                 } else if (f instanceof FieldGroup) {
392                     List<InputField> fields = ComponentUtils.getComponentsOfTypeDeep(((FieldGroup) f).getGroup(),
393                             InputField.class);
394                     for (InputField nestedField : fields) {
395                         Control control = nestedField.getControl();
396                         if (control != null) {
397                             control.addStyleClass("ignoreValid");
398                             selector = selector + ",#" + nestedField.getId() + UifConstants.IdSuffixes.CONTROL;
399                         }
400                     }
401                 }
402             }
403             collectionGroup.addDataAttribute("addControls", selector.replaceFirst(",", ""));
404         }
405     }
406 
407     /**
408      * Iterates through the given items checking for {@code RemotableFieldsHolder}, if found
409      * the holder is invoked to retrieved the remotable fields and translate to attribute fields. The translated list
410      * is then inserted into the returned list at the position of the holder
411      *
412      * @param view - view instance containing the container
413      * @param model - object instance containing the view data
414      * @param group - collection group instance to check for any remotable fields holder
415      * @param items - list of items to process
416      */
417     protected List<Field> processAnyRemoteFieldsHolder(View view, Object model, CollectionGroup group,
418             List<? extends Component> items) {
419         List<Field> processedItems = new ArrayList<Field>();
420 
421         // check for holders and invoke to retrieve the remotable fields and translate
422         // translated fields are placed into the processed items list at the position of the holder
423         for (Component item : items) {
424             if (item instanceof RemoteFieldsHolder) {
425                 List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(view,
426                         model, group);
427                 processedItems.addAll(translatedFields);
428             } else {
429                 processedItems.add((Field) item);
430             }
431         }
432 
433         return processedItems;
434     }
435 
436     /**
437      * Evaluates the render property for the given list of {@code Field}
438      * instances for the line and removes any fields from the returned list that
439      * have render false. The conditional render string is also taken into
440      * account. This needs to be done here as opposed to during the normal
441      * condition evaluation so the the fields are not used while building the
442      * collection lines
443      *
444      * @param view - view instance the collection group belongs to
445      * @param model - object containing the view data
446      * @param collectionGroup - collection group for the line fields
447      * @param lineFields - list of fields configured for the line
448      * @param currentLine - object containing the line data
449      * @param lineIndex - index of the line in the collection
450      * @return List<Field> list of field instances that should be rendered
451      */
452     protected List<Field> removeNonRenderLineFields(View view, Object model, CollectionGroup collectionGroup,
453             List<Field> lineFields, Object currentLine, int lineIndex) {
454         List<Field> fields = new ArrayList<Field>();
455 
456         for (Field lineField : lineFields) {
457             String conditionalRender = lineField.getPropertyExpression("render");
458 
459             // evaluate conditional render string if set
460             if (StringUtils.isNotBlank(conditionalRender)) {
461                 Map<String, Object> context = getContextForField(view, collectionGroup, lineField);
462 
463                 // Adjust the condition as ExpressionUtils.adjustPropertyExpressions will only be
464                 // executed after the collection is built.
465                 conditionalRender = KRADServiceLocatorWeb.getExpressionEvaluatorService().replaceBindingPrefixes(view,
466                         lineField, conditionalRender);
467 
468                 Boolean render = (Boolean) getExpressionEvaluatorService().evaluateExpression(model, context,
469                         conditionalRender);
470                 lineField.setRender(render);
471             }
472 
473             // only add line field if set to render or if it is hidden by progressive render
474             if (lineField.isRender() || StringUtils.isNotBlank(lineField.getProgressiveRender())) {
475                 fields.add(lineField);
476             }
477         }
478 
479         return fields;
480     }
481 
482     /**
483      * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
484      * to view the line (if a permission has been established)
485      *
486      * @param view - view instance the collection belongs to and from which the authorizer/presentation controller will
487      * be pulled
488      * @param model - object containing the view's data
489      * @param collectionGroup - collection group containing the line
490      * @param line - object containing the lines data
491      * @return boolean true if the user can view the line, false if not
492      */
493     protected boolean checkViewLineAuthorizationAndPresentationLogic(View view, ViewModel model,
494             CollectionGroup collectionGroup, Object line) {
495         ViewPresentationController presentationController = view.getPresentationController();
496         ViewAuthorizer authorizer = view.getAuthorizer();
497 
498         Person user = GlobalVariables.getUserSession().getPerson();
499 
500         // check view line auth
501         boolean canViewLine = authorizer.canViewLine(view, model, collectionGroup, collectionGroup.getPropertyName(),
502                 line, user);
503         if (canViewLine) {
504             canViewLine = presentationController.canViewLine(view, model, collectionGroup,
505                     collectionGroup.getPropertyName(), line);
506         }
507 
508         return canViewLine;
509     }
510 
511     /**
512      * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
513      * to edit the line (if a permission has been established)
514      *
515      * @param view - view instance the collection belongs to and from which the authorizer/presentation controller will
516      * be pulled
517      * @param model - object containing the view's data
518      * @param collectionGroup - collection group containing the line
519      * @param line - object containing the lines data
520      * @return boolean true if the user can edit the line, false if not
521      */
522     protected boolean checkEditLineAuthorizationAndPresentationLogic(View view, ViewModel model,
523             CollectionGroup collectionGroup, Object line) {
524         ViewPresentationController presentationController = view.getPresentationController();
525         ViewAuthorizer authorizer = view.getAuthorizer();
526 
527         Person user = GlobalVariables.getUserSession().getPerson();
528 
529         // check edit line auth
530         boolean canEditLine = authorizer.canEditLine(view, model, collectionGroup, collectionGroup.getPropertyName(),
531                 line, user);
532         if (canEditLine) {
533             canEditLine = presentationController.canEditLine(view, model, collectionGroup,
534                     collectionGroup.getPropertyName(), line);
535         }
536 
537         return canEditLine;
538     }
539 
540     /**
541      * Iterates through the line fields and checks the view field authorization using the view's configured authorizer
542      * and presentation controller. If the field is viewable, then sets the edit field authorization. Finally iterates
543      * through the line actions invoking the authorizer and presentation controller to authorizer the action
544      *
545      * @param view - view instance the collection belongs to and from which the authorizer/presentation controller will
546      * be pulled
547      * @param model - object containing the view's data
548      * @param collectionGroup - collection group containing the line
549      * @param line - object containing the lines data
550      * @param readOnlyLine - flag indicating whether the line has been marked as read only (which will force the fields
551      * to be read only)
552      * @param lineFields - list of fields instances for the line
553      * @param actions - list of action field instances for the line
554      */
555     protected void applyLineFieldAuthorizationAndPresentationLogic(View view, ViewModel model,
556             CollectionGroup collectionGroup, Object line, boolean readOnlyLine, List<Field> lineFields,
557             List<Action> actions) {
558         ViewPresentationController presentationController = view.getPresentationController();
559         ViewAuthorizer authorizer = view.getAuthorizer();
560 
561         Person user = GlobalVariables.getUserSession().getPerson();
562 
563         for (Field lineField : lineFields) {
564             String propertyName = null;
565             if (lineField instanceof DataBinding) {
566                 propertyName = ((DataBinding) lineField).getPropertyName();
567             }
568 
569             // evaluate expression on fields component security (since apply model phase has not been invoked on
570             // them yet
571             ComponentSecurity componentSecurity = lineField.getComponentSecurity();
572 
573             Map<String, Object> context = getContextForField(view, collectionGroup, lineField);
574             getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, componentSecurity, model, context);
575 
576             // check view field auth
577             if (lineField.isRender() && !lineField.isHidden()) {
578                 boolean canViewField = authorizer.canViewLineField(view, model, collectionGroup,
579                         collectionGroup.getPropertyName(), line, lineField, propertyName, user);
580                 if (canViewField) {
581                     canViewField = presentationController.canViewLineField(view, model, collectionGroup,
582                             collectionGroup.getPropertyName(), line, lineField, propertyName);
583                 }
584 
585                 if (!canViewField) {
586                     // since removing can impact layout, set to hidden
587                     // TODO: check into encryption setting
588                     lineField.setHidden(true);
589 
590                     if (lineField.getPropertyExpressions().containsKey("hidden")) {
591                         lineField.getPropertyExpressions().remove("hidden");
592                     }
593 
594                     continue;
595                 }
596 
597                 // check edit field auth
598                 boolean canEditField = !readOnlyLine;
599                 if (!readOnlyLine) {
600                     canEditField = authorizer.canEditLineField(view, model, collectionGroup,
601                             collectionGroup.getPropertyName(), line, lineField, propertyName, user);
602                     if (canEditField) {
603                         canEditField = presentationController.canEditLineField(view, model, collectionGroup,
604                                 collectionGroup.getPropertyName(), line, lineField, propertyName);
605                     }
606                 }
607 
608                 if (readOnlyLine || !canEditField) {
609                     lineField.setReadOnly(true);
610 
611                     if (lineField.getPropertyExpressions().containsKey("readOnly")) {
612                         lineField.getPropertyExpressions().remove("readOnly");
613                     }
614                 }
615             }
616         }
617 
618         // check auth on line actions
619         for (Action action : actions) {
620             if (action.isRender()) {
621                 boolean canPerformAction = authorizer.canPerformLineAction(view, model, collectionGroup,
622                         collectionGroup.getPropertyName(), line, action, action.getActionEvent(), action.getId(), user);
623                 if (canPerformAction) {
624                     canPerformAction = presentationController.canPerformLineAction(view, model, collectionGroup,
625                             collectionGroup.getPropertyName(), line, action, action.getActionEvent(), action.getId());
626                 }
627 
628                 if (!canPerformAction) {
629                     action.setRender(false);
630 
631                     if (action.getPropertyExpressions().containsKey("render")) {
632                         action.getPropertyExpressions().remove("render");
633                     }
634                 }
635             }
636         }
637     }
638 
639     /**
640      * Checks whether the given sub-collection should be rendered, any
641      * conditional render string is evaluated
642      *
643      * @param view - view instance the sub collection belongs to
644      * @param model - object containing the view data
645      * @param collectionGroup - collection group the sub collection belongs to
646      * @param subCollectionGroup - sub collection group to check render status for
647      * @return boolean true if sub collection should be rendered, false if it
648      *         should not be rendered
649      */
650     protected boolean checkSubCollectionRender(View view, Object model, CollectionGroup collectionGroup,
651             CollectionGroup subCollectionGroup) {
652         String conditionalRender = subCollectionGroup.getPropertyExpression("render");
653 
654         // TODO: check authorizer
655 
656         // evaluate conditional render string if set
657         if (StringUtils.isNotBlank(conditionalRender)) {
658             Map<String, Object> context = new HashMap<String, Object>();
659             context.putAll(view.getContext());
660             context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup);
661             context.put(UifConstants.ContextVariableNames.COMPONENT, subCollectionGroup);
662 
663             Boolean render = (Boolean) getExpressionEvaluatorService().evaluateExpression(model, context,
664                     conditionalRender);
665             subCollectionGroup.setRender(render);
666         }
667 
668         return subCollectionGroup.isRender();
669     }
670 
671     /**
672      * Creates new {@code Action} instances for the line
673      *
674      * <p>
675      * Adds context to the action fields for the given line so that the line the
676      * action was performed on can be determined when that action is selected
677      * </p>
678      *
679      * @param lineActions - the actions to copy
680      * @param view - view instance the collection belongs to
681      * @param model - top level object containing the data
682      * @param collectionGroup - collection group component for the collection
683      * @param collectionLine - object instance for the current line
684      * @param lineIndex - index of the line the actions should apply to
685      */
686     protected List<Action> initializeLineActions(List<Action> lineActions, View view, Object model,
687             CollectionGroup collectionGroup, Object collectionLine, int lineIndex) {
688         String lineSuffix = UifConstants.IdSuffixes.LINE + Integer.toString(lineIndex);
689         if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) {
690             lineSuffix = collectionGroup.getSubCollectionSuffix() + lineSuffix;
691         }
692         List<Action> actions = ComponentUtils.copyComponentList(lineActions, lineSuffix);
693 
694         for (Action action : actions) {
695             if (ComponentUtils.containsPropertyExpression(action, UifPropertyPaths.ACTION_PARAMETERS, true)) {
696                 // need to update the actions expressions so our settings do not get overridden
697                 action.getPropertyExpressions().put(
698                         UifPropertyPaths.ACTION_PARAMETERS + "['" + UifParameters.SELLECTED_COLLECTION_PATH + "']",
699                         UifConstants.EL_PLACEHOLDER_PREFIX + "'" + collectionGroup.getBindingInfo().getBindingPath() +
700                                 "'" + UifConstants.EL_PLACEHOLDER_SUFFIX);
701                 action.getPropertyExpressions().put(
702                         UifPropertyPaths.ACTION_PARAMETERS + "['" + UifParameters.SELECTED_LINE_INDEX + "']",
703                         UifConstants.EL_PLACEHOLDER_PREFIX + "'" + Integer.toString(lineIndex) +
704                                 "'" + UifConstants.EL_PLACEHOLDER_SUFFIX);
705             } else {
706                 action.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH,
707                         collectionGroup.getBindingInfo().getBindingPath());
708                 action.addActionParameter(UifParameters.SELECTED_LINE_INDEX, Integer.toString(lineIndex));
709             }
710             
711             action.setJumpToIdAfterSubmit(collectionGroup.getId());
712             action.setRefreshId(collectionGroup.getId());
713 
714             // if marked for validation, add call to validate the line and set validation flag to false
715             // so the entire form will not be validated
716             if (action.isPerformClientSideValidation()) {
717                 String preSubmitScript = "valid=valid && validateLine('" +
718                         collectionGroup.getBindingInfo().getBindingPath() + "'," + Integer.toString(lineIndex) +
719                         ");return valid;";
720 
721                 // prepend custom presubmit script which should evaluate to a boolean
722                 if (StringUtils.isNotBlank(action.getPreSubmitCall())) {
723                     preSubmitScript = ScriptUtils.appendScript("var valid=true;valid=" + action.getPreSubmitCall(),
724                             preSubmitScript);
725                 } else {
726                     preSubmitScript = "var valid=true;" + preSubmitScript;
727                 }
728 
729                 action.setPreSubmitCall(preSubmitScript);
730                 action.setPerformClientSideValidation(false);
731             }
732         }
733 
734         ComponentUtils.updateContextsForLine(actions, collectionLine, lineIndex, lineSuffix);
735 
736         return actions;
737     }
738 
739     /**
740      * Creates new {@code Action} instances for the add line
741      *
742      * <p>
743      * Adds context to the action fields for the add line so that the collection
744      * the action was performed on can be determined
745      * </p>
746      *
747      * @param view - view instance the collection belongs to
748      * @param model - top level object containing the data
749      * @param collectionGroup - collection group component for the collection
750      */
751     protected List<Action> getAddLineActions(View view, Object model, CollectionGroup collectionGroup) {
752         String lineSuffix = UifConstants.IdSuffixes.ADD_LINE;
753         if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) {
754             lineSuffix = collectionGroup.getSubCollectionSuffix() + lineSuffix;
755         }
756         List<Action> lineActions = ComponentUtils.copyComponentList(collectionGroup.getAddLineActions(), lineSuffix);
757 
758         for (Action action : lineActions) {
759             action.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH,
760                     collectionGroup.getBindingInfo().getBindingPath());
761             action.setJumpToIdAfterSubmit(collectionGroup.getId());
762             action.addActionParameter(UifParameters.ACTION_TYPE, UifParameters.ADD_LINE);
763             action.setRefreshId(collectionGroup.getId());
764 
765             String baseId = collectionGroup.getBaseId();
766             if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) {
767                 baseId += collectionGroup.getSubCollectionSuffix();
768             }
769 
770             String preSubmitScript = "valid=valid && ";
771             if (collectionGroup.isAddViaLightBox()) {
772                 preSubmitScript += "validateAddLine('" + collectionGroup.getId() + "', true); if (valid) {closeLightbox();}";
773             }  else {
774                 preSubmitScript += "validateAddLine('" + collectionGroup.getId() + "');";
775             }
776             preSubmitScript += "return valid;";
777 
778             // prepend custom presubmit script which should evaluate to a boolean
779             if (StringUtils.isNotBlank(action.getPreSubmitCall())) {
780                 preSubmitScript = ScriptUtils.appendScript("var valid=true;valid=" + action.getPreSubmitCall(),
781                         preSubmitScript);
782             } else {
783                 preSubmitScript = "var valid=true;" + preSubmitScript;
784             }
785 
786             action.setPreSubmitCall(preSubmitScript);
787         }
788 
789         // get add line for context
790         String addLinePath = collectionGroup.getAddLineBindingInfo().getBindingPath();
791         Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLinePath);
792 
793         ComponentUtils.updateContextsForLine(lineActions, addLine, -1, lineSuffix);
794 
795         return lineActions;
796     }
797 
798     /**
799      * Helper method to build the context for a field (needed because the apply model phase for line fields has
800      * not been applied yet and their full context not set)
801      *
802      * @param view - view instance the field belongs to
803      * @param collectionGroup - collection group instance the field belongs to
804      * @param field - field instance to build context for
805      * @return Map<String, Object> context for field
806      */
807     protected Map<String, Object> getContextForField(View view, CollectionGroup collectionGroup, Field field) {
808         Map<String, Object> context = new HashMap<String, Object>();
809 
810         context.putAll(view.getContext());
811         context.putAll(field.getContext());
812         context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup);
813         context.put(UifConstants.ContextVariableNames.COMPONENT, field);
814 
815         return context;
816     }
817 
818     /**
819      * Initializes a new instance of the collection class
820      *
821      * <p>
822      * If the add line property was not specified for the collection group the
823      * new lines will be added to the generic map on the
824      * {@code UifFormBase}, else it will be added to the property given by
825      * the addLineBindingInfo
826      * </p>
827      *
828      * <p>
829      * New line will only be created if the current line property is null or
830      * clearExistingLine is true. In the case of a new line default values are
831      * also applied
832      * </p>
833      *
834      * @see CollectionGroup#
835      *      initializeNewCollectionLine(View, Object, CollectionGroup, boolean)
836      */
837     public void initializeNewCollectionLine(View view, Object model, CollectionGroup collectionGroup,
838             boolean clearExistingLine) {
839         Object newLine = null;
840 
841         // determine if we are binding to generic form map or a custom property
842         if (StringUtils.isBlank(collectionGroup.getAddLinePropertyName())) {
843             // bind to form map
844             if (!(model instanceof UifFormBase)) {
845                 throw new RuntimeException("Cannot create new collection line for group: "
846                         + collectionGroup.getPropertyName()
847                         + ". Model does not extend "
848                         + UifFormBase.class.getName());
849             }
850 
851             // get new collection line map from form
852             Map<String, Object> newCollectionLines = ObjectPropertyUtils.getPropertyValue(model,
853                     UifPropertyPaths.NEW_COLLECTION_LINES);
854             if (newCollectionLines == null) {
855                 newCollectionLines = new HashMap<String, Object>();
856                 ObjectPropertyUtils.setPropertyValue(model, UifPropertyPaths.NEW_COLLECTION_LINES, newCollectionLines);
857             }
858 
859             // set binding path for add line
860             String newCollectionLineKey = KRADUtils.translateToMapSafeKey(
861                     collectionGroup.getBindingInfo().getBindingPath());
862             String addLineBindingPath = UifPropertyPaths.NEW_COLLECTION_LINES + "['" + newCollectionLineKey + "']";
863             collectionGroup.getAddLineBindingInfo().setBindingPath(addLineBindingPath);
864 
865             // if there is not an instance available or we need to clear create a new instance
866             if (!newCollectionLines.containsKey(newCollectionLineKey) || (newCollectionLines.get(newCollectionLineKey)
867                     == null) || clearExistingLine) {
868                 // create new instance of the collection type for the add line
869                 newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass());
870                 newCollectionLines.put(newCollectionLineKey, newLine);
871             }
872         } else {
873             // bind to custom property
874             Object addLine = ObjectPropertyUtils.getPropertyValue(model,
875                     collectionGroup.getAddLineBindingInfo().getBindingPath());
876             if ((addLine == null) || clearExistingLine) {
877                 newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass());
878                 ObjectPropertyUtils.setPropertyValue(model, collectionGroup.getAddLineBindingInfo().getBindingPath(),
879                         newLine);
880             }
881         }
882 
883         // apply default values if a new line was created
884         if (newLine != null) {
885             view.getViewHelperService().applyDefaultValuesForCollectionLine(view, model, collectionGroup, newLine);
886         }
887     }
888 
889     protected ExpressionEvaluatorService getExpressionEvaluatorService() {
890         return KRADServiceLocatorWeb.getExpressionEvaluatorService();
891     }
892 
893 }