Monday, March 31, 2014

Managed Bean changes should mark their ADF Scope dirty in a HA cluster

We're starting development on a new ADF application and the plan is to run this in a high-available weblogic cluster. The documentation clearly states it is the responsibility of the developer to make ADF aware of any changes to managed beans in an ADF scope with a lifespan longer than one request. This means it is up to you to notify ADF of each change in a viewScope or pageFlowScope bean with the following code:
Map viewScope = ADFContext.getCurrent().getViewScope();
ControllerContext.getInstance().markScopeDirty(viewScope);

That's not too difficult but it's a matter of time before a developer forgets about this. That would mean the ADF scope is not marked dirty and the changed managed bean is not replicated to the other nodes in the cluster. We wanted to implement a check (at least during development) that developers do not forget about this. JSF PhaseListener to the rescue!

Full source code is at the end of this posting, but I'll explain the vital parts first. We've create a JSF PhaseListener that listens to each JSF phase transition:
public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
}

In the beforePhase method for the RESTORE_VIEW phase we retrieve the viewScope and pageFlowScope maps. Next, we serialize these and calculate a MD5 hash. These hashes are store in the requestScope

In the afterPhase method for the RENDER_RESPONSE phase we calculate the same MD5 digests and compare these with the ones stored at the beginning of the request. We know we're in trouble when the digests changed and the developer has not invoked ControllerContext.markScopeDirty. When this happens we simple log an exception to the console without actually throwing it. This is more of a development/debugging tool and shouldn't actually abort JSF processing as that would also cancel any other PhaseListeners. We don't want to make things worse.

You probably want this enabled during development all the time. However, there is a performance overhead in serializing the viewScope and pageFlowScope twice per request and calculating a MD5 digest. It might be a good idea to disable this in production. That's why we opted to use the ADF logging framework for this. First, you can set the log levels that will be used for the output through web.xml context-parameters. By default, these are set to WARNING to ensure all logging is enabled in a default development environment. In production you want to use deployment plans to override this to something like FINE or FINEST. Once you know the logging level that is being used, you can even enable/disable these checks in a deployed application by tweaking the log levels of your weblogic container. The JSF PhaseListener is smart enough to not even perform the serialization and digest calculation of logging levels are setup so that logging doesn't occur. This means you could even leave the loggers in there for production so you could still enable them when cluster replication issues occur.

The demo workspace includes a singe page application to demonstrate the DirtyScopePhaseListener. Simply run that page and press the button marked "change viewState without marking dirty". This will log an error in the console that neatly explains the internal state of myViewScopeBean in the ADF viewScope was changed:

Hopefully this JSF PhaseListener can be a valuable tool for anyone building a ADF application intended to run on a session-replicating cluster.

As always you can download the full sample application or browse the subversion repository to look at the source code. If you want to have a quick look at the solution start with the com.redheap.dirtyscope.DirtyScopePhaseListener phase listener and the com.redheap.dirtyscope.ADFScopeChecker class that does all the digest calculating and checking.

Update: The sample code is broken. It only looks at the pageFlowScope and viewScope of the unbounded taskflow and does not consider any state in bounded taskflows and viewScopes running in regions. I am working on an improved version and will post a new blog entry once that is finished.