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