Coverage Report - org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
ViewHelperServiceImpl
0%
0/457
0%
0/252
4.286
 
 1  
 /**
 2  
  * Copyright 2005-2011 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.exception.RiceRuntimeException;
 20  
 import org.kuali.rice.kim.api.identity.Person;
 21  
 import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
 22  
 import org.kuali.rice.krad.datadictionary.AttributeDefinition;
 23  
 import org.kuali.rice.krad.inquiry.Inquirable;
 24  
 import org.kuali.rice.krad.service.DataDictionaryService;
 25  
 import org.kuali.rice.krad.service.KRADServiceLocator;
 26  
 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
 27  
 import org.kuali.rice.krad.service.ModuleService;
 28  
 import org.kuali.rice.krad.uif.UifConstants;
 29  
 import org.kuali.rice.krad.uif.authorization.Authorizer;
 30  
 import org.kuali.rice.krad.uif.authorization.PresentationController;
 31  
 import org.kuali.rice.krad.uif.component.BindingInfo;
 32  
 import org.kuali.rice.krad.uif.component.ClientSideState;
 33  
 import org.kuali.rice.krad.uif.component.Component;
 34  
 import org.kuali.rice.krad.uif.component.DataBinding;
 35  
 import org.kuali.rice.krad.uif.component.PropertyReplacer;
 36  
 import org.kuali.rice.krad.uif.component.RequestParameter;
 37  
 import org.kuali.rice.krad.uif.container.CollectionGroup;
 38  
 import org.kuali.rice.krad.uif.container.Container;
 39  
 import org.kuali.rice.krad.uif.control.Control;
 40  
 import org.kuali.rice.krad.uif.field.DataField;
 41  
 import org.kuali.rice.krad.uif.field.InputField;
 42  
 import org.kuali.rice.krad.uif.field.Field;
 43  
 import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
 44  
 import org.kuali.rice.krad.uif.layout.LayoutManager;
 45  
 import org.kuali.rice.krad.uif.modifier.ComponentModifier;
 46  
 import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
 47  
 import org.kuali.rice.krad.uif.service.ViewDictionaryService;
 48  
 import org.kuali.rice.krad.uif.service.ViewHelperService;
 49  
 import org.kuali.rice.krad.uif.util.BooleanMap;
 50  
 import org.kuali.rice.krad.uif.util.CloneUtils;
 51  
 import org.kuali.rice.krad.uif.util.ComponentFactory;
 52  
 import org.kuali.rice.krad.uif.util.ComponentUtils;
 53  
 import org.kuali.rice.krad.uif.util.ExpressionUtils;
 54  
 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
 55  
 import org.kuali.rice.krad.uif.util.ScriptUtils;
 56  
 import org.kuali.rice.krad.uif.util.ViewModelUtils;
 57  
 import org.kuali.rice.krad.uif.view.View;
 58  
 import org.kuali.rice.krad.uif.view.ViewModel;
 59  
 import org.kuali.rice.krad.uif.widget.Inquiry;
 60  
 import org.kuali.rice.krad.util.GlobalVariables;
 61  
 import org.kuali.rice.krad.util.KRADConstants;
 62  
 import org.kuali.rice.krad.util.ObjectUtils;
 63  
 import org.kuali.rice.krad.valuefinder.ValueFinder;
 64  
 import org.kuali.rice.krad.web.form.UifFormBase;
 65  
 import org.springframework.util.MethodInvoker;
 66  
 
 67  
 import java.io.Serializable;
 68  
 import java.lang.annotation.Annotation;
 69  
 import java.util.ArrayList;
 70  
 import java.util.Collection;
 71  
 import java.util.Collections;
 72  
 import java.util.HashMap;
 73  
 import java.util.HashSet;
 74  
 import java.util.List;
 75  
 import java.util.Map;
 76  
 import java.util.Map.Entry;
 77  
 import java.util.Set;
 78  
 
 79  
 /**
 80  
  * Default Implementation of <code>ViewHelperService</code>
 81  
  *
 82  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 83  
  */
 84  0
 public class ViewHelperServiceImpl implements ViewHelperService, Serializable {
 85  0
     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ViewHelperServiceImpl.class);
 86  
 
 87  
     private transient DataDictionaryService dataDictionaryService;
 88  
     private transient ExpressionEvaluatorService expressionEvaluatorService;
 89  
     private transient ViewDictionaryService viewDictionaryService;
 90  
 
 91  
     /**
 92  
      * Uses reflection to find all fields defined on the <code>View</code> instance that have
 93  
      * the <code>RequestParameter</code> annotation (which indicates the field may be populated by the request).
 94  
      *
 95  
      * <p>
 96  
      * For each field found, if there is a corresponding key/value pair in the request parameters,
 97  
      * the value is used to populate the field. In addition, any conditional properties of
 98  
      * <code>PropertyReplacers</code> configured for the field are cleared so that the request parameter
 99  
      * value does not get overridden by the dictionary conditional logic
 100  
      * </p>
 101  
      *
 102  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#populateViewFromRequestParameters(org.kuali.rice.krad.uif.view.View,
 103  
      *      java.util.Map)
 104  
      */
 105  
     @Override
 106  
     public void populateViewFromRequestParameters(View view, Map<String, String> parameters) {
 107  
         // build Map of property replacers by property name so that we can remove them
 108  
         // if the property was set by a request parameter
 109  0
         Map<String, Set<PropertyReplacer>> viewPropertyReplacers = new HashMap<String, Set<PropertyReplacer>>();
 110  0
         for (PropertyReplacer replacer : view.getPropertyReplacers()) {
 111  0
             Set<PropertyReplacer> propertyReplacers = new HashSet<PropertyReplacer>();
 112  0
             if (viewPropertyReplacers.containsKey(replacer.getPropertyName())) {
 113  0
                 propertyReplacers = viewPropertyReplacers.get(replacer.getPropertyName());
 114  
             }
 115  0
             propertyReplacers.add(replacer);
 116  
 
 117  0
             viewPropertyReplacers.put(replacer.getPropertyName(), propertyReplacers);
 118  0
         }
 119  
 
 120  0
         Map<String, Annotation> annotatedFields = CloneUtils.getFieldsWithAnnotation(view.getClass(),
 121  
                 RequestParameter.class);
 122  
 
 123  
         // for each request parameter allowed on the view, if the request contains a value use
 124  
         // to set on View, and clear and conditional expressions or property replacers for that field
 125  0
         Map<String, String> viewRequestParameters = new HashMap<String, String>();
 126  0
         for (String fieldToPopulate : annotatedFields.keySet()) {
 127  0
             RequestParameter requestParameter = (RequestParameter) annotatedFields.get(fieldToPopulate);
 128  
 
 129  
             // use specified parameter name if given, else use field name to retrieve parameter value
 130  0
             String requestParameterName = requestParameter.parameterName();
 131  0
             if (StringUtils.isBlank(requestParameterName)) {
 132  0
                 requestParameterName = fieldToPopulate;
 133  
             }
 134  
 
 135  0
             if (!parameters.containsKey(requestParameterName)) {
 136  0
                 continue;
 137  
             }
 138  
 
 139  0
             String fieldValue = parameters.get(requestParameterName);
 140  0
             if (StringUtils.isNotBlank(fieldValue)) {
 141  0
                 viewRequestParameters.put(requestParameterName, fieldValue);
 142  0
                 ObjectPropertyUtils.setPropertyValue(view, fieldToPopulate, fieldValue);
 143  
 
 144  
                 // remove any conditional configuration so value is not
 145  
                 // overridden later during the apply model phase
 146  0
                 if (view.getPropertyExpressions().containsKey(fieldToPopulate)) {
 147  0
                     view.getPropertyExpressions().remove(fieldToPopulate);
 148  
                 }
 149  
 
 150  0
                 if (viewPropertyReplacers.containsKey(fieldToPopulate)) {
 151  0
                     Set<PropertyReplacer> propertyReplacers = viewPropertyReplacers.get(fieldToPopulate);
 152  0
                     for (PropertyReplacer replacer : propertyReplacers) {
 153  0
                         view.getPropertyReplacers().remove(replacer);
 154  
                     }
 155  
                 }
 156  
             }
 157  0
         }
 158  
 
 159  0
         view.setViewRequestParameters(viewRequestParameters);
 160  0
     }
 161  
 
 162  
     /**
 163  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performInitialization(org.kuali.rice.krad.uif.view.View,
 164  
      *      java.lang.Object)
 165  
      */
 166  
     @Override
 167  
     public void performInitialization(View view, Object model) {
 168  0
         view.assignComponentIds(view);
 169  0
         performComponentInitialization(view, model, view);
 170  0
     }
 171  
 
 172  
     /**
 173  
      * Performs the complete component lifecycle on the component passed in, in this order:
 174  
      * performComponentInitialization, performComponentApplyModel, and performComponentFinalize.
 175  
      *
 176  
      * @see {@link org.kuali.rice.krad.uif.service.ViewHelperService#performComponentLifecycle(
 177  
      * org.kuali.rice.krad.uif.view.View, java.lang.Object, org.kuali.rice.krad.uif.component.Component, java.lang.String)
 178  
      * @see {@link #performComponentInitialization(org.kuali.rice.krad.uif.view.View, Object,
 179  
      *      org.kuali.rice.krad.uif.component.Component)}
 180  
      * @see {@link #performComponentApplyModel(View, Component, Object)}
 181  
      * @see {@link #performComponentFinalize(View, Component, Object, Component, Map)}
 182  
      */
 183  
     public void performComponentLifecycle(View view, Object model, Component component, String origId) {
 184  0
         Component origComponent = view.getViewIndex().getComponentById(origId);
 185  
 
 186  0
         Component parent = (Component) origComponent.getContext().get(UifConstants.ContextVariableNames.PARENT);
 187  0
         component.pushAllToContext(origComponent.getContext());
 188  
 
 189  
         // adjust IDs for suffixes that might have been added by a parent component during the full view lifecycle
 190  0
         String suffix = StringUtils.replaceOnce(origComponent.getId(), origComponent.getFactoryId(), "");
 191  
         // remove attribute suffix since that gets added in lifecycle
 192  0
         if (suffix.endsWith(UifConstants.IdSuffixes.ATTRIBUTE)) {
 193  0
             suffix = StringUtils.removeEnd(suffix, UifConstants.IdSuffixes.ATTRIBUTE);
 194  
         }
 195  0
         ComponentUtils.updateIdsWithSuffix(component, suffix);
 196  
 
 197  
         // binding path should stay the same
 198  0
         if (component instanceof DataBinding) {
 199  0
             ((DataBinding) component).setBindingInfo(((DataBinding) origComponent).getBindingInfo());
 200  0
             ((DataBinding) component).getBindingInfo().setBindingPath(
 201  
                     ((DataBinding) origComponent).getBindingInfo().getBindingPath());
 202  
         }
 203  
 
 204  
         // copy properties that are set by parent components in the full view lifecycle
 205  0
         if (component instanceof Field) {
 206  0
             ((Field) component).setLabelFieldRendered(((Field) origComponent).isLabelFieldRendered());
 207  0
         } else if (component instanceof CollectionGroup) {
 208  0
             ((CollectionGroup) component).setSubCollectionSuffix(
 209  
                     ((CollectionGroup) origComponent).getSubCollectionSuffix());
 210  
         }
 211  
 
 212  
         // reset data if needed
 213  0
         if (component.isResetDataOnRefresh()) {
 214  
             // TODO: this should handle groups as well, going through nested data fields
 215  0
             if (component instanceof DataField) {
 216  
                 // TODO: should check default value
 217  
 
 218  
                 // clear value
 219  0
                 ObjectPropertyUtils.initializeProperty(model,
 220  
                         ((DataField) component).getBindingInfo().getBindingPath());
 221  
             }
 222  
         }
 223  
 
 224  0
         performComponentInitialization(view, model, component);
 225  0
         performComponentApplyModel(view, component, model);
 226  
 
 227  
         // TODO: need to handle updating client state for component refresh
 228  0
         Map<String, Object> clientState = new HashMap<String, Object>();
 229  0
         performComponentFinalize(view, component, model, parent, clientState);
 230  
 
 231  
         // make sure binding and label settings stay the same as initial
 232  
 //        if (component instanceof Group || component instanceof FieldGroup) {
 233  
 //            List<Component> nestedComponents = ComponentUtils.getAllNestedComponents(component);
 234  
 //            for (Component nestedComponent : nestedComponents) {
 235  
 //                Component origNestedComponent = view.getViewIndex().getComponentById(nestedComponent.getId());
 236  
 //                if (origNestedComponent != null) {
 237  
 //                    if (component instanceof DataBinding) {
 238  
 //                        ((DataBinding) nestedComponent).setBindingInfo(
 239  
 //                                ((DataBinding) origNestedComponent).getBindingInfo());
 240  
 //                        ((DataBinding) nestedComponent).getBindingInfo().setBindingPath(
 241  
 //                                ((DataBinding) origNestedComponent).getBindingInfo().getBindingPath());
 242  
 //                    }
 243  
 //
 244  
 //                    if (component instanceof Field) {
 245  
 //                        ((Field) nestedComponent).setLabelFieldRendered(
 246  
 //                                ((Field) origNestedComponent).isLabelFieldRendered());
 247  
 //                    }
 248  
 //                }
 249  
 //            }
 250  
 //        }
 251  
 
 252  
         // get client state for component and build update script for on load
 253  0
         String clientStateScript = buildClientSideStateScript(view, clientState, true);
 254  0
         String onLoadScript = component.getOnLoadScript();
 255  0
         if (StringUtils.isNotBlank(onLoadScript)) {
 256  0
             clientStateScript = onLoadScript + clientStateScript;
 257  
         }
 258  0
         component.setOnLoadScript(clientStateScript);
 259  
 
 260  
         // update index for component
 261  0
         view.getViewIndex().indexComponent(component);
 262  0
     }
 263  
 
 264  
     /**
 265  
      * Performs initialization of a component by these steps:
 266  
      *
 267  
      * <ul>
 268  
      * <li>If component id not set, assigns to next available int for view</li>
 269  
      * <li>For <code>InputField</code> instances, set defaults from the data
 270  
      * dictionary.</li>
 271  
      * <li>Invoke the initialize method on the component. Here the component can
 272  
      * setup defaults and do other initialization that is specific to that
 273  
      * component.</li>
 274  
      * <li>Invoke any configured <code>ComponentModifier</code> instances for
 275  
      * the component.</li>
 276  
      * <li>Call the component to get the List of components that are nested
 277  
      * within and recursively call this method to initialize those components.</li>
 278  
      * <li>Call custom initialize hook for service overrides</li>
 279  
      * </ul>
 280  
      *
 281  
      * <p>
 282  
      * Note the order various initialize points are called, this can sometimes
 283  
      * be an important factor to consider when initializing a component
 284  
      * </p>
 285  
      *
 286  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performComponentInitialization(org.kuali.rice.krad.uif.view.View,
 287  
      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
 288  
      */
 289  
     public void performComponentInitialization(View view, Object model, Component component) {
 290  0
         if (component == null) {
 291  0
             return;
 292  
         }
 293  
 
 294  0
         if (StringUtils.isBlank(component.getId()) || StringUtils.isBlank(component.getFactoryId())) {
 295  0
             throw new RiceRuntimeException("Ids are not set, this should happen unless a component is misconfigured");
 296  
         }
 297  
 
 298  
         // TODO: duplicate ID check
 299  
 
 300  0
         LOG.debug("Initializing component: " + component.getId() + " with type: " + component.getClass());
 301  
 
 302  
         // invoke component to initialize itself after properties have been set
 303  0
         component.performInitialization(view, model);
 304  
 
 305  
         // for attribute fields, set defaults from dictionary entry
 306  0
         if (component instanceof DataField) {
 307  0
             initializeDataFieldFromDataDictionary(view, (DataField) component);
 308  
         }
 309  
 
 310  
         // add initial state to the view index for component refreshes
 311  0
         view.getViewIndex().addInitialComponentState(component);
 312  
 
 313  0
         if (component instanceof Container) {
 314  0
             LayoutManager layoutManager = ((Container) component).getLayoutManager();
 315  
 
 316  
             // invoke hook point for adding components through code
 317  0
             addCustomContainerComponents(view, model, (Container) component);
 318  
 
 319  
             // process any remote fields holder that might be in the containers items
 320  0
             processAnyRemoteFieldsHolder(view, model, (Container) component);
 321  
         }
 322  
 
 323  
         // for collection groups set defaults from dictionary entry
 324  0
         if (component instanceof CollectionGroup) {
 325  
             // TODO: initialize from dictionary
 326  
         }
 327  
 
 328  
         // invoke component modifiers setup to run in the initialize phase
 329  0
         runComponentModifiers(view, component, null, UifConstants.ViewPhases.INITIALIZE);
 330  
 
 331  
         // initialize nested components
 332  0
         for (Component nestedComponent : component.getComponentsForLifecycle()) {
 333  0
             performComponentInitialization(view, model, nestedComponent);
 334  
         }
 335  
 
 336  
         // initialize nested components in property replacements
 337  0
         for (PropertyReplacer replacer : component.getPropertyReplacers()) {
 338  0
             for (Component replacerComponent : replacer.getNestedComponents()) {
 339  0
                 performComponentInitialization(view, model, replacerComponent);
 340  
             }
 341  
         }
 342  
 
 343  
         // invoke initialize service hook
 344  0
         performCustomInitialization(view, component);
 345  0
     }
 346  
 
 347  
     /**
 348  
      * Iterates through the containers configured items checking for <code>RemotableFieldsHolder</code>, if found
 349  
      * the holder is invoked to retrieved the remotable fields and translate to attribute fields. The translated list
 350  
      * is then inserted into the container item list at the position of the holder
 351  
      *
 352  
      * @param view - view instance containing the container
 353  
      * @param model - object instance containing the view data
 354  
      * @param container - container instance to check for any remotable fields holder
 355  
      */
 356  
     protected void processAnyRemoteFieldsHolder(View view, Object model, Container container) {
 357  0
         List<Component> processedItems = new ArrayList<Component>();
 358  
 
 359  
         // check for holders and invoke to retrieve the remotable fields and translate
 360  
         // translated fields are placed into the container item list at the position of the holder
 361  0
         for (Component item : container.getItems()) {
 362  0
             if (item instanceof RemoteFieldsHolder) {
 363  0
                 List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(view,
 364  
                         model, container);
 365  0
                 processedItems.addAll(translatedFields);
 366  0
             } else {
 367  0
                 processedItems.add(item);
 368  
             }
 369  
         }
 370  
 
 371  
         // updated container items
 372  0
         container.setItems(processedItems);
 373  0
     }
 374  
 
 375  
     /**
 376  
      * Sets properties of the <code>InputField</code> (if blank) to the
 377  
      * corresponding attribute entry in the data dictionary
 378  
      *
 379  
      * @param view - view instance containing the field
 380  
      * @param field - data field instance to initialize
 381  
      */
 382  
     protected void initializeDataFieldFromDataDictionary(View view, DataField field) {
 383  0
         AttributeDefinition attributeDefinition = null;
 384  
 
 385  0
         String dictionaryAttributeName = field.getDictionaryAttributeName();
 386  0
         String dictionaryObjectEntry = field.getDictionaryObjectEntry();
 387  
 
 388  
         // if entry given but not attribute name, use field name as attribute
 389  
         // name
 390  0
         if (StringUtils.isNotBlank(dictionaryObjectEntry) && StringUtils.isBlank(dictionaryAttributeName)) {
 391  0
             dictionaryAttributeName = field.getPropertyName();
 392  
         }
 393  
 
 394  
         // if dictionary entry and attribute set, attempt to find definition
 395  0
         if (StringUtils.isNotBlank(dictionaryAttributeName) && StringUtils.isNotBlank(dictionaryObjectEntry)) {
 396  0
             attributeDefinition = getDataDictionaryService().getAttributeDefinition(dictionaryObjectEntry,
 397  
                     dictionaryAttributeName);
 398  
         }
 399  
 
 400  
         // if definition not found, recurse through path
 401  0
         if (attributeDefinition == null) {
 402  0
             String propertyPath = field.getBindingInfo().getBindingPath();
 403  0
             if (StringUtils.isNotBlank(field.getBindingInfo().getCollectionPath())) {
 404  0
                 propertyPath = field.getBindingInfo().getCollectionPath();
 405  0
                 if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
 406  0
                     propertyPath += "." + field.getBindingInfo().getBindByNamePrefix();
 407  
                 }
 408  0
                 propertyPath += "." + field.getBindingInfo().getBindingName();
 409  
             }
 410  
 
 411  0
             attributeDefinition = findNestedDictionaryAttribute(view, field, null, propertyPath);
 412  
         }
 413  
 
 414  
         // if a definition was found, initialize field from definition
 415  0
         if (attributeDefinition != null) {
 416  0
             field.copyFromAttributeDefinition(view, attributeDefinition);
 417  
         }
 418  
 
 419  
         // if control still null, assign default
 420  0
         if (field instanceof InputField) {
 421  0
             InputField inputField = (InputField) field;
 422  0
             if (inputField.getControl() == null) {
 423  0
                 Control control = ComponentFactory.getTextControl();
 424  0
                 control.setId(view.getNextId());
 425  0
                 control.setFactoryId(control.getId());
 426  
 
 427  0
                 inputField.setControl(control);
 428  
             }
 429  
         }
 430  0
     }
 431  
 
 432  
     /**
 433  
      * Recursively drills down the property path (if nested) to find an
 434  
      * AttributeDefinition, the first attribute definition found will be
 435  
      * returned
 436  
      *
 437  
      * <p>
 438  
      * e.g. suppose parentPath is 'document' and propertyPath is
 439  
      * 'account.subAccount.name', first the property type for document will be
 440  
      * retrieved using the view metadata and used as the dictionary entry, with
 441  
      * the propertyPath as the dictionary attribute, if an attribute definition
 442  
      * exists it will be returned. Else, the first part of the property path is
 443  
      * added to the parent, making the parentPath 'document.account' and the
 444  
      * propertyPath 'subAccount.name', the method is then called again to
 445  
      * perform the process with those parameters. The recursion continues until
 446  
      * an attribute field is found, or the propertyPath is no longer nested
 447  
      * </p>
 448  
      *
 449  
      * @param view - view instance containing the field
 450  
      * @param field - field we are attempting to find a supporting attribute
 451  
      * definition for
 452  
      * @param parentPath - parent path to use for getting the dictionary entry
 453  
      * @param propertyPath - path of the property relative to the parent, to use as
 454  
      * dictionary attribute and to drill down on
 455  
      * @return AttributeDefinition if found, or Null
 456  
      */
 457  
     protected AttributeDefinition findNestedDictionaryAttribute(View view, DataField field, String parentPath,
 458  
             String propertyPath) {
 459  0
         AttributeDefinition attributeDefinition = null;
 460  
 
 461  
         // attempt to find definition for parent and property
 462  0
         String dictionaryAttributeName = propertyPath;
 463  0
         String dictionaryObjectEntry = null;
 464  
 
 465  0
         if (field.getBindingInfo().isBindToMap()) {
 466  0
             parentPath = "";
 467  0
             if (!field.getBindingInfo().isBindToForm() && StringUtils.isNotBlank(
 468  
                     field.getBindingInfo().getBindingObjectPath())) {
 469  0
                 parentPath = field.getBindingInfo().getBindingObjectPath();
 470  
             }
 471  0
             if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
 472  0
                 if (StringUtils.isNotBlank(parentPath)) {
 473  0
                     parentPath += "." + field.getBindingInfo().getBindByNamePrefix();
 474  
                 } else {
 475  0
                     parentPath = field.getBindingInfo().getBindByNamePrefix();
 476  
                 }
 477  
             }
 478  
 
 479  0
             dictionaryAttributeName = field.getBindingInfo().getBindingName();
 480  
         }
 481  
 
 482  0
         if (StringUtils.isNotBlank(parentPath)) {
 483  0
             Class<?> dictionaryModelClass = ViewModelUtils.getPropertyTypeByClassAndView(view, parentPath);
 484  0
             if (dictionaryModelClass != null) {
 485  0
                 dictionaryObjectEntry = dictionaryModelClass.getName();
 486  
 
 487  0
                 attributeDefinition = getDataDictionaryService().getAttributeDefinition(dictionaryObjectEntry,
 488  
                         dictionaryAttributeName);
 489  
             }
 490  
         }
 491  
 
 492  
         // if definition not found and property is still nested, recurse down
 493  
         // one level
 494  0
         if ((attributeDefinition == null) && StringUtils.contains(propertyPath, ".")) {
 495  0
             String nextParentPath = StringUtils.substringBefore(propertyPath, ".");
 496  0
             if (StringUtils.isNotBlank(parentPath)) {
 497  0
                 nextParentPath = parentPath + "." + nextParentPath;
 498  
             }
 499  0
             String nextPropertyPath = StringUtils.substringAfter(propertyPath, ".");
 500  
 
 501  0
             return findNestedDictionaryAttribute(view, field, nextParentPath, nextPropertyPath);
 502  
         }
 503  
 
 504  
         // if a definition was found, update the fields dictionary properties
 505  0
         if (attributeDefinition != null) {
 506  0
             field.setDictionaryAttributeName(dictionaryAttributeName);
 507  0
             field.setDictionaryObjectEntry(dictionaryObjectEntry);
 508  
         }
 509  
 
 510  0
         return attributeDefinition;
 511  
     }
 512  
 
 513  
     /**
 514  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performApplyModel(org.kuali.rice.krad.uif.view.View,
 515  
      *      java.lang.Object)
 516  
      */
 517  
     @Override
 518  
     public void performApplyModel(View view, Object model) {
 519  
         // get action flag and edit modes from authorizer/presentation
 520  
         // controller
 521  0
         invokeAuthorizerPresentationController(view, (UifFormBase) model);
 522  
 
 523  
         // set view context for conditional expressions
 524  0
         setViewContext(view, model);
 525  
 
 526  0
         performComponentApplyModel(view, view, model);
 527  0
     }
 528  
 
 529  
     /**
 530  
      * Invokes the configured <code>PresentationController</code> and
 531  
      * </code>Authorizer</code> for the view to get the exported action flags
 532  
      * and edit modes that can be used in conditional logic
 533  
      *
 534  
      * @param view - view instance that is being built and
 535  
      * presentation/authorizer pulled for
 536  
      * @param model - Object that contains the model data
 537  
      */
 538  
     protected void invokeAuthorizerPresentationController(View view, UifFormBase model) {
 539  0
         PresentationController presentationController = ObjectUtils.newInstance(view.getPresentationControllerClass());
 540  0
         Authorizer authorizer = ObjectUtils.newInstance(view.getAuthorizerClass());
 541  
 
 542  0
         Person user = GlobalVariables.getUserSession().getPerson();
 543  
 
 544  0
         Set<String> actionFlags = presentationController.getActionFlags(model);
 545  0
         actionFlags = authorizer.getActionFlags(model, user, actionFlags);
 546  
 
 547  0
         view.setActionFlags(new BooleanMap(actionFlags));
 548  
 
 549  0
         Set<String> editModes = presentationController.getEditModes(model);
 550  0
         editModes = authorizer.getEditModes(model, user, editModes);
 551  
 
 552  0
         view.setEditModes(new BooleanMap(editModes));
 553  0
     }
 554  
 
 555  
     /**
 556  
      * Sets up the view context which will be available to other components
 557  
      * through their context for conditional logic evaluation
 558  
      *
 559  
      * @param view - view instance to set context for
 560  
      * @param model - object containing the view data
 561  
      */
 562  
     protected void setViewContext(View view, Object model) {
 563  0
         view.pushAllToContext(getPreModelContext(view));
 564  
 
 565  
         // evaluate view expressions for further context
 566  0
         for (Entry<String, String> variableExpression : view.getExpressionVariables().entrySet()) {
 567  0
             String variableName = variableExpression.getKey();
 568  0
             Object value = getExpressionEvaluatorService().evaluateExpression(model, view.getContext(),
 569  
                     variableExpression.getValue());
 570  0
             view.pushObjectToContext(variableName, value);
 571  0
         }
 572  0
     }
 573  
 
 574  
     /**
 575  
      * Returns the general context that is available before the apply model
 576  
      * phase (during the initialize phase)
 577  
      *
 578  
      * @param view - view instance for context
 579  
      * @return Map<String, Object> context map
 580  
      */
 581  
     protected Map<String, Object> getPreModelContext(View view) {
 582  0
         Map<String, Object> context = new HashMap<String, Object>();
 583  
 
 584  0
         context.put(UifConstants.ContextVariableNames.VIEW, view);
 585  0
         context.put(UifConstants.ContextVariableNames.VIEW_HELPER, this);
 586  
 
 587  0
         Map<String, String> properties = KRADServiceLocator.getKualiConfigurationService().getAllProperties();
 588  0
         context.put(UifConstants.ContextVariableNames.CONFIG_PROPERTIES, properties);
 589  0
         context.put(UifConstants.ContextVariableNames.CONSTANTS, KRADConstants.class);
 590  
 
 591  0
         return context;
 592  
     }
 593  
 
 594  
     /**
 595  
      * Applies the model data to a component of the View instance
 596  
      *
 597  
      * <p>
 598  
      * The component is invoked to to apply the model data. Here the component
 599  
      * can generate any additional fields needed or alter the configured fields.
 600  
      * After the component is invoked a hook for custom helper service
 601  
      * processing is invoked. Finally the method is recursively called for all
 602  
      * the component children
 603  
      * </p>
 604  
      *
 605  
      * @param view - view instance the component belongs to
 606  
      * @param component - the component instance the model should be applied to
 607  
      * @param model - top level object containing the data
 608  
      */
 609  
     protected void performComponentApplyModel(View view, Component component, Object model) {
 610  0
         if (component == null) {
 611  0
             return;
 612  
         }
 613  
 
 614  
         // evaluate expressions on properties
 615  0
         component.pushAllToContext(getCommonContext(view, component));
 616  0
         ExpressionUtils.adjustPropertyExpressions(view, component);
 617  0
         getExpressionEvaluatorService().evaluateObjectExpressions(component, model, component.getContext());
 618  
 
 619  0
         if (component instanceof Container) {
 620  0
             LayoutManager layoutManager = ((Container) component).getLayoutManager();
 621  
 
 622  0
             if (layoutManager != null) {
 623  0
                 layoutManager.getContext().putAll(getCommonContext(view, component));
 624  0
                 layoutManager.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, component);
 625  0
                 layoutManager.pushObjectToContext(UifConstants.ContextVariableNames.MANAGER, layoutManager);
 626  
 
 627  0
                 ExpressionUtils.adjustPropertyExpressions(view, layoutManager);
 628  0
                 getExpressionEvaluatorService().evaluateObjectExpressions(layoutManager, model,
 629  
                         layoutManager.getContext());
 630  
             }
 631  
         }
 632  
 
 633  0
         if (component instanceof DataBinding) {
 634  0
             BindingInfo bindingInfo = ((DataBinding) component).getBindingInfo();
 635  0
             ExpressionUtils.adjustPropertyExpressions(view, bindingInfo);
 636  0
             getExpressionEvaluatorService().evaluateObjectExpressions(bindingInfo, model, component.getContext());
 637  
         }
 638  
 
 639  
         // sync the component with previous client side state
 640  0
         syncClientSideStateForComponent(component, ((UifFormBase) model).getClientStateForSyncing());
 641  
 
 642  
         // invoke component to perform its conditional logic
 643  0
         Component parent = (Component) component.getContext().get(UifConstants.ContextVariableNames.PARENT);
 644  0
         component.performApplyModel(view, model, parent);
 645  
 
 646  
         // invoke service override hook
 647  0
         performCustomApplyModel(view, component, model);
 648  
 
 649  
         // invoke component modifiers configured to run in the apply model phase
 650  0
         runComponentModifiers(view, component, model, UifConstants.ViewPhases.APPLY_MODEL);
 651  
 
 652  
         // get children and recursively perform conditional logic
 653  0
         for (Component nestedComponent : component.getComponentsForLifecycle()) {
 654  0
             if (nestedComponent != null) {
 655  0
                 nestedComponent.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, component);
 656  
             }
 657  
 
 658  0
             performComponentApplyModel(view, nestedComponent, model);
 659  
         }
 660  0
     }
 661  
 
 662  
     /**
 663  
      * Runs any configured <code>ComponentModifiers</code> for the given
 664  
      * component that match the given run phase and who run condition evaluation
 665  
      * succeeds
 666  
      *
 667  
      * <p>
 668  
      * If called during the initialize phase, the performInitialization method will be invoked on
 669  
      * the <code>ComponentModifier</code> before running
 670  
      * </p>
 671  
      *
 672  
      * @param view - view instance for context
 673  
      * @param component - component instance whose modifiers should be run
 674  
      * @param model - model object for context
 675  
      * @param runPhase - current phase to match on
 676  
      */
 677  
     protected void runComponentModifiers(View view, Component component, Object model, String runPhase) {
 678  0
         for (ComponentModifier modifier : component.getComponentModifiers()) {
 679  
             // if run phase is initialize, invoke initialize method on modifier first
 680  0
             if (StringUtils.equals(runPhase, UifConstants.ViewPhases.INITIALIZE)) {
 681  0
                 modifier.performInitialization(view, model, component);
 682  
             }
 683  
 
 684  
             // check run phase matches
 685  0
             if (StringUtils.equals(modifier.getRunPhase(), runPhase)) {
 686  
                 // check condition (if set) evaluates to true
 687  0
                 boolean runModifier = true;
 688  0
                 if (StringUtils.isNotBlank(modifier.getRunCondition())) {
 689  0
                     Map<String, Object> context = new HashMap<String, Object>();
 690  0
                     context.put(UifConstants.ContextVariableNames.COMPONENT, component);
 691  0
                     context.put(UifConstants.ContextVariableNames.VIEW, view);
 692  
 
 693  0
                     String conditionEvaluation = getExpressionEvaluatorService().evaluateExpressionTemplate(model,
 694  
                             context, modifier.getRunCondition());
 695  0
                     runModifier = Boolean.parseBoolean(conditionEvaluation);
 696  
                 }
 697  
 
 698  0
                 if (runModifier) {
 699  0
                     modifier.performModification(view, model, component);
 700  
                 }
 701  0
             }
 702  
         }
 703  0
     }
 704  
 
 705  
     /**
 706  
      * Gets global objects for the context map and pushes them to the context
 707  
      * for the component
 708  
      *
 709  
      * @param view - view instance for component
 710  
      * @param component - component instance to push context to
 711  
      */
 712  
     protected Map<String, Object> getCommonContext(View view, Component component) {
 713  0
         Map<String, Object> context = new HashMap<String, Object>();
 714  
 
 715  0
         context.putAll(view.getContext());
 716  0
         context.put(UifConstants.ContextVariableNames.COMPONENT, component);
 717  
 
 718  0
         return context;
 719  
     }
 720  
 
 721  
     /**
 722  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#performFinalize(org.kuali.rice.krad.uif.view.View,
 723  
      *      java.lang.Object)
 724  
      */
 725  
     @Override
 726  
     public void performFinalize(View view, Object model) {
 727  0
         Map<String, Object> clientState = new HashMap<String, Object>();
 728  0
         performComponentFinalize(view, view, model, null, clientState);
 729  
 
 730  0
         String clientStateScript = buildClientSideStateScript(view, clientState, false);
 731  0
         String viewPreLoadScript = view.getPreLoadScript();
 732  0
         if (StringUtils.isNotBlank(viewPreLoadScript)) {
 733  0
             clientStateScript = viewPreLoadScript + clientStateScript;
 734  
         }
 735  0
         view.setPreLoadScript(clientStateScript);
 736  
 
 737  
         // set apply default indicator to false (since they would have been applied during the component finalize)
 738  0
         ((ViewModel) model).setDefaultsApplied(true);
 739  0
     }
 740  
 
 741  
     /**
 742  
      * Builds script that will initialize configuration parameters and component state on the client
 743  
      *
 744  
      * <p>
 745  
      * Here client side state is initialized along with configuration variables that need exposed to script
 746  
      * </p>
 747  
      *
 748  
      * @param view - view instance that is being built
 749  
      * @param clientSideState - map of key/value pairs that should be exposed as client side state
 750  
      * @param updateOnly - boolean that indicates whether we are just updating a component (true), or the full view
 751  
      */
 752  
     protected String buildClientSideStateScript(View view, Map<String, Object> clientSideState, boolean updateOnly) {
 753  
         // merge any additional client side state added to the view during processing
 754  
         // state from view will override in all cases except when both values are maps, in which the maps
 755  
         // be combined for the new value
 756  0
         for (Entry<String, Object> additionalState : view.getClientSideState().entrySet()) {
 757  0
             if (!clientSideState.containsKey(additionalState.getKey())) {
 758  0
                 clientSideState.put(additionalState.getKey(), additionalState.getValue());
 759  
             } else {
 760  0
                 Object state = clientSideState.get(additionalState.getKey());
 761  0
                 Object mergeState = additionalState.getValue();
 762  0
                 if ((state instanceof Map) && (mergeState instanceof Map)) {
 763  0
                     ((Map) state).putAll((Map) mergeState);
 764  
                 } else {
 765  0
                     clientSideState.put(additionalState.getKey(), additionalState.getValue());
 766  
                 }
 767  0
             }
 768  
         }
 769  
 
 770  
         // script for initializing client side state on load
 771  0
         String clientStateScript = "";
 772  0
         if (!clientSideState.isEmpty()) {
 773  0
             if (updateOnly) {
 774  0
                 clientStateScript = "updateViewState({";
 775  
             } else {
 776  0
                 clientStateScript = "initializeViewState({";
 777  
             }
 778  
 
 779  0
             for (Entry<String, Object> stateEntry : clientSideState.entrySet()) {
 780  0
                 clientStateScript += "'" + stateEntry.getKey() + "':";
 781  0
                 clientStateScript += ScriptUtils.translateValue(stateEntry.getValue());
 782  0
                 clientStateScript += ",";
 783  
             }
 784  0
             clientStateScript = StringUtils.removeEnd(clientStateScript, ",");
 785  0
             clientStateScript += "});";
 786  
         }
 787  
 
 788  
         // add necessary configuration parameters
 789  0
         if (!updateOnly) {
 790  0
             String kradImageLocation = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
 791  
                     "krad.externalizable.images.url");
 792  0
             clientStateScript += "setConfigParam('"
 793  
                     + UifConstants.ClientSideVariables.KRAD_IMAGE_LOCATION
 794  
                     + "','"
 795  
                     + kradImageLocation
 796  
                     + "');";
 797  
         }
 798  
 
 799  0
         return clientStateScript;
 800  
     }
 801  
 
 802  
     /**
 803  
      * Update state of the given component and does final preparation for
 804  
      * rendering
 805  
      *
 806  
      * @param view - view instance the component belongs to
 807  
      * @param component - the component instance that should be updated
 808  
      * @param model - top level object containing the data
 809  
      * @param parent - Parent component for the component being finalized
 810  
      * @param clientSideState - map to add client state to
 811  
      */
 812  
     protected void performComponentFinalize(View view, Component component, Object model, Component parent,
 813  
             Map<String, Object> clientSideState) {
 814  0
         if (component == null) {
 815  0
             return;
 816  
         }
 817  
 
 818  
         // implement readonly request overrides
 819  0
         ViewModel viewModel = (ViewModel) model;
 820  0
         if ((component instanceof DataBinding) && view.isSupportsReadOnlyFieldsOverride() && !viewModel
 821  
                 .getReadOnlyFieldsList().isEmpty()) {
 822  0
             String propertyName = ((DataBinding) component).getPropertyName();
 823  0
             if (viewModel.getReadOnlyFieldsList().contains(propertyName)) {
 824  0
                 component.setReadOnly(true);
 825  
             }
 826  
         }
 827  
 
 828  
         // invoke configured method finalizers
 829  0
         invokeMethodFinalizer(view, component, model);
 830  
 
 831  
         // invoke component to update its state
 832  0
         component.performFinalize(view, model, parent);
 833  
 
 834  
         // add client side state for annotated component properties
 835  0
         addClientSideStateForComponent(component, clientSideState);
 836  
 
 837  
         // invoke service override hook
 838  0
         performCustomFinalize(view, component, model, parent);
 839  
 
 840  
         // invoke component modifiers setup to run in the finalize phase
 841  0
         runComponentModifiers(view, component, model, UifConstants.ViewPhases.FINALIZE);
 842  
 
 843  
         // apply default value if needed
 844  0
         if ((component instanceof DataField) && !((ViewModel) model).isDefaultsApplied()) {
 845  0
             populateDefaultValueForField(view, model, (DataField) component,
 846  
                     ((DataField) component).getBindingInfo().getBindingPath());
 847  
         }
 848  
 
 849  
         // get components children and recursively update state
 850  0
         for (Component nestedComponent : component.getComponentsForLifecycle()) {
 851  0
             performComponentFinalize(view, nestedComponent, model, component, clientSideState);
 852  
         }
 853  0
     }
 854  
 
 855  
     /**
 856  
      * Reflects the class for the given component to find any fields that are annotated with
 857  
      * <code>ClientSideState</code> and adds the corresponding property name/value pair to the client side state
 858  
      * map
 859  
      *
 860  
      * <p>
 861  
      * Note if the component is the <code>View</code, state is added directly to the client side state map, while
 862  
      * for other components a nested Map is created to hold the state, which is then placed into the client side
 863  
      * state map with the component id as the key
 864  
      * </p>
 865  
      *
 866  
      * @param component - component instance to get client state for
 867  
      * @param clientSideState - map to add client side variable name/values to
 868  
      */
 869  
     protected void addClientSideStateForComponent(Component component, Map<String, Object> clientSideState) {
 870  0
         Map<String, Annotation> annotatedFields = CloneUtils.getFieldsWithAnnotation(component.getClass(),
 871  
                 ClientSideState.class);
 872  
 
 873  0
         if (!annotatedFields.isEmpty()) {
 874  0
             Map<String, Object> componentClientState = null;
 875  0
             if (component instanceof View) {
 876  0
                 componentClientState = clientSideState;
 877  
             } else {
 878  0
                 if (clientSideState.containsKey(component.getId())) {
 879  0
                     componentClientState = (Map<String, Object>) clientSideState.get(component.getId());
 880  
                 } else {
 881  0
                     componentClientState = new HashMap<String, Object>();
 882  0
                     clientSideState.put(component.getId(), componentClientState);
 883  
                 }
 884  
             }
 885  
 
 886  0
             for (Entry<String, Annotation> annotatedField : annotatedFields.entrySet()) {
 887  0
                 ClientSideState clientSideStateAnnot = (ClientSideState) annotatedField.getValue();
 888  
 
 889  0
                 String variableName = clientSideStateAnnot.variableName();
 890  0
                 if (StringUtils.isBlank(variableName)) {
 891  0
                     variableName = annotatedField.getKey();
 892  
                 }
 893  
 
 894  0
                 Object value = ObjectPropertyUtils.getPropertyValue(component, annotatedField.getKey());
 895  0
                 componentClientState.put(variableName, value);
 896  0
             }
 897  
         }
 898  0
     }
 899  
 
 900  
     /**
 901  
      * Updates the properties of the given component instance with the value found from the corresponding map of
 902  
      * client state (if found)
 903  
      *
 904  
      * @param component - component instance to update
 905  
      * @param clientSideState - map of state to sync with
 906  
      */
 907  
     protected void syncClientSideStateForComponent(Component component, Map<String, Object> clientSideState) {
 908  
         // find the map of state that was sent for component (if any)
 909  0
         Map<String, Object> componentState = null;
 910  0
         if (component instanceof View) {
 911  0
             componentState = clientSideState;
 912  
         } else {
 913  0
             if (clientSideState.containsKey(component.getId())) {
 914  0
                 componentState = (Map<String, Object>) clientSideState.get(component.getId());
 915  
             }
 916  
         }
 917  
 
 918  
         // if state was sent, match with fields on the component that are annotated to have client state
 919  0
         if ((componentState != null) && (!componentState.isEmpty())) {
 920  0
             Map<String, Annotation> annotatedFields = CloneUtils.getFieldsWithAnnotation(component.getClass(),
 921  
                     ClientSideState.class);
 922  
 
 923  0
             for (Entry<String, Annotation> annotatedField : annotatedFields.entrySet()) {
 924  0
                 ClientSideState clientSideStateAnnot = (ClientSideState) annotatedField.getValue();
 925  
 
 926  0
                 String variableName = clientSideStateAnnot.variableName();
 927  0
                 if (StringUtils.isBlank(variableName)) {
 928  0
                     variableName = annotatedField.getKey();
 929  
                 }
 930  
 
 931  0
                 if (componentState.containsKey(variableName)) {
 932  0
                     Object value = componentState.get(variableName);
 933  0
                     ObjectPropertyUtils.setPropertyValue(component, annotatedField.getKey(), value);
 934  
                 }
 935  0
             }
 936  
         }
 937  0
     }
 938  
 
 939  
     /**
 940  
      * Invokes the finalize method for the component (if configured) and sets
 941  
      * the render output for the component to the returned method string (if
 942  
      * method is not a void type)
 943  
      *
 944  
      * @param view - view instance that contains the component
 945  
      * @param component - component to run finalize method for
 946  
      * @param model - top level object containing the data
 947  
      */
 948  
     protected void invokeMethodFinalizer(View view, Component component, Object model) {
 949  0
         String finalizeMethodToCall = component.getFinalizeMethodToCall();
 950  0
         MethodInvoker finalizeMethodInvoker = component.getFinalizeMethodInvoker();
 951  
 
 952  0
         if (StringUtils.isBlank(finalizeMethodToCall) && (finalizeMethodInvoker == null)) {
 953  0
             return;
 954  
         }
 955  
 
 956  0
         if (finalizeMethodInvoker == null) {
 957  0
             finalizeMethodInvoker = new MethodInvoker();
 958  
         }
 959  
 
 960  
         // if method not set on invoker, use renderingMethodToCall, note staticMethod could be set(don't know since
 961  
         // there is not a getter), if so it will override the target method in prepare
 962  0
         if (StringUtils.isBlank(finalizeMethodInvoker.getTargetMethod())) {
 963  0
             finalizeMethodInvoker.setTargetMethod(finalizeMethodToCall);
 964  
         }
 965  
 
 966  
         // if target class or object not set, use view helper service
 967  0
         if ((finalizeMethodInvoker.getTargetClass() == null) && (finalizeMethodInvoker.getTargetObject() == null)) {
 968  0
             finalizeMethodInvoker.setTargetObject(view.getViewHelperService());
 969  
         }
 970  
 
 971  
         // setup arguments for method
 972  0
         List<Object> additionalArguments = component.getFinalizeMethodAdditionalArguments();
 973  0
         if (additionalArguments == null) {
 974  0
             additionalArguments = new ArrayList<Object>();
 975  
         }
 976  
 
 977  0
         Object[] arguments = new Object[2 + additionalArguments.size()];
 978  0
         arguments[0] = component;
 979  0
         arguments[1] = model;
 980  
 
 981  0
         int argumentIndex = 1;
 982  0
         for (Object argument : additionalArguments) {
 983  0
             argumentIndex++;
 984  0
             arguments[argumentIndex] = argument;
 985  
         }
 986  0
         finalizeMethodInvoker.setArguments(arguments);
 987  
 
 988  
         // invoke method and get render output
 989  
         try {
 990  0
             LOG.debug("Invoking render method: "
 991  
                     + finalizeMethodInvoker.getTargetMethod()
 992  
                     + " for component: "
 993  
                     + component.getId());
 994  0
             finalizeMethodInvoker.prepare();
 995  
 
 996  0
             Class<?> methodReturnType = finalizeMethodInvoker.getPreparedMethod().getReturnType();
 997  0
             if (StringUtils.equals("void", methodReturnType.getName())) {
 998  0
                 finalizeMethodInvoker.invoke();
 999  
             } else {
 1000  0
                 String renderOutput = (String) finalizeMethodInvoker.invoke();
 1001  
 
 1002  0
                 component.setSelfRendered(true);
 1003  0
                 component.setRenderOutput(renderOutput);
 1004  
             }
 1005  0
         } catch (Exception e) {
 1006  0
             LOG.error("Error invoking rendering method for component: " + component.getId(), e);
 1007  0
             throw new RuntimeException("Error invoking rendering method for component: " + component.getId(), e);
 1008  0
         }
 1009  0
     }
 1010  
 
 1011  
     /**
 1012  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#processCollectionAddLine(org.kuali.rice.krad.uif.view.View,
 1013  
      *      java.lang.Object, java.lang.String)
 1014  
      */
 1015  
     @Override
 1016  
     public void processCollectionAddLine(View view, Object model, String collectionPath) {
 1017  
         // get the collection group from the view
 1018  0
         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
 1019  0
         if (collectionGroup == null) {
 1020  0
             logAndThrowRuntime("Unable to get collection group component for path: " + collectionPath);
 1021  
         }
 1022  
 
 1023  
         // get the collection instance for adding the new line
 1024  0
         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
 1025  0
         if (collection == null) {
 1026  0
             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
 1027  
         }
 1028  
 
 1029  
         // now get the new line we need to add
 1030  0
         String addLinePath = collectionGroup.getAddLineBindingInfo().getBindingPath();
 1031  0
         Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLinePath);
 1032  0
         if (addLine == null) {
 1033  0
             logAndThrowRuntime("Add line instance not found for path: " + addLinePath);
 1034  
         }
 1035  
 
 1036  0
         processBeforeAddLine(view, collectionGroup, model, addLine);
 1037  
 
 1038  
         // validate the line to make sure it is ok to add
 1039  0
         boolean isValidLine = performAddLineValidation(view, collectionGroup, model, addLine);
 1040  0
         if (isValidLine) {
 1041  
             // TODO: should check to see if there is an add line method on the
 1042  
             // collection parent and if so call that instead of just adding to
 1043  
             // the collection (so that sequence can be set)
 1044  0
             if (collection instanceof List) {
 1045  0
                 ((List) collection).add(0, addLine);
 1046  
             } else {
 1047  0
                 collection.add(addLine);
 1048  
             }
 1049  
 
 1050  
             // make a new instance for the add line
 1051  0
             collectionGroup.initializeNewCollectionLine(view, model, collectionGroup, true);
 1052  
         }
 1053  
 
 1054  0
         processAfterAddLine(view, collectionGroup, model, addLine);
 1055  0
     }
 1056  
 
 1057  
     /**
 1058  
      * Performs validation on the new collection line before it is added to the
 1059  
      * corresponding collection
 1060  
      *
 1061  
      * @param view - view instance that the action was taken on
 1062  
      * @param collectionGroup - collection group component for the collection
 1063  
      * @param addLine - new line instance to validate
 1064  
      * @param model - object instance that contain's the views data
 1065  
      * @return boolean true if the line is valid and it should be added to the
 1066  
      *         collection, false if it was not valid and should not be added to
 1067  
      *         the collection
 1068  
      */
 1069  
     protected boolean performAddLineValidation(View view, CollectionGroup collectionGroup, Object model,
 1070  
             Object addLine) {
 1071  0
         boolean isValid = true;
 1072  
 
 1073  
         // TODO: this should invoke rules, subclasses like the document view
 1074  
         // should create the document add line event
 1075  
 
 1076  0
         return isValid;
 1077  
     }
 1078  
 
 1079  
     /**
 1080  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#processCollectionDeleteLine(org.kuali.rice.krad.uif.view.View,
 1081  
      *      java.lang.Object, java.lang.String, int)
 1082  
      */
 1083  
     public void processCollectionDeleteLine(View view, Object model, String collectionPath, int lineIndex) {
 1084  
         // get the collection group from the view
 1085  0
         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
 1086  0
         if (collectionGroup == null) {
 1087  0
             logAndThrowRuntime("Unable to get collection group component for path: " + collectionPath);
 1088  
         }
 1089  
 
 1090  
         // get the collection instance for adding the new line
 1091  0
         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
 1092  0
         if (collection == null) {
 1093  0
             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
 1094  
         }
 1095  
 
 1096  
         // TODO: look into other ways of identifying a line so we can deal with
 1097  
         // unordered collections
 1098  0
         if (collection instanceof List) {
 1099  0
             Object deleteLine = ((List<Object>) collection).get(lineIndex);
 1100  
 
 1101  
             // validate the delete action is allowed for this line
 1102  0
             boolean isValid = performDeleteLineValidation(view, collectionGroup, deleteLine);
 1103  0
             if (isValid) {
 1104  0
                 ((List<Object>) collection).remove(lineIndex);
 1105  0
                 processAfterDeleteLine(view, collectionGroup, model, lineIndex);
 1106  
             }
 1107  0
         } else {
 1108  0
             logAndThrowRuntime("Only List collection implementations are supported for the delete by index method");
 1109  
         }
 1110  0
     }
 1111  
 
 1112  
     /**
 1113  
      * Performs validation on the collection line before it is removed from the
 1114  
      * corresponding collection
 1115  
      *
 1116  
      * @param view - view instance that the action was taken on
 1117  
      * @param collectionGroup - collection group component for the collection
 1118  
      * @param deleteLine - line that will be removed
 1119  
      * @return boolean true if the action is allowed and the line should be
 1120  
      *         removed, false if the line should not be removed
 1121  
      */
 1122  
     protected boolean performDeleteLineValidation(View view, CollectionGroup collectionGroup, Object deleteLine) {
 1123  0
         boolean isValid = true;
 1124  
 
 1125  
         // TODO: this should invoke rules, sublclasses like the document view
 1126  
         // should create the document delete line event
 1127  
 
 1128  0
         return isValid;
 1129  
     }
 1130  
 
 1131  
     /**
 1132  
      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processMultipleValueLookupResults
 1133  
      */
 1134  
     public void processMultipleValueLookupResults(View view, Object model, String collectionPath,
 1135  
             String lookupResultValues) {
 1136  
         // if no line values returned, no population is needed
 1137  0
         if (StringUtils.isBlank(lookupResultValues)) {
 1138  0
             return;
 1139  
         }
 1140  
 
 1141  
         // retrieve the collection group so we can get the collection class and collection lookup
 1142  0
         CollectionGroup collectionGroup = view.getViewIndex().getCollectionGroupByPath(collectionPath);
 1143  0
         if (collectionGroup == null) {
 1144  0
             throw new RuntimeException("Unable to find collection group for path: " + collectionPath);
 1145  
         }
 1146  
 
 1147  0
         Class<?> collectionObjectClass = collectionGroup.getCollectionObjectClass();
 1148  0
         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model,
 1149  
                 collectionGroup.getBindingInfo().getBindingPath());
 1150  0
         if (collection == null) {
 1151  0
             Class<?> collectionClass = ObjectPropertyUtils.getPropertyType(model,
 1152  
                     collectionGroup.getBindingInfo().getBindingPath());
 1153  0
             collection = (Collection<Object>) ObjectUtils.newInstance(collectionClass);
 1154  0
             ObjectPropertyUtils.setPropertyValue(model, collectionGroup.getBindingInfo().getBindingPath(), collection);
 1155  
         }
 1156  
 
 1157  0
         Map<String, String> fieldConversions = collectionGroup.getCollectionLookup().getFieldConversions();
 1158  0
         List<String> toFieldNamesColl = new ArrayList(fieldConversions.values());
 1159  0
         Collections.sort(toFieldNamesColl);
 1160  0
         String[] toFieldNames = new String[toFieldNamesColl.size()];
 1161  0
         toFieldNamesColl.toArray(toFieldNames);
 1162  
 
 1163  
         // first split to get the line value sets
 1164  0
         String[] lineValues = StringUtils.split(lookupResultValues, ",");
 1165  
 
 1166  
         // for each returned set create a new instance of collection class and populate with returned line values
 1167  0
         for (String lineValue : lineValues) {
 1168  0
             Object lineDataObject = null;
 1169  
 
 1170  
             // TODO: need to put this in data object service so logic can be reused
 1171  0
             ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
 1172  
                     collectionObjectClass);
 1173  0
             if (moduleService != null && moduleService.isExternalizable(collectionObjectClass)) {
 1174  0
                 lineDataObject = moduleService.createNewObjectFromExternalizableClass(collectionObjectClass.asSubclass(
 1175  
                         ExternalizableBusinessObject.class));
 1176  
             } else {
 1177  0
                 lineDataObject = ObjectUtils.newInstance(collectionObjectClass);
 1178  
             }
 1179  
 
 1180  
             // apply default values to new line
 1181  0
             applyDefaultValuesForCollectionLine(view, model, collectionGroup, lineDataObject);
 1182  
 
 1183  0
             String[] fieldValues = StringUtils.split(lineValue, ":");
 1184  0
             if (fieldValues.length != toFieldNames.length) {
 1185  0
                 throw new RuntimeException(
 1186  
                         "Value count passed back from multi-value lookup does not match field conversion count");
 1187  
             }
 1188  
 
 1189  
             // set each field value on the line
 1190  0
             for (int i = 0; i < fieldValues.length; i++) {
 1191  0
                 String fieldName = toFieldNames[i];
 1192  0
                 ObjectPropertyUtils.setPropertyValue(lineDataObject, fieldName, fieldValues[i]);
 1193  
             }
 1194  
 
 1195  
             // TODO: duplicate identifier check
 1196  
 
 1197  0
             collection.add(lineDataObject);
 1198  
         }
 1199  0
     }
 1200  
 
 1201  
     /**
 1202  
      * Finds the <code>Inquirable</code> configured for the given data object
 1203  
      * class and delegates to it for building the inquiry URL
 1204  
      *
 1205  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#buildInquiryLink(java.lang.Object,
 1206  
      *      java.lang.String, org.kuali.rice.krad.uif.widget.Inquiry)
 1207  
      */
 1208  
     public void buildInquiryLink(Object dataObject, String propertyName, Inquiry inquiry) {
 1209  0
         Inquirable inquirable = getViewDictionaryService().getInquirable(dataObject.getClass(), inquiry.getViewName());
 1210  0
         if (inquirable != null) {
 1211  0
             inquirable.buildInquirableLink(dataObject, propertyName, inquiry);
 1212  
         } else {
 1213  
             // inquirable not found, no inquiry link can be set
 1214  0
             inquiry.setRender(false);
 1215  
         }
 1216  0
     }
 1217  
 
 1218  
     /**
 1219  
      * @see org.kuali.rice.krad.uif.service.ViewHelperService#applyDefaultValuesForCollectionLine(org.kuali.rice.krad.uif.view.View,
 1220  
      *      java.lang.Object, org.kuali.rice.krad.uif.container.CollectionGroup,
 1221  
      *      java.lang.Object)
 1222  
      */
 1223  
     public void applyDefaultValuesForCollectionLine(View view, Object model, CollectionGroup collectionGroup,
 1224  
             Object line) {
 1225  
         // retrieve all data fields for the collection line
 1226  0
         List<DataField> dataFields = ComponentUtils.getComponentsOfTypeDeep(
 1227  
                 collectionGroup.getAddLineFields(), DataField.class);
 1228  0
         for (DataField dataField : dataFields) {
 1229  0
             String bindingPath = "";
 1230  0
             if (StringUtils.isNotBlank(dataField.getBindingInfo().getBindByNamePrefix())) {
 1231  0
                 bindingPath = dataField.getBindingInfo().getBindByNamePrefix() + ".";
 1232  
             }
 1233  0
             bindingPath += dataField.getBindingInfo().getBindingName();
 1234  
 
 1235  0
             populateDefaultValueForField(view, line, dataField, bindingPath);
 1236  0
         }
 1237  0
     }
 1238  
 
 1239  
     /**
 1240  
      * Applies the default value configured for the given field (if any) to the
 1241  
      * line given object property that is determined by the given binding path
 1242  
      *
 1243  
      * <p>
 1244  
      * Checks for a configured default value or default value class for the
 1245  
      * field. If both are given, the configured static default value will win.
 1246  
      * In addition, if the default value contains an el expression it is
 1247  
      * evaluated against the initial context
 1248  
      * </p>
 1249  
      *
 1250  
      * @param view - view instance the field belongs to
 1251  
      * @param object - object that should be populated
 1252  
      * @param dataField - field to check for configured default value
 1253  
      * @param bindingPath - path to the property on the object that should be populated
 1254  
      */
 1255  
     protected void populateDefaultValueForField(View view, Object object, DataField dataField,
 1256  
             String bindingPath) {
 1257  
         // check for configured default value
 1258  0
         String defaultValue = dataField.getDefaultValue();
 1259  0
         if (StringUtils.isBlank(defaultValue) && (dataField.getDefaultValueFinderClass() != null)) {
 1260  0
             ValueFinder defaultValueFinder = ObjectUtils.newInstance(dataField.getDefaultValueFinderClass());
 1261  0
             defaultValue = defaultValueFinder.getValue();
 1262  
         }
 1263  
 
 1264  
         // populate default value if given and path is valid
 1265  0
         if (StringUtils.isNotBlank(defaultValue) && ObjectPropertyUtils.isWritableProperty(object, bindingPath)) {
 1266  0
             if (getExpressionEvaluatorService().containsElPlaceholder(defaultValue)) {
 1267  0
                 Map<String, Object> context = getPreModelContext(view);
 1268  0
                 defaultValue = getExpressionEvaluatorService().evaluateExpressionTemplate(null, context, defaultValue);
 1269  
             }
 1270  
 
 1271  
             // TODO: this should go through our formatters
 1272  0
             ObjectPropertyUtils.setPropertyValue(object, bindingPath, defaultValue);
 1273  
         }
 1274  0
     }
 1275  
 
 1276  
     /**
 1277  
      * Hook for creating new components with code and adding them to a container
 1278  
      *
 1279  
      * <p>
 1280  
      * Subclasses can override this method to check for one or more containers by id and then adding components
 1281  
      * created in code. This is invoked before the initialize method on the container component, so the full
 1282  
      * lifecycle will be run on the components returned.
 1283  
      * </p>
 1284  
      *
 1285  
      * <p>
 1286  
      * New components instances can be retrieved using {@link ComponentFactory}
 1287  
      * </p>
 1288  
      *
 1289  
      * @param view - view instance the container belongs to
 1290  
      * @param model - object containing the view data
 1291  
      * @param container - container instance to add components to
 1292  
      */
 1293  
     protected void addCustomContainerComponents(View view, Object model, Container container) {
 1294  
 
 1295  0
     }
 1296  
 
 1297  
     /**
 1298  
      * Hook for service overrides to perform custom initialization on the
 1299  
      * component
 1300  
      *
 1301  
      * @param view - view instance containing the component
 1302  
      * @param component - component instance to initialize
 1303  
      */
 1304  
     protected void performCustomInitialization(View view, Component component) {
 1305  
 
 1306  0
     }
 1307  
 
 1308  
     /**
 1309  
      * Hook for service overrides to perform custom apply model logic on the
 1310  
      * component
 1311  
      *
 1312  
      * @param view - view instance containing the component
 1313  
      * @param component - component instance to apply model to
 1314  
      * @param model - Top level object containing the data (could be the form or a
 1315  
      * top level business object, dto)
 1316  
      */
 1317  
     protected void performCustomApplyModel(View view, Component component, Object model) {
 1318  
 
 1319  0
     }
 1320  
 
 1321  
     /**
 1322  
      * Hook for service overrides to perform custom component finalization
 1323  
      *
 1324  
      * @param view - view instance containing the component
 1325  
      * @param component - component instance to update
 1326  
      * @param model - Top level object containing the data
 1327  
      * @param parent - Parent component for the component being finalized
 1328  
      */
 1329  
     protected void performCustomFinalize(View view, Component component, Object model, Component parent) {
 1330  
 
 1331  0
     }
 1332  
 
 1333  
     /**
 1334  
      * Hook for service overrides to process the new collection line before it
 1335  
      * is added to the collection
 1336  
      *
 1337  
      * @param view - view instance that is being presented (the action was taken
 1338  
      * on)
 1339  
      * @param collectionGroup - collection group component for the collection the line will
 1340  
      * be added to
 1341  
      * @param model - object instance that contain's the views data
 1342  
      * @param addLine - the new line instance to be processed
 1343  
      */
 1344  
     protected void processBeforeAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
 1345  
 
 1346  0
     }
 1347  
 
 1348  
     /**
 1349  
      * Hook for service overrides to process the new collection line after it
 1350  
      * has been added to the collection
 1351  
      *
 1352  
      * @param view - view instance that is being presented (the action was taken
 1353  
      * on)
 1354  
      * @param collectionGroup - collection group component for the collection the line that
 1355  
      * was added
 1356  
      * @param model - object instance that contain's the views data
 1357  
      * @param addLine - the new line that was added
 1358  
      */
 1359  
     protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
 1360  
 
 1361  0
     }
 1362  
 
 1363  
     /**
 1364  
      * Hook for service overrides to process the collection line after it has been deleted
 1365  
      *
 1366  
      * @param view - view instance that is being presented (the action was taken on)
 1367  
      * @param collectionGroup - collection group component for the collection the line that
 1368  
      * was added
 1369  
      * @param model - object instance that contains the views data
 1370  
      * @param lineIndex - index of the line that was deleted
 1371  
      */
 1372  
     protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
 1373  
 
 1374  0
     }
 1375  
 
 1376  
     protected void logAndThrowRuntime(String message) {
 1377  0
         LOG.error(message);
 1378  0
         throw new RuntimeException(message);
 1379  
     }
 1380  
 
 1381  
     protected DataDictionaryService getDataDictionaryService() {
 1382  0
         if (this.dataDictionaryService == null) {
 1383  0
             this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
 1384  
         }
 1385  
 
 1386  0
         return this.dataDictionaryService;
 1387  
     }
 1388  
 
 1389  
     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
 1390  0
         this.dataDictionaryService = dataDictionaryService;
 1391  0
     }
 1392  
 
 1393  
     protected ExpressionEvaluatorService getExpressionEvaluatorService() {
 1394  0
         if (this.expressionEvaluatorService == null) {
 1395  0
             this.expressionEvaluatorService = KRADServiceLocatorWeb.getExpressionEvaluatorService();
 1396  
         }
 1397  
 
 1398  0
         return this.expressionEvaluatorService;
 1399  
     }
 1400  
 
 1401  
     public void setExpressionEvaluatorService(ExpressionEvaluatorService expressionEvaluatorService) {
 1402  0
         this.expressionEvaluatorService = expressionEvaluatorService;
 1403  0
     }
 1404  
 
 1405  
     public ViewDictionaryService getViewDictionaryService() {
 1406  0
         if (this.viewDictionaryService == null) {
 1407  0
             this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
 1408  
         }
 1409  0
         return this.viewDictionaryService;
 1410  
     }
 1411  
 
 1412  
     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
 1413  0
         this.viewDictionaryService = viewDictionaryService;
 1414  0
     }
 1415  
 }