package org.springframework.faces.webflow; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.faces.FacesException; import javax.faces.application.ViewHandler; import javax.faces.component.UIInput; 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.apache.el.ExpressionFactoryImpl; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.support.FluentParserContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.LocalParameterMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.definition.StateDefinition; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; import org.springframework.webflow.execution.View; import org.springframework.webflow.execution.ViewFactory; import org.springframework.webflow.expression.el.WebFlowELExpressionParser; import org.springframework.webflow.test.MockExternalContext; public class JsfViewFactoryTests extends TestCase { private static final String VIEW_ID = "/testView.xhtml"; private ViewFactory factory; private final JSFMockHelper jsfMock = new JSFMockHelper(); private final RequestContext context = EasyMock.createMock(RequestContext.class); private final LocalAttributeMap<Object> flashMap = new LocalAttributeMap<Object>(); private final ViewHandler viewHandler = new MockViewHandler(); private Lifecycle lifecycle; private PhaseListener trackingListener; private final ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); private final MockExternalContext extContext = new MockExternalContext(); private final MockServletContext servletContext = new MockServletContext(); private final MockHttpServletRequest request = new MockHttpServletRequest(); private final MockHttpServletResponse response = new MockHttpServletResponse(); protected void setUp() throws Exception { configureJsf(); this.extContext.setNativeContext(this.servletContext); this.extContext.setNativeRequest(this.request); this.extContext.setNativeResponse(this.response); RequestContextHolder.setRequestContext(this.context); EasyMock.expect(this.context.getFlashScope()).andStubReturn(this.flashMap); EasyMock.expect(this.context.getExternalContext()).andStubReturn(this.extContext); EasyMock.expect(this.context.getRequestParameters()).andStubReturn( new LocalParameterMap(new HashMap<String, Object>())); } protected void tearDown() throws Exception { super.tearDown(); this.jsfMock.tearDown(); RequestContextHolder.setRequestContext(null); } private void configureJsf() throws Exception { this.jsfMock.setUp(); ExceptionEventAwareMockApplication application = new ExceptionEventAwareMockApplication(); ((MockBaseFacesContext) FlowFacesContext.getCurrentInstance()).setApplication(application); this.trackingListener = new TrackingPhaseListener(); this.jsfMock.lifecycle().addPhaseListener(this.trackingListener); this.jsfMock.facesContext().setViewRoot(null); this.jsfMock.facesContext().getApplication().setViewHandler(this.viewHandler); } /** * View has not yet been created */ public final void testGetView_Create() { this.lifecycle = new NoExecutionLifecycle(this.jsfMock.lifecycle()); this.factory = new JsfViewFactory(this.parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)), this.lifecycle); MockUIViewRoot newRoot = new MockUIViewRoot(); newRoot.setViewId(VIEW_ID); ((MockViewHandler) this.viewHandler).setCreateView(newRoot); this.context.inViewState(); EasyMock.expectLastCall().andReturn(true); EasyMock.replay(new Object[] { this.context }); View newView = this.factory.getView(this.context); assertNotNull("A View was not created", newView); assertTrue("A JsfView was expected", newView instanceof JsfView); assertEquals("View name did not match", VIEW_ID, ((JsfView) newView).getViewRoot().getViewId()); assertFalse("An unexpected event was signaled,", newView.hasFlowEvent()); } /** * View already exists in view/flash scope and must be restored and the lifecycle executed, no flow event signaled */ public final void testGetView_Restore() { this.lifecycle = new NoExecutionLifecycle(this.jsfMock.lifecycle()); this.factory = new JsfViewFactory(this.parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)), this.lifecycle); MockUIViewRoot existingRoot = new MockUIViewRoot(); existingRoot.setViewId(VIEW_ID); UIInput input = new UIInput(); input.setId("invalidInput"); input.setValid(false); existingRoot.getChildren().add(input); ((MockViewHandler) this.viewHandler).setRestoreView(existingRoot); this.context.inViewState(); EasyMock.expectLastCall().andReturn(true); EasyMock.replay(new Object[] { this.context }); View restoredView = this.factory.getView(this.context); assertNotNull("A View was not restored", restoredView); assertTrue("A JsfView was expected", restoredView instanceof JsfView); 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 flow event signaled */ public final void testGetView_RestoreWithBindings() { this.lifecycle = new NoExecutionLifecycle(this.jsfMock.lifecycle()); this.factory = new JsfViewFactory(this.parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)), this.lifecycle); MockUIViewRoot existingRoot = new MockUIViewRoot(); existingRoot.setViewId(VIEW_ID); UIPanel panel = new UIPanel(); panel.setId("panel1"); UIOutput output = new UIOutput(); output.setValueBinding("binding", this.jsfMock.facesContext().getApplication() .createValueBinding("#{myBean.output}")); output.setId("output1"); UIInput input = new UIInput(); input.setValueBinding("binding", this.jsfMock.facesContext().getApplication().createValueBinding("#{myBean.input}")); input.setId("input1"); existingRoot.getChildren().add(panel); panel.getFacets().put("label", output); panel.getChildren().add(input); TestBean testBean = new TestBean(); this.jsfMock.externalContext().getRequestMap().put("myBean", testBean); ((MockViewHandler) this.viewHandler).setRestoreView(existingRoot); this.context.inViewState(); EasyMock.expectLastCall().andReturn(true); EasyMock.replay(new Object[] { this.context }); View restoredView = this.factory.getView(this.context); assertNotNull("A View was not restored", restoredView); assertTrue("A JsfView was expected", restoredView instanceof JsfView); assertEquals("View name did not match", VIEW_ID, ((JsfView) restoredView).getViewRoot().getViewId()); 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 flow * event signaled */ public final void testGetView_Restore_Ajax() { this.lifecycle = new NoExecutionLifecycle(this.jsfMock.lifecycle()); this.factory = new JsfViewFactory(this.parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)), this.lifecycle); MockUIViewRoot existingRoot = new MockUIViewRoot(); existingRoot.setViewId(VIEW_ID); ((MockViewHandler) this.viewHandler).setRestoreView(existingRoot); this.request.addHeader("Accept", "text/html;type=ajax"); EasyMock.expect(this.context.getCurrentState()).andReturn(new NormalViewState()); this.context.inViewState(); EasyMock.expectLastCall().andReturn(true); EasyMock.replay(new Object[] { this.context }); View restoredView = this.factory.getView(this.context); assertNotNull("A View was not restored", restoredView); assertTrue("A JsfView was expected", restoredView instanceof JsfView); 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()); } /** * Third party sets the view root before RESTORE_VIEW */ public final void testGetView_ExternalViewRoot() { this.lifecycle = new NoExecutionLifecycle(this.jsfMock.lifecycle()); this.factory = new JsfViewFactory(this.parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)), this.lifecycle); MockUIViewRoot newRoot = new MockUIViewRoot(); newRoot.setViewId(VIEW_ID); this.jsfMock.facesContext().setViewRoot(newRoot); this.jsfMock.facesContext().renderResponse(); EasyMock.replay(new Object[] { this.context }); View newView = this.factory.getView(this.context); assertNotNull("A View was not created", newView); assertTrue("A JsfView was expected", newView instanceof JsfView); 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 { this.lifecycle = new NoExecutionLifecycle(this.jsfMock.lifecycle()); this.factory = new JsfViewFactory(this.parser.parseExpression(VIEW_ID, new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class)), this.lifecycle); MockUIViewRoot existingRoot = new MockUIViewRoot(); existingRoot.setThrowOnPostRestoreStateEvent(true); existingRoot.setViewId(VIEW_ID); ((MockViewHandler) this.viewHandler).setRestoreView(existingRoot); this.context.inViewState(); EasyMock.expectLastCall().andReturn(true); EasyMock.replay(new Object[] { this.context }); this.factory.getView(this.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 { public NoExecutionLifecycle(Lifecycle delegate) { super(delegate); } public void execute(FacesContext context) throws FacesException { fail("The lifecycle should not be invoked from the ViewFactory"); } } private class TrackingPhaseListener implements PhaseListener { private final List<String> phaseCallbacks = new ArrayList<String>(); public void afterPhase(PhaseEvent event) { String phaseCallback = "AFTER_" + event.getPhaseId(); assertFalse("Phase callback " + phaseCallback + " already executed.", this.phaseCallbacks.contains(phaseCallback)); this.phaseCallbacks.add(phaseCallback); } public void beforePhase(PhaseEvent event) { String phaseCallback = "BEFORE_" + event.getPhaseId(); assertFalse("Phase callback " + phaseCallback + " already executed.", this.phaseCallbacks.contains(phaseCallback)); this.phaseCallbacks.add(phaseCallback); } public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } } private class NormalViewState implements StateDefinition { public boolean isViewState() { return true; } public String getId() { throw new UnsupportedOperationException("Auto-generated method stub"); } public FlowDefinition getOwner() { throw new UnsupportedOperationException("Auto-generated method stub"); } public MutableAttributeMap<Object> getAttributes() { throw new UnsupportedOperationException("Auto-generated method stub"); } public String getCaption() { throw new UnsupportedOperationException("Auto-generated method stub"); } public String getDescription() { throw new UnsupportedOperationException("Auto-generated method stub"); } } protected class TestBean { UIOutput output; UIInput input; public UIOutput getOutput() { return this.output; } public void setOutput(UIOutput output) { this.output = output; } public UIInput getInput() { return this.input; } public void setInput(UIInput input) { 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()); this.postRestoreStateEventSeen = true; if (this.throwOnPostRestoreStateEvent) { this.abortProcessingException = new AbortProcessingException(); throw this.abortProcessingException; } } } public void setThrowOnPostRestoreStateEvent(boolean throwOnPostRestoreStateEvent) { this.throwOnPostRestoreStateEvent = throwOnPostRestoreStateEvent; } public boolean isPostRestoreStateEventSeen() { return this.postRestoreStateEventSeen; } public AbortProcessingException getAbortProcessingException() { return this.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 this.exceptionQueuedEventContext; } } }