[KULRICE-10547] Component lifecycle processing task and execution context Created: 16/Sep/13  Updated: 22/Apr/14  Resolved: 04/Oct/13

Status: Closed
Project: Kuali Rice Development
Component/s: Development, User Experience (UX)
Affects Version/s: None
Fix Version/s: 2.4
Security Level: Public (Public: Anyone can view)

Type: Improvement Priority: Major
Reporter: Jerry Neal (Inactive) Assignee: Mark Fyffe (Inactive)
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: 0 minutes
Time Spent: 1 day, 2 hours
Original Estimate: 1 day

Attachments: Text File kulrice-10547-exec_context.patch     Text File kulrice-10547-exec_context_refactoring_pass2.patch    
Issue Links:
discovered by KULRICE-8798 Look into multithreading during the v... Closed
relates to KSENROLL-12539 Adding Cluster to Course Offering bla... Closed
Epic Link: Performance
Rice Module:
Sprint: 2.4.0-m2 KRAD Sprint 3, 2.4.0-m2 KRAD Sprint 4
KAI Review Status: Not Required
KTI Review Status: Not Required
Code Review Status: Not Required
Include in Release Notes?:

  • Component lifecycle processing task and execution context:
    Model ViewHelperService calls as discrete processing units with portable callable objects to manage execution and processing state. Which callable objects are created, and how they relate, will be driven by expectations set by unit tests.

Comment by Mark Fyffe (Inactive) [ 30/Sep/13 ]

This effort is nearly complete, and a little more testing is needed before I can commit my work. Below are some details on this update:

Added an execution context (ViewContext) for the purpose of separating structural metadata related to component definition from contextual metadata used during the lifecycle. To start with, the view context provides access to the following details:

  1. Original view, unmodified from the cached state
  2. Mutable view, in the process of being prepared for rendering
  3. View helper service instance
  4. View lifecycle event listener

The view context has two methods for encapsulating processing within the context:

  1. encapsulateInitialization: Used to encapsulate processes that build initial component state. Initialization routines may be encapsulated within other encapsulated operations, and will replace the context during encapsulation. Once the initialization routing has completed, the former context will be returned to the thread.
  2. encapsulateLifecycle: Used to encapsulate processes that prepare a view for rendering. Only one lifecycle routine may be encapsulated at a time.

Added immutability to ComponentBase, LayoutManagerBase, View, and several related classes. Collections and maps have been converted to unmodifiable with internal modifiers, and setters, copy methods, and complex operations that modify component state have all been safeguarded. Attempts to modify component state outside of an encapsulated view context result in IllegalStateException. The purpose of immutability is to reduce the need for copying components during and after the lifecycle - existing code that runs into an immutability check can be refactored to either remove behavior that modifies the component, or to move that behavior to part of the lifecycle.

During an encapsulateInitialization call, component setters may be called, and collections related to components will be externally modifiable. However, performIntitialization, performApplyModel, and performFinalize methods will result in IllegalStateException. Copy calls within encapsulateInitialization will result in an immutable component. Once the initialization routine has completed, all components created and/or modified during the routine will revert to an immutable state. The routines in DataDictionary and ComponentFactory that interact with Spring have been encapsulated.

During an encapsulateLifecycle call, components may be modified as described above for encapsualteInitialization, but only if the component was copied within the context of the encapsulated call. The performInitialization, performApplyModel, and performFinalize are available within an encapsulateLifecycle call.

The purpose of this safeguarding and encapsulation is to move control over component behavior from the application's default execution context to an execution context specifically defined for rendering the view.

To better isolate the view lifecycle from other processing, I have made the following adjustments:

  1. Split ViewHelperService into three separate interfaces:
    1. ViewHelperService, including methods only related to lifecycle processing.
    2. CollectionViewHelperService, extends ViewHelperService to define methods related to collection handling unrelated to the lifecycle
    3. InquiryViewHelperService, extends ViewHelperService to define methods related to inquriy handler unrelated to the lifecycle
  2. Split ViewHelperServiceImpl similarly:
    1. ViewHelperServiceBase, implementing only the methods related to lifecycle processing
    2. ViewHelperServiceImpl, implmeneting the CollectionViewHelperService and InquiryViewHelperService interfaces
  3. Removed the view reference from all argument lists related to lifecycle processing, in ViewHelperService, Component, and LayoutManager. Implementations have been updated to refer to the view from the context rather than from the argument list.

The steps above serve to isolate view lifecycle processing into a clean execution context, and to separate code related to the lifecycle from unrelated code. The safeguards primary serve to facilitate refactoring, and to prevent new code from modifying components outside of the lifecycle. The last remaining TODO for this request is to reorder final lifecycle processing as bottom-up, marking components as immutable as they finalized. performInitialization will remain top-down, but will be modified to create a bottom-up traversal stack for performApplyModel and performFinalize. The final two phases will be converted to per-component rather than performed on the full view. The basis for this final work is in the POC on KULRICE-8798.

Roughly 2h remains to clean up the modifications noted above, perform final TODOs, and commit. I should be able to finish this request and perform regression testing this evening.

Comment by Mark Fyffe (Inactive) [ 30/Sep/13 ]

Attached patch file of work in progress.

Comment by Mark Fyffe (Inactive) [ 04/Oct/13 ]

Committed final work on this issue. Several changes have been made since the previous description. Reordering the lifecycle has been deferred, and further lifecycle refactoring has been performed based on initial feedback from Jerry.

The view/component lifecycle is now ready to implement multi-threading.

Changes made in preparation are as follows:

  1. ViewLifecycle has been expanded to include the execution context previously described as ViewContext.
  2. All methods related to the view lifecycle have been moved out of ViewHelperServiceImpl, and added to ViewLifecycle.
  3. View lifecycle methods on the Component interface no longer take the view as an argument.
    1. The view may be accessed as needed using ViewLifecycle.getActiveLifecycle().getView()
  4. Added LifecycleElement interface to include methods relevant to the view/component lifecycles
    1. Component and LayoutManagerBase now extend LifecycleElement
    2. Mutability constraints have been added ComponentBase and LayoutManagerBase via LifecycleElement
    3. To enforce mutability constraints on collections and maps, added LifecycleAwareList and LifecycleAwareMap wrappers.

The purpose of adding mutability constraints is to lock down when components can modified. The following scenarios allow modification of components - attempts to modify components other than under these conditions will result in IllegalStateException:

  1. Within a call to ViewLifecycle.encapsulateInitialization(). The java.util.concurrent.Callable interface is use to pass an enclosure to this method for encapsulating view and component initialization routines. No restrictions on modification are present within these calls.
  2. Within a call to ViewLifecycle.encapsulateLifecycle(). The java.lang.Runnable interface is used to pass a lifecycle process related to the View provided. The only constraint present in this update is to restrict modification to components that have been copied. The primary purpose of this change is to prevent the modification cached component instances. Support for per-lifecycle mutability restrictions on copied components is commented-out, but will be reintroduced in KULRICE-10549. Restrictions to ensure that only components directly attached to the view, and only those copied within the current lifecycle, will be added for multi-threaded lifecycles. The encapsulateInitialization() method has been added in the following locations:
    1. DataDictionary.performDictionaryPostProcessing()
    2. DataDictionary.validateDD()
    3. UifDictionaryIndex.getViewById()
    4. UifDefaultingServiceImpl - all public methods
    5. ComponentFactory.getNewInstanceForRefresh()
    6. ComponentFactory.getComponentInstance()
    7. ViewCleaner.cleanView()
    8. UifServletRequestDataBinder.bind()
    9. UifControllerHelper.prepareViewForRendering() - pre-existing TODOs indicate moving these instances to the view lifecycle.
  3. Prior to calling ViewLifecycle.encapsulateLifecycle(), getMutableCopy() may be called to get an instance of the View suitable for performing the lifecycle on. Unless this method is used, or the view is copied within the encapsulateLifecycle() process, the view may not be modified. The encapsulateLifecycle() method has been added in the following locations:
    1. ViewServiceImpl.buildView()
    2. ViewLifecycle.performComponentLifecycle()
    3. UifControllerBase.addBlankLine()
    4. UifControllerBase.addLine()
    5. UifControllerBase.deleteLine()
    6. UifControllerBase.refresh()
    7. UifControllerBase.saveLine()

After adding mutability constraints the following changes were made to correct related issues:

  1. Moved setting view status to CREATED to UifDictionaryIndex.
  2. Refactored UifDictionaryIndex.getViewById() and getImmutableViewById() so that getImmutableViewById() controls view caching and initialization. getViewById() calls getImmutableViewById() to get an immutable view, then calls ViewLifecycle.getMutableCopy() to get a copy suitable for lifecycle operations.
  3. Moved assiging component ids for controls attached to InputField to after copying the cached control.
  4. Refactored BoxLayoutManager to use addStyleClass() instead of modifying the list externally.
  5. Added null check to ComponentUtils.copy()
  6. Added UifCloneable interface to support cloning of objects (via java.util.Cloneable) without a default constructor when using CloneUtils, specifically applied this interface to LifecycleAwareList and LifecycleAwareMap
  7. Modified DataTablesPagingHelper and UifControllerHelper to prepare for the possibility of ViewLifecycle returning a difference view instance than the one passed in.
Comment by Mark Fyffe (Inactive) [ 04/Oct/13 ]

Added code review


Comment by Mark Fyffe (Inactive) [ 07/Oct/13 ]

Added a config parameter to control the strictness of mutability and lifecycle state checks.

When rice.krad.lifecycle.strict is set to true, then IllegalStateException will be thrown when a component is modified or lifecycle phase performed out of context.

When set to false or null, the IllegalStateException will be logged as a warning.

Generated at Sat Jan 23 20:02:47 CST 2021 using JIRA 7.0.11#70121-sha1:19d24976997c1d95f06f3e327e087be0b71f28d4.