001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.uif.container;
017    
018    import org.apache.commons.collections.ListUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.kuali.rice.core.api.mo.common.active.Inactivatable;
021    import org.kuali.rice.kim.api.identity.Person;
022    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
023    import org.kuali.rice.krad.uif.UifConstants;
024    import org.kuali.rice.krad.uif.UifParameters;
025    import org.kuali.rice.krad.uif.UifPropertyPaths;
026    import org.kuali.rice.krad.uif.component.Component;
027    import org.kuali.rice.krad.uif.component.ComponentSecurity;
028    import org.kuali.rice.krad.uif.control.Control;
029    import org.kuali.rice.krad.uif.component.DataBinding;
030    import org.kuali.rice.krad.uif.field.ActionField;
031    import org.kuali.rice.krad.uif.field.InputField;
032    import org.kuali.rice.krad.uif.field.Field;
033    import org.kuali.rice.krad.uif.field.FieldGroup;
034    import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
035    import org.kuali.rice.krad.uif.layout.CollectionLayoutManager;
036    import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
037    import org.kuali.rice.krad.uif.util.ComponentUtils;
038    import org.kuali.rice.krad.uif.util.ExpressionUtils;
039    import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
040    import org.kuali.rice.krad.uif.view.View;
041    import org.kuali.rice.krad.uif.view.ViewAuthorizer;
042    import org.kuali.rice.krad.uif.view.ViewModel;
043    import org.kuali.rice.krad.uif.view.ViewPresentationController;
044    import org.kuali.rice.krad.util.GlobalVariables;
045    import org.kuali.rice.krad.util.KRADUtils;
046    import org.kuali.rice.krad.util.ObjectUtils;
047    import org.kuali.rice.krad.web.form.UifFormBase;
048    
049    import java.io.Serializable;
050    import java.util.ArrayList;
051    import java.util.Collection;
052    import java.util.HashMap;
053    import java.util.List;
054    import java.util.Map;
055    
056    /**
057     * Builds out the <code>Field</code> instances for a collection group with a
058     * series of steps that interact with the configured
059     * <code>CollectionLayoutManager</code> to assemble the fields as necessary for
060     * the layout
061     * 
062     * @author Kuali Rice Team (rice.collab@kuali.org)
063     */
064    public class CollectionGroupBuilder implements Serializable {
065            private static final long serialVersionUID = -4762031957079895244L;
066    
067            /**
068             * Creates the <code>Field</code> instances that make up the table
069             * 
070             * <p>
071             * The corresponding collection is retrieved from the model and iterated
072             * over to create the necessary fields. The binding path for fields that
073             * implement <code>DataBinding</code> is adjusted to point to the collection
074             * line it is apart of. For example, field 'number' of collection 'accounts'
075             * for line 1 will be set to 'accounts[0].number', and for line 2
076             * 'accounts[1].number'. Finally parameters are set on the line's action
077             * fields to indicate what collection and line they apply to.
078             * </p>
079             * 
080             * @param view
081             *            - View instance the collection belongs to
082             * @param model
083             *            - Top level object containing the data
084             * @param collectionGroup
085             *            - CollectionGroup component for the collection
086             */
087        public void build(View view, Object model, CollectionGroup collectionGroup) {
088            // create add line
089            if (collectionGroup.isRenderAddLine() && !collectionGroup.isReadOnly()) {
090                buildAddLine(view, model, collectionGroup);
091            }
092    
093            // get the collection for this group from the model
094            List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(model, ((DataBinding) collectionGroup)
095                    .getBindingInfo().getBindingPath());
096    
097            if (modelCollection != null) {
098                // filter inactive model
099                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
773                // a new instance
774                if (!newCollectionLines.containsKey(newCollectionLineKey)
775                        || (newCollectionLines.get(newCollectionLineKey) == null) || clearExistingLine) {
776                    // create new instance of the collection type for the add line
777                    newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass());
778                    newCollectionLines.put(newCollectionLineKey, newLine);
779                }
780            } else {
781                // bind to custom property
782                Object addLine = ObjectPropertyUtils.getPropertyValue(model, collectionGroup.getAddLineBindingInfo()
783                        .getBindingPath());
784                if ((addLine == null) || clearExistingLine) {
785                    newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass());
786                    ObjectPropertyUtils.setPropertyValue(model, collectionGroup.getAddLineBindingInfo().getBindingPath(),
787                            newLine);
788                }
789            }
790    
791            // apply default values if a new line was created
792            if (newLine != null) {
793                view.getViewHelperService().applyDefaultValuesForCollectionLine(view, model, collectionGroup, newLine);
794            }
795        }
796        
797        protected ExpressionEvaluatorService getExpressionEvaluatorService() {
798            return KRADServiceLocatorWeb.getExpressionEvaluatorService();
799        }
800    
801    }