Skip to end of metadata
Go to start of metadata

KRAD UIF View Lifecycle Refactoring
Impacting Changes – Rice 2.3 to 2.4

Overview

To facilitate asynchronous UIF View Lifecycle processing, as well as a more flexible approach to controlling lifecycle execution for KRAD components, the original algorithms evident in Rice 2.3 ViewHelperServiceImpl have been refactored for Rice 2.4 into discrete processing tasks which operate on lifecycle "elements", an abstract concept encompassing both Component and LayoutManager.
The following class diagram illustrates the target structure for this refactoring effort from a high level:

ViewLifecycle Thread State Control

The new class ViewLifecycle is responsible for managing thread state and providing static access to common processing components while performing the view lifecycle. ViewService and ViewHelperService are no longer responsible for controlling view lifecycle execution.
Throughout the Rice 2.4 KRAD UIF Framework, argument lists have been pruned to remove the "view" and "model" parameters. Not all references have been pruned, but many have; the eventual goal is for all references to transition use the thread state exposed by static methods in ViewLifecycle instead of relying on passing these as arguments:

  • #getActiveLifecycle() returns a handle to the a ViewLifecycle instance managing a lifecycle build process on the current thread.
    • The methods #invokeEventListeners() and #registerLifecycleCompleteListener() facilitate custom lifecycle complete notification, to be invoked when a specific lifecycle phase has been completely processed.
  • #getView() returns the view actively being built by the current lifecycle
  • #getModel() returns the model (typically UifFormBase) associated with the current lifecycle build.
  • #getHelper() returns a ViewHelperService instance suitable for working with the current lifecycle build. This method replaces View#getViewHelperSerivce()
  • #getExpressionEvaluator() returns an expression evaluator suitable for the current lifecycle build.
  • #getPhase() returns the active phase of the current lifecycle build.
  • #getProcessor() returns a handle to the ViewLifecycleProcessor working on the current thread.
  • #getRenderingContext() returns a handle to the FreeMarker environment and related metadata, which can be used for incremental rending.
  • #getRequest() returns the HttpServletRequest actively involved in the current lifecycle build.
  • #getViewPostMetadata() returns all data related to the view retained by the lifecycle between requests, for reference during component refresh builds.
  • #getRefreshComponentPostMetadata() returns data related to the component actively being refreshed, retained from the previous lifecycle build.

These methods will only work within a call to ViewLifecycle#encapsulateLifecycle(). Both forms of this method accept a Runnable argument for passing a custom continuation to handle lifecycle processing logic.
In Rice 2.3, the ViewService#buildView() and ViewHelperService#performComponentLifecycle() and #spawnSubLifecycle() were provided as extension points for customizing lifecycle execution. These methods have been replaced, and the lifecycle is no longer intended to be customized at this level.

  • ViewService#buildView() is now ViewLifecycle#buildView(). The new method is static, no longer part of an extensible interface. The actual lifecycle build is now controlled by ViewLifecycleBuild, a Runnable intended for use with encapsulateLifecycle().
  • ViewHelperServiceImpl#performComponentLifecycle() is now ViewLifecycle#performComponentLifecycle(), and the actual component refresh build is controlled by the ViewLifecycleBuild.
  • ViewHelperServiceImpl#spawnSubLifecycle() has been removed in Rice 2.4. The purpose of this method was to provide a means for components defining child components dynamically during the lifecycle to perform lifecycle processing on those dynamic child components up to the same phase as the parent component. This process is now automatic – a manual mechanism for spawning sub lifecycles is no longer needed.

As an alternative to customizing the lifecycle via interface methods, the lifecycle is controlled by the following three components:

  1. ViewLifecycleProcessor – Manages thread state and queueing for tasks related to lifecycle processing. ViewLifecycleProcessor is not aware of which processing takes place – it's only function is to manage the phase tree and execute each phase in the correct order. The only two intended implementations of this interface are SynchronousViewLifecycleProcessor and AsynchronousViewLifecycleProcessor. No custom overrides of these default behaviors are intended.
  2. ViewLifecyclePhase – Encapsulates a discrete processing phase, or full tree traversal on the view for the purpose of performing a discrete set of tasks on each element.

ViewLifecyclePhase extends Runnable, and utilizes
Five default phases are provided:

    1. PreProcessElementPhase – Invoked from UifDictionaryIndex after acquiring the view from Spring, prior to caching the view. The purpose of this phase is to assign ids and paths for consistent indexed and bean property access to individual child elements based on the view. After this phase completes, viewStatus will be CACHED (X).

NOTE: The preprocess phase is a new concept in Rice 2.4 intended to eliminate duplicate processing of operations for which the same result is expected on each run regardless of form/session data.

    1. InitializeComponentPhase – Invoked during the view and component refresh lifecycle build processes. The initialize phase performs per-request tasks:
      1. Assigns ids and paths if not covered by the pre-process phase
      2. Prepares expressions to be evaluated on the component
      3. Initializes remote fields and data dictionary attributes
      4. Runs component modifiers set to run at the initialize phase.
      5. Custom logic defined by the component and/or view helper service

NOTE: InitializeComponentPhase replaces the Rice 2.3 method ViewHelperServiceImpl#performComponentInitialization(view,model,component)

    1. ApplyModelComponentPhase - Invoked during the view and component refresh lifecycle build processes. The apply model phase phase performs per-request tasks:
      1. Evaluates expressions on the component
      2. Synchronizes state with incoming client-side data
      3. Applies authorization and presentation logic
      4. Updates component refresh state
      5. Custom logic defined by the component and/or view helper service

NOTE: ApplyModelComponentPhase replaces the 2.3 method ViewHelperServiceImpl#performComponentApplyModel(view, component, model, visitedIds)

    1. FinalizeComponentPhase – Invoked during the view and component refresh lifecycle build processes. The finalize phase performs a final tree traversal to prepare the view for rendering:
      1. Invoke and component finalizers
      2. Runs component modifiers set to run at the finalize phase.
      3. Adds template paths to the view, and/or queue RenderComponentPhase instances for FreeMarker integration.
      4. Registers property editors.

NOTE: FinalizeComponentPhase replaces the 2.3 method ViewHelperServiceImpl#performComponentFinalize(view,component,model,parent)

    1. RenderComponentPhase – When in-lifecycle rendering is enabled (by setting the rice.krad.lifecycle.render config property to true), FinalizeComponentPhase will queue RenderComponentPhase to evaluate FreeMarker templates defined for the component and capture the content as self-rendered. Note that while the other delivered phases are processed in top-down order, RenderComponentPhase is processed bottom-up. This facilitates incremental asynchronous FreeMarker rendering.
  1. ViewLifecycleTask – Encapsulates the concept of a discrete processing task to be performed during a lifecycle phase. Tasks are always performed in series, in the order configured on the phase being processed. Delivered tasks may be found in the following packages, organized by phase:
    1. org.kuali.rice.krad.uif.lifecycle.initialize
    2. org.kuali.rice.krad.uif.lifecycle.model
    3. org.kuali.rice.krad.uif.lifecycle.finalize

UIF Component Interfaces

In Rice 2.4, the interfaces Component and LayoutManager have been updated to share a common super-interface LifecycleElement. Methods common to both interfaces have been moved to LifecycleElement. Some notable methods on LifecycleElement:

  • get/setId()
  • get/setViewStatus()
  • get/setContext(), pushObjectToContext(), pushAllToContext()
  • performInitialization() – Performs lifecycle processing specific to the component for the INITIALIZE phase, noted above (2.b.v) as "Custom logic defined by the component". Note that the behavior of container invoking performInitialization on the LayoutManager, rather than the lifecycle invoking this method directly, is still present in Rice 2.4. In general lifecycle tasks only apply to components, and not to layout manager elements, though layout manager and other types of lifecycle elements are not possible.
  • performApplyModel() - Performs lifecycle processing specific to the component for the APPLY_MODEL phase.
  • performFinalize() - Performs lifecycle processing specific to the component for the FINALIZE phase.

Copyable Enhancements

To simplify the development of custom UIF components, the copyProperties() method for performing a deep copy has been removed. Instead, standard Java cloning and a reflection-based tree traversal algorithm replace the need to manually implement deep copy behavior without a significant increase in runtime overhead. In general the new copy implementation is equivalent or slightly faster than copyProperties().
When a component is copied:

  1. The component is cloned using Object#clone(). This results in an exact copy of the original component that can change independently, but that also refers to the same child elements.
  2. All fields that match one of the following conditions are iterated:
    1. Copyable is assignable from the field type.
    2. List is assignable from the field type, and Copyable is assignable from the generic list item type.
    3. Map is assignable from the field type, and Copyable is assignable from the generic map value type.
    4. Field type is an array, and the Copyable is assignable from the component type.
  3. If the field has the @ReferenceCopy annotation, it will not be copied.
    1. If @ReferenceCopy(newCollectionInstance=true) is present, then the list, map, or array will be copied, but the elements of the collection will refer to the same objects as the original.
    2. If @ReferenceCopy(referenceTransient=true) is present, not only will the field be skipped, but the field will be reset to null during the copy.
  4. Otherwise (no @ReferenceCopy), the field will be copied recursively: first cloned, then its fields iterated. If the field is a list, map, or array type, first the collection is copied then each element in the collection is copied recursively.

Delayed Copy Proxy

If the @DelayedCopy annotation is encountered on a field while iterating the component's structure during a copy operation, and that field's type is both an interface and assignable to Copyable, then rather than preform the deep copy of that field now a Proxy instance will be created on the interface to defer the deep copy of that component until the first method is invoked on the component that is expected to change component state.
This feature is particularly useful for multi-page views, where only the current page needs to be processed by the view lifecycle.

Lifecycle Tree (getComponentsForLifecycle, getComponentPrototypes)

The Rice 2.3 methods Component#getComponentsForLifecycle() and Component#getComponentPrototypes(), and their counterparts in the LayoutManager interface, have been removal in favor of a reflection-based approach.
In building a tree for processing the UIF component lifecycle, all bean properties that meet one of the following criteria will be considered:

  1. LifecycleElement is assignable from the property type
  2. List is assignable from the property type, and LifecycleElement is assignable from the property type.
  3. Map is assignable from the property type, and LifecycleElement is assignable from the map value type.
  4. The property has an array type, and LifecycleElement is assignable from the component type.

To get a mapping from bean property path to nested element for a lifecycle component, ViewLifecycleUtils#getComponentsForLifecycle() may be used. There is no longer a separate #getComponentPrototypes() method – instead ViewLifecycleUtils#getComponentsForLifecycle(UifConstants.ViewPhase.INITIALIZE) will return both lifecycle elements and "prototypes".
To prevent a property from being included in the lifecycle, use @LifecycleRestriction on the read method. Note that @LifecycleRestriction(UifConstants.ViewPhase.INITIALIZE) is equivalent to including a property in the #getComponentPrototypes() method in Rice 2.3.

View Lifecycle Tree Traversal

Path expressions are now used throughout the view lifecycle for referring to specific components by location relative to the view. The "viewPath" bean property on each lifecycle element refers to the path to the element relative to the view; the "parentPath" bean property refers to the path to the element relative to its parent node in the lifecycle tree. The view has a path of "".
These paths may be resolved using ObjectPropertyUtils.getPropertyValue(), and are equivalent to Spring BeanWrapper property expressions.

Lifecycle Element Interfaces

To facilitate more flexible use of proxy features, specifically @DelayedCopy, many of the component classes delivered with Rice 2.3 have been converted to an interface and base class.

  • Group
  • PageGroup
  • CollectionGroup
  • DataField
  • InputField
  • GridLayoutManager
  • LayoutManager
  • StackedLayoutManager
  • TableLayoutManager

All original methods from these components have been replicated on the interface, and the default base implementations all function the same as they had prior to this change.

Post metadata

In Rice 2.3 view lifecycle data was retained between requests as the "postedView" property on the form. However, this led to a potentially large memory footprint remaining in session. To remedy this scenario, view state data retained between requests is now stored in a key/value structure accessible through via the ViewPostMetadata class.
ViewPostMetdata is accessible from the form view the "viewPostMetadata" bean property, and also during the lifecycle by calling ViewLifecycle#getViewPostMetadata(). Keyed data attached to ViewPostMetadata or its composed ComponentPostMetadata objects will be retained in the user's session between requests. This consideration requires the developer to be very specific about which data are retained.
Common post metadata constants may be found in UifConstants.PostMetdata.

  • No labels