diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java index 908349806b..58d2f5320a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java @@ -180,14 +180,14 @@ public interface ActionInvocation extends Serializable { /** * Prepares instance of ActionInvocation to be serializable, - * which simple means removing all unserializable fields, eg. Container + * which simply means removing all unserializable but restorable references * * @return ActionInvocation which can be serialize (eg. into HttpSession) */ ActionInvocation serialize(); /** - * Performs opposite process to restore back ActionInvocation after deserialisation + * Performs opposite process to restore back ActionInvocation after serialisation * * @param actionContext current {@link ActionContext} * @return fully operational ActionInvocation diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java index 5777007c1c..6a3bf59f8b 100644 --- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java +++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -36,6 +36,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -501,24 +503,57 @@ protected String saveResult(ActionConfig actionConfig, Object methodResult) { } /** - * Version ready to be serialize + * Version ready to be serialize via removing all unserializable but restorable references * - * @return instance without reference to {@link Container} + * @return instance without unserializable references */ public ActionInvocation serialize() { DefaultActionInvocation that = this; that.container = null; + + if(that.getInvocationContext() != null && that.getInvocationContext().getContextMap() != null) { + Map thatContextMap = that.getInvocationContext().getContextMap(); + List keysToRemove = new ArrayList<>(); + for (Map.Entry entry : thatContextMap.entrySet()) { + Object entryValue = entry.getValue(); + // WW-4873 Remove Servlet Request and Response as they are not intended to being serializable + if (entryValue instanceof ServletRequest || + entryValue instanceof ServletResponse) { + keysToRemove.add(entry.getKey()); + } + } + for (String keyToRemove : keysToRemove) { + thatContextMap.remove(keyToRemove); + } + } + return that; } /** - * Restoring Container + * Restoring references * * @param actionContext current {@link ActionContext} * @return instance which can be used to invoke action */ public ActionInvocation deserialize(ActionContext actionContext) { DefaultActionInvocation that = this; + + if(that.getInvocationContext() != null && that.getInvocationContext().getContextMap() != null) { + Map thatContextMap = that.getInvocationContext().getContextMap(); + Map acContextMap = actionContext.getContextMap(); + for (Map.Entry entry : acContextMap.entrySet()) { + Object entryValue = entry.getValue(); + String entryKey = entry.getKey(); + // WW-4873 Restore Servlet Request and Response + if ((entryValue instanceof ServletRequest || + entryValue instanceof ServletResponse) && + !thatContextMap.containsKey(entryKey)) { + thatContextMap.put(entryKey, entryValue); + } + } + } + that.container = actionContext.getContainer(); return that; } diff --git a/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java b/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java index 20b330eaea..f7ca061d06 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java +++ b/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java @@ -32,7 +32,6 @@ public class BackgroundProcess implements Serializable { private static final long serialVersionUID = 3884464776311686443L; protected Object action; - protected ActionInvocation invocation; protected String result; protected Exception exception; protected boolean done; @@ -45,13 +44,12 @@ public class BackgroundProcess implements Serializable { * @param threadPriority The thread priority */ public BackgroundProcess(String threadName, final ActionInvocation invocation, int threadPriority) { - this.invocation = invocation; this.action = invocation.getAction(); try { final Thread t = new Thread(new Runnable() { public void run() { try { - beforeInvocation(); + beforeInvocation(invocation); result = invocation.invokeActionOnly(); afterInvocation(); } catch (Exception e) { @@ -75,7 +73,7 @@ public void run() { * * @throws Exception any exception thrown will be thrown, in turn, by the ExecuteAndWaitInterceptor */ - protected void beforeInvocation() throws Exception { + protected void beforeInvocation(ActionInvocation invocation) throws Exception { ActionContext.setContext(invocation.getInvocationContext()); } @@ -99,15 +97,6 @@ public Object getAction() { return action; } - /** - * Retrieves the action invocation. - * - * @return the action invocation - */ - public ActionInvocation getInvocation() { - return invocation; - } - /** * Gets the result of the background process. * diff --git a/core/src/main/java/org/apache/struts2/interceptor/TokenSessionStoreInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/TokenSessionStoreInterceptor.java index e08fb07bea..6957e41b1b 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/TokenSessionStoreInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/TokenSessionStoreInterceptor.java @@ -28,7 +28,6 @@ import org.apache.struts2.util.TokenHelper; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** @@ -117,7 +116,6 @@ protected String handleInvalidToken(ActionInvocation invocation) throws Exceptio ActionContext ac = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST); - HttpServletResponse response = (HttpServletResponse) ac.get(ServletActionContext.HTTP_RESPONSE); String tokenName = TokenHelper.getTokenName(); String token = TokenHelper.getToken(tokenName); @@ -134,9 +132,6 @@ protected String handleInvalidToken(ActionInvocation invocation) throws Exceptio ValueStack stack = savedInvocation.getStack(); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); - ActionContext savedContext = savedInvocation.getInvocationContext(); - savedContext.getContextMap().put(ServletActionContext.HTTP_REQUEST, request); - savedContext.getContextMap().put(ServletActionContext.HTTP_RESPONSE, response); Result result = savedInvocation.getResult(); if ((result != null) && (savedInvocation.getProxy().getExecuteResult())) { diff --git a/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java b/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java index 334839082a..19e4c87d0d 100644 --- a/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java @@ -25,12 +25,15 @@ import com.opensymphony.xwork2.mock.MockActionProxy; import com.opensymphony.xwork2.mock.MockContainer; import com.opensymphony.xwork2.mock.MockInterceptor; -import com.opensymphony.xwork2.mock.MockLazyInterceptor; import com.opensymphony.xwork2.ognl.OgnlUtil; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.ValueStackFactory; import org.apache.struts2.dispatcher.HttpParameters; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -44,6 +47,8 @@ * @author Kristian Rosenvold */ public class DefaultActionInvocationTest extends XWorkTestCase { + private static final String HTTP_REQUEST = "com.opensymphony.xwork2.DefaultActionInvocationTest.HttpServletRequest"; + private static final String HTTP_RESPONSE = "com.opensymphony.xwork2.DefaultActionInvocationTest.HttpServletResponse"; /** * Tests interceptor chain invoke. @@ -77,23 +82,53 @@ public void testInvoke() throws Exception { public void testSerialization() throws Exception { // given - DefaultActionInvocation actionInvocation = new DefaultActionInvocation(new HashMap(), false); + HashMap extraContext = new HashMap(); + DefaultActionInvocation actionInvocation = new DefaultActionInvocation(extraContext, false); actionInvocation.setContainer(new MockContainer()); + extraContext.put(HTTP_REQUEST, new MockHttpServletRequest()); + extraContext.put(HTTP_RESPONSE, new MockHttpServletResponse()); + actionInvocation.invocationContext = new ActionContext(extraContext); + // when DefaultActionInvocation serializable = (DefaultActionInvocation) actionInvocation.serialize(); // then assertNull(actionInvocation.container); assertNull(serializable.container); + + assertFalse("request should be removed from actionInvocation", + actionInvocation.invocationContext.getContextMap().containsKey(HTTP_REQUEST)); + assertFalse("response should be removed from actionInvocation", + actionInvocation.invocationContext.getContextMap().containsKey(HTTP_RESPONSE)); + assertFalse("request should be removed from serializable", + serializable.invocationContext.getContextMap().containsKey(HTTP_REQUEST)); + assertFalse("response should be removed from serializable", + serializable.invocationContext.getContextMap().containsKey(HTTP_RESPONSE)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(serializable); + oos.close(); + assertTrue("should have serialized data", baos.size() > 0); + baos.close(); } public void testDeserialization() throws Exception { // given - DefaultActionInvocation actionInvocation = new DefaultActionInvocation(new HashMap(), false); + HashMap extraContext = new HashMap(); + DefaultActionInvocation actionInvocation = new DefaultActionInvocation(extraContext, false); + actionInvocation.invocationContext = new ActionContext(extraContext); + MockContainer mockContainer = new MockContainer(); ActionContext.getContext().setContainer(mockContainer); + Map acContextMap = ActionContext.getContext().getContextMap(); + MockHttpServletRequest request = new MockHttpServletRequest(); + acContextMap.put(HTTP_REQUEST, request); + MockHttpServletResponse response = new MockHttpServletResponse(); + acContextMap.put(HTTP_RESPONSE, response); + // when DefaultActionInvocation deserializable = (DefaultActionInvocation) actionInvocation.deserialize(ActionContext.getContext()); @@ -101,6 +136,20 @@ public void testDeserialization() throws Exception { assertNotNull(actionInvocation.container); assertNotNull(deserializable.container); assertEquals(mockContainer, deserializable.container); + + assertTrue("request should be restored into actionInvocation", + actionInvocation.invocationContext.getContextMap().containsKey(HTTP_REQUEST)); + assertTrue("response should be restored into actionInvocation", + actionInvocation.invocationContext.getContextMap().containsKey(HTTP_RESPONSE)); + assertTrue("request should be restored into deserializable", + deserializable.invocationContext.getContextMap().containsKey(HTTP_REQUEST)); + assertTrue("response should be restored into deserializable", + deserializable.invocationContext.getContextMap().containsKey(HTTP_RESPONSE)); + + assertEquals(request, actionInvocation.invocationContext.getContextMap().get(HTTP_REQUEST)); + assertEquals(response, actionInvocation.invocationContext.getContextMap().get(HTTP_RESPONSE)); + assertEquals(request, deserializable.invocationContext.getContextMap().get(HTTP_REQUEST)); + assertEquals(response, deserializable.invocationContext.getContextMap().get(HTTP_RESPONSE)); } public void testInvokingExistingExecuteMethod() throws Exception { diff --git a/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java b/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java new file mode 100644 index 0000000000..4969e85fca --- /dev/null +++ b/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.struts2.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.mock.MockActionInvocation; +import org.apache.struts2.StrutsInternalTestCase; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; + +/** + * Test case for BackgroundProcessTest. + */ +public class BackgroundProcessTest extends StrutsInternalTestCase { + + public void testIsSerializable() throws Exception { + MockActionInvocation invocation = new MockActionInvocation(); + invocation.setResultCode("BackgroundProcessTest.testIsSerializable"); + invocation.setInvocationContext(ActionContext.getContext()); + + BackgroundProcess bp = new BackgroundProcess("BackgroundProcessTest.testIsSerializable", invocation + , Thread.MIN_PRIORITY); + + bp.exception = new Exception(); + bp.done = true; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(bp); + oos.close(); + assertTrue("should have serialized data", baos.size() > 0); + baos.close(); + } +}