Add support for PostRestoreStateEvent
A PostRestoreStateEvent should be delivered whenever a JSF view is restored. Typically we rely on JSF to generate required events but in this case since WebFlow handles the RESTORE_VIEW phase itself, we need to deliver this event ourselves. Issues: SWF-1500
This commit is contained in:
committed by
Rossen Stoyanchev
parent
8e1aa91b40
commit
27cd5cca31
@@ -9,6 +9,7 @@ Modify Jsf2FlowFacesContext.isValidationFailed() to check Web Flow's MessageCont
|
||||
Recognize class-level bean validation messages in BindingResult.getGlobalErrors()
|
||||
Fix "embedded" mode to be flow session local, i.e. specific to a specific flow or subflow.
|
||||
Allow JSF view root to survive redirect in same state (following fix in JSF Mojarra 2.1)
|
||||
Ensure PostRestoreStateEvent is delivered to registered listeners.
|
||||
|
||||
Changes in version 2.3.0.RELEASE (Feb 28, 2011)
|
||||
-----------------------------------------------
|
||||
|
||||
@@ -26,7 +26,11 @@ import javax.faces.application.Resource;
|
||||
import javax.faces.application.ResourceHandler;
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.component.behavior.Behavior;
|
||||
import javax.faces.component.visit.VisitContext;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.event.AbortProcessingException;
|
||||
import javax.faces.event.ExceptionQueuedEvent;
|
||||
import javax.faces.event.ExceptionQueuedEventContext;
|
||||
import javax.faces.event.SystemEvent;
|
||||
import javax.faces.event.SystemEventListener;
|
||||
|
||||
@@ -116,4 +120,17 @@ public class Jsf2FlowApplication extends FlowApplication {
|
||||
getDelegate().unsubscribeFromEvent(systemEventClass, listener);
|
||||
}
|
||||
|
||||
// Ideally this method should be in JsfView
|
||||
// We keep it here to avoid ClassNotFoundExceptions for JSF 1.2 apps
|
||||
|
||||
static void publishPostRestoreStateEvent() {
|
||||
FacesContext facesContext = FlowFacesContext.getCurrentInstance();
|
||||
try {
|
||||
facesContext.getViewRoot().visitTree(VisitContext.createVisitContext(facesContext),
|
||||
new PostRestoreStateEventVisitCallback());
|
||||
} catch (AbortProcessingException e) {
|
||||
facesContext.getApplication().publishEvent(facesContext, ExceptionQueuedEvent.class,
|
||||
new ExceptionQueuedEventContext(facesContext, e, null, facesContext.getCurrentPhaseId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
*/
|
||||
package org.springframework.faces.webflow;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.event.PhaseEvent;
|
||||
import javax.faces.event.PhaseId;
|
||||
import javax.faces.event.PhaseListener;
|
||||
import javax.faces.lifecycle.Lifecycle;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.webflow.execution.RequestContextHolder;
|
||||
|
||||
/**
|
||||
@@ -66,4 +69,18 @@ public class JsfUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// This method is here for JSF 1.2 backwards compatibility
|
||||
|
||||
static void publishPostRestoreStateEvent() {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("org.springframework.faces.webflow.Jsf2FlowApplication");
|
||||
Method method = ReflectionUtils.findMethod(clazz, "publishPostRestoreStateEvent");
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
ReflectionUtils.invokeMethod(method, null);
|
||||
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Expected Jsf2FlowApplication: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -128,6 +128,9 @@ public class JsfViewFactory implements ViewFactory {
|
||||
view = createJsfView(viewRoot, lifecycle, context);
|
||||
}
|
||||
}
|
||||
if (isAtLeastJsf20()) {
|
||||
JsfUtils.publishPostRestoreStateEvent();
|
||||
}
|
||||
if (!facesContext.getRenderResponse()) {
|
||||
JsfUtils.notifyAfterListeners(PhaseId.RESTORE_VIEW, lifecycle, facesContext);
|
||||
}
|
||||
@@ -182,4 +185,5 @@ public class JsfViewFactory implements ViewFactory {
|
||||
processTree(context, child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.springframework.faces.webflow;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.component.visit.VisitCallback;
|
||||
import javax.faces.component.visit.VisitContext;
|
||||
import javax.faces.component.visit.VisitResult;
|
||||
import javax.faces.event.PostRestoreStateEvent;
|
||||
|
||||
/**
|
||||
* A VisitCallback used to deliver a PostRestoreStataEvent similar to
|
||||
* {@code RestoreViewPhase.deliverPostRestoreStateEvent(..)} in Sun's JSF.
|
||||
*
|
||||
* @since 2.3.1
|
||||
*/
|
||||
class PostRestoreStateEventVisitCallback implements VisitCallback {
|
||||
|
||||
private PostRestoreStateEvent event;
|
||||
|
||||
public VisitResult visit(VisitContext context, UIComponent target) {
|
||||
if (this.event == null) {
|
||||
this.event = new PostRestoreStateEvent(target);
|
||||
} else {
|
||||
this.event.setComponent(target);
|
||||
}
|
||||
target.processEvent(event);
|
||||
return VisitResult.ACCEPT;
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,14 @@ import org.apache.myfaces.test.mock.MockServletConfig;
|
||||
import org.apache.myfaces.test.mock.MockServletContext;
|
||||
import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;
|
||||
import org.apache.myfaces.test.mock.lifecycle.MockLifecycleFactory;
|
||||
import org.apache.myfaces.test.mock.visit.MockVisitContextFactory;
|
||||
|
||||
/**
|
||||
* Helper for using the mock JSF environment provided by shale-test inside unit tests that do not extend
|
||||
* {@link AbstractJsfTestCase}
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
* @author Phil Webb
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class JSFMockHelper {
|
||||
|
||||
@@ -137,7 +138,9 @@ public class JSFMockHelper {
|
||||
FactoryFinder.setFactory(FactoryFinder.FACES_CONTEXT_FACTORY, MockBaseFacesContextFactory.class.getName());
|
||||
FactoryFinder.setFactory(FactoryFinder.LIFECYCLE_FACTORY, MockLifecycleFactory.class.getName());
|
||||
FactoryFinder.setFactory(FactoryFinder.RENDER_KIT_FACTORY, MockRenderKitFactory.class.getName());
|
||||
FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY, MockPartialViewContextFactory.class.getName());
|
||||
FactoryFinder.setFactory(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY,
|
||||
MockPartialViewContextFactory.class.getName());
|
||||
FactoryFinder.setFactory(FactoryFinder.VISIT_CONTEXT_FACTORY, MockVisitContextFactory.class.getName());
|
||||
lifecycleFactory = (MockLifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
|
||||
lifecycle = (MockLifecycle) lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
|
||||
facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
|
||||
|
||||
@@ -11,13 +11,20 @@ import javax.faces.component.UIOutput;
|
||||
import javax.faces.component.UIPanel;
|
||||
import javax.faces.component.UIViewRoot;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.event.AbortProcessingException;
|
||||
import javax.faces.event.ComponentSystemEvent;
|
||||
import javax.faces.event.ExceptionQueuedEvent;
|
||||
import javax.faces.event.ExceptionQueuedEventContext;
|
||||
import javax.faces.event.PhaseEvent;
|
||||
import javax.faces.event.PhaseId;
|
||||
import javax.faces.event.PhaseListener;
|
||||
import javax.faces.event.PostRestoreStateEvent;
|
||||
import javax.faces.event.SystemEvent;
|
||||
import javax.faces.lifecycle.Lifecycle;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.myfaces.test.mock.MockApplication20;
|
||||
import org.easymock.EasyMock;
|
||||
import org.jboss.el.ExpressionFactoryImpl;
|
||||
import org.springframework.binding.expression.ExpressionParser;
|
||||
@@ -82,6 +89,8 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
|
||||
private void configureJsf() throws Exception {
|
||||
jsfMock.setUp();
|
||||
ExceptionEventAwareMockApplication application = new ExceptionEventAwareMockApplication();
|
||||
((MockBaseFacesContext) FlowFacesContext.getCurrentInstance()).setApplication(application);
|
||||
trackingListener = new TrackingPhaseListener();
|
||||
jsfMock.lifecycle().addPhaseListener(trackingListener);
|
||||
jsfMock.facesContext().setViewRoot(null);
|
||||
@@ -98,7 +107,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)),
|
||||
lifecycle);
|
||||
|
||||
UIViewRoot newRoot = new UIViewRoot();
|
||||
MockUIViewRoot newRoot = new MockUIViewRoot();
|
||||
newRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setCreateView(newRoot);
|
||||
context.inViewState();
|
||||
@@ -115,7 +124,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* View already exists in view/flash scope and must be restored and the lifecycle executed, no event signaled
|
||||
* View already exists in view/flash scope and must be restored and the lifecycle executed, no flow event signaled
|
||||
*/
|
||||
public final void testGetView_Restore() {
|
||||
|
||||
@@ -124,7 +133,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)),
|
||||
lifecycle);
|
||||
|
||||
UIViewRoot existingRoot = new UIViewRoot();
|
||||
MockUIViewRoot existingRoot = new MockUIViewRoot();
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
UIInput input = new UIInput();
|
||||
input.setId("invalidInput");
|
||||
@@ -144,10 +153,11 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
assertEquals("View name did not match", VIEW_ID, ((JsfView) restoredView).getViewRoot().getViewId());
|
||||
assertFalse("An unexpected event was signaled,", restoredView.hasFlowEvent());
|
||||
assertTrue("The input component's valid flag was not reset", input.isValid());
|
||||
assertTrue("The PostRestoreViewEvent was not seen", existingRoot.isPostRestoreStateEventSeen());
|
||||
}
|
||||
|
||||
/**
|
||||
* View already exists in view/flash scope and must be restored and the lifecycle executed, no event signaled
|
||||
* View already exists in view/flash scope and must be restored and the lifecycle executed, no flow event signaled
|
||||
*/
|
||||
public final void testGetView_RestoreWithBindings() {
|
||||
|
||||
@@ -156,7 +166,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)),
|
||||
lifecycle);
|
||||
|
||||
UIViewRoot existingRoot = new UIViewRoot();
|
||||
MockUIViewRoot existingRoot = new MockUIViewRoot();
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
UIPanel panel = new UIPanel();
|
||||
panel.setId("panel1");
|
||||
@@ -190,11 +200,12 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
assertFalse("An unexpected event was signaled,", restoredView.hasFlowEvent());
|
||||
assertSame("The UIInput binding was not restored properly", input, testBean.getInput());
|
||||
assertSame("The faceted UIOutput binding was not restored properly", output, testBean.getOutput());
|
||||
assertTrue("The PostRestoreViewEvent was not seen", existingRoot.isPostRestoreStateEventSeen());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax Request - View already exists in view/flash scope and must be restored and the lifecycle executed, no event
|
||||
* signaled
|
||||
* Ajax Request - View already exists in view/flash scope and must be restored and the lifecycle executed, no flow
|
||||
* event signaled
|
||||
*/
|
||||
public final void testGetView_Restore_Ajax() {
|
||||
|
||||
@@ -203,7 +214,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)),
|
||||
lifecycle);
|
||||
|
||||
UIViewRoot existingRoot = new UIViewRoot();
|
||||
MockUIViewRoot existingRoot = new MockUIViewRoot();
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setRestoreView(existingRoot);
|
||||
|
||||
@@ -223,6 +234,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
assertTrue("An ViewRoot was not set", ((JsfView) restoredView).getViewRoot() instanceof UIViewRoot);
|
||||
assertEquals("View name did not match", VIEW_ID, ((JsfView) restoredView).getViewRoot().getViewId());
|
||||
assertFalse("An unexpected event was signaled,", restoredView.hasFlowEvent());
|
||||
assertTrue("The PostRestoreViewEvent was not seen", existingRoot.isPostRestoreStateEventSeen());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,7 +246,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)),
|
||||
lifecycle);
|
||||
|
||||
UIViewRoot newRoot = new UIViewRoot();
|
||||
MockUIViewRoot newRoot = new MockUIViewRoot();
|
||||
newRoot.setViewId(VIEW_ID);
|
||||
jsfMock.facesContext().setViewRoot(newRoot);
|
||||
jsfMock.facesContext().renderResponse();
|
||||
@@ -248,6 +260,30 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
assertEquals("View name did not match", VIEW_ID, ((JsfView) newView).getViewRoot().getViewId());
|
||||
assertSame("View root was not the third party instance", newRoot, ((JsfView) newView).getViewRoot());
|
||||
assertFalse("An unexpected event was signaled,", newView.hasFlowEvent());
|
||||
assertTrue("The PostRestoreViewEvent was not seen", newRoot.isPostRestoreStateEventSeen());
|
||||
}
|
||||
|
||||
public void testGetView_ExceptionsOnPostRestoreStateEvent() throws Exception {
|
||||
lifecycle = new NoExecutionLifecycle(jsfMock.lifecycle());
|
||||
factory = new JsfViewFactory(parser.parseExpression(VIEW_ID,
|
||||
new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)),
|
||||
lifecycle);
|
||||
|
||||
MockUIViewRoot existingRoot = new MockUIViewRoot();
|
||||
existingRoot.setThrowOnPostRestoreStateEvent(true);
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setRestoreView(existingRoot);
|
||||
|
||||
context.inViewState();
|
||||
EasyMock.expectLastCall().andReturn(Boolean.TRUE);
|
||||
|
||||
EasyMock.replay(new Object[] { context });
|
||||
factory.getView(context);
|
||||
ExceptionEventAwareMockApplication application = (ExceptionEventAwareMockApplication) FlowFacesContext
|
||||
.getCurrentInstance().getApplication();
|
||||
assertNotNull("Expected exception event", application.getExceptionQueuedEventContext());
|
||||
assertSame("Expected same exception", existingRoot.getAbortProcessingException(), application
|
||||
.getExceptionQueuedEventContext().getException());
|
||||
}
|
||||
|
||||
private class NoExecutionLifecycle extends FlowLifecycle {
|
||||
@@ -337,4 +373,51 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
this.input = input;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockUIViewRoot extends UIViewRoot {
|
||||
|
||||
private boolean postRestoreStateEventSeen;
|
||||
private boolean throwOnPostRestoreStateEvent;
|
||||
private AbortProcessingException abortProcessingException;
|
||||
|
||||
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
|
||||
if (event instanceof PostRestoreStateEvent) {
|
||||
assertSame("Component did not match", this, ((PostRestoreStateEvent) event).getComponent());
|
||||
postRestoreStateEventSeen = true;
|
||||
if (throwOnPostRestoreStateEvent) {
|
||||
abortProcessingException = new AbortProcessingException();
|
||||
throw abortProcessingException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setThrowOnPostRestoreStateEvent(boolean throwOnPostRestoreStateEvent) {
|
||||
this.throwOnPostRestoreStateEvent = throwOnPostRestoreStateEvent;
|
||||
}
|
||||
|
||||
public boolean isPostRestoreStateEventSeen() {
|
||||
return postRestoreStateEventSeen;
|
||||
}
|
||||
|
||||
public AbortProcessingException getAbortProcessingException() {
|
||||
return abortProcessingException;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExceptionEventAwareMockApplication extends MockApplication20 {
|
||||
|
||||
private ExceptionQueuedEventContext exceptionQueuedEventContext;
|
||||
|
||||
public void publishEvent(FacesContext facesContext, Class<? extends SystemEvent> systemEventClass, Object source) {
|
||||
if (ExceptionQueuedEvent.class.equals(systemEventClass)) {
|
||||
this.exceptionQueuedEventContext = (ExceptionQueuedEventContext) source;
|
||||
} else {
|
||||
super.publishEvent(facesContext, systemEventClass, source);
|
||||
}
|
||||
}
|
||||
|
||||
public ExceptionQueuedEventContext getExceptionQueuedEventContext() {
|
||||
return exceptionQueuedEventContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +38,8 @@ public class MockBaseFacesContext extends MockFacesContext20 {
|
||||
}
|
||||
return application;
|
||||
}
|
||||
|
||||
public void setApplication(Application application) {
|
||||
this.application = application;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user