View Javadoc

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