From 09eb2860207f9237b3326ea2100591771dadf3a5 Mon Sep 17 00:00:00 2001
From: JCgH4164838Gh792C124B5
<43964333+JCgH4164838Gh792C124B5@users.noreply.github.com>
Date: Sun, 6 Oct 2024 15:37:16 -0400
Subject: [PATCH 01/33] Initial Commit: - Fix for boundary condition bug in
JakartaMultipartRequest that results in a NPE when
struts.multipart.maxStringLength is not explicitly set, and normal fields are
processed along with a file upload. - Additional unit tests for file upload
interceptors to confirm functionality with-or-without max parameters being
set when a file upload is processed alone as well as with normal fields.
---
.../multipart/JakartaMultiPartRequest.java | 2 +-
.../ActionFileUploadInterceptorTest.java | 151 +++++++++++++++
.../FileUploadInterceptorTest.java | 181 +++++++++++++++++-
3 files changed, 332 insertions(+), 2 deletions(-)
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
index 491d3d41d..c2cc07dbb 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
@@ -142,7 +142,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
}
long size = item.getSize();
- if (size > maxStringLength) {
+ if (maxStringLength != null && size > maxStringLength) {
LOG.debug("Form field {} of size {} bytes exceeds limit of {}.", sanitizeNewlines(item.getFieldName()), size, maxStringLength);
String errorKey = "struts.messages.upload.error.parameter.too.long";
LocalizedMessage localizedMessage = new LocalizedMessage(this.getClass(), errorKey, null,
diff --git a/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java
index 1fbb5017b..81aa122ed 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java
@@ -324,6 +324,148 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
assertNotNull("deleteme.txt", files.get(0).getOriginalName());
}
+ public void testSuccessUploadOfATextFileMultipartRequestNoMaxParamsSet() throws Exception {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setMethod("post");
+ req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
+
+ // inspired by the unit tests for jakarta commons fileupload
+ String content = ("-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
+ "Content-Type: text/html\r\n" +
+ "\r\n" +
+ "Unit test of ActionFileUploadInterceptor" +
+ "\r\n" +
+ "-----1234--\r\n");
+ req.setContent(content.getBytes(StandardCharsets.US_ASCII));
+
+ MyFileUploadAction action = new MyFileUploadAction();
+
+ MockActionInvocation mai = new MockActionInvocation();
+ mai.setAction(action);
+ mai.setResultCode("success");
+ mai.setInvocationContext(ActionContext.getContext());
+ ActionContext.getContext().withServletRequest(createMultipartRequestNoMaxParamsSet(req));
+
+ interceptor.intercept(mai);
+
+ assertFalse(action.hasErrors());
+
+ List files = action.getUploadFiles();
+
+ assertNotNull(files);
+ assertEquals(1, files.size());
+ assertEquals("text/html", files.get(0).getContentType());
+ assertNotNull("deleteme.txt", files.get(0).getOriginalName());
+ }
+
+ public void testSuccessUploadOfATextFileMultipartRequestWithNormalFieldsMaxParamsSet() throws Exception {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setMethod("post");
+ req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
+
+ // inspired by the unit tests for jakarta commons fileupload
+ String content = ("-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
+ "Content-Type: text/html\r\n" +
+ "\r\n" +
+ "Unit test of ActionFileUploadInterceptor" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
+ "\r\n" +
+ "normal field 1" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
+ "\r\n" +
+ "normal field 2" +
+ "\r\n" +
+ "-----1234--\r\n");
+ req.setContent(content.getBytes(StandardCharsets.US_ASCII));
+
+ MyFileUploadAction action = new MyFileUploadAction();
+
+ MockActionInvocation mai = new MockActionInvocation();
+ mai.setAction(action);
+ mai.setResultCode("success");
+ mai.setInvocationContext(ActionContext.getContext());
+ ActionContext.getContext().withServletRequest(createMultipartRequest(req, 2000, 2000, 5, 100));
+
+ interceptor.intercept(mai);
+
+ assertFalse(action.hasErrors());
+
+ List files = action.getUploadFiles();
+
+ assertNotNull(files);
+ assertEquals(1, files.size());
+ assertEquals("text/html", files.get(0).getContentType());
+ assertNotNull("deleteme.txt", files.get(0).getOriginalName());
+
+ // Confirm normalFormField1, normalFormField2 were processed by the MultiPartRequestWrapper.
+ HttpServletRequest invocationServletRequest = mai.getInvocationContext().getServletRequest();
+ assertTrue("invocation servelt request is not a MultiPartRequestWrapper ?", invocationServletRequest instanceof MultiPartRequestWrapper);
+ MultiPartRequestWrapper multipartRequestWrapper = (MultiPartRequestWrapper) invocationServletRequest;
+ assertNotNull("normalFormField1 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField1"));
+ assertNotNull("normalFormField2 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField2"));
+ }
+
+ public void testSuccessUploadOfATextFileMultipartRequestWithNormalFieldsNoMaxParamsSet() throws Exception {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setMethod("post");
+ req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
+
+ // inspired by the unit tests for jakarta commons fileupload
+ String content = ("-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
+ "Content-Type: text/html\r\n" +
+ "\r\n" +
+ "Unit test of ActionFileUploadInterceptor" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
+ "\r\n" +
+ "normal field 1" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
+ "\r\n" +
+ "normal field 2" +
+ "\r\n" +
+ "-----1234--\r\n");
+ req.setContent(content.getBytes(StandardCharsets.US_ASCII));
+
+ MyFileUploadAction action = new MyFileUploadAction();
+
+ MockActionInvocation mai = new MockActionInvocation();
+ mai.setAction(action);
+ mai.setResultCode("success");
+ mai.setInvocationContext(ActionContext.getContext());
+ ActionContext.getContext().withServletRequest(createMultipartRequestNoMaxParamsSet(req));
+
+ interceptor.intercept(mai);
+
+ assertFalse(action.hasErrors());
+
+ List files = action.getUploadFiles();
+
+ assertNotNull(files);
+ assertEquals(1, files.size());
+ assertEquals("text/html", files.get(0).getContentType());
+ assertNotNull("deleteme.txt", files.get(0).getOriginalName());
+
+ // Confirm normalFormField1, normalFormField2 were processed by the MultiPartRequestWrapper.
+ HttpServletRequest invocationServletRequest = mai.getInvocationContext().getServletRequest();
+ assertTrue("invocation servelt request is not a MultiPartRequestWrapper ?", invocationServletRequest instanceof MultiPartRequestWrapper);
+ MultiPartRequestWrapper multipartRequestWrapper = (MultiPartRequestWrapper) invocationServletRequest;
+ assertNotNull("normalFormField1 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField1"));
+ assertNotNull("normalFormField2 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField2"));
+ }
+
/**
* tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
* ActionFileUploadInterceptor.setAllowedTypes(...) ) are sorted out.
@@ -564,6 +706,12 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
}
+ private MultiPartRequestWrapper createMultipartRequestNoMaxParamsSet(HttpServletRequest req) {
+
+ JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
+ return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
+ }
+
protected void setUp() throws Exception {
super.setUp();
@@ -583,6 +731,9 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
public static class MyFileUploadAction extends ActionSupport implements UploadedFilesAware {
private List uploadedFiles;
+ // Note: We do not currently need fields/getters/setters for normalFormField1, normalFormField2 since
+ // the upload interceptor only prepares the normal field parameters.
+
@Override
public void withUploadedFiles(List uploadedFiles) {
this.uploadedFiles = uploadedFiles;
diff --git a/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
index 040a2f61a..36fad5847 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
@@ -329,6 +329,178 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
assertNotNull("deleteme.txt", fileRealFilenames[0]);
}
+ public void testSuccessUploadOfATextFileMultipartRequestNoMaxParamsSet() throws Exception {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setMethod("post");
+ req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
+
+ // inspired by the unit tests for jakarta commons fileupload
+ String content = ("-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
+ "Content-Type: text/html\r\n" +
+ "\r\n" +
+ "Unit test of FileUploadInterceptor" +
+ "\r\n" +
+ "-----1234--\r\n");
+ req.setContent(content.getBytes(StandardCharsets.US_ASCII));
+
+ MyFileupAction action = new MyFileupAction();
+
+ MockActionInvocation mai = new MockActionInvocation();
+ mai.setAction(action);
+ mai.setResultCode("success");
+ mai.setInvocationContext(ActionContext.getContext());
+ Map param = new HashMap<>();
+ ActionContext.getContext().withParameters(HttpParameters.create(param).build());
+ ActionContext.getContext().withServletRequest(createMultipartRequestNoMaxParamsSet(req));
+
+ interceptor.intercept(mai);
+
+ assertFalse(action.hasErrors());
+
+ HttpParameters parameters = mai.getInvocationContext().getParameters();
+ assertEquals(3, parameters.keySet().size());
+ UploadedFile[] files = (UploadedFile[]) parameters.get("file").getObject();
+ String[] fileContentTypes = parameters.get("fileContentType").getMultipleValues();
+ String[] fileRealFilenames = parameters.get("fileFileName").getMultipleValues();
+
+ assertNotNull(files);
+ assertNotNull(fileContentTypes);
+ assertNotNull(fileRealFilenames);
+ assertEquals(1, files.length);
+ assertEquals(1, fileContentTypes.length);
+ assertEquals(1, fileRealFilenames.length);
+ assertEquals("text/html", fileContentTypes[0]);
+ assertNotNull("deleteme.txt", fileRealFilenames[0]);
+ }
+
+ public void testSuccessUploadOfATextFileMultipartRequestWithNormalFieldsMaxParamsSet() throws Exception {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setMethod("post");
+ req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
+
+ // inspired by the unit tests for jakarta commons fileupload
+ String content = ("-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
+ "Content-Type: text/html\r\n" +
+ "\r\n" +
+ "Unit test of FileUploadInterceptor" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
+ "\r\n" +
+ "normal field 1" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
+ "\r\n" +
+ "normal field 2" +
+ "\r\n" +
+ "-----1234--\r\n");
+ req.setContent(content.getBytes(StandardCharsets.US_ASCII));
+
+ MyFileupAction action = new MyFileupAction();
+
+ MockActionInvocation mai = new MockActionInvocation();
+ mai.setAction(action);
+ mai.setResultCode("success");
+ mai.setInvocationContext(ActionContext.getContext());
+ Map param = new HashMap<>();
+ ActionContext.getContext().withParameters(HttpParameters.create(param).build());
+ ActionContext.getContext().withServletRequest(createMultipartRequest(req, 2000, 2000, 5, 100));
+
+ interceptor.intercept(mai);
+
+ assertFalse(action.hasErrors());
+
+ HttpParameters parameters = mai.getInvocationContext().getParameters();
+ assertEquals(3, parameters.keySet().size());
+ UploadedFile[] files = (UploadedFile[]) parameters.get("file").getObject();
+ String[] fileContentTypes = parameters.get("fileContentType").getMultipleValues();
+ String[] fileRealFilenames = parameters.get("fileFileName").getMultipleValues();
+
+ assertNotNull(files);
+ assertNotNull(fileContentTypes);
+ assertNotNull(fileRealFilenames);
+ assertEquals(1, files.length);
+ assertEquals(1, fileContentTypes.length);
+ assertEquals(1, fileRealFilenames.length);
+ assertEquals("text/html", fileContentTypes[0]);
+ assertNotNull("deleteme.txt", fileRealFilenames[0]);
+
+ // Confirm normalFormField1, normalFormField2 were processed by the MultiPartRequestWrapper.
+ HttpServletRequest invocationServletRequest = mai.getInvocationContext().getServletRequest();
+ assertTrue("invocation servelt request is not a MultiPartRequestWrapper ?", invocationServletRequest instanceof MultiPartRequestWrapper);
+ MultiPartRequestWrapper multipartRequestWrapper = (MultiPartRequestWrapper) invocationServletRequest;
+ assertNotNull("normalFormField1 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField1"));
+ assertNotNull("normalFormField2 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField2"));
+ }
+
+ public void testSuccessUploadOfATextFileMultipartRequestWithNormalFieldsNoMaxParamsSet() throws Exception {
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ req.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ req.setMethod("post");
+ req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
+
+ // inspired by the unit tests for jakarta commons fileupload
+ String content = ("-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
+ "Content-Type: text/html\r\n" +
+ "\r\n" +
+ "Unit test of FileUploadInterceptor" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
+ "\r\n" +
+ "normal field 1 with no max parameters set" +
+ "\r\n" +
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
+ "\r\n" +
+ "normal field 2 with no max parameters set" +
+ "\r\n" +
+ "-----1234--\r\n");
+ req.setContent(content.getBytes(StandardCharsets.US_ASCII));
+
+ MyFileupAction action = new MyFileupAction();
+
+ MockActionInvocation mai = new MockActionInvocation();
+ mai.setAction(action);
+ mai.setResultCode("success");
+ mai.setInvocationContext(ActionContext.getContext());
+ Map param = new HashMap<>();
+ ActionContext.getContext().withParameters(HttpParameters.create(param).build());
+ ActionContext.getContext().withServletRequest(createMultipartRequestNoMaxParamsSet(req));
+
+ interceptor.intercept(mai);
+
+ assertFalse(action.hasErrors());
+
+ HttpParameters parameters = mai.getInvocationContext().getParameters();
+ assertEquals(3, parameters.keySet().size());
+ UploadedFile[] files = (UploadedFile[]) parameters.get("file").getObject();
+ String[] fileContentTypes = parameters.get("fileContentType").getMultipleValues();
+ String[] fileRealFilenames = parameters.get("fileFileName").getMultipleValues();
+
+ assertNotNull(files);
+ assertNotNull(fileContentTypes);
+ assertNotNull(fileRealFilenames);
+ assertEquals(1, files.length);
+ assertEquals(1, fileContentTypes.length);
+ assertEquals(1, fileRealFilenames.length);
+ assertEquals("text/html", fileContentTypes[0]);
+ assertNotNull("deleteme.txt", fileRealFilenames[0]);
+
+ // Confirm normalFormField1, normalFormField2 were processed by the MultiPartRequestWrapper.
+ HttpServletRequest invocationServletRequest = mai.getInvocationContext().getServletRequest();
+ assertTrue("invocation servelt request is not a MultiPartRequestWrapper ?", invocationServletRequest instanceof MultiPartRequestWrapper);
+ MultiPartRequestWrapper multipartRequestWrapper = (MultiPartRequestWrapper) invocationServletRequest;
+ assertNotNull("normalFormField1 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField1"));
+ assertNotNull("normalFormField2 missing from MultiPartRequestWrapper parameters ?", multipartRequestWrapper.getParameter("normalFormField2"));
+ }
+
/**
* tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
* FileUploadInterceptor.setAllowedTypes(...) ) are sorted out.
@@ -598,6 +770,12 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
}
+ private MultiPartRequestWrapper createMultipartRequestNoMaxParamsSet(HttpServletRequest req) {
+
+ JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
+ return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -621,7 +799,8 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
private static final long serialVersionUID = 6255238895447968889L;
// no methods
+ // Note: We do not currently need fields/getters/setters for normalFormField1, normalFormField2 since
+ // the upload interceptor only prepares the normal field parameters.
}
-
}
From 49ddf6130a4cc28bddee84c2d56887c138991713 Mon Sep 17 00:00:00 2001
From: Lukasz Lenart
Date: Mon, 14 Oct 2024 07:50:00 +0200
Subject: [PATCH 02/33] WW-5471 Marks Sitemesh plugin as deprecated
---
plugins/sitemesh/pom.xml | 2 +-
.../apache/struts2/sitemesh/FreemarkerDecoratorServlet.java | 4 ++++
.../org/apache/struts2/sitemesh/VelocityDecoratorServlet.java | 4 ++++
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/plugins/sitemesh/pom.xml b/plugins/sitemesh/pom.xml
index 0c3d1a35a..11db635f4 100644
--- a/plugins/sitemesh/pom.xml
+++ b/plugins/sitemesh/pom.xml
@@ -29,7 +29,7 @@
struts2-sitemesh-pluginjar
- Struts 2 Sitemesh Plugin
+ DEPRECATED: Struts 2 Sitemesh Plugin
diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/FreemarkerDecoratorServlet.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/FreemarkerDecoratorServlet.java
index b6bd1ac74..a63b7c0fa 100644
--- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/FreemarkerDecoratorServlet.java
+++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/FreemarkerDecoratorServlet.java
@@ -53,7 +53,10 @@ import java.util.Locale;
*
It overrides the SiteMesh servlet to rely on the
* Freemarker Manager in Struts instead of creating it's
* own manager
+ *
+ * @deprecated Sitemesh 2 based plugin is not supported anymore
*/
+@Deprecated
public class FreemarkerDecoratorServlet extends freemarker.ext.servlet.FreemarkerServlet {
private static final Logger LOG = LogManager.getLogger(FreemarkerDecoratorServlet.class);
@@ -69,6 +72,7 @@ public class FreemarkerDecoratorServlet extends freemarker.ext.servlet.Freemarke
private boolean noCharsetInContentType;
public void init() throws ServletException {
+ LOG.warn("This plugin is deprecated. Please migrate to a plugin which bases on Sitemesh 3!");
try {
Dispatcher dispatcher = Dispatcher.getInstance(getServletContext());
if (dispatcher == null) {
diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java
index b2079c2f2..32f202913 100644
--- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java
+++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java
@@ -53,7 +53,10 @@ import java.io.StringWriter;
*
It overrides the SiteMesh servlet to rely on the
* Velocity Manager in Struts instead of creating it's
* own manager
+ *
+ * @deprecated Sitemesh 2 based plugin is not supported anymore
*/
+@Deprecated
public class VelocityDecoratorServlet extends VelocityViewServlet {
private static final Logger LOG = LogManager.getLogger(VelocityDecoratorServlet.class);
@@ -76,6 +79,7 @@ public class VelocityDecoratorServlet extends VelocityViewServlet {
* @param config servlet configuration
*/
public void init(ServletConfig config) throws ServletException {
+ LOG.warn("This plugin is deprecated. Please migrate to a plugin which bases on Sitemesh 3!");
super.init(config);
Dispatcher dispatcher = Dispatcher.getInstance(getServletContext());
if (dispatcher == null) {
From 7deb4812982af9ee073eead29cc359d0db57bebf Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 13:12:57 +1100
Subject: [PATCH 03/33] WW-3714 Deprecate and migrate Action, Interceptor,
Result
---
.../java/com/opensymphony/xwork2/Action.java | 70 +-----
.../java/com/opensymphony/xwork2/Result.java | 30 +--
.../xwork2/interceptor/Interceptor.java | 202 +---------------
.../main/java/org/apache/struts2/Action.java | 88 +++++++
.../main/java/org/apache/struts2/Result.java | 53 +++++
.../struts2/interceptor/Interceptor.java | 222 ++++++++++++++++++
...onfigurationProviderOgnlAllowlistTest.java | 15 +-
7 files changed, 388 insertions(+), 292 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/Action.java
create mode 100644 core/src/main/java/org/apache/struts2/Result.java
create mode 100644 core/src/main/java/org/apache/struts2/interceptor/Interceptor.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/Action.java b/core/src/main/java/com/opensymphony/xwork2/Action.java
index 4c96617c4..57d767834 100644
--- a/core/src/main/java/com/opensymphony/xwork2/Action.java
+++ b/core/src/main/java/com/opensymphony/xwork2/Action.java
@@ -19,70 +19,10 @@
package com.opensymphony.xwork2;
/**
- * All actions may implement this interface, which exposes the execute() method.
- *
- * However, as of XWork 1.1, this is not required and is only here to assist users. You are free to create POJOs
- * that honor the same contract defined by this interface without actually implementing the interface.
- *
+ * {@inheritDoc}
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.Action} instead.
*/
-public interface Action {
-
- /**
- * The action execution was successful. Show result
- * view to the end user.
- */
- public static final String SUCCESS = "success";
-
- /**
- * The action execution was successful but do not
- * show a view. This is useful for actions that are
- * handling the view in another fashion like redirect.
- */
- public static final String NONE = "none";
-
- /**
- * The action execution was a failure.
- * Show an error view, possibly asking the
- * user to retry entering data.
- */
- public static final String ERROR = "error";
-
- /**
- *
- * The action execution require more input
- * in order to succeed.
- * This result is typically used if a form
- * handling action has been executed so as
- * to provide defaults for a form. The
- * form associated with the handler should be
- * shown to the end user.
- *
- *
- *
- * This result is also used if the given input
- * params are invalid, meaning the user
- * should try providing input again.
- *
- */
- public static final String INPUT = "input";
-
- /**
- * The action could not execute, since the
- * user most was not logged in. The login view
- * should be shown.
- */
- public static final String LOGIN = "login";
-
-
- /**
- * Where the logic of the action is executed.
- *
- * @return a string representing the logical result of the execution.
- * See constants in this interface for a list of standard result values.
- * @throws Exception thrown if a system level exception occurs.
- * Note: Application level exceptions should be handled by returning
- * an error value, such as Action.ERROR.
- */
- public String execute() throws Exception;
-
+@Deprecated
+public interface Action extends org.apache.struts2.Action {
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/Result.java b/core/src/main/java/com/opensymphony/xwork2/Result.java
index 8c1687e5a..ccd238751 100644
--- a/core/src/main/java/com/opensymphony/xwork2/Result.java
+++ b/core/src/main/java/com/opensymphony/xwork2/Result.java
@@ -18,33 +18,11 @@
*/
package com.opensymphony.xwork2;
-import java.io.Serializable;
-
/**
- * All results (except for Action.NONE) of an {@link Action} are mapped to a View implementation.
+ * {@inheritDoc}
*
- *
- * Examples of Views might be:
- *
- *
- *
- *
SwingPanelView - pops up a new Swing panel
- *
ActionChainView - executes another action
- *
SerlvetRedirectView - redirects the HTTP response to a URL
- *
ServletDispatcherView - dispatches the HTTP response to a URL
- *
- *
- * @author plightbo
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.Result} instead.
*/
-public interface Result extends Serializable {
-
- /**
- * Represents a generic interface for all action execution results.
- * Whether that be displaying a webpage, generating an email, sending a JMS message, etc.
- *
- * @param invocation the invocation context.
- * @throws Exception can be thrown.
- */
- void execute(ActionInvocation invocation) throws Exception;
-
+@Deprecated
+public interface Result extends org.apache.struts2.Result {
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
index cafa08fc0..87041a269 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
@@ -18,205 +18,11 @@
*/
package com.opensymphony.xwork2.interceptor;
-import com.opensymphony.xwork2.ActionInvocation;
-
-import java.io.Serializable;
-
/**
- *
+ * {@inheritDoc}
*
- *
- * An interceptor is a stateless class that follows the interceptor pattern, as
- * found in {@link javax.servlet.Filter} and in AOP languages.
- *
- *
- *
- * Interceptors are objects that dynamically intercept Action invocations.
- * They provide the developer with the opportunity to define code that can be executed
- * before and/or after the execution of an action. They also have the ability
- * to prevent an action from executing. Interceptors provide developers a way to
- * encapsulate common functionality in a re-usable form that can be applied to
- * one or more Actions.
- *
- *
- *
- * Interceptors must be stateless and not assume that a new instance will be created for each request or Action.
- * Interceptors may choose to either short-circuit the {@link ActionInvocation} execution and return a return code
- * (such as {@link com.opensymphony.xwork2.Action#SUCCESS}), or it may choose to do some processing before
- * and/or after delegating the rest of the procesing using {@link ActionInvocation#invoke()}.
- *
- *
- *
- *
- *
- * Interceptor's parameter could be overridden through the following ways :-
- *
- * In the first method, the whole default stack is copied and the parameter then
- * changed accordingly.
- *
- *
- *
- * In the second method, the 'interceptor-ref' refer to an existing
- * interceptor-stack, namely defaultStack in this example, and override the validator
- * and workflow interceptor excludeMethods typically in this case. Note that in the
- * 'param' tag, the name attribute contains a dot (.) the word before the dot(.)
- * specifies the interceptor name whose parameter is to be overridden and the word after
- * the dot (.) specifies the parameter itself. Essetially it is as follows :-
- *
- *
- *
- * <interceptor-name>.<parameter-name>
- *
- *
- * Note also that in this case the 'interceptor-ref' name attribute
- * is used to indicate an interceptor stack which makes sense as if it is referring
- * to the interceptor itself it would be just using Method 1 describe above.
- *
- *
- *
- *
- * Nested Interceptor param overriding
- *
- *
- *
- *
- * Interceptor stack parameter overriding could be nested into as many level as possible, though it would
- * be advisable not to nest it too deep as to avoid confusion, For example,
- *
- * Assuming the interceptor has the following properties
- *
- *
- *
- *
- *
Interceptor
- *
property
- *
- *
- *
Interceptor1
- *
param1
- *
- *
- *
Interceptor2
- *
param2
- *
- *
- *
Interceptor3
- *
param3
- *
- *
- *
Interceptor4
- *
param4
- *
- *
- *
- *
- * We could override them as follows :
- *
- *
- *
- * <action ... >
- * <!-- to override parameters of interceptor located directly in the stack -->
- * <interceptor-ref name="stack4">
- * <param name="interceptor4.param4"> ... </param>
- * </interceptor-ref>
- * </action>
- *
- * <action ... >
- * <!-- to override parameters of interceptor located under nested stack -->
- * <interceptor-ref name="stack4">
- * <param name="stack3.interceptor3.param3"> ... </param>
- * <param name="stack3.stack2.interceptor2.param2"> ... </param>
- * <param name="stack3.stack2.stack1.interceptor1.param1"> ... </param>
- * </interceptor-ref>
- * </action>
- *
- *
- *
- *
- * @author Jason Carreira
- * @author tmjee
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.Interceptor} instead.
*/
-public interface Interceptor extends Serializable {
-
- /**
- * Called to let an interceptor clean up any resources it has allocated.
- */
- void destroy();
-
- /**
- * Called after an interceptor is created, but before any requests are processed using
- * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving
- * the Interceptor a chance to initialize any needed resources.
- */
- void init();
-
- /**
- * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the
- * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code.
- *
- * @param invocation the action invocation
- * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself.
- * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}.
- */
- String intercept(ActionInvocation invocation) throws Exception;
-
+@Deprecated
+public interface Interceptor extends org.apache.struts2.interceptor.Interceptor {
}
diff --git a/core/src/main/java/org/apache/struts2/Action.java b/core/src/main/java/org/apache/struts2/Action.java
new file mode 100644
index 000000000..cc3fb83ac
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/Action.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+/**
+ * All actions may implement this interface, which exposes the execute() method.
+ *
+ * However, as of XWork 1.1, this is not required and is only here to assist users. You are free to create POJOs
+ * that honor the same contract defined by this interface without actually implementing the interface.
+ *
+ */
+public interface Action {
+
+ /**
+ * The action execution was successful. Show result
+ * view to the end user.
+ */
+ String SUCCESS = "success";
+
+ /**
+ * The action execution was successful but do not
+ * show a view. This is useful for actions that are
+ * handling the view in another fashion like redirect.
+ */
+ String NONE = "none";
+
+ /**
+ * The action execution was a failure.
+ * Show an error view, possibly asking the
+ * user to retry entering data.
+ */
+ String ERROR = "error";
+
+ /**
+ *
+ * The action execution require more input
+ * in order to succeed.
+ * This result is typically used if a form
+ * handling action has been executed so as
+ * to provide defaults for a form. The
+ * form associated with the handler should be
+ * shown to the end user.
+ *
+ *
+ *
+ * This result is also used if the given input
+ * params are invalid, meaning the user
+ * should try providing input again.
+ *
+ */
+ String INPUT = "input";
+
+ /**
+ * The action could not execute, since the
+ * user most was not logged in. The login view
+ * should be shown.
+ */
+ String LOGIN = "login";
+
+
+ /**
+ * Where the logic of the action is executed.
+ *
+ * @return a string representing the logical result of the execution.
+ * See constants in this interface for a list of standard result values.
+ * @throws Exception thrown if a system level exception occurs.
+ * Note: Application level exceptions should be handled by returning
+ * an error value, such as Action.ERROR.
+ */
+ String execute() throws Exception;
+
+}
diff --git a/core/src/main/java/org/apache/struts2/Result.java b/core/src/main/java/org/apache/struts2/Result.java
new file mode 100644
index 000000000..4dc912772
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/Result.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionInvocation;
+
+import java.io.Serializable;
+
+/**
+ * All results (except for Action.NONE) of an {@link Action} are mapped to a View implementation.
+ *
+ *
+ * Examples of Views might be:
+ *
+ *
+ *
+ *
SwingPanelView - pops up a new Swing panel
+ *
ActionChainView - executes another action
+ *
SerlvetRedirectView - redirects the HTTP response to a URL
+ *
ServletDispatcherView - dispatches the HTTP response to a URL
+ *
+ *
+ * @author plightbo
+ */
+public interface Result extends Serializable {
+
+ /**
+ * Represents a generic interface for all action execution results.
+ * Whether that be displaying a webpage, generating an email, sending a JMS message, etc.
+ *
+ * @param invocation the invocation context.
+ * @throws Exception can be thrown.
+ */
+ void execute(ActionInvocation invocation) throws Exception;
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java b/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java
new file mode 100644
index 000000000..9c36bde77
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java
@@ -0,0 +1,222 @@
+/*
+ * 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.ActionInvocation;
+
+import java.io.Serializable;
+
+/**
+ *
+ *
+ *
+ * An interceptor is a stateless class that follows the interceptor pattern, as
+ * found in {@link javax.servlet.Filter} and in AOP languages.
+ *
+ *
+ *
+ * Interceptors are objects that dynamically intercept Action invocations.
+ * They provide the developer with the opportunity to define code that can be executed
+ * before and/or after the execution of an action. They also have the ability
+ * to prevent an action from executing. Interceptors provide developers a way to
+ * encapsulate common functionality in a re-usable form that can be applied to
+ * one or more Actions.
+ *
+ *
+ *
+ * Interceptors must be stateless and not assume that a new instance will be created for each request or Action.
+ * Interceptors may choose to either short-circuit the {@link ActionInvocation} execution and return a return code
+ * (such as {@link com.opensymphony.xwork2.Action#SUCCESS}), or it may choose to do some processing before
+ * and/or after delegating the rest of the procesing using {@link ActionInvocation#invoke()}.
+ *
+ *
+ *
+ *
+ *
+ * Interceptor's parameter could be overridden through the following ways :-
+ *
+ * In the first method, the whole default stack is copied and the parameter then
+ * changed accordingly.
+ *
+ *
+ *
+ * In the second method, the 'interceptor-ref' refer to an existing
+ * interceptor-stack, namely defaultStack in this example, and override the validator
+ * and workflow interceptor excludeMethods typically in this case. Note that in the
+ * 'param' tag, the name attribute contains a dot (.) the word before the dot(.)
+ * specifies the interceptor name whose parameter is to be overridden and the word after
+ * the dot (.) specifies the parameter itself. Essetially it is as follows :-
+ *
+ *
+ *
+ * <interceptor-name>.<parameter-name>
+ *
+ *
+ * Note also that in this case the 'interceptor-ref' name attribute
+ * is used to indicate an interceptor stack which makes sense as if it is referring
+ * to the interceptor itself it would be just using Method 1 describe above.
+ *
+ *
+ *
+ *
+ * Nested Interceptor param overriding
+ *
+ *
+ *
+ *
+ * Interceptor stack parameter overriding could be nested into as many level as possible, though it would
+ * be advisable not to nest it too deep as to avoid confusion, For example,
+ *
+ * Assuming the interceptor has the following properties
+ *
+ *
+ *
+ *
+ *
Interceptor
+ *
property
+ *
+ *
+ *
Interceptor1
+ *
param1
+ *
+ *
+ *
Interceptor2
+ *
param2
+ *
+ *
+ *
Interceptor3
+ *
param3
+ *
+ *
+ *
Interceptor4
+ *
param4
+ *
+ *
+ *
+ *
+ * We could override them as follows :
+ *
+ *
+ *
+ * <action ... >
+ * <!-- to override parameters of interceptor located directly in the stack -->
+ * <interceptor-ref name="stack4">
+ * <param name="interceptor4.param4"> ... </param>
+ * </interceptor-ref>
+ * </action>
+ *
+ * <action ... >
+ * <!-- to override parameters of interceptor located under nested stack -->
+ * <interceptor-ref name="stack4">
+ * <param name="stack3.interceptor3.param3"> ... </param>
+ * <param name="stack3.stack2.interceptor2.param2"> ... </param>
+ * <param name="stack3.stack2.stack1.interceptor1.param1"> ... </param>
+ * </interceptor-ref>
+ * </action>
+ *
+ *
+ *
+ *
+ * @author Jason Carreira
+ * @author tmjee
+ */
+public interface Interceptor extends Serializable {
+
+ /**
+ * Called to let an interceptor clean up any resources it has allocated.
+ */
+ void destroy();
+
+ /**
+ * Called after an interceptor is created, but before any requests are processed using
+ * {@link #intercept(ActionInvocation) intercept} , giving
+ * the Interceptor a chance to initialize any needed resources.
+ */
+ void init();
+
+ /**
+ * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the
+ * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code.
+ *
+ * @param invocation the action invocation
+ * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself.
+ * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}.
+ */
+ String intercept(ActionInvocation invocation) throws Exception;
+
+}
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java
index 4fa4aad8b..77d6bbb22 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java
@@ -61,7 +61,10 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase
Class.forName("com.opensymphony.xwork2.Action"),
Class.forName("com.opensymphony.xwork2.interceptor.AbstractInterceptor"),
Class.forName("com.opensymphony.xwork2.Result"),
- Class.forName("com.opensymphony.xwork2.SimpleAction")
+ Class.forName("com.opensymphony.xwork2.SimpleAction"),
+ Class.forName("org.apache.struts2.interceptor.Interceptor"),
+ Class.forName("org.apache.struts2.Result"),
+ Class.forName("org.apache.struts2.Action")
);
}
@@ -85,7 +88,10 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase
Class.forName("com.opensymphony.xwork2.Action"),
Class.forName("com.opensymphony.xwork2.interceptor.AbstractInterceptor"),
Class.forName("com.opensymphony.xwork2.Result"),
- Class.forName("com.opensymphony.xwork2.SimpleAction")
+ Class.forName("com.opensymphony.xwork2.SimpleAction"),
+ Class.forName("org.apache.struts2.interceptor.Interceptor"),
+ Class.forName("org.apache.struts2.Result"),
+ Class.forName("org.apache.struts2.Action")
);
}
@@ -108,7 +114,10 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase
Class.forName("com.opensymphony.xwork2.Validateable"),
Class.forName("com.opensymphony.xwork2.Action"),
Class.forName("com.opensymphony.xwork2.interceptor.AbstractInterceptor"),
- Class.forName("com.opensymphony.xwork2.Result")
+ Class.forName("com.opensymphony.xwork2.Result"),
+ Class.forName("org.apache.struts2.interceptor.Interceptor"),
+ Class.forName("org.apache.struts2.Result"),
+ Class.forName("org.apache.struts2.Action")
);
}
}
From 8da6a79926b5bf04cc376fb639c1da1a0d587b48 Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 13:13:28 +1100
Subject: [PATCH 04/33] WW-3714 Deprecate and migrate ActionContext
---
.../opensymphony/xwork2/ActionContext.java | 459 +++------------
.../org/apache/struts2/ActionContext.java | 554 ++++++++++++++++++
2 files changed, 644 insertions(+), 369 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/ActionContext.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
index fb7b0abcb..c2a4a39c2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
@@ -21,8 +21,6 @@ package com.opensymphony.xwork2;
import com.opensymphony.xwork2.conversion.impl.ConversionData;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.util.ValueStack;
-import org.apache.struts2.StrutsException;
-import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
@@ -30,515 +28,238 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;
-import java.io.Serializable;
-import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
- *
- * The ActionContext is the context in which an {@link Action} is executed. Each context is basically a
- * container of objects an action needs for execution like the session, parameters, locale, etc.
- *
+ * {@inheritDoc}
*
- *
- * The ActionContext is thread local which means that values stored in the ActionContext are
- * unique per thread. See the {@link ThreadLocal} class for more information. The benefit of
- * this is you don't need to worry about a user specific action context, you just get it:
- *
- * Finally, because of the thread local usage you don't need to worry about making your actions thread safe.
- *
- *
- * @author Patrick Lightbody
- * @author Bill Lynch (docs)
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.ActionContext} instead.
*/
-public class ActionContext implements Serializable {
+@Deprecated
+public class ActionContext extends org.apache.struts2.ActionContext {
- private static final ThreadLocal actionContext = new ThreadLocal<>();
-
- /**
- * Constant for the name of the action being executed.
- */
- private static final String ACTION_NAME = "org.apache.struts2.ActionContext.name";
-
- /**
- * Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}.
- */
- private static final String VALUE_STACK = ValueStack.VALUE_STACK;
-
- /**
- * Constant for the action's session.
- */
- private static final String SESSION = "org.apache.struts2.ActionContext.session";
-
- /**
- * Constant for the action's application context.
- */
- private static final String APPLICATION = "org.apache.struts2.ActionContext.application";
-
- /**
- * Constant for the action's parameters.
- */
- private static final String PARAMETERS = "org.apache.struts2.ActionContext.parameters";
-
- /**
- * Constant for the action's locale.
- */
- private static final String LOCALE = "org.apache.struts2.ActionContext.locale";
-
- /**
- * Constant for the action's {@link com.opensymphony.xwork2.ActionInvocation invocation} context.
- */
- private static final String ACTION_INVOCATION = "org.apache.struts2.ActionContext.actionInvocation";
-
- /**
- * Constant for the map of type conversion errors.
- */
- private static final String CONVERSION_ERRORS = "org.apache.struts2.ActionContext.conversionErrors";
-
- /**
- * Constant for the container
- */
- private static final String CONTAINER = "org.apache.struts2.ActionContext.container";
-
- private final Map context;
-
- /**
- * Creates a new ActionContext initialized with another context.
- *
- * @param context a context map.
- */
- protected ActionContext(Map context) {
- this.context = context;
+ private ActionContext(org.apache.struts2.ActionContext actualContext) {
+ super(actualContext.getContextMap());
+ }
+
+ private static ActionContext adapt(org.apache.struts2.ActionContext actualContext) {
+ return actualContext != null ? new ActionContext(actualContext) : null;
}
- /**
- * Creates a new ActionContext based on passed in Map
- *
- * @param context a map with context values
- * @return new ActionContext
- */
public static ActionContext of(Map context) {
- if (context == null) {
- throw new IllegalArgumentException("Context cannot be null!");
- }
- return new ActionContext(context);
+ return adapt(org.apache.struts2.ActionContext.of(context));
}
- /**
- * Creates a new ActionContext based on empty Map
- *
- * @return new ActionContext
- */
public static ActionContext of() {
- return of(new HashMap<>());
+ return adapt(org.apache.struts2.ActionContext.of());
}
- /**
- * Binds the provided context with the current thread
- *
- * @param actionContext context to bind to the thread
- * @return context which was bound to the thread
- */
public static ActionContext bind(ActionContext actionContext) {
- ActionContext.setContext(actionContext);
- return ActionContext.getContext();
+ return adapt(org.apache.struts2.ActionContext.bind(actionContext));
}
public static boolean containsValueStack(Map context) {
- return context != null && context.containsKey(VALUE_STACK);
+ return org.apache.struts2.ActionContext.containsValueStack(context);
}
- /**
- * Binds this context with the current thread
- *
- * @return this context which was bound to the thread
- */
- public ActionContext bind() {
- ActionContext.setContext(this);
- return ActionContext.getContext();
- }
-
- /**
- * Wipes out current ActionContext, use wisely!
- */
public static void clear() {
- actionContext.remove();
+ org.apache.struts2.ActionContext.clear();
}
- /**
- * Sets the action context for the current thread.
- *
- * @param context the action context.
- */
- private static void setContext(ActionContext context) {
- actionContext.set(context);
- }
-
- /**
- * Returns the ActionContext specific to the current thread.
- *
- * @return the ActionContext for the current thread, is never null.
- */
public static ActionContext getContext() {
- return actionContext.get();
+ return adapt(org.apache.struts2.ActionContext.getContext());
}
- /**
- * Sets the action invocation (the execution state).
- *
- * @param actionInvocation the action execution state.
- */
+ @Override
+ public ActionContext bind() {
+ super.bind();
+ return this;
+ }
+
+ @Override
public ActionContext withActionInvocation(ActionInvocation actionInvocation) {
- put(ACTION_INVOCATION, actionInvocation);
+ super.withActionInvocation(actionInvocation);
return this;
}
- /**
- * Gets the action invocation (the execution state).
- *
- * @return the action invocation (the execution state).
- */
+ @Override
public ActionInvocation getActionInvocation() {
- return (ActionInvocation) get(ACTION_INVOCATION);
+ return super.getActionInvocation();
}
- /**
- * Sets the action's application context.
- *
- * @param application the action's application context.
- */
+ @Override
public ActionContext withApplication(Map application) {
- put(APPLICATION, application);
+ super.withApplication(application);
return this;
}
- /**
- * Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise.
- *
- * @return a Map of ServletContext or generic application level Map
- */
- @SuppressWarnings("unchecked")
+ @Override
public Map getApplication() {
- return (Map) get(APPLICATION);
+ return super.getApplication();
}
- /**
- * Gets the context map.
- *
- * @return the context map.
- */
+ @Override
public Map getContextMap() {
- return context;
+ return super.getContextMap();
}
- /**
- * Sets conversion errors which occurred when executing the action.
- *
- * @param conversionErrors a Map of errors which occurred when executing the action.
- */
+ @Override
public ActionContext withConversionErrors(Map conversionErrors) {
- put(CONVERSION_ERRORS, conversionErrors);
+ super.withConversionErrors(conversionErrors);
return this;
}
- /**
- * Gets the map of conversion errors which occurred when executing the action.
- *
- * @return the map of conversion errors which occurred when executing the action or an empty map if
- * there were no errors.
- */
- @SuppressWarnings("unchecked")
+ @Override
public Map getConversionErrors() {
- Map errors = (Map) get(CONVERSION_ERRORS);
-
- if (errors == null) {
- errors = withConversionErrors(new HashMap<>()).getConversionErrors();
- }
-
- return errors;
+ return super.getConversionErrors();
}
- /**
- * Sets the Locale for the current action.
- *
- * @param locale the Locale for the current action.
- */
+ @Override
public ActionContext withLocale(Locale locale) {
- put(LOCALE, locale);
+ super.withLocale(locale);
return this;
}
- /**
- * Gets the Locale of the current action. If no locale was ever specified the platform's
- * {@link java.util.Locale#getDefault() default locale} is used.
- *
- * @return the Locale of the current action.
- */
+ @Override
public Locale getLocale() {
- Locale locale = (Locale) get(LOCALE);
-
- if (locale == null) {
- locale = Locale.getDefault();
- withLocale(locale);
- }
-
- return locale;
+ return super.getLocale();
}
- /**
- * Sets the name of the current Action in the ActionContext.
- *
- * @param actionName the name of the current action.
- */
+ @Override
public ActionContext withActionName(String actionName) {
- put(ACTION_NAME, actionName);
+ super.withActionName(actionName);
return this;
}
- /**
- * Gets the name of the current Action.
- *
- * @return the name of the current action.
- */
+ @Override
public String getActionName() {
- return (String) get(ACTION_NAME);
+ return super.getActionName();
}
- /**
- * Sets the action parameters.
- *
- * @param parameters the parameters for the current action.
- */
+ @Override
public ActionContext withParameters(HttpParameters parameters) {
- put(PARAMETERS, parameters);
+ super.withParameters(parameters);
return this;
}
- /**
- * Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of
- * parameters otherwise.
- *
- * @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a
- * generic Map of parameters otherwise.
- */
+ @Override
public HttpParameters getParameters() {
- return (HttpParameters) get(PARAMETERS);
+ return super.getParameters();
}
- /**
- * Sets a map of action session values.
- *
- * @param session the session values.
- */
+ @Override
public ActionContext withSession(Map session) {
- put(SESSION, session);
+ super.withSession(session);
return this;
}
- /**
- * Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
- *
- * @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
- */
- @SuppressWarnings("unchecked")
+ @Override
public Map getSession() {
- return (Map) get(SESSION);
+ return super.getSession();
}
- /**
- * Sets the OGNL value stack.
- *
- * @param valueStack the OGNL value stack.
- */
+ @Override
public ActionContext withValueStack(ValueStack valueStack) {
- put(VALUE_STACK, valueStack);
+ super.withValueStack(valueStack);
return this;
}
- /**
- * Gets the OGNL value stack.
- *
- * @return the OGNL value stack.
- */
+ @Override
public ValueStack getValueStack() {
- return (ValueStack) get(VALUE_STACK);
+ return super.getValueStack();
}
- /**
- * Gets the container for this request
- *
- * @param container The container
- */
+ @Override
public ActionContext withContainer(Container container) {
- put(CONTAINER, container);
+ super.withContainer(container);
return this;
}
- /**
- * Sets the container for this request
- *
- * @return The container
- */
+ @Override
public Container getContainer() {
- return (Container) get(CONTAINER);
+ return super.getContainer();
}
+ @Override
public T getInstance(Class type) {
- Container cont = getContainer();
- if (cont != null) {
- return cont.getInstance(type);
- } else {
- throw new StrutsException("Cannot find an initialized container for this request.");
- }
+ return super.getInstance(type);
}
- /**
- * Returns a value that is stored in the current ActionContext by doing a lookup using the value's key.
- *
- * @param key the key used to find the value.
- * @return the value that was found using the key or null if the key was not found.
- */
+ @Override
public Object get(String key) {
- return context.get(key);
+ return super.get(key);
}
- /**
- * Stores a value in the current ActionContext. The value can be looked up using the key.
- *
- * @param key the key of the value.
- * @param value the value to be stored.
- */
+ @Override
public void put(String key, Object value) {
- context.put(key, value);
+ super.put(key, value);
}
- /**
- * Gets ServletContext associated with current action
- *
- * @return current ServletContext
- */
+ @Override
public ServletContext getServletContext() {
- return (ServletContext) get(StrutsStatics.SERVLET_CONTEXT);
+ return super.getServletContext();
}
- /**
- * Assigns ServletContext to action context
- *
- * @param servletContext associated with current request
- * @return ActionContext
- */
+ @Override
public ActionContext withServletContext(ServletContext servletContext) {
- put(StrutsStatics.SERVLET_CONTEXT, servletContext);
+ super.withServletContext(servletContext);
return this;
}
- /**
- * Gets ServletRequest associated with current action
- *
- * @return current ServletRequest
- */
+ @Override
public HttpServletRequest getServletRequest() {
- return (HttpServletRequest) get(StrutsStatics.HTTP_REQUEST);
+ return super.getServletRequest();
}
- /**
- * Assigns ServletRequest to action context
- *
- * @param request associated with current request
- * @return ActionContext
- */
+ @Override
public ActionContext withServletRequest(HttpServletRequest request) {
- put(StrutsStatics.HTTP_REQUEST, request);
+ super.withServletRequest(request);
return this;
}
- /**
- * Gets ServletResponse associated with current action
- *
- * @return current ServletResponse
- */
+ @Override
public HttpServletResponse getServletResponse() {
- return (HttpServletResponse) get(StrutsStatics.HTTP_RESPONSE);
+ return super.getServletResponse();
}
- /**
- * Assigns ServletResponse to action context
- *
- * @param response associated with current request
- * @return ActionContext
- */
+ @Override
public ActionContext withServletResponse(HttpServletResponse response) {
- put(StrutsStatics.HTTP_RESPONSE, response);
+ super.withServletResponse(response);
return this;
}
- /**
- * Gets PageContext associated with current action
- *
- * @return current PageContext
- */
+ @Override
public PageContext getPageContext() {
- return (PageContext) get(StrutsStatics.PAGE_CONTEXT);
+ return super.getPageContext();
}
- /**
- * Assigns PageContext to action context
- *
- * @param pageContext associated with current request
- * @return ActionContext
- */
+ @Override
public ActionContext withPageContext(PageContext pageContext) {
- put(StrutsStatics.PAGE_CONTEXT, pageContext);
+ super.withPageContext(pageContext);
return this;
}
- /**
- * Gets ActionMapping associated with current action
- *
- * @return current ActionMapping
- */
+ @Override
public ActionMapping getActionMapping() {
- return (ActionMapping) get(StrutsStatics.ACTION_MAPPING);
+ return super.getActionMapping();
}
- /**
- * Assigns ActionMapping to action context
- *
- * @param actionMapping associated with current request
- * @return ActionContext
- */
+ @Override
public ActionContext withActionMapping(ActionMapping actionMapping) {
- put(StrutsStatics.ACTION_MAPPING, actionMapping);
+ super.withActionMapping(actionMapping);
return this;
}
- /**
- * Assigns an extra context map to action context
- *
- * @param extraContext to add to the current action context
- * @return ActionContext
- */
+ @Override
public ActionContext withExtraContext(Map extraContext) {
- if (extraContext != null) {
- context.putAll(extraContext);
- }
+ super.withExtraContext(extraContext);
return this;
}
- /**
- * Adds arbitrary key to action context
- *
- * @param key a string
- * @param value an object
- * @return ActionContext
- */
+ @Override
public ActionContext with(String key, Object value) {
- put(key, value);
+ super.with(key, value);
return this;
}
}
diff --git a/core/src/main/java/org/apache/struts2/ActionContext.java b/core/src/main/java/org/apache/struts2/ActionContext.java
new file mode 100644
index 000000000..8f155cc06
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/ActionContext.java
@@ -0,0 +1,554 @@
+/*
+ * 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;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.conversion.impl.ConversionData;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.PageContext;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ *
+ * The ActionContext is the context in which an {@link Action} is executed. Each context is basically a
+ * container of objects an action needs for execution like the session, parameters, locale, etc.
+ *
+ *
+ *
+ * The ActionContext is thread local which means that values stored in the ActionContext are
+ * unique per thread. See the {@link ThreadLocal} class for more information. The benefit of
+ * this is you don't need to worry about a user specific action context, you just get it:
+ *
+ * Finally, because of the thread local usage you don't need to worry about making your actions thread safe.
+ *
+ *
+ * @author Patrick Lightbody
+ * @author Bill Lynch (docs)
+ */
+public class ActionContext implements Serializable {
+
+ private static final ThreadLocal actionContext = new ThreadLocal<>();
+
+ /**
+ * Constant for the name of the action being executed.
+ */
+ private static final String ACTION_NAME = "org.apache.struts2.ActionContext.name";
+
+ /**
+ * Constant for the {@link ValueStack OGNL value stack}.
+ */
+ private static final String VALUE_STACK = ValueStack.VALUE_STACK;
+
+ /**
+ * Constant for the action's session.
+ */
+ private static final String SESSION = "org.apache.struts2.ActionContext.session";
+
+ /**
+ * Constant for the action's application context.
+ */
+ private static final String APPLICATION = "org.apache.struts2.ActionContext.application";
+
+ /**
+ * Constant for the action's parameters.
+ */
+ private static final String PARAMETERS = "org.apache.struts2.ActionContext.parameters";
+
+ /**
+ * Constant for the action's locale.
+ */
+ private static final String LOCALE = "org.apache.struts2.ActionContext.locale";
+
+ /**
+ * Constant for the action's {@link ActionInvocation invocation} context.
+ */
+ private static final String ACTION_INVOCATION = "org.apache.struts2.ActionContext.actionInvocation";
+
+ /**
+ * Constant for the map of type conversion errors.
+ */
+ private static final String CONVERSION_ERRORS = "org.apache.struts2.ActionContext.conversionErrors";
+
+ /**
+ * Constant for the container
+ */
+ private static final String CONTAINER = "org.apache.struts2.ActionContext.container";
+
+ private final Map context;
+
+ /**
+ * Creates a new ActionContext initialized with another context.
+ *
+ * @param context a context map.
+ */
+ protected ActionContext(Map context) {
+ this.context = context;
+ }
+
+ /**
+ * Creates a new ActionContext based on passed in Map
+ *
+ * @param context a map with context values
+ * @return new ActionContext
+ */
+ public static ActionContext of(Map context) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null!");
+ }
+ return new ActionContext(context);
+ }
+
+ /**
+ * Creates a new ActionContext based on empty Map
+ *
+ * @return new ActionContext
+ */
+ public static ActionContext of() {
+ return of(new HashMap<>());
+ }
+
+ /**
+ * Binds the provided context with the current thread
+ *
+ * @param actionContext context to bind to the thread
+ * @return context which was bound to the thread
+ */
+ public static ActionContext bind(ActionContext actionContext) {
+ ActionContext.setContext(actionContext);
+ return ActionContext.getContext();
+ }
+
+ public static boolean containsValueStack(Map context) {
+ return context != null && context.containsKey(VALUE_STACK);
+ }
+
+ /**
+ * Binds this context with the current thread
+ *
+ * @return this context which was bound to the thread
+ */
+ public ActionContext bind() {
+ ActionContext.setContext(this);
+ return ActionContext.getContext();
+ }
+
+ /**
+ * Wipes out current ActionContext, use wisely!
+ */
+ public static void clear() {
+ actionContext.remove();
+ }
+
+ /**
+ * Sets the action context for the current thread.
+ *
+ * @param context the action context.
+ */
+ private static void setContext(ActionContext context) {
+ actionContext.set(context);
+ }
+
+ /**
+ * Returns the ActionContext specific to the current thread.
+ *
+ * @return the ActionContext for the current thread, is never null.
+ */
+ public static ActionContext getContext() {
+ return actionContext.get();
+ }
+
+ /**
+ * Sets the action invocation (the execution state).
+ *
+ * @param actionInvocation the action execution state.
+ */
+ public ActionContext withActionInvocation(ActionInvocation actionInvocation) {
+ put(ACTION_INVOCATION, actionInvocation);
+ return this;
+ }
+
+ /**
+ * Gets the action invocation (the execution state).
+ *
+ * @return the action invocation (the execution state).
+ */
+ public ActionInvocation getActionInvocation() {
+ return (ActionInvocation) get(ACTION_INVOCATION);
+ }
+
+ /**
+ * Sets the action's application context.
+ *
+ * @param application the action's application context.
+ */
+ public ActionContext withApplication(Map application) {
+ put(APPLICATION, application);
+ return this;
+ }
+
+ /**
+ * Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise.
+ *
+ * @return a Map of ServletContext or generic application level Map
+ */
+ @SuppressWarnings("unchecked")
+ public Map getApplication() {
+ return (Map) get(APPLICATION);
+ }
+
+ /**
+ * Gets the context map.
+ *
+ * @return the context map.
+ */
+ public Map getContextMap() {
+ return context;
+ }
+
+ /**
+ * Sets conversion errors which occurred when executing the action.
+ *
+ * @param conversionErrors a Map of errors which occurred when executing the action.
+ */
+ public ActionContext withConversionErrors(Map conversionErrors) {
+ put(CONVERSION_ERRORS, conversionErrors);
+ return this;
+ }
+
+ /**
+ * Gets the map of conversion errors which occurred when executing the action.
+ *
+ * @return the map of conversion errors which occurred when executing the action or an empty map if
+ * there were no errors.
+ */
+ @SuppressWarnings("unchecked")
+ public Map getConversionErrors() {
+ Map errors = (Map) get(CONVERSION_ERRORS);
+
+ if (errors == null) {
+ errors = withConversionErrors(new HashMap<>()).getConversionErrors();
+ }
+
+ return errors;
+ }
+
+ /**
+ * Sets the Locale for the current action.
+ *
+ * @param locale the Locale for the current action.
+ */
+ public ActionContext withLocale(Locale locale) {
+ put(LOCALE, locale);
+ return this;
+ }
+
+ /**
+ * Gets the Locale of the current action. If no locale was ever specified the platform's
+ * {@link Locale#getDefault() default locale} is used.
+ *
+ * @return the Locale of the current action.
+ */
+ public Locale getLocale() {
+ Locale locale = (Locale) get(LOCALE);
+
+ if (locale == null) {
+ locale = Locale.getDefault();
+ withLocale(locale);
+ }
+
+ return locale;
+ }
+
+ /**
+ * Sets the name of the current Action in the ActionContext.
+ *
+ * @param actionName the name of the current action.
+ */
+ public ActionContext withActionName(String actionName) {
+ put(ACTION_NAME, actionName);
+ return this;
+ }
+
+ /**
+ * Gets the name of the current Action.
+ *
+ * @return the name of the current action.
+ */
+ public String getActionName() {
+ return (String) get(ACTION_NAME);
+ }
+
+ /**
+ * Sets the action parameters.
+ *
+ * @param parameters the parameters for the current action.
+ */
+ public ActionContext withParameters(HttpParameters parameters) {
+ put(PARAMETERS, parameters);
+ return this;
+ }
+
+ /**
+ * Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of
+ * parameters otherwise.
+ *
+ * @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a
+ * generic Map of parameters otherwise.
+ */
+ public HttpParameters getParameters() {
+ return (HttpParameters) get(PARAMETERS);
+ }
+
+ /**
+ * Sets a map of action session values.
+ *
+ * @param session the session values.
+ */
+ public ActionContext withSession(Map session) {
+ put(SESSION, session);
+ return this;
+ }
+
+ /**
+ * Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
+ *
+ * @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
+ */
+ @SuppressWarnings("unchecked")
+ public Map getSession() {
+ return (Map) get(SESSION);
+ }
+
+ /**
+ * Sets the OGNL value stack.
+ *
+ * @param valueStack the OGNL value stack.
+ */
+ public ActionContext withValueStack(ValueStack valueStack) {
+ put(VALUE_STACK, valueStack);
+ return this;
+ }
+
+ /**
+ * Gets the OGNL value stack.
+ *
+ * @return the OGNL value stack.
+ */
+ public ValueStack getValueStack() {
+ return (ValueStack) get(VALUE_STACK);
+ }
+
+ /**
+ * Gets the container for this request
+ *
+ * @param container The container
+ */
+ public ActionContext withContainer(Container container) {
+ put(CONTAINER, container);
+ return this;
+ }
+
+ /**
+ * Sets the container for this request
+ *
+ * @return The container
+ */
+ public Container getContainer() {
+ return (Container) get(CONTAINER);
+ }
+
+ public T getInstance(Class type) {
+ Container cont = getContainer();
+ if (cont != null) {
+ return cont.getInstance(type);
+ } else {
+ throw new StrutsException("Cannot find an initialized container for this request.");
+ }
+ }
+
+ /**
+ * Returns a value that is stored in the current ActionContext by doing a lookup using the value's key.
+ *
+ * @param key the key used to find the value.
+ * @return the value that was found using the key or null if the key was not found.
+ */
+ public Object get(String key) {
+ return context.get(key);
+ }
+
+ /**
+ * Stores a value in the current ActionContext. The value can be looked up using the key.
+ *
+ * @param key the key of the value.
+ * @param value the value to be stored.
+ */
+ public void put(String key, Object value) {
+ context.put(key, value);
+ }
+
+ /**
+ * Gets ServletContext associated with current action
+ *
+ * @return current ServletContext
+ */
+ public ServletContext getServletContext() {
+ return (ServletContext) get(StrutsStatics.SERVLET_CONTEXT);
+ }
+
+ /**
+ * Assigns ServletContext to action context
+ *
+ * @param servletContext associated with current request
+ * @return ActionContext
+ */
+ public ActionContext withServletContext(ServletContext servletContext) {
+ put(StrutsStatics.SERVLET_CONTEXT, servletContext);
+ return this;
+ }
+
+ /**
+ * Gets ServletRequest associated with current action
+ *
+ * @return current ServletRequest
+ */
+ public HttpServletRequest getServletRequest() {
+ return (HttpServletRequest) get(StrutsStatics.HTTP_REQUEST);
+ }
+
+ /**
+ * Assigns ServletRequest to action context
+ *
+ * @param request associated with current request
+ * @return ActionContext
+ */
+ public ActionContext withServletRequest(HttpServletRequest request) {
+ put(StrutsStatics.HTTP_REQUEST, request);
+ return this;
+ }
+
+ /**
+ * Gets ServletResponse associated with current action
+ *
+ * @return current ServletResponse
+ */
+ public HttpServletResponse getServletResponse() {
+ return (HttpServletResponse) get(StrutsStatics.HTTP_RESPONSE);
+ }
+
+ /**
+ * Assigns ServletResponse to action context
+ *
+ * @param response associated with current request
+ * @return ActionContext
+ */
+ public ActionContext withServletResponse(HttpServletResponse response) {
+ put(StrutsStatics.HTTP_RESPONSE, response);
+ return this;
+ }
+
+ /**
+ * Gets PageContext associated with current action
+ *
+ * @return current PageContext
+ */
+ public PageContext getPageContext() {
+ return (PageContext) get(StrutsStatics.PAGE_CONTEXT);
+ }
+
+ /**
+ * Assigns PageContext to action context
+ *
+ * @param pageContext associated with current request
+ * @return ActionContext
+ */
+ public ActionContext withPageContext(PageContext pageContext) {
+ put(StrutsStatics.PAGE_CONTEXT, pageContext);
+ return this;
+ }
+
+ /**
+ * Gets ActionMapping associated with current action
+ *
+ * @return current ActionMapping
+ */
+ public ActionMapping getActionMapping() {
+ return (ActionMapping) get(StrutsStatics.ACTION_MAPPING);
+ }
+
+ /**
+ * Assigns ActionMapping to action context
+ *
+ * @param actionMapping associated with current request
+ * @return ActionContext
+ */
+ public ActionContext withActionMapping(ActionMapping actionMapping) {
+ put(StrutsStatics.ACTION_MAPPING, actionMapping);
+ return this;
+ }
+
+ /**
+ * Assigns an extra context map to action context
+ *
+ * @param extraContext to add to the current action context
+ * @return ActionContext
+ */
+ public ActionContext withExtraContext(Map extraContext) {
+ if (extraContext != null) {
+ context.putAll(extraContext);
+ }
+ return this;
+ }
+
+ /**
+ * Adds arbitrary key to action context
+ *
+ * @param key a string
+ * @param value an object
+ * @return ActionContext
+ */
+ public ActionContext with(String key, Object value) {
+ put(key, value);
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ActionContext)) {
+ return false;
+ }
+ ActionContext other = (ActionContext) obj;
+ return Objects.equals(getContextMap(), other.getContextMap());
+ }
+}
From ae9dc42da76bd3128f59945f60710ea4173d89d9 Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 14:19:18 +1100
Subject: [PATCH 05/33] WW-3714 Deprecate and migrate ConditionalInterceptor
---
.../interceptor/ConditionalInterceptor.java | 19 ++-------
.../interceptor/ConditionalInterceptor.java | 39 +++++++++++++++++++
...onfigurationProviderOgnlAllowlistTest.java | 3 ++
3 files changed, 46 insertions(+), 15 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
index 12752fce8..40d856f48 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
@@ -18,22 +18,11 @@
*/
package com.opensymphony.xwork2.interceptor;
-import com.opensymphony.xwork2.ActionInvocation;
-
/**
- * A marking interface, when implemented allows to conditionally execute a given interceptor
- * within the current action invocation.
+ * {@inheritDoc}
*
- * @since Struts 6.1.1
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.Interceptor} instead.
*/
-public interface ConditionalInterceptor extends Interceptor {
-
- /**
- * Determines if a given interceptor should be executed in the current processing of action invocation.
- *
- * @param invocation current {@link ActionInvocation} to determine if the interceptor should be executed
- * @return true if the given interceptor should be included in the current action invocation
- * @since 6.1.1
- */
- boolean shouldIntercept(ActionInvocation invocation);
+@Deprecated
+public interface ConditionalInterceptor extends org.apache.struts2.interceptor.ConditionalInterceptor, Interceptor {
}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java
new file mode 100644
index 000000000..a6071327c
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ActionInvocation;
+
+/**
+ * A marking interface, when implemented allows to conditionally execute a given interceptor
+ * within the current action invocation.
+ *
+ * @since Struts 6.1.1
+ */
+public interface ConditionalInterceptor extends Interceptor {
+
+ /**
+ * Determines if a given interceptor should be executed in the current processing of action invocation.
+ *
+ * @param invocation current {@link ActionInvocation} to determine if the interceptor should be executed
+ * @return true if the given interceptor should be included in the current action invocation
+ * @since 6.1.1
+ */
+ boolean shouldIntercept(ActionInvocation invocation);
+}
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java
index 77d6bbb22..0349f6812 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java
@@ -63,6 +63,7 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase
Class.forName("com.opensymphony.xwork2.Result"),
Class.forName("com.opensymphony.xwork2.SimpleAction"),
Class.forName("org.apache.struts2.interceptor.Interceptor"),
+ Class.forName("org.apache.struts2.interceptor.ConditionalInterceptor"),
Class.forName("org.apache.struts2.Result"),
Class.forName("org.apache.struts2.Action")
);
@@ -90,6 +91,7 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase
Class.forName("com.opensymphony.xwork2.Result"),
Class.forName("com.opensymphony.xwork2.SimpleAction"),
Class.forName("org.apache.struts2.interceptor.Interceptor"),
+ Class.forName("org.apache.struts2.interceptor.ConditionalInterceptor"),
Class.forName("org.apache.struts2.Result"),
Class.forName("org.apache.struts2.Action")
);
@@ -116,6 +118,7 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase
Class.forName("com.opensymphony.xwork2.interceptor.AbstractInterceptor"),
Class.forName("com.opensymphony.xwork2.Result"),
Class.forName("org.apache.struts2.interceptor.Interceptor"),
+ Class.forName("org.apache.struts2.interceptor.ConditionalInterceptor"),
Class.forName("org.apache.struts2.Result"),
Class.forName("org.apache.struts2.Action")
);
From 60095a6934266eb50e0636c9e3c39560e14a7c8f Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 14:49:22 +1100
Subject: [PATCH 06/33] WW-3714 Deprecate and migrate ActionInvocation
---
.../opensymphony/xwork2/ActionContext.java | 2 +-
.../opensymphony/xwork2/ActionInvocation.java | 215 +++++++-----------
.../java/com/opensymphony/xwork2/Result.java | 25 ++
.../interceptor/ConditionalInterceptor.java | 8 +
.../xwork2/interceptor/Interceptor.java | 9 +
.../org/apache/struts2/ActionInvocation.java | 182 +++++++++++++++
.../main/java/org/apache/struts2/Result.java | 3 -
.../interceptor/ConditionalInterceptor.java | 2 +-
.../struts2/interceptor/Interceptor.java | 2 +-
9 files changed, 303 insertions(+), 145 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/ActionInvocation.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
index c2a4a39c2..d62da3219 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
@@ -43,7 +43,7 @@ public class ActionContext extends org.apache.struts2.ActionContext {
super(actualContext.getContextMap());
}
- private static ActionContext adapt(org.apache.struts2.ActionContext actualContext) {
+ static ActionContext adapt(org.apache.struts2.ActionContext actualContext) {
return actualContext != null ? new ActionContext(actualContext) : null;
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
index 472f23ea7..6f70f993f 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -22,158 +22,95 @@ import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.ValueStack;
/**
- * An {@link ActionInvocation} represents the execution state of an {@link Action}. It holds the Interceptors and the Action instance.
- * By repeated re-entrant execution of the invoke() method, initially by the {@link ActionProxy}, then by the Interceptors, the
- * Interceptors are all executed, and then the {@link Action} and the {@link Result}.
+ * {@inheritDoc}
*
- * @author Jason Carreira
- * @see com.opensymphony.xwork2.ActionProxy
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.ActionInvocation} instead.
*/
-public interface ActionInvocation {
+@Deprecated
+public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
- /**
- * Get the Action associated with this ActionInvocation.
- *
- * @return the Action
- */
- Object getAction();
-
- /**
- * Gets whether this ActionInvocation has executed before.
- * This will be set after the Action and the Result have executed.
- *
- * @return true if this ActionInvocation has executed before.
- */
- boolean isExecuted();
-
- /**
- * Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
- * responsible for setting this ActionContext onto the ThreadLocal before invoking
- * the ActionInvocation and resetting the old ActionContext afterwards.
- *
- * @return the ActionContext.
- */
+ @Override
ActionContext getInvocationContext();
- /**
- * Get the ActionProxy holding this ActionInvocation.
- *
- * @return the ActionProxy.
- */
- ActionProxy getProxy();
-
- /**
- * If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
- * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
- * ActionInvocation's result has not been executed before, the Result instance will be created and populated with
- * the result params.
- *
- * @return the result.
- * @throws Exception can be thrown.
- */
+ @Override
Result getResult() throws Exception;
- /**
- * Gets the result code returned from this ActionInvocation.
- *
- * @return the result code
- */
- String getResultCode();
+ static ActionInvocation adapt(org.apache.struts2.ActionInvocation actualInvocation) {
+ return actualInvocation != null ? new LegacyAdapter(actualInvocation) : null;
+ }
- /**
- * Sets the result code, possibly overriding the one returned by the
- * action.
- *
- *
- * The "intended" purpose of this method is to allow PreResultListeners to
- * override the result code returned by the Action.
- *
- *
- *
- * If this method is used before the Action executes, the Action's returned
- * result code will override what was set. However the Action could (if
- * specifically coded to do so) inspect the ActionInvocation to see that
- * someone "upstream" (e.g. an Interceptor) had suggested a value as the
- * result, and it could therefore return the same value itself.
- *
- *
- *
- * If this method is called between the Action execution and the Result
- * execution, then the value set here will override the result code the
- * action had returned. Creating an Interceptor that implements
- * {@link PreResultListener} will give you this opportunity.
- *
- *
- *
- * If this method is called after the Result has been executed, it will
- * have the effect of raising an IllegalStateException.
- *
- *
- * @param resultCode the result code.
- * @throws IllegalStateException if called after the Result has been executed.
- * @see #isExecuted()
- */
- void setResultCode(String resultCode);
+ class LegacyAdapter implements ActionInvocation {
- /**
- * Gets the ValueStack associated with this ActionInvocation.
- *
- * @return the ValueStack
- */
- ValueStack getStack();
+ private final org.apache.struts2.ActionInvocation adaptee;
- /**
- * Register a {@link PreResultListener} to be notified after the Action is executed and
- * before the Result is executed.
- *
- *
- * The ActionInvocation implementation must guarantee that listeners will be called in
- * the order in which they are registered.
- *
- *
- *
- * Listener registration and execution does not need to be thread-safe.
- *
- *
- * @param listener the listener to add.
- */
- void addPreResultListener(PreResultListener listener);
+ private LegacyAdapter(org.apache.struts2.ActionInvocation adaptee) {
+ this.adaptee = adaptee;
+ }
- /**
- * Invokes the next step in processing this ActionInvocation.
- *
- *
- * If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit
- * ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor
- * to execute. If there are no more Interceptors to be applied, the Action is executed.
- * If the {@link ActionProxy#getExecuteResult()} method returns true, the Result is also executed.
- *
- *
- * @throws Exception can be thrown.
- * @return the return code.
- */
- String invoke() throws Exception;
+ @Override
+ public Object getAction() {
+ return adaptee.getAction();
+ }
- /**
- * Invokes only the Action (not Interceptors or Results).
- *
- *
- * This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
- * being manipulated for certain functionality.
- *
- *
- * @return the return code.
- * @throws Exception can be thrown.
- */
- String invokeActionOnly() throws Exception;
+ @Override
+ public boolean isExecuted() {
+ return adaptee.isExecuted();
+ }
- /**
- * Sets the action event listener to respond to key action events.
- *
- * @param listener the listener.
- */
- void setActionEventListener(ActionEventListener listener);
+ @Override
+ public ActionContext getInvocationContext() {
+ return ActionContext.adapt(adaptee.getInvocationContext());
+ }
- void init(ActionProxy proxy) ;
+ @Override
+ public ActionProxy getProxy() {
+ return adaptee.getProxy();
+ }
+
+ @Override
+ public Result getResult() throws Exception {
+ return Result.adapt(adaptee.getResult());
+ }
+
+ @Override
+ public String getResultCode() {
+ return adaptee.getResultCode();
+ }
+
+ @Override
+ public void setResultCode(String resultCode) {
+ adaptee.setResultCode(resultCode);
+ }
+
+ @Override
+ public ValueStack getStack() {
+ return adaptee.getStack();
+ }
+
+ @Override
+ public void addPreResultListener(PreResultListener listener) {
+ adaptee.addPreResultListener(listener);
+ }
+
+ @Override
+ public String invoke() throws Exception {
+ return adaptee.invoke();
+ }
+
+ @Override
+ public String invokeActionOnly() throws Exception {
+ return adaptee.invokeActionOnly();
+ }
+
+ @Override
+ public void setActionEventListener(ActionEventListener listener) {
+ adaptee.setActionEventListener(listener);
+ }
+
+ @Override
+ public void init(ActionProxy proxy) {
+ adaptee.init(proxy);
+ }
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/Result.java b/core/src/main/java/com/opensymphony/xwork2/Result.java
index ccd238751..294ada4d7 100644
--- a/core/src/main/java/com/opensymphony/xwork2/Result.java
+++ b/core/src/main/java/com/opensymphony/xwork2/Result.java
@@ -25,4 +25,29 @@ package com.opensymphony.xwork2;
*/
@Deprecated
public interface Result extends org.apache.struts2.Result {
+
+ @Override
+ default void execute(org.apache.struts2.ActionInvocation invocation) throws Exception {
+ execute(ActionInvocation.adapt(invocation));
+ }
+
+ void execute(ActionInvocation invocation) throws Exception;
+
+ static Result adapt(org.apache.struts2.Result actualResult) {
+ return actualResult != null ? new LegacyAdapter(actualResult) : null;
+ }
+
+ class LegacyAdapter implements Result {
+
+ private final org.apache.struts2.Result adaptee;
+
+ private LegacyAdapter(org.apache.struts2.Result adaptee) {
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public void execute(ActionInvocation invocation) throws Exception {
+ adaptee.execute(ActionInvocation.adapt(invocation));
+ }
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
index 40d856f48..83c2bcb3e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
@@ -18,6 +18,8 @@
*/
package com.opensymphony.xwork2.interceptor;
+import com.opensymphony.xwork2.ActionInvocation;
+
/**
* {@inheritDoc}
*
@@ -25,4 +27,10 @@ package com.opensymphony.xwork2.interceptor;
*/
@Deprecated
public interface ConditionalInterceptor extends org.apache.struts2.interceptor.ConditionalInterceptor, Interceptor {
+
+ default boolean shouldIntercept(org.apache.struts2.ActionInvocation invocation) {
+ return shouldIntercept(ActionInvocation.adapt(invocation));
+ }
+
+ boolean shouldIntercept(ActionInvocation invocation);
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
index 87041a269..628dda6f5 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
@@ -18,6 +18,8 @@
*/
package com.opensymphony.xwork2.interceptor;
+import com.opensymphony.xwork2.ActionInvocation;
+
/**
* {@inheritDoc}
*
@@ -25,4 +27,11 @@ package com.opensymphony.xwork2.interceptor;
*/
@Deprecated
public interface Interceptor extends org.apache.struts2.interceptor.Interceptor {
+
+ @Override
+ default String intercept(org.apache.struts2.ActionInvocation invocation) throws Exception {
+ return intercept(ActionInvocation.adapt(invocation));
+ }
+
+ String intercept(ActionInvocation invocation) throws Exception;
}
diff --git a/core/src/main/java/org/apache/struts2/ActionInvocation.java b/core/src/main/java/org/apache/struts2/ActionInvocation.java
new file mode 100644
index 000000000..46f1a5b0e
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/ActionInvocation.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+
+import com.opensymphony.xwork2.ActionChainResult;
+import com.opensymphony.xwork2.ActionEventListener;
+import com.opensymphony.xwork2.ActionProxy;
+import com.opensymphony.xwork2.interceptor.PreResultListener;
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ * An {@link ActionInvocation} represents the execution state of an {@link com.opensymphony.xwork2.Action}. It holds the Interceptors and the Action instance.
+ * By repeated re-entrant execution of the invoke() method, initially by the {@link ActionProxy}, then by the Interceptors, the
+ * Interceptors are all executed, and then the {@link Action} and the {@link com.opensymphony.xwork2.Result}.
+ *
+ * @author Jason Carreira
+ * @see ActionProxy
+ */
+public interface ActionInvocation {
+
+ /**
+ * Get the Action associated with this ActionInvocation.
+ *
+ * @return the Action
+ */
+ Object getAction();
+
+ /**
+ * Gets whether this ActionInvocation has executed before.
+ * This will be set after the Action and the Result have executed.
+ *
+ * @return true if this ActionInvocation has executed before.
+ */
+ boolean isExecuted();
+
+ /**
+ * Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
+ * responsible for setting this ActionContext onto the ThreadLocal before invoking
+ * the ActionInvocation and resetting the old ActionContext afterwards.
+ *
+ * @return the ActionContext.
+ */
+ ActionContext getInvocationContext();
+
+ /**
+ * Get the ActionProxy holding this ActionInvocation.
+ *
+ * @return the ActionProxy.
+ */
+ ActionProxy getProxy();
+
+ /**
+ * If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
+ * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
+ * ActionInvocation's result has not been executed before, the Result instance will be created and populated with
+ * the result params.
+ *
+ * @return the result.
+ * @throws Exception can be thrown.
+ */
+ Result getResult() throws Exception;
+
+ /**
+ * Gets the result code returned from this ActionInvocation.
+ *
+ * @return the result code
+ */
+ String getResultCode();
+
+ /**
+ * Sets the result code, possibly overriding the one returned by the
+ * action.
+ *
+ *
+ * The "intended" purpose of this method is to allow PreResultListeners to
+ * override the result code returned by the Action.
+ *
+ *
+ *
+ * If this method is used before the Action executes, the Action's returned
+ * result code will override what was set. However the Action could (if
+ * specifically coded to do so) inspect the ActionInvocation to see that
+ * someone "upstream" (e.g. an Interceptor) had suggested a value as the
+ * result, and it could therefore return the same value itself.
+ *
+ *
+ *
+ * If this method is called between the Action execution and the Result
+ * execution, then the value set here will override the result code the
+ * action had returned. Creating an Interceptor that implements
+ * {@link PreResultListener} will give you this opportunity.
+ *
+ *
+ *
+ * If this method is called after the Result has been executed, it will
+ * have the effect of raising an IllegalStateException.
+ *
+ *
+ * @param resultCode the result code.
+ * @throws IllegalStateException if called after the Result has been executed.
+ * @see #isExecuted()
+ */
+ void setResultCode(String resultCode);
+
+ /**
+ * Gets the ValueStack associated with this ActionInvocation.
+ *
+ * @return the ValueStack
+ */
+ ValueStack getStack();
+
+ /**
+ * Register a {@link PreResultListener} to be notified after the Action is executed and
+ * before the Result is executed.
+ *
+ *
+ * The ActionInvocation implementation must guarantee that listeners will be called in
+ * the order in which they are registered.
+ *
+ *
+ *
+ * Listener registration and execution does not need to be thread-safe.
+ *
+ *
+ * @param listener the listener to add.
+ */
+ void addPreResultListener(PreResultListener listener);
+
+ /**
+ * Invokes the next step in processing this ActionInvocation.
+ *
+ *
+ * If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit
+ * ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor
+ * to execute. If there are no more Interceptors to be applied, the Action is executed.
+ * If the {@link ActionProxy#getExecuteResult()} method returns true, the Result is also executed.
+ *
+ *
+ * @throws Exception can be thrown.
+ * @return the return code.
+ */
+ String invoke() throws Exception;
+
+ /**
+ * Invokes only the Action (not Interceptors or Results).
+ *
+ *
+ * This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
+ * being manipulated for certain functionality.
+ *
+ *
+ * @return the return code.
+ * @throws Exception can be thrown.
+ */
+ String invokeActionOnly() throws Exception;
+
+ /**
+ * Sets the action event listener to respond to key action events.
+ *
+ * @param listener the listener.
+ */
+ void setActionEventListener(ActionEventListener listener);
+
+ void init(ActionProxy proxy) ;
+
+}
diff --git a/core/src/main/java/org/apache/struts2/Result.java b/core/src/main/java/org/apache/struts2/Result.java
index 4dc912772..407994eab 100644
--- a/core/src/main/java/org/apache/struts2/Result.java
+++ b/core/src/main/java/org/apache/struts2/Result.java
@@ -18,9 +18,6 @@
*/
package org.apache.struts2;
-import com.opensymphony.xwork2.Action;
-import com.opensymphony.xwork2.ActionInvocation;
-
import java.io.Serializable;
/**
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java
index a6071327c..716b58195 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java
@@ -18,7 +18,7 @@
*/
package org.apache.struts2.interceptor;
-import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.struts2.ActionInvocation;
/**
* A marking interface, when implemented allows to conditionally execute a given interceptor
diff --git a/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java b/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java
index 9c36bde77..7eabb85a8 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java
@@ -18,7 +18,7 @@
*/
package org.apache.struts2.interceptor;
-import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.struts2.ActionInvocation;
import java.io.Serializable;
From 272c2e7bba62fb0e82514f5c6f1b5ac71ea57677 Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 15:04:53 +1100
Subject: [PATCH 07/33] WW-3714 Deprecate and migrate PreResultListener
---
.../opensymphony/xwork2/ActionInvocation.java | 7 ++++
.../xwork2/interceptor/PreResultListener.java | 38 +++++++++++------
.../org/apache/struts2/ActionInvocation.java | 2 +-
.../interceptor/PreResultListener.java | 41 +++++++++++++++++++
4 files changed, 75 insertions(+), 13 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/interceptor/PreResultListener.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
index 6f70f993f..1d6e34859 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -35,6 +35,13 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
@Override
Result getResult() throws Exception;
+ @Override
+ default void addPreResultListener(org.apache.struts2.interceptor.PreResultListener listener) {
+ addPreResultListener(PreResultListener.adapt(listener));
+ }
+
+ void addPreResultListener(PreResultListener listener);
+
static ActionInvocation adapt(org.apache.struts2.ActionInvocation actualInvocation) {
return actualInvocation != null ? new LegacyAdapter(actualInvocation) : null;
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java
index f9faa2377..469d3521b 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java
@@ -21,21 +21,35 @@ package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
/**
- * PreResultListeners may be registered with an {@link ActionInvocation} to get a callback after the
- * {@link com.opensymphony.xwork2.Action} has been executed but before the {@link com.opensymphony.xwork2.Result}
- * is executed.
+ * {@inheritDoc}
*
- * @author Jason Carreira
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.PreResultListener} instead.
*/
-public interface PreResultListener {
+@Deprecated
+public interface PreResultListener extends org.apache.struts2.interceptor.PreResultListener {
+
+ @Override
+ default void beforeResult(org.apache.struts2.ActionInvocation invocation, String resultCode) {
+ beforeResult(ActionInvocation.adapt(invocation), resultCode);
+ }
- /**
- * This callback method will be called after the {@link com.opensymphony.xwork2.Action} execution and
- * before the {@link com.opensymphony.xwork2.Result} execution.
- *
- * @param invocation the action invocation
- * @param resultCode the result code returned by the action (eg. success).
- */
void beforeResult(ActionInvocation invocation, String resultCode);
+ static PreResultListener adapt(org.apache.struts2.interceptor.PreResultListener actualListener) {
+ return actualListener != null ? new LegacyAdapter(actualListener) : null;
+ }
+
+ class LegacyAdapter implements PreResultListener {
+
+ private final org.apache.struts2.interceptor.PreResultListener adaptee;
+
+ private LegacyAdapter(org.apache.struts2.interceptor.PreResultListener adaptee) {
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public void beforeResult(ActionInvocation invocation, String resultCode) {
+ adaptee.beforeResult(invocation, resultCode);
+ }
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/ActionInvocation.java b/core/src/main/java/org/apache/struts2/ActionInvocation.java
index 46f1a5b0e..8b789f50d 100644
--- a/core/src/main/java/org/apache/struts2/ActionInvocation.java
+++ b/core/src/main/java/org/apache/struts2/ActionInvocation.java
@@ -21,8 +21,8 @@ package org.apache.struts2;
import com.opensymphony.xwork2.ActionChainResult;
import com.opensymphony.xwork2.ActionEventListener;
import com.opensymphony.xwork2.ActionProxy;
-import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.struts2.interceptor.PreResultListener;
/**
* An {@link ActionInvocation} represents the execution state of an {@link com.opensymphony.xwork2.Action}. It holds the Interceptors and the Action instance.
diff --git a/core/src/main/java/org/apache/struts2/interceptor/PreResultListener.java b/core/src/main/java/org/apache/struts2/interceptor/PreResultListener.java
new file mode 100644
index 000000000..a8e9f6250
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/PreResultListener.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.apache.struts2.ActionInvocation;
+
+/**
+ * PreResultListeners may be registered with an {@link ActionInvocation} to get a callback after the
+ * {@link com.opensymphony.xwork2.Action} has been executed but before the {@link com.opensymphony.xwork2.Result}
+ * is executed.
+ *
+ * @author Jason Carreira
+ */
+public interface PreResultListener {
+
+ /**
+ * This callback method will be called after the {@link com.opensymphony.xwork2.Action} execution and
+ * before the {@link com.opensymphony.xwork2.Result} execution.
+ *
+ * @param invocation the action invocation
+ * @param resultCode the result code returned by the action (eg. success).
+ */
+ void beforeResult(ActionInvocation invocation, String resultCode);
+
+}
From e3fbe883556c6c20a9315b549aba3e1aa60369e4 Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 17:21:24 +1100
Subject: [PATCH 08/33] WW-3714 Update new ActionContext with new
ActionInvocation
---
.../main/java/com/opensymphony/xwork2/ActionContext.java | 8 ++++++--
core/src/main/java/org/apache/struts2/ActionContext.java | 2 --
.../java/org/apache/struts2/views/jsp/ActionTagTest.java | 6 ++++--
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
index d62da3219..c7864571f 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
@@ -77,15 +77,19 @@ public class ActionContext extends org.apache.struts2.ActionContext {
return this;
}
- @Override
public ActionContext withActionInvocation(ActionInvocation actionInvocation) {
+ return withActionInvocation((org.apache.struts2.ActionInvocation) actionInvocation);
+ }
+
+ @Override
+ public ActionContext withActionInvocation(org.apache.struts2.ActionInvocation actionInvocation) {
super.withActionInvocation(actionInvocation);
return this;
}
@Override
public ActionInvocation getActionInvocation() {
- return super.getActionInvocation();
+ return ActionInvocation.adapt(super.getActionInvocation());
}
@Override
diff --git a/core/src/main/java/org/apache/struts2/ActionContext.java b/core/src/main/java/org/apache/struts2/ActionContext.java
index 8f155cc06..bfde35f12 100644
--- a/core/src/main/java/org/apache/struts2/ActionContext.java
+++ b/core/src/main/java/org/apache/struts2/ActionContext.java
@@ -18,8 +18,6 @@
*/
package org.apache.struts2;
-import com.opensymphony.xwork2.Action;
-import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.conversion.impl.ConversionData;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.util.ValueStack;
diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ActionTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ActionTagTest.java
index 9678b600e..0319ca7cc 100644
--- a/core/src/test/java/org/apache/struts2/views/jsp/ActionTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/ActionTagTest.java
@@ -403,6 +403,7 @@ public class ActionTagTest extends AbstractTagTest {
public void testExecuteButResetReturnSameInvocation() throws Exception {
Mock mockActionInv = new Mock(ActionInvocation.class);
+ mockActionInv.matchAndReturn("invoke", "TEST");
ActionTag tag = new ActionTag();
tag.setPageContext(pageContext);
tag.setNamespace("");
@@ -419,7 +420,7 @@ public class ActionTagTest extends AbstractTagTest {
ActionComponent component = (ActionComponent) tag.getComponent();
tag.doEndTag();
- assertSame(oldInvocation, ActionContext.getContext().getActionInvocation());
+ assertEquals(oldInvocation.invoke(), ActionContext.getContext().getActionInvocation().invoke());
// Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
ActionTag freshTag = new ActionTag();
@@ -432,6 +433,7 @@ public class ActionTagTest extends AbstractTagTest {
public void testExecuteButResetReturnSameInvocation_clearTagStateSet() throws Exception {
Mock mockActionInv = new Mock(ActionInvocation.class);
+ mockActionInv.matchAndReturn("invoke", "TEST");
ActionTag tag = new ActionTag();
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
tag.setPageContext(pageContext);
@@ -450,7 +452,7 @@ public class ActionTagTest extends AbstractTagTest {
ActionComponent component = (ActionComponent) tag.getComponent();
tag.doEndTag();
- assertTrue(oldInvocation == ActionContext.getContext().getActionInvocation());
+ assertEquals(oldInvocation.invoke(), ActionContext.getContext().getActionInvocation().invoke());
// Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
ActionTag freshTag = new ActionTag();
From 36a890ba69ac80f5a279478f9229b7b4ff8912cd Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 20:05:59 +1100
Subject: [PATCH 09/33] WW-3714 Add factory support for new Interceptor, Result
interfaces
---
.../factory/DefaultInterceptorFactory.java | 13 ++++---
.../xwork2/factory/DefaultResultFactory.java | 12 ++++++-
.../interceptor/ConditionalInterceptor.java | 23 +++++++++++++
.../xwork2/interceptor/Interceptor.java | 34 +++++++++++++++++++
.../struts2/factory/StrutsResultFactory.java | 13 +++++--
5 files changed, 88 insertions(+), 7 deletions(-)
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
index a1d9ee2ce..580464130 100644
--- a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
@@ -70,13 +70,18 @@ public class DefaultInterceptorFactory implements InterceptorFactory {
reflectionProvider.setProperties(params, o);
}
+ Interceptor interceptor = null;
if (o instanceof Interceptor) {
- Interceptor interceptor = (Interceptor) o;
- interceptor.init();
- return interceptor;
+ interceptor = (Interceptor) o;
+ } else if (o instanceof org.apache.struts2.interceptor.Interceptor) {
+ interceptor = Interceptor.adapt((org.apache.struts2.interceptor.Interceptor) o);
}
- throw new ConfigurationException("Class [" + interceptorClassName + "] does not implement Interceptor", interceptorConfig);
+ if (interceptor == null) {
+ throw new ConfigurationException("Class [" + interceptorClassName + "] does not implement Interceptor", interceptorConfig);
+ }
+ interceptor.init();
+ return interceptor;
} catch (InstantiationException e) {
cause = e;
message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
index e5fe5f8d5..5466a52ea 100644
--- a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
@@ -20,6 +20,7 @@ package com.opensymphony.xwork2.factory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.reflection.ReflectionException;
@@ -51,7 +52,16 @@ public class DefaultResultFactory implements ResultFactory {
Result result = null;
if (resultClassName != null) {
- result = (Result) objectFactory.buildBean(resultClassName, extraContext);
+ Object o = objectFactory.buildBean(resultClassName, extraContext);
+ if (o instanceof Result) {
+ result = (Result) o;
+ } else if (o instanceof org.apache.struts2.Result) {
+ result = Result.adapt((org.apache.struts2.Result) o);
+ }
+ if (result == null) {
+ throw new ConfigurationException("Class [" + resultClassName + "] does not implement Result", resultConfig);
+ }
+
Map params = resultConfig.getParams();
if (params != null) {
for (Map.Entry paramEntry : params.entrySet()) {
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
index 83c2bcb3e..0e83f64b2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java
@@ -28,9 +28,32 @@ import com.opensymphony.xwork2.ActionInvocation;
@Deprecated
public interface ConditionalInterceptor extends org.apache.struts2.interceptor.ConditionalInterceptor, Interceptor {
+ @Override
default boolean shouldIntercept(org.apache.struts2.ActionInvocation invocation) {
return shouldIntercept(ActionInvocation.adapt(invocation));
}
boolean shouldIntercept(ActionInvocation invocation);
+
+ static ConditionalInterceptor adapt(org.apache.struts2.interceptor.ConditionalInterceptor actualInterceptor) {
+ if (actualInterceptor instanceof ConditionalInterceptor) {
+ return (ConditionalInterceptor) actualInterceptor;
+ }
+ return actualInterceptor != null ? new LegacyAdapter(actualInterceptor) : null;
+ }
+
+ class LegacyAdapter extends Interceptor.LegacyAdapter implements ConditionalInterceptor {
+
+ private final org.apache.struts2.interceptor.ConditionalInterceptor adaptee;
+
+ private LegacyAdapter(org.apache.struts2.interceptor.ConditionalInterceptor adaptee) {
+ super(adaptee);
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public boolean shouldIntercept(ActionInvocation invocation) {
+ return adaptee.shouldIntercept(invocation);
+ }
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
index 628dda6f5..e6ff42998 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
@@ -34,4 +34,38 @@ public interface Interceptor extends org.apache.struts2.interceptor.Interceptor
}
String intercept(ActionInvocation invocation) throws Exception;
+
+ static Interceptor adapt(org.apache.struts2.interceptor.Interceptor actualInterceptor) {
+ if (actualInterceptor instanceof org.apache.struts2.interceptor.ConditionalInterceptor) {
+ return ConditionalInterceptor.adapt((org.apache.struts2.interceptor.ConditionalInterceptor) actualInterceptor);
+ }
+ if (actualInterceptor instanceof Interceptor) {
+ return (Interceptor) actualInterceptor;
+ }
+ return actualInterceptor != null ? new LegacyAdapter(actualInterceptor) : null;
+ }
+
+ class LegacyAdapter implements Interceptor {
+
+ private final org.apache.struts2.interceptor.Interceptor adaptee;
+
+ protected LegacyAdapter(org.apache.struts2.interceptor.Interceptor adaptee) {
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ return adaptee.intercept(invocation);
+ }
+
+ @Override
+ public void destroy() {
+ adaptee.destroy();
+ }
+
+ @Override
+ public void init() {
+ adaptee.init();
+ }
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java b/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java
index 0a758f213..2818f33dd 100644
--- a/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java
+++ b/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java
@@ -20,13 +20,14 @@ package org.apache.struts2.factory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.factory.ResultFactory;
import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.result.ParamNameAwareResult;
import com.opensymphony.xwork2.util.reflection.ReflectionException;
import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler;
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
-import com.opensymphony.xwork2.result.ParamNameAwareResult;
import java.util.Map;
@@ -53,7 +54,15 @@ public class StrutsResultFactory implements ResultFactory {
Result result = null;
if (resultClassName != null) {
- result = (Result) objectFactory.buildBean(resultClassName, extraContext);
+ Object o = objectFactory.buildBean(resultClassName, extraContext);
+ if (o instanceof Result) {
+ result = (Result) o;
+ } else if (o instanceof org.apache.struts2.Result) {
+ result = Result.adapt((org.apache.struts2.Result) o);
+ }
+ if (result == null) {
+ throw new ConfigurationException("Class [" + resultClassName + "] does not implement Result", resultConfig);
+ }
Map params = resultConfig.getParams();
if (params != null) {
setParameters(extraContext, result, params);
From d1695f7a4d83c8ed8aa9b67176795053f4d2d748 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 21 Oct 2024 01:29:19 +0000
Subject: [PATCH 10/33] Bump github/codeql-action from 3.26.12 to 3.26.13
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.12 to 3.26.13.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.26.12...v3.26.13)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
.github/workflows/codeql.yml | 6 +++---
.github/workflows/scorecards-analysis.yaml | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 26db59be1..5e616eab6 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -44,12 +44,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3.26.12
+ uses: github/codeql-action/init@v3.26.13
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v3.26.12
+ uses: github/codeql-action/autobuild@v3.26.13
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3.26.12
+ uses: github/codeql-action/analyze@v3.26.13
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/scorecards-analysis.yaml b/.github/workflows/scorecards-analysis.yaml
index 866d87936..44c6ac646 100644
--- a/.github/workflows/scorecards-analysis.yaml
+++ b/.github/workflows/scorecards-analysis.yaml
@@ -64,6 +64,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@ea2cd92c21b192add69983116b8b3222b09da33b # 2.22.11
+ uses: github/codeql-action/upload-sarif@af56b044b5d41c317aef5d19920b3183cb4fbbec # 2.22.11
with:
sarif_file: results.sarif
From 28c8f1503d6a99e049aeb31da37cf38a59fa0497 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 21 Oct 2024 01:47:53 +0000
Subject: [PATCH 11/33] Bump maven-surefire-plugin.version from 3.5.0 to 3.5.1
Bumps `maven-surefire-plugin.version` from 3.5.0 to 3.5.1.
Updates `org.apache.maven.surefire:surefire-junit47` from 3.5.0 to 3.5.1
Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.0 to 3.5.1
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.0...surefire-3.5.1)
---
updated-dependencies:
- dependency-name: org.apache.maven.surefire:surefire-junit47
dependency-type: direct:production
update-type: version-update:semver-patch
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 8534610fe..e23efe790 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,7 +117,7 @@
5.3.393.0.81.0.7
- 3.5.0
+ 3.5.16.2.4.Final2.3.33
From b18fbda1f7e971c357453343a36be20f7a330013 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 21 Oct 2024 01:48:01 +0000
Subject: [PATCH 12/33] Bump org.apache.maven.doxia:doxia-core from 1.12.0 to
2.0.0
Bumps [org.apache.maven.doxia:doxia-core](https://github.com/apache/maven-doxia) from 1.12.0 to 2.0.0.
- [Release notes](https://github.com/apache/maven-doxia/releases)
- [Commits](https://github.com/apache/maven-doxia/compare/doxia-1.12.0...doxia-2.0.0)
---
updated-dependencies:
- dependency-name: org.apache.maven.doxia:doxia-core
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 8534610fe..378d31163 100644
--- a/pom.xml
+++ b/pom.xml
@@ -415,7 +415,7 @@
org.apache.maven.doxiadoxia-core
- 1.12.0
+ 2.0.0org.apache.maven.doxia
From 48ee44bbc2ae83baa809a63d81fa0984273f6af7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 21 Oct 2024 01:48:04 +0000
Subject: [PATCH 13/33] Bump org.apache.commons:commons-lang3 from 3.15.0 to
3.17.0
Bumps org.apache.commons:commons-lang3 from 3.15.0 to 3.17.0.
---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 8534610fe..7f5785a77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -866,7 +866,7 @@
org.apache.commonscommons-lang3
- 3.15.0
+ 3.17.0org.apache.commons
From cf6cbf3816ea43f1eb35c08d121aa8049f64ed61 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 21 Oct 2024 01:48:11 +0000
Subject: [PATCH 14/33] Bump org.apache.maven.plugins:maven-failsafe-plugin
from 3.3.1 to 3.5.1
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.3.1 to 3.5.1.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.3.1...surefire-3.5.1)
---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-failsafe-plugin
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
apps/showcase/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml
index 21a02f768..7054c18f6 100644
--- a/apps/showcase/pom.xml
+++ b/apps/showcase/pom.xml
@@ -163,7 +163,7 @@
org.apache.maven.pluginsmaven-failsafe-plugin
- 3.3.1
+ 3.5.1it.org.apache.struts2.showcase.*Test
From b622e5d725857664cfb5905c0d4501911fa5596f Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Tue, 22 Oct 2024 13:50:45 +1100
Subject: [PATCH 15/33] WW-3714 Ensure ReflectionExceptionHandler,
WithLazyParams, ParamNameAwareResult marker interfaces respected
---
.../xwork2/DefaultActionInvocation.java | 3 ++
.../xwork2/factory/DefaultResultFactory.java | 27 +++++++--------
.../xwork2/interceptor/Interceptor.java | 4 +++
.../xwork2/interceptor/WithLazyParams.java | 5 ++-
.../struts2/factory/StrutsResultFactory.java | 33 ++++++++++++++++---
5 files changed, 53 insertions(+), 19 deletions(-)
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
index 772e0adb0..84accbef7 100644
--- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
@@ -248,6 +248,9 @@ public class DefaultActionInvocation implements ActionInvocation {
Interceptor interceptor = interceptorMapping.getInterceptor();
if (interceptor instanceof WithLazyParams) {
interceptor = lazyParamInjector.injectParams(interceptor, interceptorMapping.getParams(), invocationContext);
+ } else if (interceptor instanceof Interceptor.LegacyAdapter && ((Interceptor.LegacyAdapter) interceptor).getAdaptee() instanceof WithLazyParams) {
+ org.apache.struts2.interceptor.Interceptor adaptee = ((Interceptor.LegacyAdapter) interceptor).getAdaptee();
+ lazyParamInjector.injectParams(adaptee, interceptorMapping.getParams(), invocationContext);
}
if (interceptor instanceof ConditionalInterceptor) {
resultCode = executeConditional((ConditionalInterceptor) interceptor);
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
index 5466a52ea..42527494e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
@@ -53,6 +53,20 @@ public class DefaultResultFactory implements ResultFactory {
if (resultClassName != null) {
Object o = objectFactory.buildBean(resultClassName, extraContext);
+
+ Map params = resultConfig.getParams();
+ if (params != null) {
+ for (Map.Entry paramEntry : params.entrySet()) {
+ try {
+ reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), o, extraContext, true);
+ } catch (ReflectionException ex) {
+ if (o instanceof ReflectionExceptionHandler) {
+ ((ReflectionExceptionHandler) o).handle(ex);
+ }
+ }
+ }
+ }
+
if (o instanceof Result) {
result = (Result) o;
} else if (o instanceof org.apache.struts2.Result) {
@@ -61,19 +75,6 @@ public class DefaultResultFactory implements ResultFactory {
if (result == null) {
throw new ConfigurationException("Class [" + resultClassName + "] does not implement Result", resultConfig);
}
-
- Map params = resultConfig.getParams();
- if (params != null) {
- for (Map.Entry paramEntry : params.entrySet()) {
- try {
- reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), result, extraContext, true);
- } catch (ReflectionException ex) {
- if (result instanceof ReflectionExceptionHandler) {
- ((ReflectionExceptionHandler) result).handle(ex);
- }
- }
- }
- }
}
return result;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
index e6ff42998..4287ca8c0 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
@@ -53,6 +53,10 @@ public interface Interceptor extends org.apache.struts2.interceptor.Interceptor
this.adaptee = adaptee;
}
+ public org.apache.struts2.interceptor.Interceptor getAdaptee() {
+ return adaptee;
+ }
+
@Override
public String intercept(ActionInvocation invocation) throws Exception {
return adaptee.intercept(invocation);
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java
index 3e111d69c..5b9401d3d 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java
@@ -70,11 +70,14 @@ public interface WithLazyParams {
}
public Interceptor injectParams(Interceptor interceptor, Map params, ActionContext invocationContext) {
+ return (Interceptor) injectParams((org.apache.struts2.interceptor.Interceptor) interceptor, params, invocationContext);
+ }
+
+ public org.apache.struts2.interceptor.Interceptor injectParams(org.apache.struts2.interceptor.Interceptor interceptor, Map params, ActionContext invocationContext) {
for (Map.Entry entry : params.entrySet()) {
Object paramValue = textParser.evaluate(new char[]{ '$' }, entry.getValue(), valueEvaluator, TextParser.DEFAULT_LOOP_COUNT);
ognlUtil.setProperty(entry.getKey(), paramValue, interceptor, invocationContext.getContextMap());
}
-
return interceptor;
}
}
diff --git a/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java b/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java
index 2818f33dd..8a653bdaf 100644
--- a/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java
+++ b/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java
@@ -55,6 +55,10 @@ public class StrutsResultFactory implements ResultFactory {
if (resultClassName != null) {
Object o = objectFactory.buildBean(resultClassName, extraContext);
+ Map params = resultConfig.getParams();
+ if (params != null) {
+ setParameters(extraContext, o, params);
+ }
if (o instanceof Result) {
result = (Result) o;
} else if (o instanceof org.apache.struts2.Result) {
@@ -63,15 +67,23 @@ public class StrutsResultFactory implements ResultFactory {
if (result == null) {
throw new ConfigurationException("Class [" + resultClassName + "] does not implement Result", resultConfig);
}
- Map params = resultConfig.getParams();
- if (params != null) {
- setParameters(extraContext, result, params);
- }
}
return result;
}
protected void setParameters(Map extraContext, Result result, Map params) {
+ setParametersHelper(extraContext, result, params);
+ }
+
+ protected void setParameters(Map extraContext, Object result, Map params) {
+ if (result instanceof Result) {
+ setParameters(extraContext, (Result) result, params);
+ } else {
+ setParametersHelper(extraContext, result, params);
+ }
+ }
+
+ private void setParametersHelper(Map extraContext, Object result, Map params) {
for (Map.Entry paramEntry : params.entrySet()) {
try {
String name = paramEntry.getKey();
@@ -86,6 +98,18 @@ public class StrutsResultFactory implements ResultFactory {
}
protected void setParameter(Result result, String name, String value, Map extraContext) {
+ setParameterHelper(result, name, value, extraContext);
+ }
+
+ private void setParameter(Object result, String name, String value, Map extraContext) {
+ if (result instanceof Result) {
+ setParameter((Result) result, name, value, extraContext);
+ } else {
+ setParameterHelper(result, name, value, extraContext);
+ }
+ }
+
+ private void setParameterHelper(Object result, String name, String value, Map extraContext) {
if (result instanceof ParamNameAwareResult) {
if (((ParamNameAwareResult) result).acceptableParameterName(name, value)) {
reflectionProvider.setProperty(name, value, result, extraContext, true);
@@ -94,5 +118,4 @@ public class StrutsResultFactory implements ResultFactory {
reflectionProvider.setProperty(name, value, result, extraContext, true);
}
}
-
}
From bbca2717f9815af2fccdc56aeb5194efc28f05dc Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 15:17:14 +1100
Subject: [PATCH 16/33] WW-3714 Deprecate and migrate ActionEventListener
---
.../xwork2/ActionEventListener.java | 47 +++++++++++--------
.../opensymphony/xwork2/ActionInvocation.java | 7 +++
.../apache/struts2/ActionEventListener.java | 44 +++++++++++++++++
.../org/apache/struts2/ActionInvocation.java | 1 -
4 files changed, 79 insertions(+), 20 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/ActionEventListener.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
index 00690a7f4..4d2143848 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
@@ -21,24 +21,33 @@ package com.opensymphony.xwork2;
import com.opensymphony.xwork2.util.ValueStack;
/**
- * Provides hooks for handling key action events
+ * {@inheritDoc}
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.ActionEventListener} instead.
*/
-public interface ActionEventListener {
- /**
- * Called after an action has been created.
- *
- * @param action The action
- * @param stack The current value stack
- * @return The action to use
- */
- public Object prepare(Object action, ValueStack stack);
-
- /**
- * Called when an exception is thrown by the action
- *
- * @param t The exception/error that was thrown
- * @param stack The current value stack
- * @return A result code to execute, can be null
- */
- public String handleException(Throwable t, ValueStack stack);
+@Deprecated
+public interface ActionEventListener extends org.apache.struts2.ActionEventListener {
+
+ static ActionEventListener adapt(org.apache.struts2.ActionEventListener actualListener) {
+ return actualListener != null ? new LegacyAdapter(actualListener) : null;
+ }
+
+ class LegacyAdapter implements ActionEventListener {
+
+ private final org.apache.struts2.ActionEventListener adaptee;
+
+ private LegacyAdapter(org.apache.struts2.ActionEventListener adaptee) {
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public Object prepare(Object action, ValueStack stack) {
+ return adaptee.prepare(action, stack);
+ }
+
+ @Override
+ public String handleException(Throwable t, ValueStack stack) {
+ return adaptee.handleException(t, stack);
+ }
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
index 1d6e34859..56ea6ad6e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -42,6 +42,13 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
void addPreResultListener(PreResultListener listener);
+ @Override
+ default void setActionEventListener(org.apache.struts2.ActionEventListener listener) {
+ setActionEventListener(ActionEventListener.adapt(listener));
+ }
+
+ void setActionEventListener(ActionEventListener listener);
+
static ActionInvocation adapt(org.apache.struts2.ActionInvocation actualInvocation) {
return actualInvocation != null ? new LegacyAdapter(actualInvocation) : null;
}
diff --git a/core/src/main/java/org/apache/struts2/ActionEventListener.java b/core/src/main/java/org/apache/struts2/ActionEventListener.java
new file mode 100644
index 000000000..8c01a9a23
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/ActionEventListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ * Provides hooks for handling key action events
+ */
+public interface ActionEventListener {
+ /**
+ * Called after an action has been created.
+ *
+ * @param action The action
+ * @param stack The current value stack
+ * @return The action to use
+ */
+ Object prepare(Object action, ValueStack stack);
+
+ /**
+ * Called when an exception is thrown by the action
+ *
+ * @param t The exception/error that was thrown
+ * @param stack The current value stack
+ * @return A result code to execute, can be null
+ */
+ String handleException(Throwable t, ValueStack stack);
+}
diff --git a/core/src/main/java/org/apache/struts2/ActionInvocation.java b/core/src/main/java/org/apache/struts2/ActionInvocation.java
index 8b789f50d..14e930a50 100644
--- a/core/src/main/java/org/apache/struts2/ActionInvocation.java
+++ b/core/src/main/java/org/apache/struts2/ActionInvocation.java
@@ -19,7 +19,6 @@
package org.apache.struts2;
import com.opensymphony.xwork2.ActionChainResult;
-import com.opensymphony.xwork2.ActionEventListener;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.struts2.interceptor.PreResultListener;
From 14bd4b80cf4201dbc08b095890bcea342c44ced9 Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 15:32:34 +1100
Subject: [PATCH 17/33] WW-3714 Deprecate and migrate ActionProxy
---
.../opensymphony/xwork2/ActionInvocation.java | 12 +-
.../com/opensymphony/xwork2/ActionProxy.java | 140 ++++++++----------
.../org/apache/struts2/ActionInvocation.java | 3 +-
.../java/org/apache/struts2/ActionProxy.java | 102 +++++++++++++
4 files changed, 176 insertions(+), 81 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/ActionProxy.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
index 56ea6ad6e..82020bbe1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -35,6 +35,9 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
@Override
Result getResult() throws Exception;
+ @Override
+ ActionProxy getProxy();
+
@Override
default void addPreResultListener(org.apache.struts2.interceptor.PreResultListener listener) {
addPreResultListener(PreResultListener.adapt(listener));
@@ -49,6 +52,13 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
void setActionEventListener(ActionEventListener listener);
+ @Override
+ default void init(org.apache.struts2.ActionProxy proxy) {
+ init(ActionProxy.adapt(proxy));
+ }
+
+ void init(ActionProxy proxy);
+
static ActionInvocation adapt(org.apache.struts2.ActionInvocation actualInvocation) {
return actualInvocation != null ? new LegacyAdapter(actualInvocation) : null;
}
@@ -78,7 +88,7 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
@Override
public ActionProxy getProxy() {
- return adaptee.getProxy();
+ return ActionProxy.adapt(adaptee.getProxy());
}
@Override
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
index 595671462..18a1e6a6e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
@@ -20,88 +20,72 @@ package com.opensymphony.xwork2;
import com.opensymphony.xwork2.config.entities.ActionConfig;
-/**
- * ActionProxy is an extra layer between XWork and the action so that different proxies are possible.
- *
- *
- * An example of this would be a remote proxy, where the layer between XWork and the action might be RMI or SOAP.
- *
- *
- * @author Jason Carreira
- */
-public interface ActionProxy {
+@Deprecated
+public interface ActionProxy extends org.apache.struts2.ActionProxy {
- /**
- * Gets the Action instance for this Proxy.
- *
- * @return the Action instance
- */
- Object getAction();
-
- /**
- * Gets the alias name this ActionProxy is mapped to.
- *
- * @return the alias name
- */
- String getActionName();
-
- /**
- * Gets the ActionConfig this ActionProxy is built from.
- *
- * @return the ActionConfig
- */
- ActionConfig getConfig();
-
- /**
- * Sets whether this ActionProxy should also execute the Result after executing the Action.
- *
- * @param executeResult true to also execute the Result.
- */
- void setExecuteResult(boolean executeResult);
-
- /**
- * Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed.
- *
- * @return the status
- */
- boolean getExecuteResult();
-
- /**
- * Gets the ActionInvocation associated with this ActionProxy.
- *
- * @return the ActionInvocation
- */
+ @Override
ActionInvocation getInvocation();
- /**
- * Gets the namespace the ActionConfig for this ActionProxy is mapped to.
- *
- * @return the namespace
- */
- String getNamespace();
+ static ActionProxy adapt(org.apache.struts2.ActionProxy actualProxy) {
+ return actualProxy != null ? new LegacyAdapter(actualProxy) : null;
+ }
- /**
- * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
- * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
- *
- * @return the result code returned from executing the ActionInvocation
- * @throws Exception can be thrown.
- * @see ActionInvocation
- */
- String execute() throws Exception;
+ class LegacyAdapter implements ActionProxy {
- /**
- * Gets the method name to execute, or null if no method has been specified (meaning execute will be invoked).
- *
- * @return the method to execute
- */
- String getMethod();
+ private final org.apache.struts2.ActionProxy adaptee;
- /**
- * Gets status of the method value's initialization.
- *
- * @return true if the method returned by getMethod() is not a default initializer value.
- */
- boolean isMethodSpecified();
-
+ private LegacyAdapter(org.apache.struts2.ActionProxy adaptee) {
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public Object getAction() {
+ return adaptee.getAction();
+ }
+
+ @Override
+ public String getActionName() {
+ return adaptee.getActionName();
+ }
+
+ @Override
+ public ActionConfig getConfig() {
+ return adaptee.getConfig();
+ }
+
+ @Override
+ public void setExecuteResult(boolean executeResult) {
+ adaptee.setExecuteResult(executeResult);
+ }
+
+ @Override
+ public boolean getExecuteResult() {
+ return adaptee.getExecuteResult();
+ }
+
+ @Override
+ public ActionInvocation getInvocation() {
+ return ActionInvocation.adapt(adaptee.getInvocation());
+ }
+
+ @Override
+ public String getNamespace() {
+ return adaptee.getNamespace();
+ }
+
+ @Override
+ public String execute() throws Exception {
+ return adaptee.execute();
+ }
+
+ @Override
+ public String getMethod() {
+ return adaptee.getMethod();
+ }
+
+ @Override
+ public boolean isMethodSpecified() {
+ return adaptee.isMethodSpecified();
+ }
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/ActionInvocation.java b/core/src/main/java/org/apache/struts2/ActionInvocation.java
index 14e930a50..e3d446bc0 100644
--- a/core/src/main/java/org/apache/struts2/ActionInvocation.java
+++ b/core/src/main/java/org/apache/struts2/ActionInvocation.java
@@ -19,7 +19,6 @@
package org.apache.struts2;
import com.opensymphony.xwork2.ActionChainResult;
-import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.struts2.interceptor.PreResultListener;
@@ -176,6 +175,6 @@ public interface ActionInvocation {
*/
void setActionEventListener(ActionEventListener listener);
- void init(ActionProxy proxy) ;
+ void init(ActionProxy proxy);
}
diff --git a/core/src/main/java/org/apache/struts2/ActionProxy.java b/core/src/main/java/org/apache/struts2/ActionProxy.java
new file mode 100644
index 000000000..d5e19e44d
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/ActionProxy.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+/**
+ * ActionProxy is an extra layer between XWork and the action so that different proxies are possible.
+ *
+ *
+ * An example of this would be a remote proxy, where the layer between XWork and the action might be RMI or SOAP.
+ *
+ *
+ * @author Jason Carreira
+ */
+public interface ActionProxy {
+
+ /**
+ * Gets the Action instance for this Proxy.
+ *
+ * @return the Action instance
+ */
+ Object getAction();
+
+ /**
+ * Gets the alias name this ActionProxy is mapped to.
+ *
+ * @return the alias name
+ */
+ String getActionName();
+
+ /**
+ * Gets the ActionConfig this ActionProxy is built from.
+ *
+ * @return the ActionConfig
+ */
+ ActionConfig getConfig();
+
+ /**
+ * Sets whether this ActionProxy should also execute the Result after executing the Action.
+ *
+ * @param executeResult true to also execute the Result.
+ */
+ void setExecuteResult(boolean executeResult);
+
+ /**
+ * Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed.
+ *
+ * @return the status
+ */
+ boolean getExecuteResult();
+
+ ActionInvocation getInvocation();
+
+ /**
+ * Gets the namespace the ActionConfig for this ActionProxy is mapped to.
+ *
+ * @return the namespace
+ */
+ String getNamespace();
+
+ /**
+ * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
+ * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
+ *
+ * @return the result code returned from executing the ActionInvocation
+ * @throws Exception can be thrown.
+ * @see ActionInvocation
+ */
+ String execute() throws Exception;
+
+ /**
+ * Gets the method name to execute, or null if no method has been specified (meaning execute will be invoked).
+ *
+ * @return the method to execute
+ */
+ String getMethod();
+
+ /**
+ * Gets status of the method value's initialization.
+ *
+ * @return true if the method returned by getMethod() is not a default initializer value.
+ */
+ boolean isMethodSpecified();
+
+}
From 8ba8ee5fe6298aaa3debfb5a1197abb4cae6ad99 Mon Sep 17 00:00:00 2001
From: Kusal Kithul-Godage
Date: Thu, 17 Oct 2024 15:46:25 +1100
Subject: [PATCH 18/33] WW-3714 Deprecate and migrate ValueStack
---
.../opensymphony/xwork2/ActionContext.java | 2 +-
.../xwork2/ActionEventListener.java | 14 ++
.../opensymphony/xwork2/ActionInvocation.java | 5 +-
.../opensymphony/xwork2/util/ValueStack.java | 222 ++++++++----------
.../apache/struts2/ActionEventListener.java | 2 +-
.../org/apache/struts2/ActionInvocation.java | 2 +-
.../org/apache/struts2/util/ValueStack.java | 167 +++++++++++++
7 files changed, 289 insertions(+), 125 deletions(-)
create mode 100644 core/src/main/java/org/apache/struts2/util/ValueStack.java
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
index c7864571f..786c896d2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
@@ -43,7 +43,7 @@ public class ActionContext extends org.apache.struts2.ActionContext {
super(actualContext.getContextMap());
}
- static ActionContext adapt(org.apache.struts2.ActionContext actualContext) {
+ public static ActionContext adapt(org.apache.struts2.ActionContext actualContext) {
return actualContext != null ? new ActionContext(actualContext) : null;
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
index 4d2143848..5bd4f86d5 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
@@ -28,6 +28,20 @@ import com.opensymphony.xwork2.util.ValueStack;
@Deprecated
public interface ActionEventListener extends org.apache.struts2.ActionEventListener {
+ @Override
+ default Object prepare(Object action, org.apache.struts2.util.ValueStack stack) {
+ return prepare(action, ValueStack.adapt(stack));
+ }
+
+ @Override
+ default String handleException(Throwable t, org.apache.struts2.util.ValueStack stack) {
+ return handleException(t, ValueStack.adapt(stack));
+ }
+
+ Object prepare(Object action, ValueStack stack);
+
+ String handleException(Throwable t, ValueStack stack);
+
static ActionEventListener adapt(org.apache.struts2.ActionEventListener actualListener) {
return actualListener != null ? new LegacyAdapter(actualListener) : null;
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
index 82020bbe1..76929a647 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -38,6 +38,9 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
@Override
ActionProxy getProxy();
+ @Override
+ ValueStack getStack();
+
@Override
default void addPreResultListener(org.apache.struts2.interceptor.PreResultListener listener) {
addPreResultListener(PreResultListener.adapt(listener));
@@ -108,7 +111,7 @@ public interface ActionInvocation extends org.apache.struts2.ActionInvocation {
@Override
public ValueStack getStack() {
- return adaptee.getStack();
+ return ValueStack.adapt(adaptee.getStack());
}
@Override
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java b/core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java
index 4d02b235f..9e3e98b57 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/ValueStack.java
@@ -23,144 +23,124 @@ import com.opensymphony.xwork2.ActionContext;
import java.util.Map;
/**
- * ValueStack allows multiple beans to be pushed in and dynamic EL expressions to be evaluated against it. When
- * evaluating an expression, the stack will be searched down the stack, from the latest objects pushed in to the
- * earliest, looking for a bean with a getter or setter for the given property or a method of the given name (depending
- * on the expression being evaluated).
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.util.ValueStack} instead.
*/
-public interface ValueStack {
-
- String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack";
-
- String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork2.util.ValueStack.ReportErrorsOnNoProp";
-
- /**
- * Gets the context for this value stack. The context holds all the information in the value stack and it's surroundings.
- *
- * @return the context.
- */
- Map getContext();
+@Deprecated
+public interface ValueStack extends org.apache.struts2.util.ValueStack {
+ @Override
ActionContext getActionContext();
- /**
- * Sets the default type to convert to if no type is provided when getting a value.
- *
- * @param defaultType the new default type
- */
- void setDefaultType(Class defaultType);
+ static ValueStack adapt(org.apache.struts2.util.ValueStack actualStack) {
+ return actualStack != null ? new LegacyAdapter(actualStack) : null;
+ }
- /**
- * Set a override map containing key -> values that takes precedent when doing find operations on the ValueStack.
- *
- * See the unit test for ValueStackTest for examples.
- *
* Note: Since 2.5.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
* able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
* (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
*
- *
+ *
*
*
*
Interceptor parameters:
@@ -85,7 +85,10 @@ import java.util.Map;
*
*
* @author Jason Carreira
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ConversionErrorInterceptor} instead.
*/
+@Deprecated
public class ConversionErrorInterceptor extends MethodFilterInterceptor {
public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
index 05749ae19..d238e1ceb 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
@@ -32,7 +32,7 @@ import org.apache.struts2.interceptor.ValidationWorkflowAware;
/**
*
*
- * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue.
+ * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue.
* If a single FieldError or ActionError (including the ones replicated by the Message Store Interceptor in a redirection) is found, the INPUT result will be triggered.
* This interceptor does not perform any validation.
*
@@ -132,7 +132,10 @@ import org.apache.struts2.interceptor.ValidationWorkflowAware;
* @author Alexandru Popescu
* @author Philip Luppens
* @author tm_jee
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.DefaultWorkflowInterceptor} instead.
*/
+@Deprecated
public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
private static final long serialVersionUID = 7563014655616490865L;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
index e60550ca6..3bb70bcb8 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
@@ -20,8 +20,8 @@ package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.HttpParameters;
import java.util.List;
@@ -153,7 +153,10 @@ import java.util.Map;
*
* @author Matthew E. Porter (matthew dot porter at metissian dot com)
* @author Claus Ibsen
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ExceptionMappingInterceptor} instead.
*/
+@Deprecated
public class ExceptionMappingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
index 6ba498b3c..3f012288c 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
@@ -59,7 +59,10 @@ import org.apache.logging.log4j.Logger;
*
*
* @author Jason Carreira
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.LoggingInterceptor} instead.
*/
+@Deprecated
public class LoggingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(LoggingInterceptor.class);
private static final String FINISH_MESSAGE = "Finishing execution stack for action ";
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
index f513deb1c..84170b669 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
@@ -71,10 +71,13 @@ import org.apache.struts2.ModelDriven;
* </action>
*
*
- *
+ *
* @author tm_jee
* @version $Date$ $Id$
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ModelDrivenInterceptor} instead.
*/
+@Deprecated
public class ModelDrivenInterceptor extends AbstractInterceptor {
protected boolean refreshModelBeforeResult = false;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
index c0f83765c..f33ebf6e2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
@@ -66,7 +66,10 @@ import java.util.Set;
* ...
* </action>
*
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ParameterRemoverInterceptor} instead.
*/
+@Deprecated
public class ParameterRemoverInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class);
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
index 040080824..0ac840c7a 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
@@ -19,8 +19,8 @@
package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -28,7 +28,7 @@ import java.lang.reflect.Method;
/**
*
* A utility class for invoking prefixed methods in action class.
- *
+ *
* Interceptors that made use of this class are:
*
else if the action class have validateDo{MethodName}(), it will be invoked
*
no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is "true" (which is by default "true"), validate() will be invoked.
else if the action class have prepareDo(MethodName()}(), it will be invoked
*
no matter if 1] or 2] is performed, if alwaysinvokePrepare property of the interceptor is "true" (which is by default "true"), prepare() will be invoked.
*
- *
+ *
*
- *
+ *
* @author Philip Luppens
* @author tm_jee
*/
public class PrefixMethodInvocationUtil {
-
+
private static final Logger LOG = LogManager.getLogger(PrefixMethodInvocationUtil.class);
private static final String DEFAULT_INVOCATION_METHODNAME = "execute";
@@ -76,7 +76,7 @@ public class PrefixMethodInvocationUtil {
*
* This method will prefix actionInvocation's ActionProxy's
* method with prefixes before invoking the prefixed method.
- * Order of the prefixes is important, as this method will return once
+ * Order of the prefixes is important, as this method will return once
* a prefixed method is found in the action class.
*
*
@@ -89,7 +89,7 @@ public class PrefixMethodInvocationUtil {
*
*
*
- * Assuming actionInvocation.getProxy(),getMethod() returns "submit",
+ * Assuming actionInvocation.getProxy(),getMethod() returns "submit",
* the order of invocation would be as follows:-
*
*
@@ -99,12 +99,12 @@ public class PrefixMethodInvocationUtil {
*
*
*
- * If prepareSubmit() exists, it will be invoked and this method
- * will return, prepareDoSubmit() will NOT be invoked.
+ * If prepareSubmit() exists, it will be invoked and this method
+ * will return, prepareDoSubmit() will NOT be invoked.
*
*
*
- * On the other hand, if prepareDoSubmit() does not exists, and
+ * On the other hand, if prepareDoSubmit() does not exists, and
* prepareDoSubmit() exists, it will be invoked.
*
*
@@ -119,29 +119,32 @@ public class PrefixMethodInvocationUtil {
*/
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
Object action = actionInvocation.getAction();
-
+
String methodName = actionInvocation.getProxy().getMethod();
-
+
if (methodName == null) {
- // if null returns (possible according to the docs), use the default execute
+ // if null returns (possible according to the docs), use the default execute
methodName = DEFAULT_INVOCATION_METHODNAME;
}
-
+
Method method = getPrefixedMethod(prefixes, methodName, action);
if (method != null) {
method.invoke(action, new Object[0]);
}
}
-
-
+
+ public static void invokePrefixMethod(org.apache.struts2.ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
+ invokePrefixMethod(ActionInvocation.adapt(actionInvocation), prefixes);
+ }
+
/**
- * This method returns a {@link Method} in action. The method
+ * This method returns a {@link Method} in action. The method
* returned is found by searching for method in action whose method name
* is equals to the result of appending each prefixes
* to methodName. Only the first method found will be returned, hence
* the order of prefixes is important. If none is found this method
* will return null.
- *
+ *
* @param prefixes the prefixes to prefix the methodName
* @param methodName the method name to be prefixed with prefixes
* @param action the action class of which the prefixed method is to be search for.
@@ -162,7 +165,7 @@ public class PrefixMethodInvocationUtil {
}
return null;
}
-
+
/**
*
* This method capitalized the first character of methodName.
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
index e4d5af634..43bb12c2b 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
@@ -96,7 +96,10 @@ import java.lang.reflect.InvocationTargetException;
* @author Philip Luppens
* @author tm_jee
* @see com.opensymphony.xwork2.Preparable
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.PrepareInterceptor} instead.
*/
+@Deprecated
public class PrepareInterceptor extends MethodFilterInterceptor {
private static final long serialVersionUID = -5216969014510719786L;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java
index ae2266be0..03473034d 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java
@@ -36,7 +36,7 @@ import java.util.Map;
*
*
This interceptor only activates on actions that implement the {@link ScopedModelDriven} interface. If
* detected, it will retrieve the model class from the configured scope, then provide it to the Action.
- *
+ *
*
*
*
Interceptor parameters:
@@ -46,7 +46,7 @@ import java.util.Map;
*
*
*
className - The model class name. Defaults to the class name of the object returned by the getModel() method.
- *
+ *
*
name - The key to use when storing or retrieving the instance in a scope. Defaults to the model
* class name.
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ScopedModelDrivenInterceptor} instead.
*/
+@Deprecated
public class ScopedModelDrivenInterceptor extends AbstractInterceptor {
private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
-
+
private static final String GET_MODEL = "getModel";
private String scope;
private String name;
private String className;
private ObjectFactory objectFactory;
-
+
@Inject
public void setObjectFactory(ObjectFactory factory) {
this.objectFactory = factory;
}
-
+
protected Object resolveModel(ObjectFactory factory, ActionContext actionContext, String modelClassName, String modelScope, String modelName) throws Exception {
Object model;
Map scopeMap = actionContext.getContextMap();
if ("session".equals(modelScope)) {
scopeMap = actionContext.getSession();
}
-
+
model = scopeMap.get(modelName);
if (model == null) {
model = factory.buildBean(modelClassName, null);
@@ -120,7 +123,7 @@ public class ScopedModelDrivenInterceptor extends AbstractInterceptor {
if (modelDriven.getModel() == null) {
ActionContext ctx = ActionContext.getContext();
ActionConfig config = invocation.getProxy().getConfig();
-
+
String cName = className;
if (cName == null) {
try {
@@ -162,5 +165,5 @@ public class ScopedModelDrivenInterceptor extends AbstractInterceptor {
*/
public void setScope(String scope) {
this.scope = scope;
- }
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java
index d560e1dd4..f5d4382ae 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java
@@ -85,7 +85,10 @@ import java.util.Map;
*
*
* @author Patrick Lightbody
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.StaticParametersInterceptor} instead.
*/
+@Deprecated
public class StaticParametersInterceptor extends AbstractInterceptor {
private boolean parse;
diff --git a/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java
new file mode 100644
index 000000000..c5aa9fb3c
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java
@@ -0,0 +1,293 @@
+/*
+ * 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.LocalizedTextProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
+import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
+import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
+import com.opensymphony.xwork2.util.ClearableValueStack;
+import com.opensymphony.xwork2.util.Evaluated;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.dispatcher.Parameter;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * The aim of this Interceptor is to alias a named parameter to a different named parameter. By acting as the glue
+ * between actions sharing similar parameters (but with different names), it can help greatly with action chaining.
+ *
+ *
Action's alias expressions should be in the form of #{ "name1" : "alias1", "name2" : "alias2" }.
+ * This means that assuming an action (or something else in the stack) has a value for the expression named name1 and the
+ * action this interceptor is applied to has a setter named alias1, alias1 will be set with the value from
+ * name1.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
aliasesKey (optional) - the name of the action parameter to look for the alias map (by default this is
+ * aliases).
+ *
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * This interceptor does not have any known extension points.
+ *
+ *
+ *
+ *
Example code:
+ *
+ *
+ *
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <!-- The value for the foo parameter will be applied as if it were named bar -->
+ * <param name="aliases">#{ 'foo' : 'bar' }</param>
+ *
+ * <interceptor-ref name="alias"/>
+ * <interceptor-ref name="basicStack"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ * Sets the name of the action parameter to look for the alias map.
+ *
+ *
+ *
+ * Default is aliases.
+ *
+ *
+ * @param aliasesKey the name of the action parameter
+ */
+ public void setAliasesKey(String aliasesKey) {
+ this.aliasesKey = aliasesKey;
+ }
+
+ @Override public String intercept(ActionInvocation invocation) throws Exception {
+
+ ActionConfig config = invocation.getProxy().getConfig();
+ ActionContext ac = invocation.getInvocationContext();
+ Object action = invocation.getAction();
+
+ // get the action's parameters
+ final Map parameters = config.getParams();
+
+ if (parameters.containsKey(aliasesKey)) {
+
+ String aliasExpression = parameters.get(aliasesKey);
+ ValueStack stack = ac.getValueStack();
+ Object obj = stack.findValue(aliasExpression);
+
+ if (obj instanceof Map) {
+ //get secure stack
+ ValueStack newStack = valueStackFactory.createValueStack(com.opensymphony.xwork2.util.ValueStack.adapt(stack));
+ boolean clearableStack = newStack instanceof ClearableValueStack;
+ if (clearableStack) {
+ //if the stack's context can be cleared, do that to prevent OGNL
+ //from having access to objects in the stack, see XW-641
+ ((ClearableValueStack)newStack).clearContextValues();
+ Map context = newStack.getContext();
+ ReflectionContextState.setCreatingNullObjects(context, true);
+ ReflectionContextState.setDenyMethodExecution(context, true);
+ ReflectionContextState.setReportingConversionErrors(context, true);
+
+ //keep locale from original context
+ newStack.getActionContext().withLocale(stack.getActionContext().getLocale());
+ }
+
+ // override
+ Map aliases = (Map) obj;
+ for (Object o : aliases.entrySet()) {
+ Map.Entry entry = (Map.Entry) o;
+ String name = entry.getKey().toString();
+ if (isNotAcceptableExpression(name)) {
+ continue;
+ }
+ String alias = (String) entry.getValue();
+ if (isNotAcceptableExpression(alias)) {
+ continue;
+ }
+ Evaluated value = new Evaluated(stack.findValue(name));
+ if (!value.isDefined()) {
+ // workaround
+ HttpParameters contextParameters = ActionContext.getContext().getParameters();
+
+ if (null != contextParameters) {
+ Parameter param = contextParameters.get(name);
+ if (param.isDefined()) {
+ value = new Evaluated(param.getValue());
+ }
+ }
+ }
+ if (value.isDefined()) {
+ try {
+ newStack.setValue(alias, value.get());
+ } catch (RuntimeException e) {
+ if (devMode) {
+ String developerNotification = localizedTextProvider.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{
+ "Unexpected Exception caught setting '" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage()
+ });
+ LOG.error(developerNotification);
+ if (action instanceof ValidationAware) {
+ ((ValidationAware) action).addActionMessage(developerNotification);
+ }
+ }
+ }
+ }
+ }
+
+ if (clearableStack) {
+ stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
+ }
+ } else {
+ LOG.debug("invalid alias expression: {}", aliasesKey);
+ }
+ }
+
+ return invocation.invoke();
+ }
+
+ protected boolean isAccepted(String paramName) {
+ AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
+ if (result.isAccepted()) {
+ return true;
+ }
+
+ LOG.warn("Parameter [{}] didn't match accepted pattern [{}]! See Accepted / Excluded patterns at\n" +
+ "https://struts.apache.org/security/#accepted--excluded-patterns",
+ paramName, result.getAcceptedPattern());
+
+ return false;
+ }
+
+ protected boolean isExcluded(String paramName) {
+ ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
+ if (!result.isExcluded()) {
+ return false;
+ }
+
+ LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / Excluded patterns at\n" +
+ "https://struts.apache.org/security/#accepted--excluded-patterns",
+ paramName, result.getExcludedPattern());
+
+ return true;
+ }
+
+ /**
+ * Checks if expression contains vulnerable code
+ *
+ * @param expression of interceptor
+ * @return true|false
+ */
+ protected boolean isNotAcceptableExpression(String expression) {
+ return isExcluded(expression) || !isAccepted(expression);
+ }
+
+ /**
+ * Sets a comma-delimited list of regular expressions to match
+ * parameters that are allowed in the parameter map (aka whitelist).
+ *
+ * Don't change the default unless you know what you are doing in terms
+ * of security implications.
+ *
+ *
+ * @param commaDelim A comma-delimited list of regular expressions
+ */
+ public void setAcceptParamNames(String commaDelim) {
+ acceptedPatterns.setAcceptedPatterns(commaDelim);
+ }
+
+ /**
+ * Sets a comma-delimited list of regular expressions to match
+ * parameters that should be removed from the parameter map.
+ *
+ * @param commaDelim A comma-delimited list of regular expressions
+ */
+ public void setExcludeParams(String commaDelim) {
+ excludedPatterns.setExcludedPatterns(commaDelim);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java
new file mode 100644
index 000000000..fd3c25a65
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java
@@ -0,0 +1,275 @@
+/*
+ * 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.ActionChainResult;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.ProxyUtil;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.Result;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.Unchainable;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * An interceptor that copies all the properties of every object in the value stack to the currently executing object,
+ * except for any object that implements {@link Unchainable}. A collection of optional includes and
+ * excludes may be provided to control how and which parameters are copied. Only includes or excludes may be
+ * specified. Specifying both results in undefined behavior. See the javadocs for {@link ReflectionProvider#copy(Object, Object,
+ * Map, Collection, Collection)} for more information.
+ *
+ *
+ *
+ * Note: It is important to remember that this interceptor does nothing if there are no objects already on the stack.
+ * This means two things:
+ * One, you can safely apply it to all your actions without any worry of adverse affects.
+ * Two, it is up to you to ensure an object exists in the stack prior to invoking this action. The most typical way this is done
+ * is through the use of the chain result type, which combines with this interceptor to make up the action
+ * chaining feature.
+ *
+ *
+ *
+ * Note: By default Errors, Field errors and Message aren't copied during chaining, to change the behaviour you can specify
+ * the below three constants in struts.properties or struts.xml:
+ *
+ *
+ *
+ *
struts.chaining.copyErrors - set to true to copy Action Errors
+ *
struts.chaining.copyFieldErrors - set to true to copy Field Errors
+ *
struts.chaining.copyMessages - set to true to copy Action Messages
+ *
+ *
+ *
+ * @author mrdon
+ * @author tm_jee ( tm_jee(at)yahoo.co.uk )
+ * @see ActionChainResult
+ */
+public class ChainingInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ChainingInterceptor.class);
+
+ private static final String ACTION_ERRORS = "actionErrors";
+ private static final String FIELD_ERRORS = "fieldErrors";
+ private static final String ACTION_MESSAGES = "actionMessages";
+
+ private boolean copyMessages = false;
+ private boolean copyErrors = false;
+ private boolean copyFieldErrors = false;
+
+ protected Collection excludes;
+
+ protected Collection includes;
+ protected ReflectionProvider reflectionProvider;
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider prov) {
+ this.reflectionProvider = prov;
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_ERRORS, required = false)
+ public void setCopyErrors(String copyErrors) {
+ this.copyErrors = "true".equalsIgnoreCase(copyErrors);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_FIELD_ERRORS, required = false)
+ public void setCopyFieldErrors(String copyFieldErrors) {
+ this.copyFieldErrors = "true".equalsIgnoreCase(copyFieldErrors);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_MESSAGES, required = false)
+ public void setCopyMessages(String copyMessages) {
+ this.copyMessages = "true".equalsIgnoreCase(copyMessages);
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ ValueStack stack = invocation.getStack();
+ CompoundRoot root = stack.getRoot();
+ if (shouldCopyStack(invocation, root)) {
+ copyStack(invocation, root);
+ }
+ return invocation.invoke();
+ }
+
+ private void copyStack(ActionInvocation invocation, CompoundRoot root) {
+ List list = prepareList(root);
+ Map ctxMap = invocation.getInvocationContext().getContextMap();
+ for (Object object : list) {
+ if (shouldCopy(object)) {
+ Object action = invocation.getAction();
+ Class> editable = null;
+ if(ProxyUtil.isProxy(action)) {
+ editable = ProxyUtil.ultimateTargetClass(action);
+ }
+ reflectionProvider.copy(object, action, ctxMap, prepareExcludes(), includes, editable);
+ }
+ }
+ }
+
+ private Collection prepareExcludes() {
+ Collection localExcludes = excludes;
+ if (!copyErrors || !copyMessages ||!copyFieldErrors) {
+ if (localExcludes == null) {
+ localExcludes = new HashSet();
+ if (!copyErrors) {
+ localExcludes.add(ACTION_ERRORS);
+ }
+ if (!copyMessages) {
+ localExcludes.add(ACTION_MESSAGES);
+ }
+ if (!copyFieldErrors) {
+ localExcludes.add(FIELD_ERRORS);
+ }
+ }
+ }
+ return localExcludes;
+ }
+
+ private boolean shouldCopy(Object o) {
+ return o != null && !(o instanceof Unchainable);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List prepareList(CompoundRoot root) {
+ List list = new ArrayList(root);
+ list.remove(0);
+ Collections.reverse(list);
+ return list;
+ }
+
+ private boolean shouldCopyStack(ActionInvocation invocation, CompoundRoot root) throws Exception {
+ Result result = invocation.getResult();
+ return root.size() > 1 && (result == null || ActionChainResult.class.isAssignableFrom(result.getClass()));
+ }
+
+ /**
+ * Gets list of parameter names to exclude
+ *
+ * @return the exclude list
+ */
+ public Collection getExcludes() {
+ return excludes;
+ }
+
+ /**
+ * Sets the list of parameter names to exclude from copying (all others will be included).
+ *
+ * @param excludes the excludes list as comma separated String
+ */
+ public void setExcludes(String excludes) {
+ this.excludes = TextParseUtil.commaDelimitedStringToSet(excludes);
+ }
+
+ /**
+ * Sets the list of parameter names to exclude from copying (all others will be included).
+ *
+ * @param excludes the excludes list
+ */
+ public void setExcludesCollection(Collection excludes) {
+ this.excludes = excludes;
+ }
+
+ /**
+ * Gets list of parameter names to include
+ *
+ * @return the include list
+ */
+ public Collection getIncludes() {
+ return includes;
+ }
+
+ /**
+ * Sets the list of parameter names to include when copying (all others will be excluded).
+ *
+ * @param includes the includes list as comma separated String
+ */
+ public void setIncludes(String includes) {
+ this.includes = TextParseUtil.commaDelimitedStringToSet(includes);
+ }
+
+
+ /**
+ * Sets the list of parameter names to include when copying (all others will be excluded).
+ *
+ * @param includes the includes list
+ */
+ public void setIncludesCollection(Collection includes) {
+ this.includes = includes;
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
new file mode 100644
index 000000000..e795543d4
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
@@ -0,0 +1,149 @@
+/*
+ * 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.conversion.impl.ConversionData;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import org.apache.commons.text.StringEscapeUtils;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ *
+ * ConversionErrorInterceptor adds conversion errors from the ActionContext to the Action's field errors.
+ *
+ *
+ * This interceptor adds any error found in the {@link ActionContext}'s conversionErrors map as a field error (provided
+ * that the action implements {@link ValidationAware}). In addition, any field that contains a validation error has its
+ * original value saved such that any subsequent requests for that value return the original value rather than the value
+ * in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to
+ * display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to
+ * the user).
+ *
+ *
+ *
+ * Note: Since 2.5.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
+ * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
+ * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
None
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * Because this interceptor is not web-specific, it abstracts the logic for whether an error should be added. This
+ * allows for web-specific interceptors to use more complex logic in the {@link #shouldAddError} method for when a value
+ * has a conversion error but is null or empty or otherwise indicates that the value was never actually entered by the
+ * user.
+ *
+ *
+ *
+ *
+ *
+ * @author Jason Carreira
+ */
+public class ConversionErrorInterceptor extends MethodFilterInterceptor {
+
+ public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
+
+ protected Object getOverrideExpr(ActionInvocation invocation, Object value) {
+ return escape(value);
+ }
+
+ protected String escape(Object value) {
+ return "\"" + StringEscapeUtils.escapeJava(String.valueOf(value)) + "\"";
+ }
+
+ @Override
+ public String doIntercept(ActionInvocation invocation) throws Exception {
+
+ ActionContext invocationContext = invocation.getInvocationContext();
+ Map conversionErrors = invocationContext.getConversionErrors();
+ ValueStack stack = invocationContext.getValueStack();
+
+ HashMap fakie = null;
+
+ for (Map.Entry entry : conversionErrors.entrySet()) {
+ String propertyName = entry.getKey();
+ ConversionData conversionData = entry.getValue();
+
+ if (shouldAddError(propertyName, conversionData.getValue())) {
+ String message = XWorkConverter.getConversionErrorMessage(propertyName, conversionData.getToClass(), com.opensymphony.xwork2.util.ValueStack.adapt(stack));
+
+ Object action = invocation.getAction();
+ if (action instanceof ValidationAware) {
+ ValidationAware va = (ValidationAware) action;
+ va.addFieldError(propertyName, message);
+ }
+
+ if (fakie == null) {
+ fakie = new HashMap<>();
+ }
+
+ fakie.put(propertyName, getOverrideExpr(invocation, conversionData.getValue()));
+ }
+ }
+
+ if (fakie != null) {
+ // if there were some errors, put the original (fake) values in place right before the result
+ stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
+ invocation.addPreResultListener(new PreResultListener() {
+ public void beforeResult(ActionInvocation invocation, String resultCode) {
+ Map fakie = (Map) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
+
+ if (fakie != null) {
+ invocation.getStack().setExprOverrides(fakie);
+ }
+ }
+ });
+ }
+ return invocation.invoke();
+ }
+
+ protected boolean shouldAddError(String propertyName, Object value) {
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java
new file mode 100644
index 000000000..c4dd70154
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java
@@ -0,0 +1,245 @@
+/*
+ * 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.Action;
+import com.opensymphony.xwork2.interceptor.annotations.InputConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+
+/**
+ *
+ *
+ * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue.
+ * If a single FieldError or ActionError (including the ones replicated by the Message Store Interceptor in a redirection) is found, the INPUT result will be triggered.
+ * This interceptor does not perform any validation.
+ *
+ *
+ *
+ * This interceptor does nothing if the name of the method being invoked is specified in the excludeMethods
+ * parameter. excludeMethods accepts a comma-delimited list of method names. For example, requests to
+ * foo!input.action and foo!back.action will be skipped by this interceptor if you set the
+ * excludeMethods parameter to "input, back".
+ *
+ *
+ *
+ * Note: As this method extends off MethodFilterInterceptor, it is capable of
+ * deciding if it is applicable only to selective methods in the action class. This is done by adding param tags
+ * for the interceptor element, naming either a list of excluded method names and/or a list of included method
+ * names, whereby includeMethods overrides excludedMethods. A single * sign is interpreted as wildcard matching
+ * all methods for both parameters.
+ * See {@link MethodFilterInterceptor} for more info.
+ *
+ *
+ *
+ * This interceptor also supports the following interfaces which can implemented by actions:
+ *
+ *
+ *
+ *
ValidationAware - implemented by ActionSupport class
+ *
ValidationWorkflowAware - allows changing result name programmatically
+ *
ValidationErrorAware - notifies action about errors and also allow change result name
+ *
+ *
+ *
+ * You can also use InputConfig annotation to change result name returned when validation errors occurred.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
inputResultName - Default to "input". Determine the result name to be returned when
+ * an action / field error is found.
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ *
There are no known extension points for this interceptor.
+ *
+ *
+ *
+ *
Example code:
+ *
+ *
+ *
+ *
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="validation"/>
+ * <interceptor-ref name="workflow"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <-- In this case myMethod as well as mySecondMethod of the action class
+ * will not pass through the workflow process -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="validation"/>
+ * <interceptor-ref name="workflow">
+ * <param name="excludeMethods">myMethod,mySecondMethod</param>
+ * </interceptor-ref name="workflow">
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <-- In this case, the result named "error" will be used when
+ * an action / field error is found -->
+ * <-- The Interceptor will only be applied for myWorkflowMethod method of action
+ * classes, since this is the only included method while any others are excluded -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="validation"/>
+ * <interceptor-ref name="workflow">
+ * <param name="inputResultName">error</param>
+ * <param name="excludeMethods">*</param>
+ * <param name="includeMethods">myWorkflowMethod</param>
+ * </interceptor-ref>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ *
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * @author Alexandru Popescu
+ * @author Philip Luppens
+ * @author tm_jee
+ */
+public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
+
+ private static final long serialVersionUID = 7563014655616490865L;
+
+ private static final Logger LOG = LogManager.getLogger(DefaultWorkflowInterceptor.class);
+
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ private String inputResultName = Action.INPUT;
+
+ /**
+ * Set the inputResultName (result name to be returned when
+ * a action / field error is found registered). Default to {@link Action#INPUT}
+ *
+ * @param inputResultName what result name to use when there was validation error(s).
+ */
+ public void setInputResultName(String inputResultName) {
+ this.inputResultName = inputResultName;
+ }
+
+ /**
+ * Intercept {@link ActionInvocation} and returns a inputResultName
+ * when action / field errors is found registered.
+ *
+ * @param invocation the action invocation
+ * @return String result name
+ */
+ @Override
+ protected String doIntercept(ActionInvocation invocation) throws Exception {
+ Object action = invocation.getAction();
+
+ if (action instanceof ValidationAware) {
+ ValidationAware validationAwareAction = (ValidationAware) action;
+
+ if (validationAwareAction.hasErrors()) {
+ LOG.debug("Errors on action [{}], returning result name [{}]", validationAwareAction, inputResultName);
+
+ String resultName = inputResultName;
+ resultName = processValidationWorkflowAware(action, resultName);
+ resultName = processInputConfig(action, invocation.getProxy().getMethod(), resultName);
+ resultName = processValidationErrorAware(action, resultName);
+
+ return resultName;
+ }
+ }
+
+ return invocation.invoke();
+ }
+
+ /**
+ * Process {@link com.opensymphony.xwork2.interceptor.ValidationWorkflowAware} interface
+ *
+ * @param action action object
+ * @param currentResultName current result name
+ *
+ * @return result name
+ */
+ private String processValidationWorkflowAware(final Object action, final String currentResultName) {
+ String resultName = currentResultName;
+ if (action instanceof ValidationWorkflowAware) {
+ resultName = ((ValidationWorkflowAware) action).getInputResultName();
+ LOG.debug("Changing result name from [{}] to [{}] because of processing [{}] interface applied to [{}]",
+ currentResultName, resultName, ValidationWorkflowAware.class.getSimpleName(), action);
+ }
+ return resultName;
+ }
+
+ /**
+ * Process {@link InputConfig} annotation applied to method
+ * @param action action object
+ * @param method method
+ * @param currentResultName current result name
+ *
+ * @return result name
+ *
+ * @throws Exception in case of any errors
+ */
+ protected String processInputConfig(final Object action, final String method, final String currentResultName) throws Exception {
+ String resultName = currentResultName;
+ InputConfig annotation = MethodUtils.getAnnotation(action.getClass().getMethod(method, EMPTY_CLASS_ARRAY),
+ InputConfig.class ,true,true);
+ if (annotation != null) {
+ if (StringUtils.isNotEmpty(annotation.methodName())) {
+ resultName = (String) MethodUtils.invokeMethod(action, true, annotation.methodName());
+ } else {
+ resultName = annotation.resultName();
+ }
+ LOG.debug("Changing result name from [{}] to [{}] because of processing annotation [{}] on action [{}]",
+ currentResultName, resultName, InputConfig.class.getSimpleName(), action);
+ }
+ return resultName;
+ }
+
+ /**
+ * Notify action if it implements {@link com.opensymphony.xwork2.interceptor.ValidationErrorAware} interface
+ *
+ * @param action action object
+ * @param currentResultName current result name
+ *
+ * @return result name
+ * @see com.opensymphony.xwork2.interceptor.ValidationErrorAware
+ */
+ protected String processValidationErrorAware(final Object action, final String currentResultName) {
+ String resultName = currentResultName;
+ if (action instanceof ValidationErrorAware) {
+ resultName = ((ValidationErrorAware) action).actionErrorOccurred(currentResultName);
+ LOG.debug("Changing result name from [{}] to [{}] because of processing interface [{}] on action [{}]",
+ currentResultName, resultName, ValidationErrorAware.class.getSimpleName(), action);
+ }
+ return resultName;
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java
new file mode 100644
index 000000000..277fc33df
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java
@@ -0,0 +1,324 @@
+/*
+ * 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.config.entities.ExceptionMappingConfig;
+import com.opensymphony.xwork2.interceptor.ExceptionHolder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.dispatcher.HttpParameters;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ *
+ * This interceptor forms the core functionality of the exception handling feature. Exception handling allows you to map
+ * an exception to a result code, just as if the action returned a result code instead of throwing an unexpected
+ * exception. When an exception is encountered, it is wrapped with an {@link ExceptionHolder} and pushed on the stack,
+ * providing easy access to the exception from within your result.
+ *
+ *
+ *
+ * Note: While you can configure exception mapping in your configuration file at any point, the configuration
+ * will not have any effect if this interceptor is not in the interceptor stack for your actions. It is recommended that
+ * you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
+ * exception, even those caused by other interceptors.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
logEnabled (optional) - Should exceptions also be logged? (boolean true|false)
+ *
+ *
logLevel (optional) - what log level should we use (trace, debug, info, warn, error, fatal)? - defaut is debug
+ *
+ *
logCategory (optional) - If provided we would use this category (eg. com.mycompany.app).
+ * Default is to use com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.
+ *
+ *
+ *
+ *
+ * The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
+ * and present a friendly webpage (with no stacktrace) to the end user.
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * If you want to add custom handling for publishing the Exception, you may override
+ * {@link #publishException(ActionInvocation, ExceptionHolder)}. The default implementation
+ * pushes the given ExceptionHolder on value stack. A custom implementation could add additional logging etc.
+ *
+ *
+ * @author Matthew E. Porter (matthew dot porter at metissian dot com)
+ * @author Claus Ibsen
+ */
+public class ExceptionMappingInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
+
+ protected Logger categoryLogger;
+ protected boolean logEnabled = false;
+ protected String logCategory;
+ protected String logLevel;
+
+
+ public boolean isLogEnabled() {
+ return logEnabled;
+ }
+
+ public void setLogEnabled(boolean logEnabled) {
+ this.logEnabled = logEnabled;
+ }
+
+ public String getLogCategory() {
+ return logCategory;
+ }
+
+ public void setLogCategory(String logCatgory) {
+ this.logCategory = logCatgory;
+ }
+
+ public String getLogLevel() {
+ return logLevel;
+ }
+
+ public void setLogLevel(String logLevel) {
+ this.logLevel = logLevel;
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ String result;
+
+ try {
+ result = invocation.invoke();
+ } catch (Exception e) {
+ if (isLogEnabled()) {
+ handleLogging(e);
+ }
+ List exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
+ ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
+ if (mappingConfig != null && mappingConfig.getResult()!=null) {
+ Map mappingParams = mappingConfig.getParams();
+ // create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
+ HttpParameters parameters = HttpParameters.create(mappingParams).build();
+ invocation.getInvocationContext().withParameters(parameters);
+ result = mappingConfig.getResult();
+ publishException(invocation, new ExceptionHolder(e));
+ } else {
+ throw e;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Handles the logging of the exception.
+ *
+ * @param e the exception to log.
+ */
+ protected void handleLogging(Exception e) {
+ if (logCategory != null) {
+ if (categoryLogger == null) {
+ // init category logger
+ categoryLogger = LogManager.getLogger(logCategory);
+ }
+ doLog(categoryLogger, e);
+ } else {
+ doLog(LOG, e);
+ }
+ }
+
+ /**
+ * Performs the actual logging.
+ *
+ * @param logger the provided logger to use.
+ * @param e the exception to log.
+ */
+ protected void doLog(Logger logger, Exception e) {
+ if (logLevel == null) {
+ logger.debug(e.getMessage(), e);
+ return;
+ }
+
+ if ("trace".equalsIgnoreCase(logLevel)) {
+ logger.trace(e.getMessage(), e);
+ } else if ("debug".equalsIgnoreCase(logLevel)) {
+ logger.debug(e.getMessage(), e);
+ } else if ("info".equalsIgnoreCase(logLevel)) {
+ logger.info(e.getMessage(), e);
+ } else if ("warn".equalsIgnoreCase(logLevel)) {
+ logger.warn(e.getMessage(), e);
+ } else if ("error".equalsIgnoreCase(logLevel)) {
+ logger.error(e.getMessage(), e);
+ } else if ("fatal".equalsIgnoreCase(logLevel)) {
+ logger.fatal(e.getMessage(), e);
+ } else {
+ throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
+ }
+ }
+
+ /**
+ * Try to find appropriate {@link ExceptionMappingConfig} based on provided Throwable
+ *
+ * @param exceptionMappings list of defined exception mappings
+ * @param t caught exception
+ * @return appropriate mapping or null
+ */
+ protected ExceptionMappingConfig findMappingFromExceptions(List exceptionMappings, Throwable t) {
+ ExceptionMappingConfig config = null;
+ // Check for specific exception mappings.
+ if (exceptionMappings != null) {
+ int deepest = Integer.MAX_VALUE;
+ for (Object exceptionMapping : exceptionMappings) {
+ ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
+ int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
+ if (depth >= 0 && depth < deepest) {
+ deepest = depth;
+ config = exceptionMappingConfig;
+ }
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
+ * Otherwise, returns depth. Lowest depth wins.
+ *
+ * @param exceptionMapping the mapping classname
+ * @param t the cause
+ * @return the depth, if not found -1 is returned.
+ */
+ public int getDepth(String exceptionMapping, Throwable t) {
+ return getDepth(exceptionMapping, t.getClass(), 0);
+ }
+
+ private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
+ if (exceptionClass.getName().contains(exceptionMapping)) {
+ // Found it!
+ return depth;
+ }
+ // If we've gone as far as we can go and haven't found it...
+ if (exceptionClass.equals(Throwable.class)) {
+ return -1;
+ }
+ return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
+ }
+
+ /**
+ * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
+ * Subclasses may override this to customize publishing.
+ *
+ * @param invocation The invocation to publish Exception for.
+ * @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
+ */
+ protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
+ invocation.getStack().push(exceptionHolder);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java
new file mode 100644
index 000000000..4536d462b
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java
@@ -0,0 +1,90 @@
+/*
+ * 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 org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+
+
+/**
+ *
+ *
+ * This interceptor logs the start and end of the execution an action (in English-only, not internationalized).
+ *
+ * Note:: This interceptor will log at INFO level.
+ *
+ *
+ *
+ *
+ * There are no parameters for this interceptor.
+ *
+ *
+ *
+ * There are no obvious extensions to the existing interceptor.
+ *
+ *
+ *
+ *
+ * <!-- prints out a message before and after the immediate action execution -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="completeStack"/>
+ * <interceptor-ref name="logger"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ * <!-- prints out a message before any more interceptors continue and after they have finished -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="logger"/>
+ * <interceptor-ref name="completeStack"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ *
+ * @author Jason Carreira
+ */
+public class LoggingInterceptor extends AbstractInterceptor {
+ private static final Logger LOG = LogManager.getLogger(LoggingInterceptor.class);
+ private static final String FINISH_MESSAGE = "Finishing execution stack for action ";
+ private static final String START_MESSAGE = "Starting execution stack for action ";
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ logMessage(invocation, START_MESSAGE);
+ String result = invocation.invoke();
+ logMessage(invocation, FINISH_MESSAGE);
+ return result;
+ }
+
+ private void logMessage(ActionInvocation invocation, String baseMessage) {
+ if (LOG.isInfoEnabled()) {
+ StringBuilder message = new StringBuilder(baseMessage);
+ String namespace = invocation.getProxy().getNamespace();
+
+ if ((namespace != null) && (namespace.trim().length() > 0)) {
+ message.append(namespace).append("/");
+ }
+
+ message.append(invocation.getProxy().getActionName());
+ LOG.info(message.toString());
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java
new file mode 100644
index 000000000..d2679ebd6
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.util.CompoundRoot;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.ModelDriven;
+import org.apache.struts2.interceptor.parameter.ParametersInterceptor;
+import org.apache.struts2.util.ValueStack;
+
+/**
+ *
+ *
+ * Watches for {@link ModelDriven} actions and adds the action's model on to the value stack.
+ *
+ *
Note: The ModelDrivenInterceptor must come before the both {@link StaticParametersInterceptor} and
+ * {@link ParametersInterceptor} if you want the parameters to be applied to the model.
+ *
+ *
Note: The ModelDrivenInterceptor will only push the model into the stack when the
+ * model is not null, else it will be ignored.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
refreshModelBeforeResult - set to true if you want the model to be refreshed on the value stack after action
+ * execution and before result execution. The setting is useful if you want to change the model instance during the
+ * action execution phase, like when loading it from the data layer. This will result in getModel() being called at
+ * least twice.
+ *
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * There are no known extension points to this interceptor.
+ *
+ *
+ *
+ *
+ *
+ * @author tm_jee
+ * @version $Date$ $Id$
+ */
+public class ModelDrivenInterceptor extends AbstractInterceptor {
+
+ protected boolean refreshModelBeforeResult = false;
+
+ public void setRefreshModelBeforeResult(boolean val) {
+ this.refreshModelBeforeResult = val;
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ Object action = invocation.getAction();
+
+ if (action instanceof ModelDriven) {
+ ModelDriven modelDriven = (ModelDriven) action;
+ ValueStack stack = invocation.getStack();
+ Object model = modelDriven.getModel();
+ if (model != null) {
+ stack.push(model);
+ }
+ if (refreshModelBeforeResult) {
+ invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
+ }
+ }
+ return invocation.invoke();
+ }
+
+ /**
+ * Refreshes the model instance on the value stack, if it has changed
+ */
+ protected static class RefreshModelBeforeResult implements PreResultListener {
+ private Object originalModel;
+ protected ModelDriven action;
+
+
+ public RefreshModelBeforeResult(ModelDriven action, Object model) {
+ this.originalModel = model;
+ this.action = action;
+ }
+
+ public void beforeResult(ActionInvocation invocation, String resultCode) {
+ ValueStack stack = invocation.getStack();
+ CompoundRoot root = stack.getRoot();
+
+ boolean needsRefresh = true;
+ Object newModel = action.getModel();
+
+ // Check to see if the new model instance is already on the stack
+ if (newModel != null) {
+ for (Object item : root) {
+ if (item == newModel) {
+ needsRefresh = false;
+ break;
+ }
+ }
+ }
+
+ // Add the new model on the stack
+ if (needsRefresh) {
+
+ // Clear off the old model instance
+ if (originalModel != null) {
+ root.remove(originalModel);
+ }
+ if (newModel != null) {
+ stack.push(newModel);
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.java
new file mode 100644
index 000000000..ad72d5853
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.java
@@ -0,0 +1,124 @@
+/*
+ * 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.util.TextParseUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.action.NoParameters;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.dispatcher.Parameter;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * This is a simple XWork interceptor that allows parameters (matching
+ * one of the paramNames attribute csv value) to be
+ * removed from the parameter map if they match a certain value
+ * (matching one of the paramValues attribute csv value), before they
+ * are set on the action. A typical usage would be to want a dropdown/select
+ * to map onto a boolean value on an action. The select had the options
+ * none, yes and no with values -1, true and false. The true and false would
+ * map across correctly. However the -1 would be set to false.
+ * This was not desired as one might needed the value on the action to stay null.
+ * This interceptor fixes this by preventing the parameter from ever reaching
+ * the action.
+ *
+ *
+ *
paramNames - A comma separated value (csv) indicating the parameter name
+ * whose param value should be considered that if they match any of the
+ * comma separated value (csv) from paramValues attribute, shall be
+ * removed from the parameter map such that they will not be applied
+ * to the action
+ *
paramValues - A comma separated value (csv) indicating the parameter value that if
+ * matched shall have its parameter be removed from the parameter map
+ * such that they will not be applied to the action
+ */
+public class ParameterRemoverInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class);
+
+ private Set paramNames = Collections.emptySet();
+ private Set paramValues = Collections.emptySet();
+
+ /**
+ * Decide if the parameter should be removed from the parameter map based on
+ * paramNames and paramValues.
+ *
+ * @see AbstractInterceptor
+ */
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ if (!(invocation.getAction() instanceof NoParameters)
+ && (null != this.paramNames)) {
+ ActionContext ac = invocation.getInvocationContext();
+ HttpParameters parameters = ac.getParameters();
+
+ if (parameters != null) {
+ for (String removeName : paramNames) {
+ try {
+ Parameter parameter = parameters.get(removeName);
+ if (parameter.isDefined() && this.paramValues.contains(parameter.getValue())) {
+ parameters.remove(removeName);
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to convert parameter to string", e);
+ }
+ }
+ }
+ }
+ return invocation.invoke();
+ }
+
+ /**
+ * Allows paramNames attribute to be set as comma-separated-values (csv).
+ *
+ * @param paramNames the paramNames to set
+ */
+ public void setParamNames(String paramNames) {
+ this.paramNames = TextParseUtil.commaDelimitedStringToSet(paramNames);
+ }
+
+ /**
+ * Allows paramValues attribute to be set as a comma-separated-values (csv).
+ *
+ * @param paramValues the paramValues to set
+ */
+ public void setParamValues(String paramValues) {
+ this.paramValues = TextParseUtil.commaDelimitedStringToSet(paramValues);
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java
new file mode 100644
index 000000000..a410c3b12
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java
@@ -0,0 +1,177 @@
+/*
+ * 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.interceptor.PrefixMethodInvocationUtil;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.Preparable;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ *
+ *
+ * This interceptor calls prepare() on actions which implement
+ * {@link Preparable}. This interceptor is very useful for any situation where
+ * you need to ensure some logic runs before the actual execute method runs.
+ *
+ *
+ * A typical use of this is to run some logic to load an object from the
+ * database so that when parameters are set they can be set on this object. For
+ * example, suppose you have a User object with two properties: id and
+ * name. Provided that the params interceptor is called twice (once
+ * before and once after this interceptor), you can load the User object using
+ * the id property, and then when the second params interceptor is called the
+ * parameter user.name will be set, as desired, on the actual object
+ * loaded from the database. See the example for more info.
+ *
+ *
+ * Note: Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
+ * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
+ * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
+ *
+ *
+ *
+ * Update: Added logic to execute a prepare{MethodName} and conditionally
+ * the a general prepare() Method, depending on the 'alwaysInvokePrepare' parameter/property
+ * which is by default true. This allows us to run some logic based on the method
+ * name we specify in the {@link org.apache.struts2.ActionProxy}. For example, you can specify a
+ * prepareInput() method that will be run before the invocation of the input method.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
alwaysInvokePrepare - Default to true. If true, prepare will always be invoked,
+ * otherwise it will not.
+ *
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * There are no known extension points to this interceptor.
+ *
+ *
+ *
+ *
Example code:
+ *
+ *
+ *
+ * <!-- Calls the params interceptor twice, allowing you to
+ * pre-load data for the second time parameters are set -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="prepare"/>
+ * <interceptor-ref name="basicStack"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ *
+ * @author Jason Carreira
+ * @author Philip Luppens
+ * @author tm_jee
+ * @see Preparable
+ */
+public class PrepareInterceptor extends MethodFilterInterceptor {
+
+ private static final long serialVersionUID = -5216969014510719786L;
+
+ private final static String PREPARE_PREFIX = "prepare";
+ private final static String ALT_PREPARE_PREFIX = "prepareDo";
+
+ private boolean alwaysInvokePrepare = true;
+ private boolean firstCallPrepareDo = false;
+
+ /**
+ * Sets if the prepare method should always be executed.
+ *
+ * Default is true.
+ *
+ *
+ * @param alwaysInvokePrepare if prepare should always be executed or not.
+ */
+ public void setAlwaysInvokePrepare(String alwaysInvokePrepare) {
+ this.alwaysInvokePrepare = Boolean.parseBoolean(alwaysInvokePrepare);
+ }
+
+ /**
+ * Sets if the prepareDoXXX method should be called first
+ *
+ * Default is false for backward compatibility
+ *
+ * @param firstCallPrepareDo if prepareDoXXX should be called first
+ */
+ public void setFirstCallPrepareDo(String firstCallPrepareDo) {
+ this.firstCallPrepareDo = Boolean.parseBoolean(firstCallPrepareDo);
+ }
+
+ @Override
+ public String doIntercept(ActionInvocation invocation) throws Exception {
+ Object action = invocation.getAction();
+
+ if (action instanceof Preparable) {
+ try {
+ String[] prefixes;
+ if (firstCallPrepareDo) {
+ prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
+ } else {
+ prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
+ }
+ PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
+ }
+ catch (InvocationTargetException e) {
+ /*
+ * The invoked method threw an exception and reflection wrapped it
+ * in an InvocationTargetException.
+ * If possible re-throw the original exception so that normal
+ * exception handling will take place.
+ */
+ Throwable cause = e.getCause();
+ if (cause instanceof Exception) {
+ throw (Exception) cause;
+ } else if(cause instanceof Error) {
+ throw (Error) cause;
+ } else {
+ /*
+ * The cause is not an Exception or Error (must be Throwable) so
+ * just re-throw the wrapped exception.
+ */
+ throw e;
+ }
+ }
+
+ if (alwaysInvokePrepare) {
+ ((Preparable) action).prepare();
+ }
+ }
+
+ return invocation.invoke();
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java
new file mode 100644
index 000000000..ddf426519
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java
@@ -0,0 +1,165 @@
+/*
+ * 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.ObjectFactory;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsException;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ *
+ *
+ * An interceptor that enables scoped model-driven actions.
+ *
+ *
This interceptor only activates on actions that implement the {@link ScopedModelDriven} interface. If
+ * detected, it will retrieve the model class from the configured scope, then provide it to the Action.
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
className - The model class name. Defaults to the class name of the object returned by the getModel() method.
+ *
+ *
name - The key to use when storing or retrieving the instance in a scope. Defaults to the model
+ * class name.
+ *
+ *
scope - The scope to store and retrieve the model. Defaults to 'request' but can also be 'session'.
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * There are no known extension points for this interceptor.
+ *
+ *
+ *
+ *
+ */
+public class ScopedModelDrivenInterceptor extends AbstractInterceptor {
+
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ private static final String GET_MODEL = "getModel";
+ private String scope;
+ private String name;
+ private String className;
+ private ObjectFactory objectFactory;
+
+ @Inject
+ public void setObjectFactory(ObjectFactory factory) {
+ this.objectFactory = factory;
+ }
+
+ protected Object resolveModel(ObjectFactory factory, ActionContext actionContext, String modelClassName, String modelScope, String modelName) throws Exception {
+ Object model;
+ Map scopeMap = actionContext.getContextMap();
+ if ("session".equals(modelScope)) {
+ scopeMap = actionContext.getSession();
+ }
+
+ model = scopeMap.get(modelName);
+ if (model == null) {
+ model = factory.buildBean(modelClassName, null);
+ scopeMap.put(modelName, model);
+ }
+ return model;
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ Object action = invocation.getAction();
+
+ if (action instanceof ScopedModelDriven) {
+ ScopedModelDriven modelDriven = (ScopedModelDriven) action;
+ if (modelDriven.getModel() == null) {
+ ActionContext ctx = ActionContext.getContext();
+ ActionConfig config = invocation.getProxy().getConfig();
+
+ String cName = className;
+ if (cName == null) {
+ try {
+ Method method = action.getClass().getMethod(GET_MODEL, EMPTY_CLASS_ARRAY);
+ Class cls = method.getReturnType();
+ cName = cls.getName();
+ } catch (NoSuchMethodException e) {
+ throw new StrutsException("The " + GET_MODEL + "() is not defined in action " + action.getClass() + "", config);
+ }
+ }
+ String modelName = name;
+ if (modelName == null) {
+ modelName = cName;
+ }
+ Object model = resolveModel(objectFactory, ctx, cName, scope, modelName);
+ modelDriven.setModel(model);
+ modelDriven.setScopeKey(modelName);
+ }
+ }
+ return invocation.invoke();
+ }
+
+ /**
+ * @param className the className to set
+ */
+ public void setClassName(String className) {
+ this.className = className;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @param scope the scope to set
+ */
+ public void setScope(String scope) {
+ this.scope = scope;
+ }
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java
new file mode 100644
index 000000000..5b00ffca6
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java
@@ -0,0 +1,242 @@
+/*
+ * 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.LocalizedTextProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.Parameterizable;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
+import com.opensymphony.xwork2.util.ClearableValueStack;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ *
+ *
+ * This interceptor populates the action with the static parameters defined in the action configuration. If the action
+ * implements {@link Parameterizable}, a map of the static parameters will be also be passed directly to the action.
+ * The static params will be added to the request params map, unless "merge" is set to false.
+ *
+ *
Parameters are typically defined with <param> elements within xwork.xml.
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
None
+ *
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ *
There are no extension points to this interceptor.