001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.container;
017
018import org.apache.commons.collections.ListUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.mo.common.active.Inactivatable;
021import org.kuali.rice.kim.api.identity.Person;
022import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
023import org.kuali.rice.krad.uif.UifConstants;
024import org.kuali.rice.krad.uif.UifParameters;
025import org.kuali.rice.krad.uif.UifPropertyPaths;
026import org.kuali.rice.krad.uif.component.Component;
027import org.kuali.rice.krad.uif.component.ComponentSecurity;
028import org.kuali.rice.krad.uif.control.Control;
029import org.kuali.rice.krad.uif.component.DataBinding;
030import org.kuali.rice.krad.uif.field.ActionField;
031import org.kuali.rice.krad.uif.field.InputField;
032import org.kuali.rice.krad.uif.field.Field;
033import org.kuali.rice.krad.uif.field.FieldGroup;
034import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
035import org.kuali.rice.krad.uif.layout.CollectionLayoutManager;
036import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
037import org.kuali.rice.krad.uif.util.ComponentUtils;
038import org.kuali.rice.krad.uif.util.ExpressionUtils;
039import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
040import org.kuali.rice.krad.uif.view.View;
041import org.kuali.rice.krad.uif.view.ViewAuthorizer;
042import org.kuali.rice.krad.uif.view.ViewModel;
043import org.kuali.rice.krad.uif.view.ViewPresentationController;
044import org.kuali.rice.krad.util.GlobalVariables;
045import org.kuali.rice.krad.util.KRADUtils;
046import org.kuali.rice.krad.util.ObjectUtils;
047import org.kuali.rice.krad.web.form.UifFormBase;
048
049import java.io.Serializable;
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.HashMap;
053import java.util.List;
054import 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 */
064public 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 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}