From 4b63c514aba622d210d6b7f0cde1a2e0c7b8b7a0 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Wed, 12 Nov 2008 16:39:28 +0000 Subject: [PATCH] SWF-953 View data binding results lost after Action phase in Portlet environment, not propogating to Render phase. The mapping results are cached in flash scope for portlet mvc requests. JSF portlets still demonstrate this issue. --- .../mvc/portlet/MappingResultsHolder.java | 65 ++++++++++++++++++ .../webflow/mvc/portlet/PortletMvcView.java | 12 ++++ .../mvc/portlet/PortletMvcViewFactory.java | 19 ++++++ .../webflow/mvc/view/AbstractMvcView.java | 26 +++++++ .../mvc/portlet/PortletMvcViewTests.java | 68 +++++++++++++++++++ 5 files changed, 190 insertions(+) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/MappingResultsHolder.java diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/MappingResultsHolder.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/MappingResultsHolder.java new file mode 100644 index 00000000..d11a049c --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/MappingResultsHolder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.mvc.portlet; + +import org.springframework.binding.mapping.MappingResults; + +/** + * Holder class for mapping results to pass them from an ActionRequest to a RenderRequest + * + * @author Scott Andrews + */ +class MappingResultsHolder { + + static final String MAPPING_RESULTS_HOLDER_KEY = "org.springframework.webflow.mvc.portlet.MAPPING_RESULTS_HOLDER"; + + private String eventId; + + private MappingResults mappingResults; + + private boolean viewErrors; + + public MappingResultsHolder(String eventId, MappingResults mappingResults, boolean viewErrors) { + this.eventId = eventId; + this.mappingResults = mappingResults; + this.viewErrors = viewErrors; + } + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public MappingResults getMappingResults() { + return mappingResults; + } + + public void setMappingResults(MappingResults mappingResults) { + this.mappingResults = mappingResults; + } + + public boolean getViewErrors() { + return viewErrors; + } + + public void setViewErrors(boolean viewErrors) { + this.viewErrors = viewErrors; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java index c8dcfa6c..9f98ff41 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcView.java @@ -43,6 +43,18 @@ public class PortletMvcView extends AbstractMvcView { */ public PortletMvcView(org.springframework.web.servlet.View view, RequestContext context) { super(view, context); + + } + + /* + * Cache {@link MappingResultsHolder} attributes into flow scope so they can be accessed during the RenderRequest + * + * @see AbstractMvcView#processUserEvent() + */ + public void processUserEvent() { + super.processUserEvent(); + MappingResultsHolder holder = new MappingResultsHolder(getEventId(), getMappingResults(), getViewErrors()); + this.getRequestContext().getFlashScope().put(MappingResultsHolder.MAPPING_RESULTS_HOLDER_KEY, holder); } protected void doRender(Map model) throws Exception { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java index b50f5803..b263bbf9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/portlet/PortletMvcViewFactory.java @@ -29,6 +29,7 @@ import org.springframework.webflow.mvc.view.FlowViewResolver; * Creates Portlet MVC views. * * @author Keith Donald + * @author Scott Andrews */ public class PortletMvcViewFactory extends AbstractMvcViewFactory { @@ -49,4 +50,22 @@ public class PortletMvcViewFactory extends AbstractMvcViewFactory { return new PortletMvcView(view, context); } + /* + * Populates attributes from {@link MappingResultsHolder}, if available, into the view. + * + * @see AbstractMvcViewFactory#getView(RequestContext) + */ + public org.springframework.webflow.execution.View getView(RequestContext context) { + org.springframework.webflow.execution.View view = super.getView(context); + if (view instanceof AbstractMvcView + && context.getFlashScope().contains(MappingResultsHolder.MAPPING_RESULTS_HOLDER_KEY)) { + AbstractMvcView mvcView = (AbstractMvcView) view; + MappingResultsHolder holder = (MappingResultsHolder) context.getFlashScope().get( + MappingResultsHolder.MAPPING_RESULTS_HOLDER_KEY); + mvcView.setEventId(holder.getEventId()); + mvcView.setMappingResults(holder.getMappingResults()); + mvcView.setViewErrors(holder.getViewErrors()); + } + return view; + } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java index c4778ab0..202a719b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/AbstractMvcView.java @@ -415,6 +415,32 @@ public abstract class AbstractMvcView implements View { eventId = WebUtils.findParameterValue(context.getRequestParameters().asMap(), eventIdParameterName); } + // accessors for mapping results + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public MappingResults getMappingResults() { + return mappingResults; + } + + public void setMappingResults(MappingResults mappingResults) { + this.mappingResults = mappingResults; + } + + public boolean getViewErrors() { + return viewErrors; + } + + public void setViewErrors(boolean viewErrors) { + this.viewErrors = viewErrors; + } + private static class PropertyNotFoundError implements MappingResultsCriteria { public boolean test(MappingResult result) { return result.isError() && "propertyNotFound".equals(result.getCode()); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/portlet/PortletMvcViewTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/portlet/PortletMvcViewTests.java index 107e1fc1..b9481db1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/portlet/PortletMvcViewTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/portlet/PortletMvcViewTests.java @@ -1,21 +1,37 @@ package org.springframework.webflow.mvc.portlet; +import java.util.Map; + import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import junit.framework.TestCase; import org.easymock.EasyMock; +import org.springframework.binding.expression.support.StaticExpression; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.portlet.MockPortletContext; import org.springframework.mock.web.portlet.MockRenderRequest; import org.springframework.mock.web.portlet.MockRenderResponse; +import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewRendererServlet; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.expression.DefaultExpressionParserFactory; import org.springframework.webflow.mvc.view.AbstractMvcView; +import org.springframework.webflow.mvc.view.MvcViewTests.BindBean; import org.springframework.webflow.test.MockFlowExecutionKey; import org.springframework.webflow.test.MockRequestContext; public class PortletMvcViewTests extends TestCase { + private boolean renderCalled; + + private Map model; + public void testRender() throws Exception { RenderRequest request = new MockRenderRequest(); RenderResponse response = new MockRenderResponse(); @@ -32,4 +48,56 @@ public class PortletMvcViewTests extends TestCase { assertNotNull(request.getAttribute(ViewRendererServlet.MODEL_ATTRIBUTE)); } + public void testResumeEventModelBindingFieldMarkerFieldPresent() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.putRequestParameter("_eventId", "submit"); + context.putRequestParameter("booleanProperty", "true"); + context.putRequestParameter("_booleanProperty", "whatever"); + BindBean bindBean = new BindBean(); + StaticExpression modelObject = new StaticExpression(bindBean); + modelObject.setExpressionString("bindBean"); + context.getCurrentState().getAttributes().put("model", modelObject); + context.getFlowScope().put("bindBean", bindBean); + context.getMockExternalContext().setNativeContext(new MockServletContext()); + context.getMockExternalContext().setNativeRequest(new MockHttpServletRequest()); + context.getMockExternalContext().setNativeResponse(new MockHttpServletResponse()); + context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1")); + org.springframework.web.servlet.View mvcView = new MockView(); + AbstractMvcView view = new MockPortletMvcView(mvcView, context); + view.setExpressionParser(DefaultExpressionParserFactory.getExpressionParser()); + view.processUserEvent(); + assertEquals(true, bindBean.getBooleanProperty()); + MappingResultsHolder holder = (MappingResultsHolder) context.getFlashScope().get( + MappingResultsHolder.MAPPING_RESULTS_HOLDER_KEY); + assertEquals("submit", holder.getEventId()); + assertNotNull(holder.getMappingResults()); + assertFalse(holder.getViewErrors()); + } + + private class MockPortletMvcView extends PortletMvcView { + + public MockPortletMvcView(View view, RequestContext context) { + super(view, context); + } + + protected void doRender(Map model) throws Exception { + getView().render(model, (HttpServletRequest) getRequestContext().getExternalContext().getNativeRequest(), + (HttpServletResponse) getRequestContext().getExternalContext().getNativeResponse()); + } + + } + + private class MockView implements View { + + public String getContentType() { + return "text/html"; + } + + public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + renderCalled = true; + model = model; + } + + } + }