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