Introduction
On a normal ADF page with multiple task flows, each of the individual task flows need to be initialized and their initial views created before the page can render. This makes it important to consider how the page design, and task flow usage affect the page load time when designing the UI for your ADF based application. This article looks at a generic method to improve the user experience in ADF and WebCenter applications by de-coupling the page's rendering from a task flow's load time. This approach makes it possible for developers to prioritize task flows and load region content through post backs without holding up the parent page's rendering. The parent page can render without waiting for the task flows, and these regions get filled-in when those task flows are ready. The net result is an improvement in the overall user experience, since the user gets much more immediate feedback and a sense of improved responsiveness from the page. In the post I use some WebCenter task flows, for building my example, but the approach is valid for any task flow. I chose a handful of built-in WebCenter task flow so that I can demonstrate the generic approach that can be implemented in any custom developed task flow, and also how this can be applied to the built -in task flows in WebCenter or other products through MDS customization.This approach de-couples page rendering from task flow load times to start the page rendering earlier, and the end user experience better. This is however, not an alternative for proper task-flow and page design. Performance issues should be addressed by identifying and resolving the bottlenecks that cause the them. This approach should be viewed as a fall-back option when tuning options have been exhausted in addressing the root cause, or the root cause is the result of a fundamental application design choice that makes it difficult or expensive to address.
Main Article
In most cases, the slow page loads can be attributed to a few or a single task flow that has an initial view, whose page definition has a list of executable bindings that perform fairly expensive operations. These may include remote web service calls or expensive queries. Sometimes it is not a single poor-performing task flow, but the presence of just too many task flows on a page, and the sum of execution times, puts the page response time beyond the SLA. The end result is a page that needs to execute a long or expensive list of time consuming bindings to get all the data for the individual regions on a page. The approach examined here attempts to break this pattern by decoupling the rendering of a page from the rendering on individual regions on the page. In order to do this, we load an initial view with no bindings, so that there is no processing delay to create this view, and then use a polling component in the view to trigger a post back that delivers the original view.Task flows do support conditional activation and EL based refresh conditions that make it possible to not execute or load a task flow until it is actually required or visible on screen (like when task flows appear on tabs or popups). This article assumes that these options have already been exhausted and the absolute minimum active task flows on the page is still pushing the page load time beyond user expectations.
Offsetting task flow load times
The first step is to identify the task flow(s) causing the performance bottleneck. In our example, we will use 3 content presenter task flows, a quick poll and a message board. The main content is served by a content presenter displaying a document, and we also have a message board, a quick poll and two query based content presenter instances. The resulting page looks like this: Once we have identified one or more task flows that are holding up the page and have exhausted configuration options to optimize them, we can try to prevent it from affecting the overall page load by offsetting the task flow load time. In our example, the main content presenter is the focus of the page, and is loaded along with the page. This way the user does not have to wait for the main content on the page, and avoids the page being just made up of the page template elements when its first rendered. The other ancillary task flow instances are loaded progressively. The Quick Poll and Message Board task flows are really fast, but we load them progressively to demonstrate the concept of being able to introduce this pattern to any task flow, and we will make the query based content presenters use some inefficient queries to simulate a slow load time. We use a simple loading animation on a stand-in view activity with no bindings to execute, so that the parent page rendering can immediately occur. Once this view loads, a poll is fired to display the real view (with the bindings). When we have multiple task flows that we want to load progressively, it is better to stagger the load times so that the user always sees content pop-in to the page with the shortest possible pauses. To stagger the post backs, we associate a delay for each instance A general pattern that can be used with this approach is to make the progressive load optional, to address cases where it can be conditionally disabled. We introduce two parameters to our task flow, a delay which we will use to stagger multiple slow task flows and a boolean switch that toggles the progressive load feature. We introduce a router as the default activity and based on the value of the switch we decide to use the progressive poll based load, or to load the default activity the task flow was originally designed with. For our example, we customize the content presenter task flow with the new input parameters shown below. Notice that the delay value is set to a property on a manged bean. This is a custom managed bean that we register with the task flow, and will have the custom code to perform navigation based on the poll. This manged bean also is configured with a managed property that developers set while the registering this bean. This property is the navigationOutcome property, which should be set to a navigation outcome that will navigate to the original default activity for the task flow (the one that was default before we introduced our router and made it the default) 1. The new Default Activity is a router that checks for the new input parameter that toggles the progressive load behavior. 2. The new View Activity that displays a loading animation and houses the poll. If lazy loading is enabled through the task flow parameter, then the router routes to this view activity. 3. The original Default Activity for this task flow, before we introduced progressive load. This is the activity we will navigate to based on the poll. The fragment for the new View Activity is below. It is simple with just a loading animation(which can be replaced with text for even better performance) and an af:Poll component. The interval and the pollListener for the poll is set through the :<?xml version='1.0' encoding='UTF-8'?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:af="http://xmlns.oracle.com/adf/faces/rich" xmlns:f="http://java.sun.com/jsf/core"> <af:panelFormLayout id="pfl1"> <af:image source="/images/loading.gif" shortDesc="Loading..." id="i1"/> <af:poll id="p1" interval="#{pageFlowScope.PollBean.priority}" pollListener="#{pageFlowScope.PollBean.pollExpired}"/> </af:panelFormLayout> <!--oracle-jdev-comment:preferred-managed-bean-name:PollBean--> </jsp:root>The PollBean implementation is simple, and contains only the properties for the delay and the navigation outcome that will be used. The navigation outcome is best set as a managed property when registering the managed bean, and should be set to an outcome that will navigate to the task flow's original default activity. When the page loads, the poll component starts counting down and when the delay timeout is reached, the pollListener fires. The pollListener executes the navigation action and this navigates to the original default action of the task flow, thus triggering the actual task flow processing. Once the task flow is ready with a view activity, then the content for that region is delivered by PPR. The PollBean code for programmatically triggering the navigation action is shown below :
package ateam.webcenter.view; import javax.faces.application.NavigationHandler; import javax.faces.context.FacesContext; import org.apache.myfaces.trinidad.event.PollEvent; public class PollBean { private Integer priority; private String navigationOutcome; public PollBean() { this.priority=0; } /** * This method is invoked when the poll interval has been reached. * The method executes a navigation case. * The outcome is a managed property that the developer should set when registering this bean * Ideally this should be set to an outcome that navigates to the original default * activity for the taskflow * * For demostration only. * @param pollEvent is not used as this method blindly executes a specific navigation in the example. */ public void pollExpired(PollEvent pollEvent) { FacesContext facesContext = FacesContext.getCurrentInstance(); NavigationHandler navHandler = facesContext.getApplication().getNavigationHandler(); navHandler.handleNavigation(facesContext, null, getNavigationOutcome()); } public void setPriority(Integer priority) { this.priority = priority; } public Integer getPriority() { return priority; } public void setNavigationOutcome(String navigationOutcome) { this.navigationOutcome = navigationOutcome; } public String getNavigationOutcome() { return navigationOutcome; } }
Using the Sample Application
Even though we talked about customizing the Content presenter task flow in the article above, the sample code applies progressive load to the Message Board and Quick Poll task flows as well. The sample application has two parts. The MDS customization is packaged as a metadata archive (.mar) and the custom code is packaged as an independent shared library that can be deployed to the Weblogic server. This custom code can be packaged in a number of other ways depending on your choice. Options include making it part of your own code base if you are using this in an ADF custom application, or including this as part of the portal project if you are using it as part of a WebCenter Portal framework application. I've chosen to package it as a separate shared library since it keeps this customization isolated and separate from other code. To use this application, you need to download both the projects. The 'WARProject' in the LazyContentPresenter application will generate the shared library that you need to deploy. Once this is done and the servers are restarted, you can generate the MAR file from the WebCenterCustomization application and import it through WLST commands or Enterprise Manager. Once the shared library and the customization are deployed, you can configure the new properties on Content Presenter instances. You can download the sample application from here :LazyContentPresenster, WebCenterCustomizationAll content listed on this page is the property of Oracle Corp. Redistribution now allowed without written permission