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.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.config.property.ConfigurationService;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.kim.api.identity.Person;
22  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
23  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
24  import org.kuali.rice.krad.inquiry.Inquirable;
25  import org.kuali.rice.krad.messages.MessageService;
26  import org.kuali.rice.krad.service.DataDictionaryService;
27  import org.kuali.rice.krad.service.KRADServiceLocator;
28  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
29  import org.kuali.rice.krad.service.ModuleService;
30  import org.kuali.rice.krad.uif.UifConstants;
31  import org.kuali.rice.krad.uif.component.ComponentSecurity;
32  import org.kuali.rice.krad.uif.container.Group;
33  import org.kuali.rice.krad.uif.element.Action;
34  import org.kuali.rice.krad.uif.field.ActionField;
35  import org.kuali.rice.krad.uif.field.FieldGroup;
36  import org.kuali.rice.krad.uif.util.ViewCleaner;
37  import org.kuali.rice.krad.uif.view.ViewAuthorizer;
38  import org.kuali.rice.krad.uif.view.ViewPresentationController;
39  import org.kuali.rice.krad.uif.component.BindingInfo;
40  import org.kuali.rice.krad.uif.component.ClientSideState;
41  import org.kuali.rice.krad.uif.component.Component;
42  import org.kuali.rice.krad.uif.component.DataBinding;
43  import org.kuali.rice.krad.uif.component.PropertyReplacer;
44  import org.kuali.rice.krad.uif.component.RequestParameter;
45  import org.kuali.rice.krad.uif.container.CollectionGroup;
46  import org.kuali.rice.krad.uif.container.Container;
47  import org.kuali.rice.krad.uif.control.Control;
48  import org.kuali.rice.krad.uif.field.DataField;
49  import org.kuali.rice.krad.uif.field.InputField;
50  import org.kuali.rice.krad.uif.field.Field;
51  import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
52  import org.kuali.rice.krad.uif.layout.LayoutManager;
53  import org.kuali.rice.krad.uif.modifier.ComponentModifier;
54  import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
55  import org.kuali.rice.krad.uif.service.ViewDictionaryService;
56  import org.kuali.rice.krad.uif.service.ViewHelperService;
57  import org.kuali.rice.krad.uif.util.BooleanMap;
58  import org.kuali.rice.krad.uif.util.CloneUtils;
59  import org.kuali.rice.krad.uif.util.ComponentFactory;
60  import org.kuali.rice.krad.uif.util.ComponentUtils;
61  import org.kuali.rice.krad.uif.util.ExpressionUtils;
62  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
63  import org.kuali.rice.krad.uif.util.ScriptUtils;
64  import org.kuali.rice.krad.uif.util.ViewModelUtils;
65  import org.kuali.rice.krad.uif.view.View;
66  import org.kuali.rice.krad.uif.view.ViewModel;
67  import org.kuali.rice.krad.uif.widget.Inquiry;
68  import org.kuali.rice.krad.uif.widget.Widget;
69  import org.kuali.rice.krad.util.ErrorMessage;
70  import org.kuali.rice.krad.util.GlobalVariables;
71  import org.kuali.rice.krad.util.GrowlMessage;
72  import org.kuali.rice.krad.util.KRADConstants;
73  import org.kuali.rice.krad.util.MessageMap;
74  import org.kuali.rice.krad.util.ObjectUtils;
75  import org.kuali.rice.krad.valuefinder.ValueFinder;
76  import org.kuali.rice.krad.web.form.UifFormBase;
77  import org.springframework.util.ClassUtils;
78  import org.springframework.util.MethodInvoker;
79  
80  import java.io.Serializable;
81  import java.lang.annotation.Annotation;
82  import java.text.MessageFormat;
83  import java.util.ArrayList;
84  import java.util.Collection;
85  import java.util.Collections;
86  import java.util.HashMap;
87  import java.util.HashSet;
88  import java.util.List;
89  import java.util.Map;
90  import java.util.Map.Entry;
91  import java.util.Set;
92  
93  /**
94   * Default Implementation of <code>ViewHelperService</code>
95   *
96   * @author Kuali Rice Team (rice.collab@kuali.org)
97   */
98  public class ViewHelperServiceImpl implements ViewHelperService, Serializable {
99      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ViewHelperServiceImpl.class);
100 
101     private transient DataDictionaryService dataDictionaryService;
102     private transient ExpressionEvaluatorService expressionEvaluatorService;
103     private transient ViewDictionaryService viewDictionaryService;
104     private transient ConfigurationService configurationService;
105 
106     /**
107      * Uses reflection to find all fields defined on the <code>View</code> instance that have
108      * the <code>RequestParameter</code> annotation (which indicates the field may be populated by the request).
109      *
110      * <p>
111      * For each field found, if there is a corresponding key/value pair in the request parameters,
112      * the value is used to populate the field. In addition, any conditional properties of
113      * <code>PropertyReplacers</code> configured for the field are cleared so that the request parameter
114      * value does not get overridden by the dictionary conditional logic
115      * </p>
116      *
117      * @see org.kuali.rice.krad.uif.service.ViewHelperService#populateViewFromRequestParameters(org.kuali.rice.krad.uif.view.View,
118      *      java.util.Map)
119      */
120     @Override
121     public void populateViewFromRequestParameters(View view, Map<String, String> parameters) {
122         // build Map of property replacers by property name so that we can remove them
123         // if the property was set by a request parameter
124         Map<String, Set<PropertyReplacer>> viewPropertyReplacers = new HashMap<String, Set<PropertyReplacer>>();
125         for (PropertyReplacer replacer : view.getPropertyReplacers()) {
126             Set<PropertyReplacer> propertyReplacers = new HashSet<PropertyReplacer>();
127             if (viewPropertyReplacers.containsKey(replacer.getPropertyName())) {
128                 propertyReplacers = viewPropertyReplacers.get(replacer.getPropertyName());
129             }
130             propertyReplacers.add(replacer);
131 
132             viewPropertyReplacers.put(replacer.getPropertyName(), propertyReplacers);
133         }
134 
135         Map<String, Annotation> annotatedFields = CloneUtils.getFieldsWithAnnotation(view.getClass(),
136                 RequestParameter.class);
137 
138         // for each request parameter allowed on the view, if the request contains a value use
139         // to set on View, and clear and conditional expressions or property replacers for that field
140         Map<String, String> viewRequestParameters = new HashMap<String, String>();
141         for (String fieldToPopulate : annotatedFields.keySet()) {
142             RequestParameter requestParameter = (RequestParameter) annotatedFields.get(fieldToPopulate);
143 
144             // use specified parameter name if given, else use field name to retrieve parameter value
145             String requestParameterName = requestParameter.parameterName();
146             if (StringUtils.isBlank(requestParameterName)) {
147                 requestParameterName = fieldToPopulate;
148             }
149 
150             if (!parameters.containsKey(requestParameterName)) {
151                 continue;
152             }
153 
154             String fieldValue = parameters.get(requestParameterName);
155             if (StringUtils.isNotBlank(fieldValue)) {
156                 viewRequestParameters.put(requestParameterName, fieldValue);
157                 ObjectPropertyUtils.setPropertyValue(view, fieldToPopulate, fieldValue);
158 
159                 // remove any conditional configuration so value is not
160                 // overridden later during the apply model phase
161                 if (view.getPropertyExpressions().containsKey(fieldToPopulate)) {
162                     view.getPropertyExpressions().remove(fieldToPopulate);
163                 }
164 
165                 if (viewPropertyReplacers.containsKey(fieldToPopulate)) {
166                     Set<PropertyReplacer> propertyReplacers = viewPropertyReplacers.get(fieldToPopulate);
167                     for (PropertyReplacer replacer : propertyReplacers) {
168                         view.getPropertyReplacers().remove(replacer);
169                     }
170                 }
171             }
172         }
173 
174         view.setViewRequestParameters(viewRequestParameters);
175     }
176 
177     /**
178      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performInitialization(org.kuali.rice.krad.uif.view.View,
179      *      java.lang.Object)
180      */
181     @Override
182     public void performInitialization(View view, Object model) {
183         view.assignComponentIds(view);
184 
185         // increment the id sequence so components added later to the static view components
186         // will not conflict with components on the page when navigation happens
187         view.setIdSequence(100000);
188         performComponentInitialization(view, model, view);
189 
190         // get the list of dialogs from the view and then set the refreshedByAction on the dialog to true.
191         // This will leave the component in the viewIndex to be updated using an AJAX call
192         // TODO: Figure out a better way to store dialogs only if it is rendered using an ajax request
193         for (Component dialog : view.getDialogs()) {
194             dialog.setRefreshedByAction(true);
195         }
196     }
197 
198     /**
199      * Performs the complete component lifecycle on the component passed in, in this order:
200      * performComponentInitialization, performComponentApplyModel, and performComponentFinalize.
201      *
202      * @see {@link org.kuali.rice.krad.uif.service.ViewHelperService#performComponentLifecycle(
203      *org.kuali.rice.krad.uif.view.View, java.lang.Object, org.kuali.rice.krad.uif.component.Component,
204      *      java.lang.String)
205      * @see {@link #performComponentInitialization(org.kuali.rice.krad.uif.view.View, Object,
206      *      org.kuali.rice.krad.uif.component.Component)}
207      * @see {@link #performComponentApplyModel(View, Component, Object)}
208      * @see {@link #performComponentFinalize(View, Component, Object, Component, Map)}
209      */
210     public void performComponentLifecycle(View view, Object model, Component component, String origId) {
211         Component origComponent = view.getViewIndex().getComponentById(origId);
212 
213         // run through and assign any ids starting with the id for the refreshed component (this might be
214         // necessary if we are getting a new component instance from the bean factory)
215         Integer currentSequenceVal = view.getIdSequence();
216         Integer startingSequenceVal = view.getViewIndex().getIdSequenceSnapshot().get(component.getId());
217         // if the component was retrieved from the initial states map in ViewIndex, startingSequenceVal is null
218         if (startingSequenceVal != null) {
219             view.setIdSequence(startingSequenceVal);
220         }
221 
222         view.assignComponentIds(component);
223 
224         // now set back from the ending view sequence so IDs for any dynamically created (newly) will not stomp
225         // on existing components
226         view.setIdSequence(currentSequenceVal);
227 
228         String suffix = StringUtils.replaceOnce(origComponent.getId(), origComponent.getBaseId(), "");
229         if (StringUtils.isNotBlank(suffix)) {
230             ComponentUtils.updateIdWithSuffix(component, suffix);
231         }
232 
233         Component parent = (Component) origComponent.getContext().get(UifConstants.ContextVariableNames.PARENT);
234 
235         // update context on all components within the refresh component to catch context set by parent
236         component.pushAllToContext(origComponent.getContext());
237         List<Component> nestedComponents = ComponentUtils.getAllNestedComponents(component);
238         for (Component nestedComponent : nestedComponents) {
239             nestedComponent.pushAllToContext(origComponent.getContext());
240         }
241 
242         // the expression graph for refreshed components is captured in the view index (initially it might expressions
243         // might have come from a parent), after getting the expression graph then we need to populate the expressions
244         // on the configurable for which they apply
245         Map<String, String> expressionGraph = view.getViewIndex().getComponentExpressionGraphs().get(
246                 component.getBaseId());
247         component.setExpressionGraph(expressionGraph);
248         ExpressionUtils.populatePropertyExpressionsFromGraph(component, false);
249 
250         // binding path should stay the same
251         if (component instanceof DataBinding) {
252             ((DataBinding) component).setBindingInfo(((DataBinding) origComponent).getBindingInfo());
253             ((DataBinding) component).getBindingInfo().setBindingPath(
254                     ((DataBinding) origComponent).getBindingInfo().getBindingPath());
255         }
256 
257         // copy properties that are set by parent components in the full view lifecycle
258         if (component instanceof Field) {
259             ((Field) component).setLabelRendered(((Field) origComponent).isLabelRendered());
260         } else if (component instanceof CollectionGroup) {
261             String subCollectionSuffix = ((CollectionGroup) origComponent).getSubCollectionSuffix();
262             ((CollectionGroup) component).setSubCollectionSuffix(subCollectionSuffix);
263 
264             suffix = StringUtils.removeStart(suffix, subCollectionSuffix);
265         }
266 
267         if (origComponent.isRefreshedByAction()) {
268             component.setRefreshedByAction(true);
269         }
270 
271         // reset data if needed
272         if (component.isResetDataOnRefresh()) {
273             // TODO: this should handle groups as well, going through nested data fields
274             if (component instanceof DataField) {
275                 // TODO: should check default value
276 
277                 // clear value
278                 ObjectPropertyUtils.initializeProperty(model,
279                         ((DataField) component).getBindingInfo().getBindingPath());
280             }
281         }
282 
283         performComponentInitialization(view, model, component);
284         view.getViewIndex().indexComponent(component);
285 
286         Map<String, Integer> visitedIds = new HashMap<String, Integer>();
287         performComponentApplyModel(view, component, model, visitedIds);
288         view.getViewIndex().indexComponent(component);
289 
290         // adjust IDs for suffixes that might have been added by a parent component during the full view lifecycle
291         if (StringUtils.isNotBlank(suffix)) {
292             ComponentUtils.updateChildIdsWithSuffixNested(component, suffix);
293         }
294 
295         // if disclosed by action and request was made, make sure the component will display
296         if (component.isDisclosedByAction()) {
297             component.setRender(true);
298             component.setHidden(false);
299         }
300 
301         performComponentFinalize(view, component, model, parent);
302 
303         // make sure id, binding, and label settings stay the same as initial
304         if (component instanceof Group || component instanceof FieldGroup) {
305             List<Component> nestedGroupComponents = ComponentUtils.getAllNestedComponents(component);
306             List<Component> originalNestedGroupComponents = ComponentUtils.getAllNestedComponents(origComponent);
307 
308             for (Component nestedComponent : nestedGroupComponents) {
309                 Component origNestedComponent = ComponentUtils.findComponentInList(originalNestedGroupComponents,
310                         nestedComponent.getId());
311 
312                 if (origNestedComponent != null) {
313                     // update binding
314                     if (nestedComponent instanceof DataBinding) {
315                         ((DataBinding) nestedComponent).setBindingInfo(
316                                 ((DataBinding) origNestedComponent).getBindingInfo());
317                         ((DataBinding) nestedComponent).getBindingInfo().setBindingPath(
318                                 ((DataBinding) origNestedComponent).getBindingInfo().getBindingPath());
319                     }
320 
321                     // update label rendered flag
322                     if (nestedComponent instanceof Field) {
323                         ((Field) nestedComponent).setLabelRendered(((Field) origNestedComponent).isLabelRendered());
324                     }
325 
326                     if (origNestedComponent.isRefreshedByAction()) {
327                         nestedComponent.setRefreshedByAction(true);
328                     }
329                 }
330             }
331         }
332 
333         // get script for generating growl messages
334         String growlScript = buildGrowlScript(view);
335         ((ViewModel) model).setGrowlScript(growlScript);
336 
337         view.getViewIndex().indexComponent(component);
338     }
339 
340     /**
341      * Performs initialization of a component by these steps:
342      *
343      * <ul>
344      * <li>For <code>DataField</code> instances, set defaults from the data
345      * dictionary.</li>
346      * <li>Invoke the initialize method on the component. Here the component can
347      * setup defaults and do other initialization that is specific to that
348      * component.</li>
349      * <li>Invoke any configured <code>ComponentModifier</code> instances for
350      * the component.</li>
351      * <li>Call the component to get the List of components that are nested
352      * within and recursively call this method to initialize those components.</li>
353      * <li>Call custom initialize hook for service overrides</li>
354      * </ul>
355      *
356      * <p>
357      * Note the order various initialize points are called, this can sometimes
358      * be an important factor to consider when initializing a component
359      * </p>
360      *
361      * @throws RiceRuntimeException if the component id or factoryId is not specified
362      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performComponentInitialization(org.kuali.rice.krad.uif.view.View,
363      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
364      */
365     public void performComponentInitialization(View view, Object model, Component component) {
366         if (component == null) {
367             return;
368         }
369 
370         if (StringUtils.isBlank(component.getId())) {
371             throw new RiceRuntimeException("Id is not set, this should not happen unless a component is misconfigured");
372         }
373 
374         // TODO: duplicate ID check
375 
376         LOG.debug("Initializing component: " + component.getId() + " with type: " + component.getClass());
377 
378         // add initial state to the view index for component refreshes
379         if (!(component instanceof View)) {
380             view.getViewIndex().addInitialComponentStateIfNeeded(component);
381         }
382 
383         // the component can have an expression graph for which the expressions need pulled to
384         // the list the expression service will evaluate
385         ExpressionUtils.populatePropertyExpressionsFromGraph(component, true);
386 
387         // invoke component to initialize itself after properties have been set
388         component.performInitialization(view, model);
389 
390         // move expressions on property replacers and component modifiers
391         for (PropertyReplacer replacer : component.getPropertyReplacers()) {
392             ExpressionUtils.populatePropertyExpressionsFromGraph(replacer, true);
393         }
394 
395         for (ComponentModifier modifier : component.getComponentModifiers()) {
396             ExpressionUtils.populatePropertyExpressionsFromGraph(modifier, true);
397         }
398 
399         // for attribute fields, set defaults from dictionary entry
400         if (component instanceof DataField) {
401             initializeDataFieldFromDataDictionary(view, (DataField) component);
402         }
403 
404         if (component instanceof Container) {
405             LayoutManager layoutManager = ((Container) component).getLayoutManager();
406 
407             // invoke hook point for adding components through code
408             addCustomContainerComponents(view, model, (Container) component);
409 
410             // process any remote fields holder that might be in the containers items, collection items will get
411             // processed as the lines are being built
412             if (!(component instanceof CollectionGroup)) {
413                 processAnyRemoteFieldsHolder(view, model, (Container) component);
414             }
415         }
416 
417         // for collection groups set defaults from dictionary entry
418         if (component instanceof CollectionGroup) {
419             // TODO: initialize from dictionary
420         }
421 
422         // invoke initialize service hook
423         performCustomInitialization(view, component);
424 
425         // invoke component modifiers setup to run in the initialize phase
426         runComponentModifiers(view, component, null, UifConstants.ViewPhases.INITIALIZE);
427 
428         // initialize nested components
429         for (Component nestedComponent : component.getComponentsForLifecycle()) {
430             performComponentInitialization(view, model, nestedComponent);
431         }
432 
433         // initialize component prototypes
434         for (Component nestedComponent : component.getComponentPrototypes()) {
435             performComponentInitialization(view, model, nestedComponent);
436         }
437     }
438 
439     /**
440      * Iterates through the containers configured items checking for <code>RemotableFieldsHolder</code>, if found
441      * the holder is invoked to retrieved the remotable fields and translate to attribute fields. The translated list
442      * is then inserted into the container item list at the position of the holder
443      *
444      * @param view - view instance containing the container
445      * @param model - object instance containing the view data
446      * @param container - container instance to check for any remotable fields holder
447      */
448     protected void processAnyRemoteFieldsHolder(View view, Object model, Container container) {
449         List<Component> processedItems = new ArrayList<Component>();
450 
451         // check for holders and invoke to retrieve the remotable fields and translate
452         // translated fields are placed into the container item list at the position of the holder
453         for (Component item : container.getItems()) {
454             if (item instanceof RemoteFieldsHolder) {
455                 List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(view,
456                         model, container);
457                 processedItems.addAll(translatedFields);
458             } else {
459                 processedItems.add(item);
460             }
461         }
462 
463         // updated container items
464         container.setItems(processedItems);
465     }
466 
467     /**
468      * Sets properties of the <code>InputField</code> (if blank) to the
469      * corresponding attribute entry in the data dictionary
470      *
471      * @param view - view instance containing the field
472      * @param field - data field instance to initialize
473      */
474     protected void initializeDataFieldFromDataDictionary(View view, DataField field) {
475         AttributeDefinition attributeDefinition = null;
476 
477         String dictionaryAttributeName = field.getDictionaryAttributeName();
478         String dictionaryObjectEntry = field.getDictionaryObjectEntry();
479 
480         // if entry given but not attribute name, use field name as attribute
481         // name
482         if (StringUtils.isNotBlank(dictionaryObjectEntry) && StringUtils.isBlank(dictionaryAttributeName)) {
483             dictionaryAttributeName = field.getPropertyName();
484         }
485 
486         // if dictionary entry and attribute set, attempt to find definition
487         if (StringUtils.isNotBlank(dictionaryAttributeName) && StringUtils.isNotBlank(dictionaryObjectEntry)) {
488             attributeDefinition = getDataDictionaryService().getAttributeDefinition(dictionaryObjectEntry,
489                     dictionaryAttributeName);
490         }
491 
492         // if definition not found, recurse through path
493         if (attributeDefinition == null) {
494             String propertyPath = field.getBindingInfo().getBindingPath();
495             if (StringUtils.isNotBlank(field.getBindingInfo().getCollectionPath())) {
496                 propertyPath = field.getBindingInfo().getCollectionPath();
497                 if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
498                     propertyPath += "." + field.getBindingInfo().getBindByNamePrefix();
499                 }
500                 propertyPath += "." + field.getBindingInfo().getBindingName();
501             }
502 
503             attributeDefinition = findNestedDictionaryAttribute(view, field, null, propertyPath);
504         }
505 
506         // if a definition was found, initialize field from definition
507         if (attributeDefinition != null) {
508             field.copyFromAttributeDefinition(view, attributeDefinition);
509         }
510 
511         // if control still null, assign default
512         if (field instanceof InputField) {
513             InputField inputField = (InputField) field;
514             if (inputField.getControl() == null) {
515                 Control control = ComponentFactory.getTextControl();
516                 view.assignComponentIds(control);
517 
518                 inputField.setControl(control);
519             }
520         }
521     }
522 
523     /**
524      * Recursively drills down the property path (if nested) to find an
525      * AttributeDefinition, the first attribute definition found will be
526      * returned
527      *
528      * <p>
529      * e.g. suppose parentPath is 'document' and propertyPath is
530      * 'account.subAccount.name', first the property type for document will be
531      * retrieved using the view metadata and used as the dictionary entry, with
532      * the propertyPath as the dictionary attribute, if an attribute definition
533      * exists it will be returned. Else, the first part of the property path is
534      * added to the parent, making the parentPath 'document.account' and the
535      * propertyPath 'subAccount.name', the method is then called again to
536      * perform the process with those parameters. The recursion continues until
537      * an attribute field is found, or the propertyPath is no longer nested
538      * </p>
539      *
540      * @param view - view instance containing the field
541      * @param field - field we are attempting to find a supporting attribute
542      * definition for
543      * @param parentPath - parent path to use for getting the dictionary entry
544      * @param propertyPath - path of the property relative to the parent, to use as
545      * dictionary attribute and to drill down on
546      * @return AttributeDefinition if found, or Null
547      */
548     protected AttributeDefinition findNestedDictionaryAttribute(View view, DataField field, String parentPath,
549             String propertyPath) {
550         AttributeDefinition attributeDefinition = null;
551 
552         // attempt to find definition for parent and property
553         String dictionaryAttributeName = propertyPath;
554         String dictionaryObjectEntry = null;
555 
556         if (field.getBindingInfo().isBindToMap()) {
557             parentPath = "";
558             if (!field.getBindingInfo().isBindToForm() && StringUtils.isNotBlank(
559                     field.getBindingInfo().getBindingObjectPath())) {
560                 parentPath = field.getBindingInfo().getBindingObjectPath();
561             }
562             if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
563                 if (StringUtils.isNotBlank(parentPath)) {
564                     parentPath += "." + field.getBindingInfo().getBindByNamePrefix();
565                 } else {
566                     parentPath = field.getBindingInfo().getBindByNamePrefix();
567                 }
568             }
569 
570             dictionaryAttributeName = field.getBindingInfo().getBindingName();
571         }
572 
573         if (StringUtils.isNotBlank(parentPath)) {
574             Class<?> dictionaryModelClass = ViewModelUtils.getPropertyTypeByClassAndView(view, parentPath);
575             if (dictionaryModelClass != null) {
576                 dictionaryObjectEntry = dictionaryModelClass.getName();
577 
578                 attributeDefinition = getDataDictionaryService().getAttributeDefinition(dictionaryObjectEntry,
579                         dictionaryAttributeName);
580             }
581         }
582 
583         // if definition not found and property is still nested, recurse down
584         // one level
585         if ((attributeDefinition == null) && StringUtils.contains(propertyPath, ".")) {
586             String nextParentPath = StringUtils.substringBefore(propertyPath, ".");
587             if (StringUtils.isNotBlank(parentPath)) {
588                 nextParentPath = parentPath + "." + nextParentPath;
589             }
590             String nextPropertyPath = StringUtils.substringAfter(propertyPath, ".");
591 
592             return findNestedDictionaryAttribute(view, field, nextParentPath, nextPropertyPath);
593         }
594 
595         // if a definition was found, update the fields dictionary properties
596         if (attributeDefinition != null) {
597             field.setDictionaryAttributeName(dictionaryAttributeName);
598             field.setDictionaryObjectEntry(dictionaryObjectEntry);
599         }
600 
601         return attributeDefinition;
602     }
603 
604     /**
605      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performApplyModel(org.kuali.rice.krad.uif.view.View,
606      *      java.lang.Object)
607      */
608     @Override
609     public void performApplyModel(View view, Object model) {
610         // get action flag and edit modes from authorizer/presentation controller
611         retrieveEditModesAndActionFlags(view, (UifFormBase) model);
612 
613         // set view context for conditional expressions
614         setViewContext(view, model);
615 
616         Map<String, Integer> visitedIds = new HashMap<String, Integer>();
617         performComponentApplyModel(view, view, model, visitedIds);
618     }
619 
620     /**
621      * Invokes the configured <code>PresentationController</code> and
622      * </code>Authorizer</code> for the view to get the exported action flags
623      * and edit modes that can be used in conditional logic
624      *
625      * @param view - view instance that is being built and presentation/authorizer pulled for
626      * @param model - Object that contains the model data
627      */
628     protected void retrieveEditModesAndActionFlags(View view, UifFormBase model) {
629         ViewPresentationController presentationController = view.getPresentationController();
630         ViewAuthorizer authorizer = view.getAuthorizer();
631 
632         Person user = GlobalVariables.getUserSession().getPerson();
633 
634         Set<String> actionFlags = presentationController.getActionFlags(view, model);
635         actionFlags = authorizer.getActionFlags(view, model, user, actionFlags);
636 
637         view.setActionFlags(new BooleanMap(actionFlags));
638 
639         Set<String> editModes = presentationController.getEditModes(view, model);
640         editModes = authorizer.getEditModes(view, model, user, editModes);
641 
642         view.setEditModes(new BooleanMap(editModes));
643     }
644 
645     /**
646      * Sets up the view context which will be available to other components
647      * through their context for conditional logic evaluation
648      *
649      * @param view - view instance to set context for
650      * @param model - object containing the view data
651      */
652     protected void setViewContext(View view, Object model) {
653         view.pushAllToContext(getPreModelContext(view));
654 
655         // evaluate view expressions for further context
656         for (Entry<String, String> variableExpression : view.getExpressionVariables().entrySet()) {
657             String variableName = variableExpression.getKey();
658             Object value = getExpressionEvaluatorService().evaluateExpression(model, view.getContext(),
659                     variableExpression.getValue());
660             view.pushObjectToContext(variableName, value);
661         }
662     }
663 
664     /**
665      * Returns the general context that is available before the apply model
666      * phase (during the initialize phase)
667      *
668      * @param view - view instance for context
669      * @return Map<String, Object> context map
670      */
671     protected Map<String, Object> getPreModelContext(View view) {
672         Map<String, Object> context = new HashMap<String, Object>();
673 
674         context.put(UifConstants.ContextVariableNames.VIEW, view);
675         context.put(UifConstants.ContextVariableNames.VIEW_HELPER, this);
676 
677         Map<String, String> properties = KRADServiceLocator.getKualiConfigurationService().getAllProperties();
678         context.put(UifConstants.ContextVariableNames.CONFIG_PROPERTIES, properties);
679         context.put(UifConstants.ContextVariableNames.CONSTANTS, KRADConstants.class);
680         context.put(UifConstants.ContextVariableNames.UIF_CONSTANTS, UifConstants.class);
681 
682         return context;
683     }
684 
685     /**
686      * Applies the model data to a component of the View instance
687      *
688      * <p>
689      * The component is invoked to to apply the model data. Here the component
690      * can generate any additional fields needed or alter the configured fields.
691      * After the component is invoked a hook for custom helper service
692      * processing is invoked. Finally the method is recursively called for all
693      * the component children
694      * </p>
695      *
696      * @param view view instance the component belongs to
697      * @param component the component instance the model should be applied to
698      * @param model top level object containing the data
699      * @param visitedIds tracks components ids that have been seen for adjusting duplicates
700      */
701     protected void performComponentApplyModel(View view, Component component, Object model,
702             Map<String, Integer> visitedIds) {
703         if (component == null) {
704             return;
705         }
706 
707         // set context on component for evaluating expressions
708         component.pushAllToContext(getCommonContext(view, component));
709 
710         for (PropertyReplacer replacer : component.getPropertyReplacers()) {
711             getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, replacer, model,
712                     component.getContext());
713         }
714 
715         for (ComponentModifier modifier : component.getComponentModifiers()) {
716             getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, modifier, model,
717                     component.getContext());
718         }
719 
720         getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, component, model,
721                 component.getContext());
722 
723         // evaluate expressions on component security
724         ComponentSecurity componentSecurity = component.getComponentSecurity();
725         getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, componentSecurity, model,
726                 component.getContext());
727 
728         // evaluate expressions on the binding info object
729         if (component instanceof DataBinding) {
730             BindingInfo bindingInfo = ((DataBinding) component).getBindingInfo();
731             getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, bindingInfo, model,
732                     component.getContext());
733         }
734 
735         // set context evaluate expressions on the layout manager
736         if (component instanceof Container) {
737             LayoutManager layoutManager = ((Container) component).getLayoutManager();
738 
739             if (layoutManager != null) {
740                 layoutManager.getContext().putAll(getCommonContext(view, component));
741                 layoutManager.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, component);
742                 layoutManager.pushObjectToContext(UifConstants.ContextVariableNames.MANAGER, layoutManager);
743 
744                 getExpressionEvaluatorService().evaluateExpressionsOnConfigurable(view, layoutManager, model,
745                         layoutManager.getContext());
746 
747                 layoutManager.setId(adjustIdIfNecessary(layoutManager.getId(), visitedIds));
748             }
749         }
750 
751         // sync the component with previous client side state
752         syncClientSideStateForComponent(component, ((ViewModel) model).getClientStateForSyncing());
753 
754         // invoke authorizer and presentation controller to set component state
755         applyAuthorizationAndPresentationLogic(view, component, (ViewModel) model);
756 
757         // adjust ids for duplicates if necessary
758         //component.setId(adjustIdIfNecessary(component.getId(), visitedIds));
759 
760         // invoke component to perform its conditional logic
761         Component parent = (Component) component.getContext().get(UifConstants.ContextVariableNames.PARENT);
762         component.performApplyModel(view, model, parent);
763 
764         // invoke service override hook
765         performCustomApplyModel(view, component, model);
766 
767         // invoke component modifiers configured to run in the apply model phase
768         runComponentModifiers(view, component, model, UifConstants.ViewPhases.APPLY_MODEL);
769 
770         // get children and recursively perform conditional logic
771         for (Component nestedComponent : component.getComponentsForLifecycle()) {
772             if (nestedComponent != null) {
773                 nestedComponent.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, component);
774                 performComponentApplyModel(view, nestedComponent, model, visitedIds);
775             }
776         }
777     }
778 
779     /**
780      * Checks against the visited ids to see if the id is duplicate, if so it is adjusted to make
781      * an unique id by appending an unique sequence number
782      *
783      * @param id id to adjust if necessary
784      * @param visitedIds tracks components ids that have been seen for adjusting duplicates
785      * @return String original or adjusted id
786      */
787     protected String adjustIdIfNecessary(String id, Map<String, Integer> visitedIds) {
788         String adjustedId = id;
789 
790         if (visitedIds.containsKey(id)) {
791             Integer nextAdjustSeq = visitedIds.get(id);
792             adjustedId = id + nextAdjustSeq;
793 
794             // verify the adjustedId does not already exist
795             while (visitedIds.containsKey(adjustedId)) {
796                 nextAdjustSeq = nextAdjustSeq + 1;
797                 adjustedId = id + nextAdjustSeq;
798             }
799 
800             visitedIds.put(adjustedId, new Integer(1));
801 
802             nextAdjustSeq = nextAdjustSeq + 1;
803             visitedIds.put(id, nextAdjustSeq);
804         } else {
805             visitedIds.put(id, new Integer(1));
806         }
807 
808         return adjustedId;
809     }
810 
811     /**
812      * Invokes the view's configured {@link ViewAuthorizer} and {@link ViewPresentationController} to set state of
813      * the component
814      *
815      * <p>
816      * The following authorization is done here:
817      * Fields: edit, view, required, mask, and partial mask
818      * Groups: edit and view
819      * Actions: take action
820      * </p>
821      *
822      * <p>
823      * Note additional checks are also done for fields that are part of a collection group. This authorization is
824      * found in {@link org.kuali.rice.krad.uif.container.CollectionGroupBuilder}
825      * </p>
826      *
827      * @param view - view instance the component belongs to and from which the authorizer and presentation controller
828      * will be pulled
829      * @param component - component instance to authorize
830      * @param model - model object containing the data for the view
831      */
832     protected void applyAuthorizationAndPresentationLogic(View view, Component component, ViewModel model) {
833         ViewPresentationController presentationController = view.getPresentationController();
834         ViewAuthorizer authorizer = view.getAuthorizer();
835 
836         Person user = GlobalVariables.getUserSession().getPerson();
837 
838         // if component not flagged for render no need to check auth and controller logic
839         if (!component.isRender()) {
840             return;
841         }
842 
843         // check top level view edit authorization
844         if (component instanceof View) {
845             if (!view.isReadOnly()) {
846                 boolean canEditView = authorizer.canEditView(view, model, user);
847                 if (canEditView) {
848                     canEditView = presentationController.canEditView(view, model);
849                 }
850                 view.setReadOnly(!canEditView);
851             }
852         }
853 
854         // perform group authorization and presentation logic
855         else if (component instanceof Group) {
856             Group group = (Group) component;
857 
858             // if group is not hidden, do authorization for viewing the group
859             if (!group.isHidden()) {
860                 boolean canViewGroup = authorizer.canViewGroup(view, model, group, group.getId(), user);
861                 if (canViewGroup) {
862                     canViewGroup = presentationController.canViewGroup(view, model, group, group.getId());
863                 }
864                 group.setHidden(!canViewGroup);
865                 group.setRender(canViewGroup);
866             }
867 
868             // if group is editable, do authorization for editing the group
869             if (!group.isReadOnly()) {
870                 boolean canEditGroup = authorizer.canEditGroup(view, model, group, group.getId(), user);
871                 if (canEditGroup) {
872                     canEditGroup = presentationController.canEditGroup(view, model, group, group.getId());
873                 }
874                 group.setReadOnly(!canEditGroup);
875             }
876         }
877 
878         // perform field authorization and presentation logic
879         else if (component instanceof Field && !(component instanceof ActionField)) {
880             Field field = (Field) component;
881 
882             String propertyName = null;
883             if (field instanceof DataBinding) {
884                 propertyName = ((DataBinding) field).getPropertyName();
885             }
886 
887             // if field is not hidden, do authorization for viewing the field
888             if (!field.isHidden()) {
889                 boolean canViewField = authorizer.canViewField(view, model, field, propertyName, user);
890                 if (canViewField) {
891                     canViewField = presentationController.canViewField(view, model, field, propertyName);
892                 }
893                 field.setHidden(!canViewField);
894                 field.setRender(canViewField);
895             }
896 
897             // if field is not readOnly, check edit authorization
898             if (!field.isReadOnly()) {
899                 // check field edit authorization
900                 boolean canEditField = authorizer.canEditField(view, model, field, propertyName, user);
901                 if (canEditField) {
902                     canEditField = presentationController.canEditField(view, model, field, propertyName);
903                 }
904                 field.setReadOnly(!canEditField);
905             }
906 
907             // if field is not already required, invoke presentation logic to determine if it should be
908             if ((field.getRequired() == null) || !field.getRequired().booleanValue()) {
909                 boolean fieldIsRequired = presentationController.fieldIsRequired(view, model, field, propertyName);
910             }
911 
912             if (field instanceof DataField) {
913                 DataField dataField = (DataField) field;
914 
915                 // check mask authorization
916                 boolean canUnmaskValue = authorizer.canUnmaskField(view, model, dataField, dataField.getPropertyName(),
917                         user);
918                 if (!canUnmaskValue) {
919                     dataField.setApplyMask(true);
920                     dataField.setMaskFormatter(dataField.getDataFieldSecurity().getAttributeSecurity().
921                             getMaskFormatter());
922                 } else {
923                     // check partial mask authorization
924                     boolean canPartiallyUnmaskValue = authorizer.canPartialUnmaskField(view, model, dataField,
925                             dataField.getPropertyName(), user);
926                     if (!canPartiallyUnmaskValue) {
927                         dataField.setApplyMask(true);
928                         dataField.setMaskFormatter(
929                                 dataField.getDataFieldSecurity().getAttributeSecurity().getPartialMaskFormatter());
930                     }
931                 }
932             }
933         }
934 
935         // perform action authorization and presentation logic
936         else if (component instanceof ActionField || component instanceof Action) {
937             Action action = null;
938             if (component instanceof ActionField) {
939                 action = ((ActionField) component).getAction();
940             } else {
941                 action = (Action) component;
942             }
943 
944             boolean canTakeAction = authorizer.canPerformAction(view, model, action, action.getActionEvent(),
945                     action.getId(), user);
946             if (canTakeAction) {
947                 canTakeAction = presentationController.canPerformAction(view, model, action, action.getActionEvent(),
948                         action.getId());
949             }
950             action.setRender(canTakeAction);
951         }
952 
953         // perform widget authorization and presentation logic
954         else if (component instanceof Widget) {
955             Widget widget = (Widget) component;
956 
957             // if widget is not hidden, do authorization for viewing the widget
958             if (!widget.isHidden()) {
959                 boolean canViewWidget = authorizer.canViewWidget(view, model, widget, widget.getId(), user);
960                 if (canViewWidget) {
961                     canViewWidget = presentationController.canViewWidget(view, model, widget, widget.getId());
962                 }
963                 widget.setHidden(!canViewWidget);
964                 widget.setRender(canViewWidget);
965             }
966 
967             // if widget is not readOnly, check edit authorization
968             if (!widget.isReadOnly()) {
969                 boolean canEditWidget = authorizer.canEditWidget(view, model, widget, widget.getId(), user);
970                 if (canEditWidget) {
971                     canEditWidget = presentationController.canEditWidget(view, model, widget, widget.getId());
972                 }
973                 widget.setReadOnly(!canEditWidget);
974             }
975         }
976     }
977 
978     /**
979      * Runs any configured <code>ComponentModifiers</code> for the given
980      * component that match the given run phase and who run condition evaluation
981      * succeeds
982      *
983      * <p>
984      * If called during the initialize phase, the performInitialization method will be invoked on
985      * the <code>ComponentModifier</code> before running
986      * </p>
987      *
988      * @param view - view instance for context
989      * @param component - component instance whose modifiers should be run
990      * @param model - model object for context
991      * @param runPhase - current phase to match on
992      */
993     protected void runComponentModifiers(View view, Component component, Object model, String runPhase) {
994         for (ComponentModifier modifier : component.getComponentModifiers()) {
995             // if run phase is initialize, invoke initialize method on modifier first
996             if (StringUtils.equals(runPhase, UifConstants.ViewPhases.INITIALIZE)) {
997                 modifier.performInitialization(view, model, component);
998             }
999 
1000             // check run phase matches
1001             if (StringUtils.equals(modifier.getRunPhase(), runPhase)) {
1002                 // check condition (if set) evaluates to true
1003                 boolean runModifier = true;
1004                 if (StringUtils.isNotBlank(modifier.getRunCondition())) {
1005                     Map<String, Object> context = new HashMap<String, Object>();
1006                     context.put(UifConstants.ContextVariableNames.COMPONENT, component);
1007                     context.put(UifConstants.ContextVariableNames.VIEW, view);
1008 
1009                     String conditionEvaluation = getExpressionEvaluatorService().evaluateExpressionTemplate(model,
1010                             context, modifier.getRunCondition());
1011                     runModifier = Boolean.parseBoolean(conditionEvaluation);
1012                 }
1013 
1014                 if (runModifier) {
1015                     modifier.performModification(view, model, component);
1016                 }
1017             }
1018         }
1019     }
1020 
1021     /**
1022      * Gets global objects for the context map and pushes them to the context
1023      * for the component
1024      *
1025      * @param view - view instance for component
1026      * @param component - component instance to push context to
1027      */
1028     public Map<String, Object> getCommonContext(View view, Component component) {
1029         Map<String, Object> context = new HashMap<String, Object>();
1030 
1031         context.putAll(view.getContext());
1032         context.put(UifConstants.ContextVariableNames.THEME_IMAGES, view.getTheme().getImageDirectory());
1033         context.put(UifConstants.ContextVariableNames.COMPONENT, component);
1034 
1035         return context;
1036     }
1037 
1038     /**
1039      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performFinalize(org.kuali.rice.krad.uif.view.View,
1040      *      java.lang.Object)
1041      */
1042     @Override
1043     public void performFinalize(View view, Object model) {
1044         // get script for generating growl messages
1045         String growlScript = buildGrowlScript(view);
1046         ((ViewModel) model).setGrowlScript(growlScript);
1047 
1048         performComponentFinalize(view, view, model, null);
1049 
1050         String clientStateScript = buildClientSideStateScript(view, model);
1051         view.setPreLoadScript(ScriptUtils.appendScript(view.getPreLoadScript(), clientStateScript));
1052 
1053         // apply default values if they have not been applied yet
1054         if (!((ViewModel) model).isDefaultsApplied()) {
1055             applyDefaultValues(view, view, model);
1056             ((ViewModel) model).setDefaultsApplied(true);
1057         }
1058     }
1059 
1060     /**
1061      * Builds script that will initialize configuration parameters and component state on the client
1062      *
1063      * <p>
1064      * Here client side state is initialized along with configuration variables that need exposed to script
1065      * </p>
1066      *
1067      * @param view - view instance that is being built
1068      * @param model - model containing the client side state map
1069      */
1070     protected String buildClientSideStateScript(View view, Object model) {
1071         Map<String, Object> clientSideState = ((ViewModel) model).getClientStateForSyncing();
1072 
1073         // script for initializing client side state on load
1074         String clientStateScript = "";
1075         if (!clientSideState.isEmpty()) {
1076             clientStateScript = ScriptUtils.buildFunctionCall(UifConstants.JsFunctions.INITIALIZE_VIEW_STATE,
1077                     clientSideState);
1078         }
1079 
1080         // add necessary configuration parameters
1081         String kradImageLocation = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
1082                 UifConstants.ConfigProperties.KRAD_IMAGES_URL);
1083         clientStateScript += ScriptUtils.buildFunctionCall(UifConstants.JsFunctions.SET_CONFIG_PARM,
1084                 UifConstants.ClientSideVariables.KRAD_IMAGE_LOCATION, kradImageLocation);
1085 
1086         String kradURL = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
1087                 UifConstants.ConfigProperties.KRAD_URL);
1088         clientStateScript += ScriptUtils.buildFunctionCall(UifConstants.JsFunctions.SET_CONFIG_PARM,
1089                 UifConstants.ClientSideVariables.KRAD_URL, kradURL);
1090 
1091         return clientStateScript;
1092     }
1093 
1094     /**
1095      * Builds JS script that will invoke the show growl method to display a growl message when the page is
1096      * rendered
1097      *
1098      * <p>
1099      * A growl call will be created for any explicit growl messages added to the message map.
1100      * </p>
1101      *
1102      * <p>
1103      * Growls are only generated if @{link org.kuali.rice.krad.uif.view.View#isGrowlMessagingEnabled()} is enabled.
1104      * If not, the growl messages are set as info messages for the page
1105      * </p>
1106      *
1107      * @param view - view instance for which growls are being generated
1108      * @return String JS script string for generated growl messages
1109      */
1110     protected String buildGrowlScript(View view) {
1111         String growlScript = "";
1112 
1113         MessageService messageService = KRADServiceLocatorWeb.getMessageService();
1114 
1115         MessageMap messageMap = GlobalVariables.getMessageMap();
1116         for (GrowlMessage growl : messageMap.getGrowlMessages()) {
1117             if (view.isGrowlMessagingEnabled()) {
1118                 String message = messageService.getMessageText(growl.getNamespaceCode(), growl.getComponentCode(),
1119                         growl.getMessageKey());
1120 
1121                 if (StringUtils.isNotBlank(message)) {
1122                     if (growl.getMessageParameters() != null) {
1123                         message = message.replace("'", "''");
1124                         message = MessageFormat.format(message, (Object[]) growl.getMessageParameters());
1125                     }
1126 
1127                     // escape single quotes in message or title since that will cause problem with plugin
1128                     message = message.replace("'", "\\'");
1129 
1130                     String title = growl.getTitle();
1131                     if (StringUtils.isNotBlank(growl.getTitleKey())) {
1132                         title = messageService.getMessageText(growl.getNamespaceCode(), growl.getComponentCode(),
1133                                 growl.getTitleKey());
1134                     }
1135                     title = title.replace("'", "\\'");
1136 
1137                     growlScript =
1138                             growlScript + "showGrowl('" + message + "', '" + title + "', '" + growl.getTheme() + "');";
1139                 }
1140             } else {
1141                 ErrorMessage infoMessage = new ErrorMessage(growl.getMessageKey(), growl.getMessageParameters());
1142                 infoMessage.setNamespaceCode(growl.getNamespaceCode());
1143                 infoMessage.setComponentCode(growl.getComponentCode());
1144 
1145                 messageMap.putInfoForSectionId(KRADConstants.GLOBAL_INFO, infoMessage);
1146             }
1147         }
1148 
1149         return growlScript;
1150     }
1151 
1152     /**
1153      * Update state of the given component and does final preparation for
1154      * rendering
1155      *
1156      * @param view - view instance the component belongs to
1157      * @param component - the component instance that should be updated
1158      * @param model - top level object containing the data
1159      * @param parent - Parent component for the component being finalized
1160      */
1161     protected void performComponentFinalize(View view, Component component, Object model, Component parent) {
1162         if (component == null) {
1163             return;
1164         }
1165 
1166         // implement readonly request overrides
1167         ViewModel viewModel = (ViewModel) model;
1168         if ((component instanceof DataBinding) && view.isSupportsRequestOverrideOfReadOnlyFields() && !viewModel
1169                 .getReadOnlyFieldsList().isEmpty()) {
1170             String propertyName = ((DataBinding) component).getPropertyName();
1171             if (viewModel.getReadOnlyFieldsList().contains(propertyName)) {
1172                 component.setReadOnly(true);
1173             }
1174         }
1175 
1176         // invoke configured method finalizers
1177         invokeMethodFinalizer(view, component, model);
1178 
1179         // invoke component to update its state
1180         component.performFinalize(view, model, parent);
1181 
1182         // invoke service override hook
1183         performCustomFinalize(view, component, model, parent);
1184 
1185         // invoke component modifiers setup to run in the finalize phase
1186         runComponentModifiers(view, component, model, UifConstants.ViewPhases.FINALIZE);
1187         
1188         // add the components template to the views list of components
1189         if (!component.isSelfRendered() && StringUtils.isNotBlank(component.getTemplate()) &&
1190                 !view.getViewTemplates().contains(component.getTemplate())) {
1191             view.getViewTemplates().add(component.getTemplate());
1192         }
1193 
1194         // get components children and recursively update state
1195         for (Component nestedComponent : component.getComponentsForLifecycle()) {
1196             performComponentFinalize(view, nestedComponent, model, component);
1197         }
1198     }
1199 
1200     /**
1201      * Updates the properties of the given component instance with the value found from the corresponding map of
1202      * client state (if found)
1203      *
1204      * @param component - component instance to update
1205      * @param clientSideState - map of state to sync with
1206      */
1207     protected void syncClientSideStateForComponent(Component component, Map<String, Object> clientSideState) {
1208         // find the map of state that was sent for component (if any)
1209         Map<String, Object> componentState = null;
1210         if (component instanceof View) {
1211             componentState = clientSideState;
1212         } else {
1213             if (clientSideState.containsKey(component.getId())) {
1214                 componentState = (Map<String, Object>) clientSideState.get(component.getId());
1215             }
1216         }
1217 
1218         // if state was sent, match with fields on the component that are annotated to have client state
1219         if ((componentState != null) && (!componentState.isEmpty())) {
1220             Map<String, Annotation> annotatedFields = CloneUtils.getFieldsWithAnnotation(component.getClass(),
1221                     ClientSideState.class);
1222 
1223             for (Entry<String, Annotation> annotatedField : annotatedFields.entrySet()) {
1224                 ClientSideState clientSideStateAnnot = (ClientSideState) annotatedField.getValue();
1225 
1226                 String variableName = clientSideStateAnnot.variableName();
1227                 if (StringUtils.isBlank(variableName)) {
1228                     variableName = annotatedField.getKey();
1229                 }
1230 
1231                 if (componentState.containsKey(variableName)) {
1232                     Object value = componentState.get(variableName);
1233                     ObjectPropertyUtils.setPropertyValue(component, annotatedField.getKey(), value);
1234                 }
1235             }
1236         }
1237     }
1238 
1239     /**
1240      * Invokes the finalize method for the component (if configured) and sets
1241      * the render output for the component to the returned method string (if
1242      * method is not a void type)
1243      *
1244      * @param view - view instance that contains the component
1245      * @param component - component to run finalize method for
1246      * @param model - top level object containing the data
1247      */
1248     protected void invokeMethodFinalizer(View view, Component component, Object model) {
1249         String finalizeMethodToCall = component.getFinalizeMethodToCall();
1250         MethodInvoker finalizeMethodInvoker = component.getFinalizeMethodInvoker();
1251 
1252         if (StringUtils.isBlank(finalizeMethodToCall) && (finalizeMethodInvoker == null)) {
1253             return;
1254         }
1255 
1256         if (finalizeMethodInvoker == null) {
1257             finalizeMethodInvoker = new MethodInvoker();
1258         }
1259 
1260         // if method not set on invoker, use finalizeMethodToCall, note staticMethod could be set(don't know since
1261         // there is not a getter), if so it will override the target method in prepare
1262         if (StringUtils.isBlank(finalizeMethodInvoker.getTargetMethod())) {
1263             finalizeMethodInvoker.setTargetMethod(finalizeMethodToCall);
1264         }
1265 
1266         // if target class or object not set, use view helper service
1267         if ((finalizeMethodInvoker.getTargetClass() == null) && (finalizeMethodInvoker.getTargetObject() == null)) {
1268             finalizeMethodInvoker.setTargetObject(view.getViewHelperService());
1269         }
1270 
1271         // setup arguments for method
1272         List<Object> additionalArguments = component.getFinalizeMethodAdditionalArguments();
1273         if (additionalArguments == null) {
1274             additionalArguments = new ArrayList<Object>();
1275         }
1276 
1277         Object[] arguments = new Object[2 + additionalArguments.size()];
1278         arguments[0] = component;
1279         arguments[1] = model;
1280 
1281         int argumentIndex = 1;
1282         for (Object argument : additionalArguments) {
1283             argumentIndex++;
1284             arguments[argumentIndex] = argument;
1285         }
1286         finalizeMethodInvoker.setArguments(arguments);
1287 
1288         // invoke finalize method
1289         try {
1290             LOG.debug("Invoking finalize method: "
1291                     + finalizeMethodInvoker.getTargetMethod()
1292                     + " for component: "
1293                     + component.getId());
1294             finalizeMethodInvoker.prepare();
1295 
1296             Class<?> methodReturnType = finalizeMethodInvoker.getPreparedMethod().getReturnType();
1297             if (StringUtils.equals("void", methodReturnType.getName())) {
1298                 finalizeMethodInvoker.invoke();
1299             } else {
1300                 String renderOutput = (String) finalizeMethodInvoker.invoke();
1301 
1302                 component.setSelfRendered(true);
1303                 component.setRenderedHtmlOutput(renderOutput);
1304             }
1305         } catch (Exception e) {
1306             LOG.error("Error invoking finalize method for component: " + component.getId(), e);
1307             throw new RuntimeException("Error invoking finalize method for component: " + component.getId(), e);
1308         }
1309     }
1310 
1311     /**
1312      * @see org.kuali.rice.krad.uif.service.ViewHelperService#cleanViewAfterRender(org.kuali.rice.krad.uif.view.View)
1313      */
1314     @Override
1315     public void cleanViewAfterRender(View view) {
1316         ViewCleaner.cleanView(view);
1317     }
1318 
1319     /**
1320      * @see org.kuali.rice.krad.uif.service.ViewHelperService#processCollectionAddLine(org.kuali.rice.krad.uif.view.View,
1321      *      java.lang.Object, java.lang.String)
1322      */
1323     @Override
1324     public void processCollectionAddLine(View view, Object model, String collectionPath) {
1325         // get the collection group from the view
1326         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
1327         if (collectionGroup == null) {
1328             logAndThrowRuntime("Unable to get collection group component for path: " + collectionPath);
1329         }
1330 
1331         // get the collection instance for adding the new line
1332         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
1333         if (collection == null) {
1334             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
1335         }
1336 
1337         // now get the new line we need to add
1338         String addLinePath = collectionGroup.getAddLineBindingInfo().getBindingPath();
1339         Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLinePath);
1340         if (addLine == null) {
1341             logAndThrowRuntime("Add line instance not found for path: " + addLinePath);
1342         }
1343 
1344         processBeforeAddLine(view, collectionGroup, model, addLine);
1345 
1346         // validate the line to make sure it is ok to add
1347         boolean isValidLine = performAddLineValidation(view, collectionGroup, model, addLine);
1348         if (isValidLine) {
1349             // TODO: should check to see if there is an add line method on the
1350             // collection parent and if so call that instead of just adding to
1351             // the collection (so that sequence can be set)
1352             addLine(collection, addLine, collectionGroup.getAddLinePlacement().equals("TOP"));
1353 
1354             // make a new instance for the add line
1355             collectionGroup.initializeNewCollectionLine(view, model, collectionGroup, true);
1356         }
1357 
1358         ((UifFormBase) model).getAddedCollectionItems().add(addLine);
1359 
1360         processAfterAddLine(view, collectionGroup, model, addLine);
1361     }
1362 
1363     /**
1364      * @see org.kuali.rice.krad.uif.service.ViewHelperService#processCollectionSaveLine(org.kuali.rice.krad.uif.view.View,
1365      *      java.lang.Object, java.lang.String, int)
1366      */
1367     @Override
1368     public void processCollectionSaveLine(View view, Object model, String collectionPath, int selectedLineIndex) {
1369         // get the collection group from the view
1370         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
1371         if (collectionGroup == null) {
1372             logAndThrowRuntime("Unable to get collection group component for path: " + collectionPath);
1373         }
1374 
1375         // get the collection instance for adding the new line
1376         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
1377         if (collection == null) {
1378             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
1379         }
1380 
1381         // TODO: look into other ways of identifying a line so we can deal with
1382         // unordered collections
1383         if (collection instanceof List) {
1384             Object saveLine = ((List<Object>) collection).get(selectedLineIndex);
1385 
1386             processBeforeSaveLine(view, collectionGroup, model, saveLine);
1387 
1388             ((UifFormBase) model).getAddedCollectionItems().remove(saveLine);
1389 
1390             processAfterSaveLine(view, collectionGroup, model, saveLine);
1391 
1392         } else {
1393             logAndThrowRuntime("Only List collection implementations are supported for the delete by index method");
1394         }
1395 
1396     }
1397 
1398     /**
1399      * @see org.kuali.rice.krad.uif.service.ViewHelperService#processCollectionAddBlankLine(org.kuali.rice.krad.uif.view.View,
1400      *      java.lang.Object, java.lang.String)
1401      */
1402     @Override
1403     public void processCollectionAddBlankLine(View view, Object model, String collectionPath) {
1404         // get the collection group from the view
1405         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
1406         if (collectionGroup == null) {
1407             logAndThrowRuntime("Unable to get collection group component for path: " + collectionPath);
1408         }
1409 
1410         // get the collection instance for adding the new line
1411         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
1412         if (collection == null) {
1413             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
1414         }
1415 
1416         Object newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass());
1417         applyDefaultValuesForCollectionLine(view, model, collectionGroup, newLine);
1418         addLine(collection, newLine, collectionGroup.getAddLinePlacement().equals("TOP"));
1419 
1420         ((UifFormBase) model).getAddedCollectionItems().add(newLine);
1421     }
1422 
1423     /**
1424      * Add addLine to collection while giving derived classes an opportunity to override
1425      * for things like sorting.
1426      *
1427      * @param collection - the Collection to add the given addLine to
1428      * @param addLine - the line to add to the given collection
1429      * @param insertFirst - indicates if the item should be inserted as the first item
1430      */
1431     protected void addLine(Collection<Object> collection, Object addLine, boolean insertFirst) {
1432         if (insertFirst && (collection instanceof List)) {
1433             ((List) collection).add(0, addLine);
1434         } else {
1435             collection.add(addLine);
1436         }
1437     }
1438 
1439     /**
1440      * Performs validation on the new collection line before it is added to the
1441      * corresponding collection
1442      *
1443      * @param view - view instance that the action was taken on
1444      * @param collectionGroup - collection group component for the collection
1445      * @param addLine - new line instance to validate
1446      * @param model - object instance that contain's the views data
1447      * @return boolean true if the line is valid and it should be added to the
1448      *         collection, false if it was not valid and should not be added to
1449      *         the collection
1450      */
1451     protected boolean performAddLineValidation(View view, CollectionGroup collectionGroup, Object model,
1452             Object addLine) {
1453         boolean isValid = true;
1454 
1455         // TODO: this should invoke rules, subclasses like the document view
1456         // should create the document add line event
1457 
1458         return isValid;
1459     }
1460 
1461     /**
1462      * @see org.kuali.rice.krad.uif.service.ViewHelperService#processCollectionDeleteLine(org.kuali.rice.krad.uif.view.View,
1463      *      java.lang.Object, java.lang.String, int)
1464      */
1465     public void processCollectionDeleteLine(View view, Object model, String collectionPath, int lineIndex) {
1466         // get the collection group from the view
1467         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
1468         if (collectionGroup == null) {
1469             logAndThrowRuntime("Unable to get collection group component for path: " + collectionPath);
1470         }
1471 
1472         // get the collection instance for adding the new line
1473         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
1474         if (collection == null) {
1475             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
1476         }
1477 
1478         // TODO: look into other ways of identifying a line so we can deal with
1479         // unordered collections
1480         if (collection instanceof List) {
1481             Object deleteLine = ((List<Object>) collection).get(lineIndex);
1482 
1483             // validate the delete action is allowed for this line
1484             boolean isValid = performDeleteLineValidation(view, collectionGroup, deleteLine);
1485             if (isValid) {
1486                 ((List<Object>) collection).remove(lineIndex);
1487                 processAfterDeleteLine(view, collectionGroup, model, lineIndex);
1488             }
1489         } else {
1490             logAndThrowRuntime("Only List collection implementations are supported for the delete by index method");
1491         }
1492     }
1493 
1494     /**
1495      * Performs validation on the collection line before it is removed from the
1496      * corresponding collection
1497      *
1498      * @param view - view instance that the action was taken on
1499      * @param collectionGroup - collection group component for the collection
1500      * @param deleteLine - line that will be removed
1501      * @return boolean true if the action is allowed and the line should be
1502      *         removed, false if the line should not be removed
1503      */
1504     protected boolean performDeleteLineValidation(View view, CollectionGroup collectionGroup, Object deleteLine) {
1505         boolean isValid = true;
1506 
1507         // TODO: this should invoke rules, sublclasses like the document view
1508         // should create the document delete line event
1509 
1510         return isValid;
1511     }
1512 
1513     /**
1514      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processMultipleValueLookupResults
1515      */
1516     public void processMultipleValueLookupResults(View view, Object model, String collectionPath,
1517             String lookupResultValues) {
1518         // if no line values returned, no population is needed
1519         if (StringUtils.isBlank(lookupResultValues)) {
1520             return;
1521         }
1522 
1523         // retrieve the collection group so we can get the collection class and collection lookup
1524         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
1525         if (collectionGroup == null) {
1526             throw new RuntimeException("Unable to find collection group for path: " + collectionPath);
1527         }
1528 
1529         Class<?> collectionObjectClass = collectionGroup.getCollectionObjectClass();
1530         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model,
1531                 collectionGroup.getBindingInfo().getBindingPath());
1532         if (collection == null) {
1533             Class<?> collectionClass = ObjectPropertyUtils.getPropertyType(model,
1534                     collectionGroup.getBindingInfo().getBindingPath());
1535             collection = (Collection<Object>) ObjectUtils.newInstance(collectionClass);
1536             ObjectPropertyUtils.setPropertyValue(model, collectionGroup.getBindingInfo().getBindingPath(), collection);
1537         }
1538 
1539         Map<String, String> fieldConversions = collectionGroup.getCollectionLookup().getFieldConversions();
1540         List<String> toFieldNamesColl = new ArrayList(fieldConversions.values());
1541         Collections.sort(toFieldNamesColl);
1542         String[] toFieldNames = new String[toFieldNamesColl.size()];
1543         toFieldNamesColl.toArray(toFieldNames);
1544 
1545         // first split to get the line value sets
1546         String[] lineValues = StringUtils.split(lookupResultValues, ",");
1547 
1548         // for each returned set create a new instance of collection class and populate with returned line values
1549         for (String lineValue : lineValues) {
1550             Object lineDataObject = null;
1551 
1552             // TODO: need to put this in data object service so logic can be reused
1553             ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
1554                     collectionObjectClass);
1555             if (moduleService != null && moduleService.isExternalizable(collectionObjectClass)) {
1556                 lineDataObject = moduleService.createNewObjectFromExternalizableClass(collectionObjectClass.asSubclass(
1557                         ExternalizableBusinessObject.class));
1558             } else {
1559                 lineDataObject = ObjectUtils.newInstance(collectionObjectClass);
1560             }
1561 
1562             // apply default values to new line
1563             applyDefaultValuesForCollectionLine(view, model, collectionGroup, lineDataObject);
1564 
1565             String[] fieldValues = StringUtils.split(lineValue, ":");
1566             if (fieldValues.length != toFieldNames.length) {
1567                 throw new RuntimeException(
1568                         "Value count passed back from multi-value lookup does not match field conversion count");
1569             }
1570 
1571             // set each field value on the line
1572             for (int i = 0; i < fieldValues.length; i++) {
1573                 String fieldName = toFieldNames[i];
1574                 ObjectPropertyUtils.setPropertyValue(lineDataObject, fieldName, fieldValues[i]);
1575             }
1576 
1577             // TODO: duplicate identifier check
1578 
1579             collection.add(lineDataObject);
1580         }
1581     }
1582 
1583     /**
1584      * Finds the <code>Inquirable</code> configured for the given data object
1585      * class and delegates to it for building the inquiry URL
1586      *
1587      * @see org.kuali.rice.krad.uif.service.ViewHelperService#buildInquiryLink(java.lang.Object,
1588      *      java.lang.String, org.kuali.rice.krad.uif.widget.Inquiry)
1589      */
1590     public void buildInquiryLink(Object dataObject, String propertyName, Inquiry inquiry) {
1591         Inquirable inquirable = getViewDictionaryService().getInquirable(dataObject.getClass(), inquiry.getViewName());
1592         if (inquirable != null) {
1593             inquirable.buildInquirableLink(dataObject, propertyName, inquiry);
1594         } else {
1595             // TODO: should we really not render the inquiry just because the top parent doesn't have an inquirable?
1596             // it is possible the path is nested and there does exist an inquiry for the property
1597             // inquirable not found, no inquiry link can be set
1598             inquiry.setRender(false);
1599         }
1600     }
1601 
1602     /**
1603      * @see org.kuali.rice.krad.uif.service.ViewHelperService#applyDefaultValuesForCollectionLine(org.kuali.rice.krad.uif.view.View,
1604      *      java.lang.Object, org.kuali.rice.krad.uif.container.CollectionGroup,
1605      *      java.lang.Object)
1606      */
1607     public void applyDefaultValuesForCollectionLine(View view, Object model, CollectionGroup collectionGroup,
1608             Object line) {
1609         // retrieve all data fields for the collection line
1610         List<DataField> dataFields = ComponentUtils.getComponentsOfTypeDeep(collectionGroup.getAddLineItems(),
1611                 DataField.class);
1612         for (DataField dataField : dataFields) {
1613             String bindingPath = "";
1614             if (StringUtils.isNotBlank(dataField.getBindingInfo().getBindByNamePrefix())) {
1615                 bindingPath = dataField.getBindingInfo().getBindByNamePrefix() + ".";
1616             }
1617             bindingPath += dataField.getBindingInfo().getBindingName();
1618 
1619             populateDefaultValueForField(view, line, dataField, bindingPath);
1620         }
1621     }
1622 
1623     /**
1624      * Iterates through the view components picking up data fields and applying an default value configured
1625      *
1626      * @param view - view instance we are applying default values for
1627      * @param component - component that should be checked for default values
1628      * @param model - model object that values should be set on
1629      */
1630     protected void applyDefaultValues(View view, Component component, Object model) {
1631         if (component == null) {
1632             return;
1633         }
1634 
1635         // if component is a data field apply default value
1636         if (component instanceof DataField) {
1637             DataField dataField = ((DataField) component);
1638 
1639             // need to make sure binding is initialized since this could be on a page we have not initialized yet
1640             dataField.getBindingInfo().setDefaults(view, dataField.getPropertyName());
1641 
1642             populateDefaultValueForField(view, model, dataField, dataField.getBindingInfo().getBindingPath());
1643         }
1644 
1645         List<Component> nestedComponents = component.getComponentsForLifecycle();
1646 
1647         // if view, need to add all pages since only one will be on the lifecycle
1648         if (component instanceof View) {
1649             nestedComponents.addAll(((View) component).getItems());
1650         }
1651 
1652         for (Component nested : nestedComponents) {
1653             applyDefaultValues(view, nested, model);
1654         }
1655     }
1656 
1657     /**
1658      * Applies the default value configured for the given field (if any) to the
1659      * line given object property that is determined by the given binding path
1660      *
1661      * <p>
1662      * Checks for a configured default value or default value class for the
1663      * field. If both are given, the configured static default value will win.
1664      * In addition, if the default value contains an el expression it is
1665      * evaluated against the initial context
1666      * </p>
1667      *
1668      * @param view - view instance the field belongs to
1669      * @param object - object that should be populated
1670      * @param dataField - field to check for configured default value
1671      * @param bindingPath - path to the property on the object that should be populated
1672      */
1673     protected void populateDefaultValueForField(View view, Object object, DataField dataField, String bindingPath) {
1674         // check for configured default value
1675         String defaultValue = dataField.getDefaultValue();
1676         Object[] defaultValues = dataField.getDefaultValues();
1677 
1678         if (StringUtils.isBlank(defaultValue) && defaultValues != null && defaultValues.length > 0)     {
1679             ObjectPropertyUtils.setPropertyValue(object, bindingPath, defaultValues);
1680         }
1681         else {
1682             if (StringUtils.isBlank(defaultValue) && (dataField.getDefaultValueFinderClass() != null)) {
1683                 ValueFinder defaultValueFinder = ObjectUtils.newInstance(dataField.getDefaultValueFinderClass());
1684                 defaultValue = defaultValueFinder.getValue();
1685             }
1686 
1687             // populate default value if given and path is valid
1688             if (StringUtils.isNotBlank(defaultValue) && ObjectPropertyUtils.isWritableProperty(object, bindingPath)) {
1689                 if (getExpressionEvaluatorService().containsElPlaceholder(defaultValue)) {
1690                     Map<String, Object> context = getPreModelContext(view);
1691                     defaultValue = getExpressionEvaluatorService().evaluateExpressionTemplate(null, context, defaultValue);
1692                 }
1693 
1694                 // TODO: this should go through our formatters
1695                 // Skip nullable non-null non-empty objects when setting default
1696                 Object currentValue = ObjectPropertyUtils.getPropertyValue(object, bindingPath);
1697                 Class currentClazz = ObjectPropertyUtils.getPropertyType(object, bindingPath);
1698                 if(currentValue == null || StringUtils.isBlank(currentValue.toString()) ||
1699                         ClassUtils.isPrimitiveOrWrapper(currentClazz)) {
1700                     ObjectPropertyUtils.setPropertyValue(object, bindingPath, defaultValue);
1701                 }
1702             }
1703         }
1704     }
1705 
1706     /**
1707      * Hook for creating new components with code and adding them to a container
1708      *
1709      * <p>
1710      * Subclasses can override this method to check for one or more containers by id and then adding components
1711      * created in code. This is invoked before the initialize method on the container component, so the full
1712      * lifecycle will be run on the components returned.
1713      * </p>
1714      *
1715      * <p>
1716      * New components instances can be retrieved using {@link ComponentFactory}
1717      * </p>
1718      *
1719      * @param view - view instance the container belongs to
1720      * @param model - object containing the view data
1721      * @param container - container instance to add components to
1722      */
1723     protected void addCustomContainerComponents(View view, Object model, Container container) {
1724 
1725     }
1726 
1727     /**
1728      * Hook for service overrides to perform custom initialization on the
1729      * component
1730      *
1731      * @param view - view instance containing the component
1732      * @param component - component instance to initialize
1733      */
1734     protected void performCustomInitialization(View view, Component component) {
1735 
1736     }
1737 
1738     /**
1739      * Hook for service overrides to perform custom apply model logic on the
1740      * component
1741      *
1742      * @param view - view instance containing the component
1743      * @param component - component instance to apply model to
1744      * @param model - Top level object containing the data (could be the form or a
1745      * top level business object, dto)
1746      */
1747     protected void performCustomApplyModel(View view, Component component, Object model) {
1748 
1749     }
1750 
1751     /**
1752      * Hook for service overrides to perform custom component finalization
1753      *
1754      * @param view - view instance containing the component
1755      * @param component - component instance to update
1756      * @param model - Top level object containing the data
1757      * @param parent - Parent component for the component being finalized
1758      */
1759     protected void performCustomFinalize(View view, Component component, Object model, Component parent) {
1760 
1761     }
1762 
1763     /**
1764      * Hook for service overrides to process the new collection line before it
1765      * is added to the collection
1766      *
1767      * @param view - view instance that is being presented (the action was taken
1768      * on)
1769      * @param collectionGroup - collection group component for the collection the line will
1770      * be added to
1771      * @param model - object instance that contain's the views data
1772      * @param addLine - the new line instance to be processed
1773      */
1774     protected void processBeforeAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
1775 
1776     }
1777 
1778     /**
1779      * Hook for service overrides to process the new collection line after it
1780      * has been added to the collection
1781      *
1782      * @param view - view instance that is being presented (the action was taken
1783      * on)
1784      * @param collectionGroup - collection group component for the collection the line that
1785      * was added
1786      * @param model - object instance that contain's the views data
1787      * @param addLine - the new line that was added
1788      */
1789     protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
1790 
1791     }
1792 
1793     /**
1794      * Hook for service overrides to process the save collection line before it
1795      * is validated
1796      *
1797      * @param view - view instance that is being presented (the action was taken
1798      * on)
1799      * @param collectionGroup - collection group component for the collection
1800      * @param model - object instance that contain's the views data
1801      * @param addLine - the new line instance to be processed
1802      */
1803     protected void processBeforeSaveLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
1804 
1805     }
1806 
1807     /**
1808      * Hook for service overrides to process the save collection line after it
1809      * has been validated
1810      *
1811      * @param view - view instance that is being presented (the action was taken
1812      * on)
1813      * @param collectionGroup - collection group component for the collection
1814      * @param model - object instance that contains the views data
1815      * @param addLine - the new line that was added
1816      */
1817     protected void processAfterSaveLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
1818 
1819     }
1820 
1821     /**
1822      * Hook for service overrides to process the collection line after it has been deleted
1823      *
1824      * @param view - view instance that is being presented (the action was taken on)
1825      * @param collectionGroup - collection group component for the collection the line that
1826      * was added
1827      * @param model - object instance that contains the views data
1828      * @param lineIndex - index of the line that was deleted
1829      */
1830     protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
1831 
1832     }
1833 
1834     /**
1835      * Log the error and throw a new runtime exception
1836      *
1837      * @param message - the error message (both to log and throw as a new exception)
1838      */
1839     protected void logAndThrowRuntime(String message) {
1840         LOG.error(message);
1841         throw new RuntimeException(message);
1842     }
1843 
1844     /**
1845      * Gets the data dictionary service
1846      *
1847      * @return DataDictionaryService data dictionary service
1848      */
1849     protected DataDictionaryService getDataDictionaryService() {
1850         if (this.dataDictionaryService == null) {
1851             this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
1852         }
1853 
1854         return this.dataDictionaryService;
1855     }
1856 
1857     /**
1858      * Sets the data dictionary service
1859      *
1860      * @param dataDictionaryService
1861      */
1862     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1863         this.dataDictionaryService = dataDictionaryService;
1864     }
1865 
1866     /**
1867      * Gets the expression evaluator service
1868      *
1869      * @return ExpressionEvaluatorService expression evaluator service
1870      */
1871     protected ExpressionEvaluatorService getExpressionEvaluatorService() {
1872         if (this.expressionEvaluatorService == null) {
1873             this.expressionEvaluatorService = KRADServiceLocatorWeb.getExpressionEvaluatorService();
1874         }
1875 
1876         return this.expressionEvaluatorService;
1877     }
1878 
1879     /**
1880      * Sets the expression evaluator service
1881      *
1882      * @param expressionEvaluatorService
1883      */
1884     public void setExpressionEvaluatorService(ExpressionEvaluatorService expressionEvaluatorService) {
1885         this.expressionEvaluatorService = expressionEvaluatorService;
1886     }
1887 
1888     /**
1889      * Gets the view dictionary service
1890      *
1891      * @return ViewDictionaryService view dictionary service
1892      */
1893     public ViewDictionaryService getViewDictionaryService() {
1894         if (this.viewDictionaryService == null) {
1895             this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
1896         }
1897         return this.viewDictionaryService;
1898     }
1899 
1900     /**
1901      * Sets the view dictionary service
1902      *
1903      * @param viewDictionaryService
1904      */
1905     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
1906         this.viewDictionaryService = viewDictionaryService;
1907     }
1908 
1909     /**
1910      * Gets the configuration service
1911      *
1912      * @return ConfigurationService configuration service
1913      */
1914     public ConfigurationService getConfigurationService() {
1915         if (this.configurationService == null) {
1916             this.configurationService = KRADServiceLocator.getKualiConfigurationService();
1917         }
1918         return this.configurationService;
1919     }
1920 
1921     /**
1922      * Sets the configuration service
1923      *
1924      * @param configurationService
1925      */
1926     public void setConfigurationService(ConfigurationService configurationService) {
1927         this.configurationService = configurationService;
1928     }
1929 }