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 */
016 package org.kuali.rice.krad.uif.service.impl;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.core.api.config.property.ConfigurationService;
020 import org.kuali.rice.core.api.exception.RiceRuntimeException;
021 import org.kuali.rice.kim.api.identity.Person;
022 import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
023 import org.kuali.rice.krad.datadictionary.AttributeDefinition;
024 import org.kuali.rice.krad.inquiry.Inquirable;
025 import org.kuali.rice.krad.messages.MessageService;
026 import org.kuali.rice.krad.service.DataDictionaryService;
027 import org.kuali.rice.krad.service.KRADServiceLocator;
028 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
029 import org.kuali.rice.krad.service.ModuleService;
030 import org.kuali.rice.krad.uif.UifConstants;
031 import org.kuali.rice.krad.uif.component.BindingInfo;
032 import org.kuali.rice.krad.uif.component.ClientSideState;
033 import org.kuali.rice.krad.uif.component.Component;
034 import org.kuali.rice.krad.uif.component.ComponentSecurity;
035 import org.kuali.rice.krad.uif.component.DataBinding;
036 import org.kuali.rice.krad.uif.component.PropertyReplacer;
037 import org.kuali.rice.krad.uif.component.RequestParameter;
038 import org.kuali.rice.krad.uif.container.CollectionGroup;
039 import org.kuali.rice.krad.uif.container.Container;
040 import org.kuali.rice.krad.uif.container.Group;
041 import org.kuali.rice.krad.uif.control.Control;
042 import org.kuali.rice.krad.uif.element.Action;
043 import org.kuali.rice.krad.uif.field.ActionField;
044 import org.kuali.rice.krad.uif.field.DataField;
045 import org.kuali.rice.krad.uif.field.Field;
046 import org.kuali.rice.krad.uif.field.FieldGroup;
047 import org.kuali.rice.krad.uif.field.InputField;
048 import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
049 import org.kuali.rice.krad.uif.layout.LayoutManager;
050 import org.kuali.rice.krad.uif.modifier.ComponentModifier;
051 import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
052 import org.kuali.rice.krad.uif.service.ViewDictionaryService;
053 import org.kuali.rice.krad.uif.service.ViewHelperService;
054 import org.kuali.rice.krad.uif.util.BooleanMap;
055 import org.kuali.rice.krad.uif.util.CloneUtils;
056 import org.kuali.rice.krad.uif.util.ComponentFactory;
057 import org.kuali.rice.krad.uif.util.ComponentUtils;
058 import org.kuali.rice.krad.uif.util.ExpressionUtils;
059 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
060 import org.kuali.rice.krad.uif.util.ScriptUtils;
061 import org.kuali.rice.krad.uif.util.ViewCleaner;
062 import org.kuali.rice.krad.uif.util.ViewModelUtils;
063 import org.kuali.rice.krad.uif.view.View;
064 import org.kuali.rice.krad.uif.view.ViewAuthorizer;
065 import org.kuali.rice.krad.uif.view.ViewModel;
066 import org.kuali.rice.krad.uif.view.ViewPresentationController;
067 import org.kuali.rice.krad.uif.widget.Inquiry;
068 import org.kuali.rice.krad.uif.widget.Widget;
069 import org.kuali.rice.krad.util.ErrorMessage;
070 import org.kuali.rice.krad.util.GlobalVariables;
071 import org.kuali.rice.krad.util.GrowlMessage;
072 import org.kuali.rice.krad.util.KRADConstants;
073 import org.kuali.rice.krad.util.MessageMap;
074 import org.kuali.rice.krad.util.ObjectUtils;
075 import org.kuali.rice.krad.valuefinder.ValueFinder;
076 import org.kuali.rice.krad.web.form.UifFormBase;
077 import org.springframework.expression.spel.support.StandardEvaluationContext;
078 import org.springframework.util.ClassUtils;
079 import org.springframework.util.MethodInvoker;
080
081 import java.io.Serializable;
082 import java.lang.annotation.Annotation;
083 import java.text.MessageFormat;
084 import java.util.ArrayList;
085 import java.util.Collection;
086 import java.util.Collections;
087 import java.util.HashMap;
088 import java.util.HashSet;
089 import java.util.List;
090 import java.util.Map;
091 import java.util.Map.Entry;
092 import java.util.Set;
093
094 /**
095 * Default Implementation of <code>ViewHelperService</code>
096 *
097 * @author Kuali Rice Team (rice.collab@kuali.org)
098 */
099 public 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 }