diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 97a4ed500..5794f8746 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -52,12 +52,12 @@ jobs: java-version: 17 cache: 'maven' - name: Initialize CodeQL - uses: github/codeql-action/init@v3.26.12 + uses: github/codeql-action/init@v3.27.0 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v3.26.12 + uses: github/codeql-action/autobuild@v3.27.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.26.12 + uses: github/codeql-action/analyze@v3.27.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards-analysis.yaml b/.github/workflows/scorecards-analysis.yaml index 9132e371d..48e4863b6 100644 --- a/.github/workflows/scorecards-analysis.yaml +++ b/.github/workflows/scorecards-analysis.yaml @@ -65,6 +65,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@3aa71356c75a8edd8430d54dff2982203a28be45 # 2.22.11 with: sarif_file: results.sarif diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml index 9cf119743..33df6ee79 100644 --- a/apps/showcase/pom.xml +++ b/apps/showcase/pom.xml @@ -207,7 +207,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.3.1 + 3.5.1 it.org.apache.struts2.showcase.*Test diff --git a/core/src/main/java/com/opensymphony/xwork2/Action.java b/core/src/main/java/com/opensymphony/xwork2/Action.java index 768ca2678..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. - */ - 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; - +@Deprecated +public interface Action extends org.apache.struts2.Action { } diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java index 629c65c80..32a6f1303 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,249 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.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: - *

- * - * ActionContext context = ActionContext.getContext(); - * - *

- * 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()); } - /** - * 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!"); + public static ActionContext adapt(org.apache.struts2.ActionContext actualContext) { + if (actualContext instanceof ActionContext) { + return (ActionContext) actualContext; } - return new ActionContext(context); + return actualContext != null ? new ActionContext(actualContext) : null; + } + + public static ActionContext of(Map 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()); + } + + @Override + public ActionContext bind() { + super.bind(); + return this; } - /** - * Sets the action invocation (the execution state). - * - * @param actionInvocation the action execution state. - */ public ActionContext withActionInvocation(ActionInvocation actionInvocation) { - put(ACTION_INVOCATION, actionInvocation); + return withActionInvocation((org.apache.struts2.ActionInvocation) actionInvocation); + } + + @Override + public ActionContext withActionInvocation(org.apache.struts2.ActionInvocation 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 ActionInvocation.adapt(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. - */ public ActionContext withValueStack(ValueStack valueStack) { - put(VALUE_STACK, valueStack); + return withValueStack((org.apache.struts2.util.ValueStack) valueStack); + } + + @Override + public ActionContext withValueStack(org.apache.struts2.util.ValueStack 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 ValueStack.adapt(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/com/opensymphony/xwork2/ActionEventListener.java b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java index d125a683b..28d46e992 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java @@ -21,24 +21,50 @@ 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 - */ +@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); - /** - * 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); + + static ActionEventListener adapt(org.apache.struts2.ActionEventListener actualListener) { + if (actualListener instanceof ActionEventListener) { + return (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 472f23ea7..81e55d592 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java @@ -22,158 +22,125 @@ 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(); + @Override + ActionProxy getProxy(); - /** - * 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 - */ + @Override 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. - */ + @Override + default void addPreResultListener(org.apache.struts2.interceptor.PreResultListener listener) { + addPreResultListener(PreResultListener.adapt(listener)); + } + 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; + @Override + default void setActionEventListener(org.apache.struts2.ActionEventListener listener) { + setActionEventListener(ActionEventListener.adapt(listener)); + } - /** - * 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) ; + @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) { + if (actualInvocation instanceof ActionInvocation) { + return (ActionInvocation) actualInvocation; + } + return actualInvocation != null ? new LegacyAdapter(actualInvocation) : null; + } + + class LegacyAdapter implements ActionInvocation { + + private final org.apache.struts2.ActionInvocation adaptee; + + private LegacyAdapter(org.apache.struts2.ActionInvocation adaptee) { + this.adaptee = adaptee; + } + + @Override + public Object getAction() { + return adaptee.getAction(); + } + + @Override + public boolean isExecuted() { + return adaptee.isExecuted(); + } + + @Override + public ActionContext getInvocationContext() { + return ActionContext.adapt(adaptee.getInvocationContext()); + } + + @Override + public ActionProxy getProxy() { + return ActionProxy.adapt(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 ValueStack.adapt(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/ActionProxy.java b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java index 595671462..c3905a1a0 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java @@ -20,88 +20,75 @@ 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) { + if (actualProxy instanceof ActionProxy) { + return (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/com/opensymphony/xwork2/ActionSupport.java b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java index 33a88be36..be9cc29ca 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java @@ -18,346 +18,11 @@ */ package com.opensymphony.xwork2; -import com.opensymphony.xwork2.conversion.impl.ConversionData; -import com.opensymphony.xwork2.inject.Container; -import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.ValidationAware; -import com.opensymphony.xwork2.util.ValueStack; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.struts2.StrutsConstants; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; /** - * Provides a default implementation for the most common actions. - * See the documentation for all the interfaces this class implements for more detailed information. + * @deprecated since 6.7.0, use {@link org.apache.struts2.ActionSupport} instead. */ -public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable { - - private static final Logger LOG = LogManager.getLogger(ActionSupport.class); - - private final ValidationAwareSupport validationAware = new ValidationAwareSupport(); - - private transient TextProvider textProvider; - private transient LocaleProvider localeProvider; - - protected Container container; - - @Override - public void setActionErrors(Collection errorMessages) { - validationAware.setActionErrors(errorMessages); - } - - @Override - public Collection getActionErrors() { - return validationAware.getActionErrors(); - } - - @Override - public void setActionMessages(Collection messages) { - validationAware.setActionMessages(messages); - } - - @Override - public Collection getActionMessages() { - return validationAware.getActionMessages(); - } - - @Override - public void setFieldErrors(Map> errorMap) { - validationAware.setFieldErrors(errorMap); - } - - @Override - public Map> getFieldErrors() { - return validationAware.getFieldErrors(); - } - - @Override - public Locale getLocale() { - return getLocaleProvider().getLocale(); - } - - @Override - public boolean isValidLocaleString(String localeStr) { - return getLocaleProvider().isValidLocaleString(localeStr); - } - - @Override - public boolean isValidLocale(Locale locale) { - return getLocaleProvider().isValidLocale(locale); - } - - @Override - public Locale toLocale(String localeStr) { - return getLocaleProvider().toLocale(localeStr); - } - - @Override - public boolean hasKey(String key) { - return getTextProvider().hasKey(key); - } - - @Override - public String getText(String aTextName) { - return getTextProvider().getText(aTextName); - } - - @Override - public String getText(String aTextName, String defaultValue) { - return getTextProvider().getText(aTextName, defaultValue); - } - - @Override - public String getText(String aTextName, String defaultValue, String obj) { - return getTextProvider().getText(aTextName, defaultValue, obj); - } - - @Override - public String getText(String aTextName, List args) { - return getTextProvider().getText(aTextName, args); - } - - @Override - public String getText(String key, String[] args) { - return getTextProvider().getText(key, args); - } - - @Override - public String getText(String aTextName, String defaultValue, List args) { - return getTextProvider().getText(aTextName, defaultValue, args); - } - - @Override - public String getText(String key, String defaultValue, String[] args) { - return getTextProvider().getText(key, defaultValue, args); - } - - @Override - public String getText(String key, String defaultValue, List args, ValueStack stack) { - return getTextProvider().getText(key, defaultValue, args, stack); - } - - @Override - public String getText(String key, String defaultValue, String[] args, ValueStack stack) { - return getTextProvider().getText(key, defaultValue, args, stack); - } - - /** - * Dedicated method to support I10N and conversion errors - * - * @param key message which contains formatting string - * @param expr that should be formatted - * @return formatted expr with format specified by key - */ - public String getFormatted(String key, String expr) { - Map conversionErrors = ActionContext.getContext().getConversionErrors(); - if (conversionErrors.containsKey(expr)) { - String[] vals = (String[]) conversionErrors.get(expr).getValue(); - return vals[0]; - } else { - final ValueStack valueStack = ActionContext.getContext().getValueStack(); - final Object val = valueStack.findValue(expr); - return getText(key, List.of(val)); - } - } - - @Override - public ResourceBundle getTexts() { - return getTextProvider().getTexts(); - } - - @Override - public ResourceBundle getTexts(String aBundleName) { - return getTextProvider().getTexts(aBundleName); - } - - @Override - public void addActionError(String anErrorMessage) { - validationAware.addActionError(anErrorMessage); - } - - @Override - public void addActionMessage(String aMessage) { - validationAware.addActionMessage(aMessage); - } - - @Override - public void addFieldError(String fieldName, String errorMessage) { - validationAware.addFieldError(fieldName, errorMessage); - } - - public String input() throws Exception { - return INPUT; - } - - /** - * A default implementation that does nothing an returns "success". - * - *

- * Subclasses should override this method to provide their business logic. - *

- * - *

- * See also {@link com.opensymphony.xwork2.Action#execute()}. - *

- * - * @return returns {@link #SUCCESS} - * @throws Exception can be thrown by subclasses. - */ - @Override - public String execute() throws Exception { - return SUCCESS; - } - - @Override - public boolean hasActionErrors() { - return validationAware.hasActionErrors(); - } - - @Override - public boolean hasActionMessages() { - return validationAware.hasActionMessages(); - } - - @Override - public boolean hasErrors() { - return validationAware.hasErrors(); - } - - @Override - public boolean hasFieldErrors() { - return validationAware.hasFieldErrors(); - } - - /** - * Clears field errors. Useful for Continuations and other situations - * where you might want to clear parts of the state on the same action. - */ - public void clearFieldErrors() { - validationAware.clearFieldErrors(); - } - - /** - * Clears action errors. Useful for Continuations and other situations - * where you might want to clear parts of the state on the same action. - */ - public void clearActionErrors() { - validationAware.clearActionErrors(); - } - - /** - * Clears messages. Useful for Continuations and other situations - * where you might want to clear parts of the state on the same action. - */ - public void clearMessages() { - validationAware.clearMessages(); - } - - /** - * Clears all errors. Useful for Continuations and other situations - * where you might want to clear parts of the state on the same action. - */ - public void clearErrors() { - validationAware.clearErrors(); - } - - /** - * Clears all errors and messages. Useful for Continuations and other situations - * where you might want to clear parts of the state on the same action. - */ - public void clearErrorsAndMessages() { - validationAware.clearErrorsAndMessages(); - } - - /** - * A default implementation that validates nothing. - * Subclasses should override this method to provide validations. - */ - @Override - public void validate() { - // A default implementation that validates nothing - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * - * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return - * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc. - * - *

- * The next time this action is invoked (and using the same continuation ID), the method will resume immediately - * after where this method was called, with the entire call stack in the execute method restored. - *

- * - *

- * Note: this method can only be called within the {@link #execute()} method. - *

- * - * - * - * @param result the result to return - the same type of return value in the {@link #execute()} method. - */ - public void pause(String result) { - } - - /** - * If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory}, - * inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in, - * then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field - * for future references and at the returns reference to that field - * - * @return reference to field with TextProvider - */ - protected TextProvider getTextProvider() { - if (textProvider == null) { - final TextProviderFactory tpf = getContainer().getInstance(TextProviderFactory.class); - textProvider = tpf.createInstance(getClass()); - } - return textProvider; - } - - protected LocaleProvider getLocaleProvider() { - if (localeProvider == null) { - final LocaleProviderFactory localeProviderFactory = getContainer().getInstance(LocaleProviderFactory.class); - localeProvider = localeProviderFactory.createLocaleProvider(); - } - return localeProvider; - } - - /** - * TODO: This a temporary solution, maybe we should consider stop injecting container into beans - */ - protected Container getContainer() { - if (container == null) { - container = ActionContext.getContext().getContainer(); - if (container != null) { - boolean devMode = Boolean.parseBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_DEVMODE)); - if (devMode) { - LOG.warn("Container is null, action was created manually? Fallback to ActionContext"); - } else { - LOG.debug("Container is null, action was created manually? Fallback to ActionContext"); - } - } else { - LOG.warn("Container is null, action was created out of ActionContext scope?!?"); - } - } - return container; - } - - @Inject - public void setContainer(Container container) { - this.container = container; - } - +@Deprecated +public class ActionSupport extends org.apache.struts2.ActionSupport implements Action, Validateable, ValidationAware { } diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java index fe32bde35..337b2bef2 100644 --- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java +++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -258,6 +258,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/ModelDriven.java b/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java index fc7f9a348..f3ae25cab 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java +++ b/core/src/main/java/com/opensymphony/xwork2/ModelDriven.java @@ -18,25 +18,9 @@ */ package com.opensymphony.xwork2; -import org.apache.struts2.interceptor.parameter.StrutsParameter; - /** - * ModelDriven Actions provide a model object to be pushed onto the ValueStack - * in addition to the Action itself, allowing a FormBean type approach like Struts. - * - * @author Jason Carreira + * @deprecated since 6.7.0, use {@link org.apache.struts2.ModelDriven} instead. */ -public interface ModelDriven { - - /** - * Gets the model to be pushed onto the ValueStack instead of the Action itself. - *

- * Please be aware that all setters and getters of every depth on the object returned by this method are available - * for user parameter injection! - * - * @return the model - */ - @StrutsParameter(depth = Integer.MAX_VALUE) - T getModel(); - +@Deprecated +public interface ModelDriven extends org.apache.struts2.ModelDriven { } diff --git a/core/src/main/java/com/opensymphony/xwork2/Preparable.java b/core/src/main/java/com/opensymphony/xwork2/Preparable.java index 23fdf68ae..2c03088e8 100644 --- a/core/src/main/java/com/opensymphony/xwork2/Preparable.java +++ b/core/src/main/java/com/opensymphony/xwork2/Preparable.java @@ -19,19 +19,8 @@ package com.opensymphony.xwork2; /** - * Preparable Actions will have their prepare() method called if the {@link com.opensymphony.xwork2.interceptor.PrepareInterceptor} - * is applied to the ActionConfig. - * - * @author Jason Carreira - * @see com.opensymphony.xwork2.interceptor.PrepareInterceptor + * @deprecated since 6.7.0, use {@link org.apache.struts2.Preparable} instead. */ -public interface Preparable { - - /** - * This method is called to allow the action to prepare itself. - * - * @throws Exception thrown if a system level exception occurs. - */ - void prepare() throws Exception; - +@Deprecated +public interface Preparable extends org.apache.struts2.Preparable { } diff --git a/core/src/main/java/com/opensymphony/xwork2/Result.java b/core/src/main/java/com/opensymphony/xwork2/Result.java index 8c1687e5a..36a93438a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/Result.java +++ b/core/src/main/java/com/opensymphony/xwork2/Result.java @@ -18,33 +18,39 @@ */ 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 { +@Deprecated +public interface Result extends org.apache.struts2.Result { + + @Override + default void execute(org.apache.struts2.ActionInvocation invocation) throws Exception { + execute(ActionInvocation.adapt(invocation)); + } - /** - * 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; + static Result adapt(org.apache.struts2.Result actualResult) { + if (actualResult instanceof Result) { + return (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/Unchainable.java b/core/src/main/java/com/opensymphony/xwork2/Unchainable.java index 9f96b92dc..506f4f283 100644 --- a/core/src/main/java/com/opensymphony/xwork2/Unchainable.java +++ b/core/src/main/java/com/opensymphony/xwork2/Unchainable.java @@ -19,9 +19,8 @@ package com.opensymphony.xwork2; /** - * Simple marker interface to indicate an object should not have its properties copied during chaining. - * - * @see com.opensymphony.xwork2.interceptor.ChainingInterceptor + * @deprecated since 6.7.0, use {@link org.apache.struts2.Unchainable} instead. */ -public interface Unchainable { +@Deprecated +public interface Unchainable extends org.apache.struts2.Unchainable { } diff --git a/core/src/main/java/com/opensymphony/xwork2/Validateable.java b/core/src/main/java/com/opensymphony/xwork2/Validateable.java index ed7226380..c92170e73 100644 --- a/core/src/main/java/com/opensymphony/xwork2/Validateable.java +++ b/core/src/main/java/com/opensymphony/xwork2/Validateable.java @@ -19,17 +19,8 @@ package com.opensymphony.xwork2; /** - * Provides an interface in which a call for a validation check can be done. - * - * @author Jason Carreira - * @see ActionSupport - * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor + * @deprecated since 6.7.0, use {@link org.apache.struts2.Validateable} instead. */ -public interface Validateable { - - /** - * Performs validation. - */ - void validate(); - +@Deprecated +public interface Validateable extends org.apache.struts2.Validateable { } diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java index e90cd1014..1c0a9d01f 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java @@ -37,8 +37,16 @@ public class InterceptorMapping implements Serializable { private final Interceptor interceptor; private final Map params; + public InterceptorMapping(String name, org.apache.struts2.interceptor.Interceptor interceptor) { + this(name, Interceptor.adapt(interceptor)); + } + + public InterceptorMapping(String name, org.apache.struts2.interceptor.Interceptor interceptor, Map params) { + this(name, Interceptor.adapt(interceptor), params); + } + public InterceptorMapping(String name, Interceptor interceptor) { - this(name, interceptor, new HashMap()); + this(name, interceptor, new HashMap<>()); } public InterceptorMapping(String name, Interceptor interceptor, Map params) { 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 745b26efb..a3304a136 100644 --- a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java +++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java @@ -70,12 +70,18 @@ public class DefaultInterceptorFactory implements InterceptorFactory { reflectionProvider.setProperties(params, o); } - if (o instanceof Interceptor interceptor) { - interceptor.init(); - return interceptor; + Interceptor interceptor = null; + if (o instanceof 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..42527494e 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,19 +52,29 @@ public class DefaultResultFactory implements ResultFactory { Result result = null; if (resultClassName != null) { - result = (Result) objectFactory.buildBean(resultClassName, extraContext); + 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(), result, extraContext, true); + reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), o, extraContext, true); } catch (ReflectionException ex) { - if (result instanceof ReflectionExceptionHandler) { - ((ReflectionExceptionHandler) result).handle(ex); + if (o instanceof ReflectionExceptionHandler) { + ((ReflectionExceptionHandler) o).handle(ex); } } } } + + 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); + } } return result; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java index 13bf646d4..69c667462 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/AbstractInterceptor.java @@ -21,44 +21,23 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; /** - * Provides default implementations of optional lifecycle methods + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.AbstractInterceptor} instead. */ -public abstract class AbstractInterceptor implements ConditionalInterceptor { - - private boolean disabled; - - /** - * Does nothing - */ - @Override - public void init() { - } - - /** - * Does nothing - */ - @Override - public void destroy() { - } +@Deprecated +public abstract class AbstractInterceptor extends org.apache.struts2.interceptor.AbstractInterceptor implements ConditionalInterceptor { /** * Override to handle interception */ - @Override public abstract String intercept(ActionInvocation invocation) throws Exception; - /** - * Allows to skip executing a given interceptor, just define {@code true} - * or use other way to override interceptor's parameters, see - * docs. - * @param disable if set to true, execution of a given interceptor will be skipped. - */ - public void setDisabled(String disable) { - this.disabled = Boolean.parseBoolean(disable); + @Override + public String intercept(org.apache.struts2.ActionInvocation invocation) throws Exception { + return intercept(ActionInvocation.adapt(invocation)); } @Override public boolean shouldIntercept(ActionInvocation invocation) { - return !this.disabled; + return shouldIntercept((org.apache.struts2.ActionInvocation) invocation); } } diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java index 28109cbf9..acc72a662 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java @@ -35,6 +35,7 @@ import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsConstants; import org.apache.struts2.dispatcher.HttpParameters; import org.apache.struts2.dispatcher.Parameter; +import org.apache.struts2.interceptor.ValidationAware; import java.util.Map; @@ -90,7 +91,10 @@ import java.util.Map; * * * @author Matthew Payne + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.AliasInterceptor} instead. */ +@Deprecated public class AliasInterceptor extends AbstractInterceptor { private static final Logger LOG = LogManager.getLogger(AliasInterceptor.class); diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java index 7e7d132f6..e61d4dc02 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java @@ -21,7 +21,6 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionChainResult; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.Result; -import com.opensymphony.xwork2.Unchainable; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.CompoundRoot; import com.opensymphony.xwork2.util.ProxyUtil; @@ -31,6 +30,7 @@ import com.opensymphony.xwork2.util.reflection.ReflectionProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsConstants; +import org.apache.struts2.Unchainable; import java.util.ArrayList; import java.util.Collection; @@ -118,7 +118,10 @@ import java.util.Map; * @author mrdon * @author tm_jee ( tm_jee(at)yahoo.co.uk ) * @see com.opensymphony.xwork2.ActionChainResult + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ChainingInterceptor} instead. */ +@Deprecated public class ChainingInterceptor extends AbstractInterceptor { private static final Logger LOG = LogManager.getLogger(ChainingInterceptor.class); 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..0e83f64b2 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConditionalInterceptor.java @@ -21,19 +21,39 @@ 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 { +@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)); + } - /** - * 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); + + 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/ConversionErrorInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java index a30d81a00..98be0de16 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java @@ -24,6 +24,7 @@ import com.opensymphony.xwork2.conversion.impl.ConversionData; import com.opensymphony.xwork2.conversion.impl.XWorkConverter; import com.opensymphony.xwork2.util.ValueStack; import org.apache.commons.text.StringEscapeUtils; +import org.apache.struts2.interceptor.ValidationAware; import java.util.HashMap; import java.util.Map; @@ -84,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 118811a59..b590ca4af 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java @@ -25,6 +25,9 @@ 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.interceptor.ValidationAware; +import org.apache.struts2.interceptor.ValidationErrorAware; +import org.apache.struts2.interceptor.ValidationWorkflowAware; import java.io.Serial; @@ -131,7 +134,10 @@ import java.io.Serial; * @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 { @Serial 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 db6dadec9..51eed9426 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java @@ -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/Interceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java index eb7f6850e..4287ca8c0 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java @@ -20,203 +20,56 @@ 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 jakarta.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 :- - *

- - * Method 1: - *
- * <action name="myAction" class="myActionClass">
- *     <interceptor-ref name="exception"/>
- *     <interceptor-ref name="alias"/>
- *     <interceptor-ref name="params"/>
- *     <interceptor-ref name="servletConfig"/>
- *     <interceptor-ref name="prepare"/>
- *     <interceptor-ref name="i18n"/>
- *     <interceptor-ref name="chain"/>
- *     <interceptor-ref name="modelDriven"/>
- *     <interceptor-ref name="fileUpload"/>
- *     <interceptor-ref name="staticParams"/>
- *     <interceptor-ref name="params"/>
- *     <interceptor-ref name="conversionError"/>
- *     <interceptor-ref name="validation">
- *     <param name="excludeMethods">myValidationExcudeMethod</param>
- *     </interceptor-ref>
- *     <interceptor-ref name="workflow">
- *     <param name="excludeMethods">myWorkflowExcludeMethod</param>
- *     </interceptor-ref>
- * </action>
- * 
- * - * Method 2: - *
- * <action name="myAction" class="myActionClass">
- *   <interceptor-ref name="defaultStack">
- *     <param name="validation.excludeMethods">myValidationExcludeMethod</param>
- *     <param name="workflow.excludeMethods">myWorkflowExcludeMethod</param>
- *   </interceptor-ref>
- * </action>
- * 
- * - *

- * 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, - *

- *
- * <interceptor name="interceptor1" class="foo.bar.Interceptor1" />
- * <interceptor name="interceptor2" class="foo.bar.Interceptor2" />
- * <interceptor name="interceptor3" class="foo.bar.Interceptor3" />
- * <interceptor name="interceptor4" class="foo.bar.Interceptor4" />
- * <interceptor-stack name="stack1">
- *     <interceptor-ref name="interceptor1" />
- * </interceptor-stack>
- * <interceptor-stack name="stack2">
- *     <interceptor-ref name="intercetor2" />
- *     <interceptor-ref name="stack1" />
- * </interceptor-stack>
- * <interceptor-stack name="stack3">
- *     <interceptor-ref name="interceptor3" />
- *     <interceptor-ref name="stack2" />
- * </interceptor-stack>
- * <interceptor-stack name="stack4">
- *     <interceptor-ref name="interceptor4" />
- *     <interceptor-ref name="stack3" />
- *  </interceptor-stack>
- * 
- * - *

- * Assuming the interceptor has the following properties - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Interceptorproperty
Interceptor1param1
Interceptor2param2
Interceptor3param3
Interceptor4param4
- * - *

- * 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 { +@Deprecated +public interface Interceptor extends org.apache.struts2.interceptor.Interceptor { - /** - * Called to let an interceptor clean up any resources it has allocated. - */ - void destroy(); + @Override + default String intercept(org.apache.struts2.ActionInvocation invocation) throws Exception { + return intercept(ActionInvocation.adapt(invocation)); + } - /** - * 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; + 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; + } + + public org.apache.struts2.interceptor.Interceptor getAdaptee() { + return 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/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java index 271ed05be..f43fb1986 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/MethodFilterInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java index e96951cfa..bcce3da12 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java @@ -31,56 +31,59 @@ import java.util.Set; * *

* MethodFilterInterceptor is an abstract Interceptor used as - * a base class for interceptors that will filter execution based on method + * a base class for interceptors that will filter execution based on method * names according to specified included/excluded method lists. - * + * *

- * + * * Settable parameters are as follows: - * + * *
    *
  • excludeMethods - method names to be excluded from interceptor processing
  • *
  • includeMethods - method names to be included in interceptor processing
  • *
- * + * *

- * - * NOTE: If method name are available in both includeMethods and - * excludeMethods, it will be considered as an included method: + * + * NOTE: If method name are available in both includeMethods and + * excludeMethods, it will be considered as an included method: * includeMethods takes precedence over excludeMethods. - * + * *

- * + * * Interceptors that extends this capability include: - * + * *
    *
  • TokenInterceptor
  • *
  • TokenSessionStoreInterceptor
  • *
  • DefaultWorkflowInterceptor
  • *
  • ValidationInterceptor
  • *
- * + * * - * + * * @author Alexandru Popescu * @author Rainer Hermanns - * + * * @see org.apache.struts2.interceptor.TokenInterceptor * @see org.apache.struts2.interceptor.TokenSessionStoreInterceptor * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor * @see com.opensymphony.xwork2.validator.ValidationInterceptor + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.MethodFilterInterceptor} instead. */ +@Deprecated public abstract class MethodFilterInterceptor extends AbstractInterceptor { private static final Logger LOG = LogManager.getLogger(MethodFilterInterceptor.class); - + protected Set excludeMethods = Collections.emptySet(); protected Set includeMethods = Collections.emptySet(); public void setExcludeMethods(String excludeMethods) { this.excludeMethods = TextParseUtil.commaDelimitedStringToSet(excludeMethods); } - + public Set getExcludeMethodsSet() { return excludeMethods; } @@ -88,7 +91,7 @@ public abstract class MethodFilterInterceptor extends AbstractInterceptor { public void setIncludeMethods(String includeMethods) { this.includeMethods = TextParseUtil.commaDelimitedStringToSet(includeMethods); } - + public Set getIncludeMethodsSet() { return includeMethods; } @@ -97,7 +100,7 @@ public abstract class MethodFilterInterceptor extends AbstractInterceptor { public String intercept(ActionInvocation invocation) throws Exception { if (applyInterceptor(invocation)) { return doIntercept(invocation); - } + } return invocation.invoke(); } @@ -110,14 +113,14 @@ public abstract class MethodFilterInterceptor extends AbstractInterceptor { } return applyMethod; } - + /** * Subclasses must override to implement the interceptor logic. - * + * * @param invocation the action invocation * @return the result of invocation * @throws Exception in case of any errors */ protected abstract String doIntercept(ActionInvocation invocation) throws Exception; - + } diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java index ac5acee65..7e1a2c434 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java @@ -18,127 +18,9 @@ */ package com.opensymphony.xwork2.interceptor; -import com.opensymphony.xwork2.util.TextParseUtil; -import com.opensymphony.xwork2.util.WildcardHelper; - -import java.util.HashMap; -import java.util.Set; - -import static java.util.Objects.requireNonNullElse; - /** - * Utility class contains common methods used by - * {@link com.opensymphony.xwork2.interceptor.MethodFilterInterceptor}. - * - * @author tm_jee + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.MethodFilterInterceptorUtil} instead. */ -public class MethodFilterInterceptorUtil { - - /** - * Static method to decide if the specified method should be - * apply (not filtered) depending on the set of excludeMethods and - * includeMethods. - * - *
    - *
  • - * includeMethods takes precedence over excludeMethods - *
  • - *
- * Note: Supports wildcard listings in includeMethods/excludeMethods - * - * @param excludeMethods list of methods to exclude. - * @param includeMethods list of methods to include. - * @param method the specified method to check - * @return true if the method should be applied. - */ - public static boolean applyMethod(Set excludeMethods, Set includeMethods, String method) { - - // quick check to see if any actual pattern matching is needed - boolean needsPatternMatch = false; - for (String includeMethod : includeMethods) { - if (!"*".equals(includeMethod) && includeMethod.contains("*")) { - needsPatternMatch = true; - break; - } - } - - for (String excludeMethod : excludeMethods) { - if (!"*".equals(excludeMethod) && excludeMethod.contains("*")) { - needsPatternMatch = true; - break; - } - } - - // this section will try to honor the original logic, while - // still allowing for wildcards later - if (!needsPatternMatch && (includeMethods.contains("*") || includeMethods.isEmpty()) ) { - if (excludeMethods.contains(method) && !includeMethods.contains(method)) { - return false; - } - } - - // test the methods using pattern matching - WildcardHelper wildcard = new WildcardHelper(); - String methodCopy ; - // no method specified - methodCopy = requireNonNullElse(method, ""); - for (String pattern : includeMethods) { - if (pattern.contains("*")) { - int[] compiledPattern = wildcard.compilePattern(pattern); - HashMap matchedPatterns = new HashMap<>(); - boolean matches = wildcard.match(matchedPatterns, methodCopy, compiledPattern); - if (matches) { - return true; // run it, includeMethods takes precedence - } - } - else { - if (pattern.equals(methodCopy)) { - return true; // run it, includeMethods takes precedence - } - } - } - if (excludeMethods.contains("*") ) { - return false; - } - - // CHECK ME: Previous implementation used include method - for ( String pattern : excludeMethods) { - if (pattern.contains("*")) { - int[] compiledPattern = wildcard.compilePattern(pattern); - HashMap matchedPatterns = new HashMap<>(); - boolean matches = wildcard.match(matchedPatterns, methodCopy, compiledPattern); - if (matches) { - // if found, and wasn't included earlier, don't run it - return false; - } - } - else { - if (pattern.equals(methodCopy)) { - // if found, and wasn't included earlier, don't run it - return false; - } - } - } - - - // default fall-back from before changes - return includeMethods.isEmpty() || includeMethods.contains(method) || includeMethods.contains("*"); - } - - /** - * Same as {@link #applyMethod(Set, Set, String)}, except that excludeMethods - * and includeMethods are supplied as comma separated string. - * - * @param excludeMethods comma seperated string of methods to exclude. - * @param includeMethods comma seperated string of methods to include. - * @param method the specified method to check - * @return true if the method should be applied. - */ - public static boolean applyMethod(String excludeMethods, String includeMethods, String method) { - Set includeMethodsSet = TextParseUtil.commaDelimitedStringToSet(includeMethods == null? "" : includeMethods); - Set excludeMethodsSet = TextParseUtil.commaDelimitedStringToSet(excludeMethods == null? "" : excludeMethods); - - return applyMethod(excludeMethodsSet, includeMethodsSet, method); - } - +@Deprecated +public class MethodFilterInterceptorUtil extends org.apache.struts2.interceptor.MethodFilterInterceptorUtil { } 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 d8c4a3143..e3135a18c 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java @@ -19,9 +19,9 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.util.CompoundRoot; import com.opensymphony.xwork2.util.ValueStack; +import org.apache.struts2.ModelDriven; /** * @@ -75,7 +75,10 @@ import com.opensymphony.xwork2.util.ValueStack; * * @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/PreResultListener.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java index f9faa2377..25ba59a42 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,38 @@ 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) { + if (actualListener instanceof PreResultListener) { + return (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/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java index b744ed1f6..40cea29a2 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java @@ -53,7 +53,7 @@ import java.lang.reflect.Method; *
  • 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 @@ -129,6 +129,9 @@ public class PrefixMethodInvocationUtil { } } + 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 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 78b3b9c95..e617ec2b5 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java @@ -19,7 +19,7 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.Preparable; +import org.apache.struts2.Preparable; import java.io.Serial; import java.lang.reflect.InvocationTargetException; @@ -97,7 +97,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 { @Serial diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java index 42ddb09b3..d5413b4e5 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java @@ -21,23 +21,8 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ModelDriven; /** - * Adds the ability to set a model, probably retrieved from a given state. + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ScopedModelDriven} instead. */ -public interface ScopedModelDriven extends ModelDriven { - - /** - * @param model sets the model - */ - void setModel(T model); - - /** - * Sets the key under which the model is stored - * @param key The model key - */ - void setScopeKey(String key); - - /** - * @return the key under which the model is stored - */ - String getScopeKey(); +@Deprecated +public interface ScopedModelDriven extends org.apache.struts2.interceptor.ScopedModelDriven, ModelDriven { } 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 c6daee028..dbb422ca9 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java @@ -24,6 +24,7 @@ import com.opensymphony.xwork2.ObjectFactory; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.inject.Inject; import org.apache.struts2.StrutsException; +import org.apache.struts2.interceptor.ScopedModelDriven; import java.lang.reflect.Method; import java.util.Map; @@ -79,7 +80,10 @@ import java.util.Map; * * * + * + * @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]; 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 b95a0e6e6..371bcc089 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java @@ -34,6 +34,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsConstants; import org.apache.struts2.dispatcher.HttpParameters; +import org.apache.struts2.interceptor.ValidationAware; import java.util.Collections; import java.util.Map; @@ -84,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/com/opensymphony/xwork2/interceptor/ValidationAware.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationAware.java index 485cb42fb..aa9e6f5ff 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationAware.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationAware.java @@ -23,109 +23,84 @@ import java.util.List; import java.util.Map; /** - * ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept - * in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs. + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ValidationAware} instead. */ -public interface ValidationAware { +@Deprecated +public interface ValidationAware extends org.apache.struts2.interceptor.ValidationAware { - /** - * Set the Collection of Action-level String error messages. - * - * @param errorMessages Collection of String error messages - */ - void setActionErrors(Collection errorMessages); - - /** - * Get the Collection of Action-level error messages for this action. Error messages should not - * be added directly here, as implementations are free to return a new Collection or an - * Unmodifiable Collection. - * - * @return Collection of String error messages - */ - Collection getActionErrors(); - - /** - * Set the Collection of Action-level String messages (not errors). - * - * @param messages Collection of String messages (not errors). - */ - void setActionMessages(Collection messages); - - /** - * Get the Collection of Action-level messages for this action. Messages should not be added - * directly here, as implementations are free to return a new Collection or an Unmodifiable - * Collection. - * - * @return Collection of String messages - */ - Collection getActionMessages(); - - /** - * Set the field error map of fieldname (String) to Collection of String error messages. - * - * @param errorMap field error map - */ - void setFieldErrors(Map> errorMap); - - /** - * Get the field specific errors associated with this action. Error messages should not be added - * directly here, as implementations are free to return a new Collection or an Unmodifiable - * Collection. - * - * @return Map with errors mapped from fieldname (String) to Collection of String error messages - */ - Map> getFieldErrors(); - - /** - * Add an Action-level error message to this Action. - * - * @param anErrorMessage the error message - */ - void addActionError(String anErrorMessage); - - /** - * Add an Action-level message to this Action. - * - * @param aMessage the message - */ - void addActionMessage(String aMessage); - - /** - * Add an error message for a given field. - * - * @param fieldName name of field - * @param errorMessage the error message - */ - void addFieldError(String fieldName, String errorMessage); - - /** - * Check whether there are any Action-level error messages. - * - * @return true if any Action-level error messages have been registered - */ - boolean hasActionErrors(); - - /** - * Checks whether there are any Action-level messages. - * - * @return true if any Action-level messages have been registered - */ - boolean hasActionMessages(); - - /** - * Checks whether there are any action errors or field errors. - * - * @return (hasActionErrors() || hasFieldErrors()) - */ - default boolean hasErrors() { - return hasActionErrors() || hasFieldErrors(); + static ValidationAware adapt(org.apache.struts2.interceptor.ValidationAware actualValidation) { + if (actualValidation instanceof ValidationAware) { + return (ValidationAware) actualValidation; + } + return actualValidation != null ? new LegacyAdapter(actualValidation) : null; } - /** - * Check whether there are any field errors associated with this action. - * - * @return whether there are any field errors - */ - boolean hasFieldErrors(); + class LegacyAdapter implements ValidationAware { + private final org.apache.struts2.interceptor.ValidationAware adaptee; + + private LegacyAdapter(org.apache.struts2.interceptor.ValidationAware adaptee) { + this.adaptee = adaptee; + } + + @Override + public void setActionErrors(Collection errorMessages) { + adaptee.setActionErrors(errorMessages); + } + + @Override + public Collection getActionErrors() { + return adaptee.getActionErrors(); + } + + @Override + public void setActionMessages(Collection messages) { + adaptee.setActionMessages(messages); + } + + @Override + public Collection getActionMessages() { + return adaptee.getActionMessages(); + } + + @Override + public void setFieldErrors(Map> errorMap) { + adaptee.setFieldErrors(errorMap); + } + + @Override + public Map> getFieldErrors() { + return adaptee.getFieldErrors(); + } + + @Override + public void addActionError(String anErrorMessage) { + adaptee.addActionError(anErrorMessage); + } + + @Override + public void addActionMessage(String aMessage) { + adaptee.addActionMessage(aMessage); + } + + @Override + public void addFieldError(String fieldName, String errorMessage) { + adaptee.addFieldError(fieldName, errorMessage); + } + + @Override + public boolean hasActionErrors() { + return adaptee.hasActionErrors(); + } + + @Override + public boolean hasActionMessages() { + return adaptee.hasActionMessages(); + } + + @Override + public boolean hasFieldErrors() { + return adaptee.hasFieldErrors(); + } + } } diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationErrorAware.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationErrorAware.java index 4d04fa6dc..184cf1339 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationErrorAware.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationErrorAware.java @@ -19,22 +19,8 @@ package com.opensymphony.xwork2.interceptor; /** - * ValidationErrorAware classes can be notified about validation errors - * before {@link com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor} will return 'inputResultName' result - * to allow change or not the result name - * - * This interface can be only applied to action which already implements {@link ValidationAware} interface! - * - * @since 2.3.15 + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ValidationErrorAware} instead. */ -public interface ValidationErrorAware { - - /** - * Allows to notify action about occurred action/field errors - * - * @param currentResultName current result name, action can change it or return the same - * @return new result name or passed currentResultName - */ - String actionErrorOccurred(final String currentResultName); - +@Deprecated +public interface ValidationErrorAware extends org.apache.struts2.interceptor.ValidationErrorAware { } diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java index b6c25ed31..fc0218d43 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationWorkflowAware.java @@ -19,12 +19,8 @@ package com.opensymphony.xwork2.interceptor; /** - * ValidationWorkflowAware classes can programmatically change result name when errors occurred - * - * This interface can be only applied to action which already implements {@link ValidationAware} interface! + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ValidationWorkflowAware} instead. */ -public interface ValidationWorkflowAware { - - String getInputResultName(); - +@Deprecated +public interface ValidationWorkflowAware extends org.apache.struts2.interceptor.ValidationWorkflowAware { } 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 750d23af1..3802b2cec 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java @@ -67,11 +67,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/com/opensymphony/xwork2/util/DebugUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/DebugUtils.java index d0f35af05..6838d0113 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/DebugUtils.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/DebugUtils.java @@ -19,8 +19,8 @@ package com.opensymphony.xwork2.util; import com.opensymphony.xwork2.TextProvider; -import com.opensymphony.xwork2.interceptor.ValidationAware; import org.apache.logging.log4j.Logger; +import org.apache.struts2.interceptor.ValidationAware; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; diff --git a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java index abe407866..d059b85ce 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/StrutsLocalizedTextProvider.java @@ -20,12 +20,12 @@ package com.opensymphony.xwork2.util; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.conversion.impl.XWorkConverter; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.reflection.ReflectionProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.ModelDriven; import java.beans.PropertyDescriptor; import java.util.Locale; 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..22f5bc428 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,127 @@ 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) { + if (actualStack instanceof ValueStack) { + return (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. - *

    - * - * @param overrides overrides map. - */ - void setExprOverrides(Map overrides); + class LegacyAdapter implements ValueStack { - /** - * Gets the override map if anyone exists. - * - * @return the override map, null if not set. - */ - Map getExprOverrides(); + private final org.apache.struts2.util.ValueStack adaptee; - /** - * Get the CompoundRoot which holds the objects pushed onto the stack - * - * @return the root - */ - CompoundRoot getRoot(); + private LegacyAdapter(org.apache.struts2.util.ValueStack adaptee) { + this.adaptee = adaptee; + } - /** - * Attempts to set a property on a bean in the stack with the given expression using the default search order. - * - * @param expr the expression defining the path to the property to be set. - * @param value the value to be set into the named property - */ - void setValue(String expr, Object value); + @Override + public Map getContext() { + return adaptee.getContext(); + } - /** - * Attempts to set a property on a bean in the stack with the given expression using the default search order. - * N.B.: unlike #setValue(String,Object) it doesn't allow eval expression. - * @param expr the expression defining the path to the property to be set. - * @param value the value to be set into the named property - */ - void setParameter(String expr, Object value); + @Override + public ActionContext getActionContext() { + return ActionContext.adapt(adaptee.getActionContext()); + } - /** - * Attempts to set a property on a bean in the stack with the given expression using the default search order. - * - * @param expr the expression defining the path to the property to be set. - * @param value the value to be set into the named property - * @param throwExceptionOnFailure a flag to tell whether an exception should be thrown if there is no property with - * the given name. - */ - void setValue(String expr, Object value, boolean throwExceptionOnFailure); + @Override + public void setDefaultType(Class defaultType) { + adaptee.setDefaultType(defaultType); + } - String findString(String expr); - String findString(String expr, boolean throwExceptionOnFailure); + @Override + public void setExprOverrides(Map overrides) { + adaptee.setExprOverrides(overrides); + } - /** - * Find a value by evaluating the given expression against the stack in the default search order. - * - * @param expr the expression giving the path of properties to navigate to find the property value to return - * @return the result of evaluating the expression - */ - Object findValue(String expr); + @Override + public Map getExprOverrides() { + return adaptee.getExprOverrides(); + } - Object findValue(String expr, boolean throwExceptionOnFailure); + @Override + public CompoundRoot getRoot() { + return adaptee.getRoot(); + } - /** - * Find a value by evaluating the given expression against the stack in the default search order. - * - * @param expr the expression giving the path of properties to navigate to find the property value to return - * @param asType the type to convert the return value to - * @return the result of evaluating the expression - */ - Object findValue(String expr, Class asType); - Object findValue(String expr, Class asType, boolean throwExceptionOnFailure); + @Override + public void setValue(String expr, Object value) { + adaptee.setValue(expr, value); + } - /** - * Get the object on the top of the stack without changing the stack. - * - * @return the object on the top. - * @see CompoundRoot#peek() - */ - Object peek(); + @Override + public void setParameter(String expr, Object value) { + adaptee.setParameter(expr, value); + } - /** - * Get the object on the top of the stack and remove it from the stack. - * - * @return the object on the top of the stack - * @see CompoundRoot#pop() - */ - Object pop(); + @Override + public void setValue(String expr, Object value, boolean throwExceptionOnFailure) { + adaptee.setValue(expr, value, throwExceptionOnFailure); + } - /** - * Put this object onto the top of the stack - * - * @param o the object to be pushed onto the stack - * @see CompoundRoot#push(Object) - */ - void push(Object o); + @Override + public String findString(String expr) { + return adaptee.findString(expr); + } - /** - * Sets an object on the stack with the given key - * so it is retrievable by {@link #findValue(String)}, {@link #findValue(String, Class)} - * - * @param key the key - * @param o the object - */ - void set(String key, Object o); + @Override + public String findString(String expr, boolean throwExceptionOnFailure) { + return adaptee.findString(expr, throwExceptionOnFailure); + } - /** - * Get the number of objects in the stack - * - * @return the number of objects in the stack - */ - int size(); + @Override + public Object findValue(String expr) { + return adaptee.findValue(expr); + } -} \ No newline at end of file + @Override + public Object findValue(String expr, boolean throwExceptionOnFailure) { + return adaptee.findValue(expr, throwExceptionOnFailure); + } + + @Override + public Object findValue(String expr, Class asType) { + return adaptee.findValue(expr, asType); + } + + @Override + public Object findValue(String expr, Class asType, boolean throwExceptionOnFailure) { + return adaptee.findValue(expr, asType, throwExceptionOnFailure); + } + + @Override + public Object peek() { + return adaptee.peek(); + } + + @Override + public Object pop() { + return adaptee.pop(); + } + + @Override + public void push(Object o) { + adaptee.push(o); + } + + @Override + public void set(String key, Object o) { + adaptee.set(key, o); + } + + @Override + public int size() { + return adaptee.size(); + } + } +} diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java b/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java index 63e8c1d19..30bbee989 100644 --- a/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java +++ b/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java @@ -254,8 +254,8 @@ public class DelegatingValidatorContext implements ValidatorContext { } protected static ValidationAware makeValidationAware(Object object) { - if (object instanceof ValidationAware) { - return (ValidationAware) object; + if (object instanceof org.apache.struts2.interceptor.ValidationAware) { + return ValidationAware.adapt((org.apache.struts2.interceptor.ValidationAware) object); } else { return new LoggingValidationAware(object); } diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java index b0852ed03..cc22250e3 100644 --- a/core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/validator/ValidationInterceptor.java @@ -20,13 +20,13 @@ package com.opensymphony.xwork2.validator; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.Validateable; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil; import com.opensymphony.xwork2.interceptor.ValidationAware; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.Validateable; /** * @@ -71,9 +71,9 @@ import org.apache.logging.log4j.Logger; *
  • programmatic - Defaults to true. If true and the action is Validateable call validate(), * and any method that starts with "validate". *
  • - * + * *
  • declarative - Defaults to true. Perform validation based on xml or annotations.
  • - * + * * * * @@ -90,14 +90,14 @@ import org.apache.logging.log4j.Logger; * *
      * 
    - * 
    + *
      * <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 the following case myMethod of the action class will not
      *        get validated -->
      * <action name="someAction" class="com.examples.SomeAction">
    @@ -108,7 +108,7 @@ import org.apache.logging.log4j.Logger;
      *     <interceptor-ref name="workflow"/>
      *     <result name="success">good_result.ftl</result>
      * </action>
    - * 
    + *
      * <-- in the following case only annotated methods of the action class will
      *        be validated -->
      * <action name="someAction" class="com.examples.SomeAction">
    @@ -138,9 +138,9 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
         private final static String ALT_VALIDATE_PREFIX = "validateDo";
     
         private boolean validateAnnotatedMethodOnly;
    -    
    +
         private ActionValidatorManager actionValidatorManager;
    -    
    +
         private boolean alwaysInvokeValidate = true;
         private boolean programmatic = true;
         private boolean declarative = true;
    @@ -149,11 +149,11 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
         public void setActionValidatorManager(ActionValidatorManager mgr) {
             this.actionValidatorManager = mgr;
         }
    -    
    +
         /**
          * Determines if {@link Validateable}'s validate() should be called,
          * as well as methods whose name that start with "validate". Defaults to "true".
    -     * 
    +     *
          * @param programmatic true then validate() is invoked.
          */
         public void setProgrammatic(boolean programmatic) {
    @@ -161,9 +161,9 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
         }
     
         /**
    -     * Determines if validation based on annotations or xml should be performed. Defaults 
    +     * Determines if validation based on annotations or xml should be performed. Defaults
          * to "true".
    -     * 
    +     *
          * @param declarative true then perform validation based on annotations or xml.
          */
         public void setDeclarative(boolean declarative) {
    @@ -171,9 +171,9 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
         }
     
         /**
    -     * Determines if {@link Validateable}'s validate() should always 
    +     * Determines if {@link Validateable}'s validate() should always
          * be invoked. Default to "true".
    -     * 
    +     *
          * @param alwaysInvokeValidate true then validate() is always invoked.
          */
         public void setAlwaysInvokeValidate(String alwaysInvokeValidate) {
    @@ -218,7 +218,7 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Validating {}/{} with method {}.", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName(), method);
             }
    -        
    +
     
             if (declarative) {
                if (validateAnnotatedMethodOnly) {
    @@ -226,12 +226,12 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
                } else {
                    actionValidatorManager.validate(action, context);
                }
    -       }    
    -        
    +       }
    +
             if (action instanceof Validateable && programmatic) {
                 // keep exception that might occured in validateXXX or validateDoXXX
    -            Exception exception = null; 
    -            
    +            Exception exception = null;
    +
                 Validateable validateable = (Validateable) action;
                 LOG.debug("Invoking validate() on action {}", validateable);
     
    @@ -239,19 +239,19 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
                     PrefixMethodInvocationUtil.invokePrefixMethod(invocation, new String[]{VALIDATE_PREFIX, ALT_VALIDATE_PREFIX});
                 }
                 catch(Exception e) {
    -                // If any exception occurred while doing reflection, we want 
    +                // If any exception occurred while doing reflection, we want
                     // validate() to be executed
                     LOG.warn("an exception occured while executing the prefix method", e);
                     exception = e;
                 }
    -            
    -            
    +
    +
                 if (alwaysInvokeValidate) {
                     validateable.validate();
                 }
    -            
    -            if (exception != null) { 
    -                // rethrow if something is wrong while doing validateXXX / validateDoXXX 
    +
    +            if (exception != null) {
    +                // rethrow if something is wrong while doing validateXXX / validateDoXXX
                     throw exception;
                 }
             }
    @@ -262,7 +262,7 @@ public class ValidationInterceptor extends MethodFilterInterceptor {
             doBeforeInvocation(invocation);
             return invocation.invoke();
         }
    -    
    +
         /**
          * 

    * Returns the context that will be used by the 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/ActionContext.java b/core/src/main/java/org/apache/struts2/ActionContext.java new file mode 100644 index 000000000..c17ff43e4 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ActionContext.java @@ -0,0 +1,552 @@ +/* + * 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.conversion.impl.ConversionData; +import com.opensymphony.xwork2.inject.Container; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.jsp.PageContext; +import org.apache.struts2.dispatcher.HttpParameters; +import org.apache.struts2.dispatcher.mapper.ActionMapping; +import org.apache.struts2.util.ValueStack; + +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: + *

    + * + * ActionContext context = ActionContext.getContext(); + * + *

    + * 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()); + } +} 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..23077cc9a --- /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 org.apache.struts2.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 new file mode 100644 index 000000000..70599dd3b --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ActionInvocation.java @@ -0,0 +1,180 @@ +/* + * 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 org.apache.struts2.interceptor.PreResultListener; +import org.apache.struts2.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}. + * + * @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/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(); + +} diff --git a/core/src/main/java/org/apache/struts2/ActionSupport.java b/core/src/main/java/org/apache/struts2/ActionSupport.java new file mode 100644 index 000000000..22d22a66c --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ActionSupport.java @@ -0,0 +1,367 @@ +/* + * 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.LocaleProvider; +import com.opensymphony.xwork2.LocaleProviderFactory; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.TextProviderFactory; +import com.opensymphony.xwork2.ValidationAwareSupport; +import com.opensymphony.xwork2.conversion.impl.ConversionData; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.interceptor.ValidationAware; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * Provides a default implementation for the most common actions. + * See the documentation for all the interfaces this class implements for more detailed information. + */ +public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable { + + private static final Logger LOG = LogManager.getLogger(ActionSupport.class); + + private final ValidationAwareSupport validationAware = new ValidationAwareSupport(); + + private transient TextProvider textProvider; + private transient LocaleProvider localeProvider; + + protected Container container; + + @Override + public void setActionErrors(Collection errorMessages) { + validationAware.setActionErrors(errorMessages); + } + + @Override + public Collection getActionErrors() { + return validationAware.getActionErrors(); + } + + @Override + public void setActionMessages(Collection messages) { + validationAware.setActionMessages(messages); + } + + @Override + public Collection getActionMessages() { + return validationAware.getActionMessages(); + } + + @Override + public void setFieldErrors(Map> errorMap) { + validationAware.setFieldErrors(errorMap); + } + + @Override + public Map> getFieldErrors() { + return validationAware.getFieldErrors(); + } + + @Override + public Locale getLocale() { + return getLocaleProvider().getLocale(); + } + + @Override + public boolean isValidLocaleString(String localeStr) { + return getLocaleProvider().isValidLocaleString(localeStr); + } + + @Override + public boolean isValidLocale(Locale locale) { + return getLocaleProvider().isValidLocale(locale); + } + + @Override + public Locale toLocale(String localeStr) { + return getLocaleProvider().toLocale(localeStr); + } + + @Override + public boolean hasKey(String key) { + return getTextProvider().hasKey(key); + } + + @Override + public String getText(String aTextName) { + return getTextProvider().getText(aTextName); + } + + @Override + public String getText(String aTextName, String defaultValue) { + return getTextProvider().getText(aTextName, defaultValue); + } + + @Override + public String getText(String aTextName, String defaultValue, String obj) { + return getTextProvider().getText(aTextName, defaultValue, obj); + } + + @Override + public String getText(String aTextName, List args) { + return getTextProvider().getText(aTextName, args); + } + + @Override + public String getText(String key, String[] args) { + return getTextProvider().getText(key, args); + } + + @Override + public String getText(String aTextName, String defaultValue, List args) { + return getTextProvider().getText(aTextName, defaultValue, args); + } + + @Override + public String getText(String key, String defaultValue, String[] args) { + return getTextProvider().getText(key, defaultValue, args); + } + + @Override + public String getText(String key, String defaultValue, List args, ValueStack stack) { + return getTextProvider().getText(key, defaultValue, args, stack); + } + + @Override + public String getText(String key, String defaultValue, String[] args, ValueStack stack) { + return getTextProvider().getText(key, defaultValue, args, stack); + } + + /** + * Dedicated method to support I10N and conversion errors + * + * @param key message which contains formatting string + * @param expr that should be formatted + * @return formatted expr with format specified by key + */ + public String getFormatted(String key, String expr) { + Map conversionErrors = ActionContext.getContext().getConversionErrors(); + if (conversionErrors.containsKey(expr)) { + String[] vals = (String[]) conversionErrors.get(expr).getValue(); + return vals[0]; + } else { + var valueStack = ActionContext.getContext().getValueStack(); + final Object val = valueStack.findValue(expr); + return getText(key, List.of(val)); + } + } + + @Override + public ResourceBundle getTexts() { + return getTextProvider().getTexts(); + } + + @Override + public ResourceBundle getTexts(String aBundleName) { + return getTextProvider().getTexts(aBundleName); + } + + @Override + public void addActionError(String anErrorMessage) { + validationAware.addActionError(anErrorMessage); + } + + @Override + public void addActionMessage(String aMessage) { + validationAware.addActionMessage(aMessage); + } + + @Override + public void addFieldError(String fieldName, String errorMessage) { + validationAware.addFieldError(fieldName, errorMessage); + } + + public String input() throws Exception { + return INPUT; + } + + /** + * A default implementation that does nothing an returns "success". + * + *

    + * Subclasses should override this method to provide their business logic. + *

    + * + *

    + * See also {@link Action#execute()}. + *

    + * + * @return returns {@link #SUCCESS} + * @throws Exception can be thrown by subclasses. + */ + @Override + public String execute() throws Exception { + return SUCCESS; + } + + @Override + public boolean hasActionErrors() { + return validationAware.hasActionErrors(); + } + + @Override + public boolean hasActionMessages() { + return validationAware.hasActionMessages(); + } + + @Override + public boolean hasErrors() { + return validationAware.hasErrors(); + } + + @Override + public boolean hasFieldErrors() { + return validationAware.hasFieldErrors(); + } + + /** + * Clears field errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearFieldErrors() { + validationAware.clearFieldErrors(); + } + + /** + * Clears action errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearActionErrors() { + validationAware.clearActionErrors(); + } + + /** + * Clears messages. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearMessages() { + validationAware.clearMessages(); + } + + /** + * Clears all errors. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearErrors() { + validationAware.clearErrors(); + } + + /** + * Clears all errors and messages. Useful for Continuations and other situations + * where you might want to clear parts of the state on the same action. + */ + public void clearErrorsAndMessages() { + validationAware.clearErrorsAndMessages(); + } + + /** + * A default implementation that validates nothing. + * Subclasses should override this method to provide validations. + */ + @Override + public void validate() { + // A default implementation that validates nothing + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * + * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return + * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc. + * + *

    + * The next time this action is invoked (and using the same continuation ID), the method will resume immediately + * after where this method was called, with the entire call stack in the execute method restored. + *

    + * + *

    + * Note: this method can only be called within the {@link #execute()} method. + *

    + * + * + * + * @param result the result to return - the same type of return value in the {@link #execute()} method. + */ + public void pause(String result) { + } + + /** + * If called first time it will create {@link TextProviderFactory}, + * inject dependency (if {@link Container} is accesible) into in, + * then will create new {@link TextProvider} and store it in a field + * for future references and at the returns reference to that field + * + * @return reference to field with TextProvider + */ + protected TextProvider getTextProvider() { + if (textProvider == null) { + final TextProviderFactory tpf = getContainer().getInstance(TextProviderFactory.class); + textProvider = tpf.createInstance(getClass()); + } + return textProvider; + } + + protected LocaleProvider getLocaleProvider() { + if (localeProvider == null) { + final LocaleProviderFactory localeProviderFactory = getContainer().getInstance(LocaleProviderFactory.class); + localeProvider = localeProviderFactory.createLocaleProvider(); + } + return localeProvider; + } + + /** + * TODO: This a temporary solution, maybe we should consider stop injecting container into beans + */ + protected Container getContainer() { + if (container == null) { + container = ActionContext.getContext().getContainer(); + if (container != null) { + boolean devMode = Boolean.parseBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_DEVMODE)); + if (devMode) { + LOG.warn("Container is null, action was created manually? Fallback to ActionContext"); + } else { + LOG.debug("Container is null, action was created manually? Fallback to ActionContext"); + } + } else { + LOG.warn("Container is null, action was created out of ActionContext scope?!?"); + } + } + return container; + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + +} diff --git a/core/src/main/java/org/apache/struts2/ModelDriven.java b/core/src/main/java/org/apache/struts2/ModelDriven.java new file mode 100644 index 000000000..30335a1ca --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ModelDriven.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; + +import org.apache.struts2.interceptor.parameter.StrutsParameter; + +/** + * ModelDriven Actions provide a model object to be pushed onto the ValueStack + * in addition to the Action itself, allowing a FormBean type approach like Struts. + * + * @author Jason Carreira + */ +public interface ModelDriven { + + /** + * Gets the model to be pushed onto the ValueStack instead of the Action itself. + * + * @return the model + */ + @StrutsParameter(depth = Integer.MAX_VALUE) + T getModel(); + +} diff --git a/core/src/main/java/org/apache/struts2/Preparable.java b/core/src/main/java/org/apache/struts2/Preparable.java new file mode 100644 index 000000000..70b0f464d --- /dev/null +++ b/core/src/main/java/org/apache/struts2/Preparable.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Preparable Actions will have their prepare() method called if the {@link com.opensymphony.xwork2.interceptor.PrepareInterceptor} + * is applied to the ActionConfig. + * + * @author Jason Carreira + * @see com.opensymphony.xwork2.interceptor.PrepareInterceptor + */ +public interface Preparable { + + /** + * This method is called to allow the action to prepare itself. + * + * @throws Exception thrown if a system level exception occurs. + */ + void prepare() 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..407994eab --- /dev/null +++ b/core/src/main/java/org/apache/struts2/Result.java @@ -0,0 +1,50 @@ +/* + * 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 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/Unchainable.java b/core/src/main/java/org/apache/struts2/Unchainable.java new file mode 100644 index 000000000..02e010142 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/Unchainable.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * Simple marker interface to indicate an object should not have its properties copied during chaining. + * + * @see com.opensymphony.xwork2.interceptor.ChainingInterceptor + */ +public interface Unchainable { +} diff --git a/core/src/main/java/org/apache/struts2/Validateable.java b/core/src/main/java/org/apache/struts2/Validateable.java new file mode 100644 index 000000000..d563e7905 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/Validateable.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Provides an interface in which a call for a validation check can be done. + * + * @author Jason Carreira + * @see com.opensymphony.xwork2.ActionSupport + * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor + */ +public interface Validateable { + + /** + * Performs validation. + */ + void validate(); + +} 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 513ce5284..8979af0b7 100644 --- a/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java +++ b/core/src/main/java/org/apache/struts2/factory/StrutsResultFactory.java @@ -20,6 +20,7 @@ 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; @@ -54,16 +55,36 @@ public class StrutsResultFactory implements ResultFactory { Result result = null; if (resultClassName != null) { - result = (Result) objectFactory.buildBean(resultClassName, extraContext); + Object o = objectFactory.buildBean(resultClassName, extraContext); Map params = resultConfig.getParams(); if (params != null) { - setParameters(extraContext, result, params); + setParameters(extraContext, o, params); + } + 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); } } 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(); @@ -78,6 +99,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); @@ -86,5 +119,4 @@ public class StrutsResultFactory implements ResultFactory { reflectionProvider.setProperty(name, value, result, extraContext, true); } } - } diff --git a/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java index 1113f4491..ecebd3748 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java @@ -25,7 +25,6 @@ import com.opensymphony.xwork2.TextProviderFactory; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; -import com.opensymphony.xwork2.interceptor.ValidationAware; import com.opensymphony.xwork2.util.TextParseUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/core/src/main/java/org/apache/struts2/interceptor/AbstractInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AbstractInterceptor.java new file mode 100644 index 000000000..103cd85a0 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/AbstractInterceptor.java @@ -0,0 +1,64 @@ +/* + * 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; + +/** + * Provides default implementations of optional lifecycle methods + */ +public abstract class AbstractInterceptor implements ConditionalInterceptor { + + private boolean disabled; + + /** + * Does nothing + */ + @Override + public void init() { + } + + /** + * Does nothing + */ + @Override + public void destroy() { + } + + /** + * Override to handle interception + */ + @Override + public abstract String intercept(ActionInvocation invocation) throws Exception; + + /** + * Allows to skip executing a given interceptor, just define {@code true} + * or use other way to override interceptor's parameters, see + * docs. + * @param disable if set to true, execution of a given interceptor will be skipped. + */ + public void setDisabled(String disable) { + this.disabled = Boolean.parseBoolean(disable); + } + + @Override + public boolean shouldIntercept(ActionInvocation invocation) { + return !this.disabled; + } +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java index 3b6ef08aa..ecff7d41e 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java @@ -20,7 +20,6 @@ package org.apache.struts2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.interceptor.ValidationAware; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.apache.logging.log4j.LogManager; 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..58c50e404 --- /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.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.interceptor.parameter.ParametersInterceptor; +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>
    + * 
    + * 
    + * + * @author Matthew Payne + */ +public class AliasInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LogManager.getLogger(AliasInterceptor.class); + + private static final String DEFAULT_ALIAS_KEY = "aliases"; + protected String aliasesKey = DEFAULT_ALIAS_KEY; + + protected ValueStackFactory valueStackFactory; + protected LocalizedTextProvider localizedTextProvider; + protected boolean devMode = false; + + private ExcludedPatternsChecker excludedPatterns; + private AcceptedPatternsChecker acceptedPatterns; + + @Inject(StrutsConstants.STRUTS_DEVMODE) + public void setDevMode(String mode) { + this.devMode = Boolean.parseBoolean(mode); + } + + @Inject + public void setValueStackFactory(ValueStackFactory valueStackFactory) { + this.valueStackFactory = valueStackFactory; + } + + @Inject + public void setLocalizedTextProvider(LocalizedTextProvider localizedTextProvider) { + this.localizedTextProvider = localizedTextProvider; + } + + @Inject + public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) { + this.excludedPatterns = excludedPatterns; + } + + @Inject + public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) { + this.acceptedPatterns = acceptedPatterns; + } + + /** + *

    + * 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
    • + *
    + * + *

    + * Example: + *

    + * + *
    + * <constant name="struts.xwork.chaining.copyErrors" value="true"/>
    + * 
    + * + *

    + * Note: By default actionErrors and actionMessages are excluded when copping object's properties. + *

    + * + * Interceptor parameters: + * + *
      + *
    • excludes (optional) - the list of parameter names to exclude from copying (all others will be included).
    • + *
    • includes (optional) - the list of parameter names to include when copying (all others will be excluded).
    • + *
    + * + * Extending the interceptor: + * + *

    + * There are no known extension points to this interceptor. + *

    + * + * Example code: + * + * + *
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <interceptor-ref name="basicStack"/>
    + *     <result name="success" type="chain">otherAction</result>
    + * </action>
    + * 
    + * + *
    + * <action name="otherAction" class="com.examples.OtherAction">
    + *     <interceptor-ref name="chain"/>
    + *     <interceptor-ref name="basicStack"/>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + * 
    + * + * + * + * @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/ConditionalInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ConditionalInterceptor.java new file mode 100644 index 000000000..716b58195 --- /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 org.apache.struts2.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/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. + * + * + * + *

    Example code:

    + * + *
    + * 
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <interceptor-ref name="params"/>
    + *     <interceptor-ref name="conversionError"/>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + * 
    + * 
    + * + * @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. + *

    + * + * + *

    Example code:

    + * + *
    + * 
    + * <xwork>
    + *     <package name="default" extends="xwork-default">
    + *         <global-results>
    + *             <result name="error" type="freemarker">error.ftl</result>
    + *         </global-results>
    + *
    + *         <global-exception-mappings>
    + *             <exception-mapping exception="java.lang.Exception" result="error"/>
    + *         </global-exception-mappings>
    + *
    + *         <action name="test">
    + *             <interceptor-ref name="exception"/>
    + *             <interceptor-ref name="basicStack"/>
    + *             <exception-mapping exception="com.acme.CustomException" result="custom_error"/>
    + *             <result name="custom_error">custom_error.ftl</result>
    + *             <result name="success" type="freemarker">test.ftl</result>
    + *         </action>
    + *     </package>
    + * </xwork>
    + * 
    + * 
    + * + *

    + * This second example will also log the exceptions using our own category + * com.mycompany.app.unhandled at WARN level. + *

    + * + *
    + * 
    + * <xwork>
    + *   <package name="something" extends="xwork-default">
    + *      <interceptors>
    + *          <interceptor-stack name="exceptionmappingStack">
    + *              <interceptor-ref name="exception">
    + *                  <param name="logEnabled">true</param>
    + *                  <param name="logCategory">com.mycompany.app.unhandled</param>
    + *                  <param name="logLevel">WARN</param>
    + *              </interceptor-ref>
    + *              <interceptor-ref name="i18n"/>
    + *              <interceptor-ref name="staticParams"/>
    + *              <interceptor-ref name="params"/>
    + *              <interceptor-ref name="validation">
    + *                  <param name="excludeMethods">input,back,cancel,browse</param>
    + *              </interceptor-ref>
    + *          </interceptor-stack>
    + *      </interceptors>
    + *
    + *      <default-interceptor-ref name="exceptionmappingStack"/>
    + *
    + *      <global-results>
    + *           <result name="unhandledException">/unhandled-exception.jsp</result>
    + *      </global-results>
    + *
    + *      <global-exception-mappings>
    + *           <exception-mapping exception="java.lang.Exception" result="unhandledException"/>
    + *      </global-exception-mappings>
    + *
    + *      <action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction">
    + *          <exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
    + *                             result="damm"/>
    + *          <result name="input">index.jsp</result>
    + *          <result name="success">success.jsp</result>
    + *          <result name="damm">damm.jsp</result>
    + *      </action>
    + *
    + *   </package>
    + * </xwork>
    + * 
    + * 
    + * + * @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/Interceptor.java b/core/src/main/java/org/apache/struts2/interceptor/Interceptor.java new file mode 100644 index 000000000..58beef284 --- /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 org.apache.struts2.ActionInvocation; + +import java.io.Serializable; + +/** + * + * + *

    + * An interceptor is a stateless class that follows the interceptor pattern, as + * found in {@link jakarta.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 org.apache.struts2.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 :- + *

    + + * Method 1: + *
    + * <action name="myAction" class="myActionClass">
    + *     <interceptor-ref name="exception"/>
    + *     <interceptor-ref name="alias"/>
    + *     <interceptor-ref name="params"/>
    + *     <interceptor-ref name="servletConfig"/>
    + *     <interceptor-ref name="prepare"/>
    + *     <interceptor-ref name="i18n"/>
    + *     <interceptor-ref name="chain"/>
    + *     <interceptor-ref name="modelDriven"/>
    + *     <interceptor-ref name="fileUpload"/>
    + *     <interceptor-ref name="staticParams"/>
    + *     <interceptor-ref name="params"/>
    + *     <interceptor-ref name="conversionError"/>
    + *     <interceptor-ref name="validation">
    + *     <param name="excludeMethods">myValidationExcudeMethod</param>
    + *     </interceptor-ref>
    + *     <interceptor-ref name="workflow">
    + *     <param name="excludeMethods">myWorkflowExcludeMethod</param>
    + *     </interceptor-ref>
    + * </action>
    + * 
    + * + * Method 2: + *
    + * <action name="myAction" class="myActionClass">
    + *   <interceptor-ref name="defaultStack">
    + *     <param name="validation.excludeMethods">myValidationExcludeMethod</param>
    + *     <param name="workflow.excludeMethods">myWorkflowExcludeMethod</param>
    + *   </interceptor-ref>
    + * </action>
    + * 
    + * + *

    + * 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, + *

    + *
    + * <interceptor name="interceptor1" class="foo.bar.Interceptor1" />
    + * <interceptor name="interceptor2" class="foo.bar.Interceptor2" />
    + * <interceptor name="interceptor3" class="foo.bar.Interceptor3" />
    + * <interceptor name="interceptor4" class="foo.bar.Interceptor4" />
    + * <interceptor-stack name="stack1">
    + *     <interceptor-ref name="interceptor1" />
    + * </interceptor-stack>
    + * <interceptor-stack name="stack2">
    + *     <interceptor-ref name="intercetor2" />
    + *     <interceptor-ref name="stack1" />
    + * </interceptor-stack>
    + * <interceptor-stack name="stack3">
    + *     <interceptor-ref name="interceptor3" />
    + *     <interceptor-ref name="stack2" />
    + * </interceptor-stack>
    + * <interceptor-stack name="stack4">
    + *     <interceptor-ref name="interceptor4" />
    + *     <interceptor-ref name="stack3" />
    + *  </interceptor-stack>
    + * 
    + * + *

    + * Assuming the interceptor has the following properties + *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Interceptorproperty
    Interceptor1param1
    Interceptor2param2
    Interceptor3param3
    Interceptor4param4
    + * + *

    + * 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 org.apache.struts2.Action#execute()}. + */ + String intercept(ActionInvocation invocation) throws Exception; + +} 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/MessageStorePreResultListener.java b/core/src/main/java/org/apache/struts2/interceptor/MessageStorePreResultListener.java index 84abcccf7..845a4f551 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/MessageStorePreResultListener.java +++ b/core/src/main/java/org/apache/struts2/interceptor/MessageStorePreResultListener.java @@ -21,7 +21,6 @@ package org.apache.struts2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.interceptor.PreResultListener; -import com.opensymphony.xwork2.interceptor.ValidationAware; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ServletActionContext; diff --git a/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptor.java new file mode 100644 index 000000000..1ffe68261 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptor.java @@ -0,0 +1,123 @@ +/* + * 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.ActionInvocation; + +import java.util.Collections; +import java.util.Set; + +/** + * + * + *

    + * MethodFilterInterceptor is an abstract Interceptor used as + * a base class for interceptors that will filter execution based on method + * names according to specified included/excluded method lists. + * + *

    + * + * Settable parameters are as follows: + * + *
      + *
    • excludeMethods - method names to be excluded from interceptor processing
    • + *
    • includeMethods - method names to be included in interceptor processing
    • + *
    + * + *

    + * + * NOTE: If method name are available in both includeMethods and + * excludeMethods, it will be considered as an included method: + * includeMethods takes precedence over excludeMethods. + * + *

    + * + * Interceptors that extends this capability include: + * + *
      + *
    • TokenInterceptor
    • + *
    • TokenSessionStoreInterceptor
    • + *
    • DefaultWorkflowInterceptor
    • + *
    • ValidationInterceptor
    • + *
    + * + * + * + * @author Alexandru Popescu + * @author Rainer Hermanns + * + * @see TokenInterceptor + * @see TokenSessionStoreInterceptor + * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor + * @see com.opensymphony.xwork2.validator.ValidationInterceptor + */ +public abstract class MethodFilterInterceptor extends AbstractInterceptor { + + private static final Logger LOG = LogManager.getLogger(MethodFilterInterceptor.class); + + protected Set excludeMethods = Collections.emptySet(); + protected Set includeMethods = Collections.emptySet(); + + public void setExcludeMethods(String excludeMethods) { + this.excludeMethods = TextParseUtil.commaDelimitedStringToSet(excludeMethods); + } + + public Set getExcludeMethodsSet() { + return excludeMethods; + } + + public void setIncludeMethods(String includeMethods) { + this.includeMethods = TextParseUtil.commaDelimitedStringToSet(includeMethods); + } + + public Set getIncludeMethodsSet() { + return includeMethods; + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + if (applyInterceptor(invocation)) { + return doIntercept(invocation); + } + return invocation.invoke(); + } + + protected boolean applyInterceptor(ActionInvocation invocation) { + String method = invocation.getProxy().getMethod(); + // ValidationInterceptor + boolean applyMethod = MethodFilterInterceptorUtil.applyMethod(excludeMethods, includeMethods, method); + if (!applyMethod) { + LOG.debug("Skipping Interceptor... Method [{}] found in exclude list.", method); + } + return applyMethod; + } + + /** + * Subclasses must override to implement the interceptor logic. + * + * @param invocation the action invocation + * @return the result of invocation + * @throws Exception in case of any errors + */ + protected abstract String doIntercept(ActionInvocation invocation) throws Exception; + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptorUtil.java b/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptorUtil.java new file mode 100644 index 000000000..065305343 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptorUtil.java @@ -0,0 +1,142 @@ +/* + * 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 com.opensymphony.xwork2.util.WildcardHelper; + +import java.util.HashMap; +import java.util.Set; + +import static java.util.Objects.requireNonNullElse; + +/** + * Utility class contains common methods used by + * {@link MethodFilterInterceptor}. + * + * @author tm_jee + */ +public class MethodFilterInterceptorUtil { + + /** + * Static method to decide if the specified method should be + * apply (not filtered) depending on the set of excludeMethods and + * includeMethods. + * + *
      + *
    • + * includeMethods takes precedence over excludeMethods + *
    • + *
    + * Note: Supports wildcard listings in includeMethods/excludeMethods + * + * @param excludeMethods list of methods to exclude. + * @param includeMethods list of methods to include. + * @param method the specified method to check + * @return true if the method should be applied. + */ + public static boolean applyMethod(Set excludeMethods, Set includeMethods, String method) { + + // quick check to see if any actual pattern matching is needed + boolean needsPatternMatch = false; + for (String includeMethod : includeMethods) { + if (!"*".equals(includeMethod) && includeMethod.contains("*")) { + needsPatternMatch = true; + break; + } + } + + for (String excludeMethod : excludeMethods) { + if (!"*".equals(excludeMethod) && excludeMethod.contains("*")) { + needsPatternMatch = true; + break; + } + } + + // this section will try to honor the original logic, while + // still allowing for wildcards later + if (!needsPatternMatch && (includeMethods.contains("*") || includeMethods.isEmpty()) ) { + if (excludeMethods.contains(method) && !includeMethods.contains(method)) { + return false; + } + } + + // test the methods using pattern matching + WildcardHelper wildcard = new WildcardHelper(); + String methodCopy = requireNonNullElse(method, ""); + for (String pattern : includeMethods) { + if (pattern.contains("*")) { + int[] compiledPattern = wildcard.compilePattern(pattern); + HashMap matchedPatterns = new HashMap<>(); + boolean matches = wildcard.match(matchedPatterns, methodCopy, compiledPattern); + if (matches) { + return true; // run it, includeMethods takes precedence + } + } + else { + if (pattern.equals(methodCopy)) { + return true; // run it, includeMethods takes precedence + } + } + } + if (excludeMethods.contains("*") ) { + return false; + } + + // CHECK ME: Previous implementation used include method + for ( String pattern : excludeMethods) { + if (pattern.contains("*")) { + int[] compiledPattern = wildcard.compilePattern(pattern); + HashMap matchedPatterns = new HashMap<>(); + boolean matches = wildcard.match(matchedPatterns, methodCopy, compiledPattern); + if (matches) { + // if found, and wasn't included earlier, don't run it + return false; + } + } + else { + if (pattern.equals(methodCopy)) { + // if found, and wasn't included earlier, don't run it + return false; + } + } + } + + + // default fall-back from before changes + return includeMethods.isEmpty() || includeMethods.contains(method) || includeMethods.contains("*"); + } + + /** + * Same as {@link #applyMethod(Set, Set, String)}, except that excludeMethods + * and includeMethods are supplied as comma separated string. + * + * @param excludeMethods comma seperated string of methods to exclude. + * @param includeMethods comma seperated string of methods to include. + * @param method the specified method to check + * @return true if the method should be applied. + */ + public static boolean applyMethod(String excludeMethods, String includeMethods, String method) { + Set includeMethodsSet = TextParseUtil.commaDelimitedStringToSet(includeMethods == null? "" : includeMethods); + Set excludeMethodsSet = TextParseUtil.commaDelimitedStringToSet(excludeMethods == null? "" : excludeMethods); + + return applyMethod(excludeMethodsSet, includeMethodsSet, method); + } + +} 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. + * + * + * + *

    Example code:

    + * + *
    + * 
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <interceptor-ref name="modelDriven"/>
    + *     <interceptor-ref name="basicStack"/>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + * 
    + * 
    + * + * @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
    • + *
    + *

    + * No intended extension point + * + *

    + * <action name="sample" class="org.martingilday.Sample">
    + * 	<interceptor-ref name="paramRemover">
    + *          <param name="paramNames">aParam,anotherParam</param>
    + *          <param name="paramValues">--,-1</param>
    + * 	</interceptor-ref>
    + * 	<interceptor-ref name="defaultStack" />
    + * 	...
    + * </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/PreResultListener.java b/core/src/main/java/org/apache/struts2/interceptor/PreResultListener.java new file mode 100644 index 000000000..69745b925 --- /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 org.apache.struts2.Action} has been executed but before the {@link org.apache.struts2.Result} + * is executed. + * + * @author Jason Carreira + */ +public interface PreResultListener { + + /** + * This callback method will be called after the {@link org.apache.struts2.Action} execution and + * before the {@link org.apache.struts2.Result} execution. + * + * @param invocation the action invocation + * @param resultCode the result code returned by the action (eg. success). + */ + void beforeResult(ActionInvocation invocation, String resultCode); + +} 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/ScopedModelDriven.java b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDriven.java new file mode 100644 index 000000000..d18ef0880 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDriven.java @@ -0,0 +1,43 @@ +/* + * 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.ModelDriven; + +/** + * Adds the ability to set a model, probably retrieved from a given state. + */ +public interface ScopedModelDriven extends ModelDriven { + + /** + * @param model sets the model + */ + void setModel(T model); + + /** + * Sets the key under which the model is stored + * @param key The model key + */ + void setScopeKey(String key); + + /** + * @return the key under which the model is stored + */ + String getScopeKey(); +} 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. + * + * + * + *

    Example code:

    + * + *
    + * 
    + *
    + * <-- Basic usage -->
    + * <interceptor name="scopedModelDriven" class="org.apache.struts2.interceptor.ScopedModelDrivenInterceptor" />
    + *
    + * <-- Using all available parameters -->
    + * <interceptor name="gangsterForm" class="org.apache.struts2.interceptor.ScopedModelDrivenInterceptor">
    + *      <param name="scope">session</param>
    + *      <param name="name">gangsterForm</param>
    + *      <param name="className">com.opensymphony.example.GangsterForm</param>
    + *  </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..2f7ccac37 --- /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.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.interceptor.parameter.ParametersInterceptor; +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.

    + * + * + * + *

    Example code:

    + * + *
    + * 
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <interceptor-ref name="staticParams">
    + *          <param name="parse">true</param>
    + *          <param name="overwrite">false</param>
    + *     </interceptor-ref>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + * 
    + * 
    + * + * @author Patrick Lightbody + */ +public class StaticParametersInterceptor extends AbstractInterceptor { + + private boolean parse; + private boolean overwrite; + private boolean merge = true; + private boolean devMode = false; + + private static final Logger LOG = LogManager.getLogger(StaticParametersInterceptor.class); + + private ValueStackFactory valueStackFactory; + private LocalizedTextProvider localizedTextProvider; + + @Inject + public void setValueStackFactory(ValueStackFactory valueStackFactory) { + this.valueStackFactory = valueStackFactory; + } + + @Inject(StrutsConstants.STRUTS_DEVMODE) + public void setDevMode(String mode) { + devMode = BooleanUtils.toBoolean(mode); + } + + @Inject + public void setLocalizedTextProvider(LocalizedTextProvider localizedTextProvider) { + this.localizedTextProvider = localizedTextProvider; + } + + public void setParse(String value) { + this.parse = BooleanUtils.toBoolean(value); + } + + public void setMerge(String value) { + this.merge = BooleanUtils.toBoolean(value); + } + + /** + * Overwrites already existing parameters from other sources. + * Static parameters are the successor over previously set parameters, if true. + * + * @param value enable overwrites of already existing parameters from other sources + */ + public void setOverwrite(String value) { + this.overwrite = BooleanUtils.toBoolean(value); + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + ActionConfig config = invocation.getProxy().getConfig(); + Object action = invocation.getAction(); + + final Map parameters = config.getParams(); + + LOG.debug("Setting static parameters: {}", parameters); + + // for actions marked as Parameterizable, pass the static parameters directly + if (action instanceof Parameterizable) { + ((Parameterizable) action).setParams(parameters); + } + + if (parameters != null) { + ActionContext ac = ActionContext.getContext(); + Map contextMap = ac.getContextMap(); + try { + ReflectionContextState.setCreatingNullObjects(contextMap, true); + ReflectionContextState.setReportingConversionErrors(contextMap, true); + final ValueStack stack = ac.getValueStack(); + + 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()); + } + + for (Map.Entry entry : parameters.entrySet()) { + Object val = entry.getValue(); + if (parse && val instanceof String) { + val = TextParseUtil.translateVariables(val.toString(), com.opensymphony.xwork2.util.ValueStack.adapt(stack)); + } + try { + newStack.setValue(entry.getKey(), val); + } 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()); + } + + if (merge) + addParametersToContext(ac, parameters); + } finally { + ReflectionContextState.setCreatingNullObjects(contextMap, false); + ReflectionContextState.setReportingConversionErrors(contextMap, false); + } + } + return invocation.invoke(); + } + + + /** + * @param ac The action context + * @return the parameters from the action mapping in the context. If none found, returns + * an empty map. + */ + protected Map retrieveParameters(ActionContext ac) { + ActionConfig config = ac.getActionInvocation().getProxy().getConfig(); + if (config != null) { + return config.getParams(); + } else { + return Collections.emptyMap(); + } + } + + /** + * Adds the parameters into context's ParameterMap. + * As default, static parameters will not overwrite existing parameters from other sources. + * If you want the static parameters as successor over already existing parameters, set overwrite to true. + * + * @param ac The action context + * @param newParams The parameter map to apply + */ + protected void addParametersToContext(ActionContext ac, Map newParams) { + HttpParameters previousParams = ac.getParameters(); + + HttpParameters.Builder combinedParams; + if (overwrite) { + combinedParams = HttpParameters.create().withParent( previousParams); + combinedParams = combinedParams.withExtraParams(newParams); + } else { + combinedParams = HttpParameters.create(newParams); + combinedParams = combinedParams.withExtraParams(previousParams); + } + ac.withParameters(combinedParams.build()); + } +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java index 753d99d1d..2fe2a3011 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/TokenInterceptor.java @@ -21,7 +21,6 @@ package org.apache.struts2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.TextProvider; import com.opensymphony.xwork2.TextProviderFactory; -import com.opensymphony.xwork2.interceptor.ValidationAware; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; import org.apache.logging.log4j.LogManager; diff --git a/core/src/main/java/org/apache/struts2/interceptor/ValidationAware.java b/core/src/main/java/org/apache/struts2/interceptor/ValidationAware.java new file mode 100644 index 000000000..a1e611a1c --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/ValidationAware.java @@ -0,0 +1,131 @@ +/* + * 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 java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept + * in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs. + */ +public interface ValidationAware { + + /** + * Set the Collection of Action-level String error messages. + * + * @param errorMessages Collection of String error messages + */ + void setActionErrors(Collection errorMessages); + + /** + * Get the Collection of Action-level error messages for this action. Error messages should not + * be added directly here, as implementations are free to return a new Collection or an + * Unmodifiable Collection. + * + * @return Collection of String error messages + */ + Collection getActionErrors(); + + /** + * Set the Collection of Action-level String messages (not errors). + * + * @param messages Collection of String messages (not errors). + */ + void setActionMessages(Collection messages); + + /** + * Get the Collection of Action-level messages for this action. Messages should not be added + * directly here, as implementations are free to return a new Collection or an Unmodifiable + * Collection. + * + * @return Collection of String messages + */ + Collection getActionMessages(); + + /** + * Set the field error map of fieldname (String) to Collection of String error messages. + * + * @param errorMap field error map + */ + void setFieldErrors(Map> errorMap); + + /** + * Get the field specific errors associated with this action. Error messages should not be added + * directly here, as implementations are free to return a new Collection or an Unmodifiable + * Collection. + * + * @return Map with errors mapped from fieldname (String) to Collection of String error messages + */ + Map> getFieldErrors(); + + /** + * Add an Action-level error message to this Action. + * + * @param anErrorMessage the error message + */ + void addActionError(String anErrorMessage); + + /** + * Add an Action-level message to this Action. + * + * @param aMessage the message + */ + void addActionMessage(String aMessage); + + /** + * Add an error message for a given field. + * + * @param fieldName name of field + * @param errorMessage the error message + */ + void addFieldError(String fieldName, String errorMessage); + + /** + * Check whether there are any Action-level error messages. + * + * @return true if any Action-level error messages have been registered + */ + boolean hasActionErrors(); + + /** + * Checks whether there are any Action-level messages. + * + * @return true if any Action-level messages have been registered + */ + boolean hasActionMessages(); + + /** + * Checks whether there are any action errors or field errors. + * + * @return (hasActionErrors() || hasFieldErrors()) + */ + default boolean hasErrors() { + return hasActionErrors() || hasFieldErrors(); + } + + /** + * Check whether there are any field errors associated with this action. + * + * @return whether there are any field errors + */ + boolean hasFieldErrors(); + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/ValidationErrorAware.java b/core/src/main/java/org/apache/struts2/interceptor/ValidationErrorAware.java new file mode 100644 index 000000000..7722ed9ec --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/ValidationErrorAware.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * ValidationErrorAware classes can be notified about validation errors + * before {@link com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor} will return 'inputResultName' result + * to allow change or not the result name + * + * This interface can be only applied to action which already implements {@link ValidationAware} interface! + * + * @since 2.3.15 + */ +public interface ValidationErrorAware { + + /** + * Allows to notify action about occurred action/field errors + * + * @param currentResultName current result name, action can change it or return the same + * @return new result name or passed currentResultName + */ + String actionErrorOccurred(final String currentResultName); + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/ValidationWorkflowAware.java b/core/src/main/java/org/apache/struts2/interceptor/ValidationWorkflowAware.java new file mode 100644 index 000000000..e3f4a4385 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/ValidationWorkflowAware.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * ValidationWorkflowAware classes can programmatically change result name when errors occurred + * + * This interface can be only applied to action which already implements {@link ValidationAware} interface! + */ +public interface ValidationWorkflowAware { + + String getInputResultName(); + +} diff --git a/core/src/main/java/org/apache/struts2/result/StrutsResultSupport.java b/core/src/main/java/org/apache/struts2/result/StrutsResultSupport.java index d5307d279..2ec5e987f 100644 --- a/core/src/main/java/org/apache/struts2/result/StrutsResultSupport.java +++ b/core/src/main/java/org/apache/struts2/result/StrutsResultSupport.java @@ -154,7 +154,7 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { public void setLocation(String location) { this.location = location; } - + /** * Gets the location it was created with, mainly for testing * @@ -201,9 +201,10 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { * @param invocation the execution state of the action. * @throws Exception if an error occurs while executing the result. */ + @Override public void execute(ActionInvocation invocation) throws Exception { lastFinalLocation = parseLocation ? conditionalParse(location, invocation) : location; - doExecute(lastFinalLocation, invocation); + doExecute(lastFinalLocation, (org.apache.struts2.ActionInvocation) invocation); } /** @@ -216,7 +217,7 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { protected String conditionalParse(String param, ActionInvocation invocation) { if (parse && param != null && invocation != null) { return TextParseUtil.translateVariables( - param, + param, invocation.getStack(), new EncodingParsedValueEvaluator()); } else { @@ -228,7 +229,7 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { * As {@link #conditionalParse(String, ActionInvocation)} but does not * convert found object into String. If found object is a collection it is * returned if found object is not a collection it is wrapped in one. - * + * * @param param parameter * @param invocation action invocation * @param excludeEmptyElements 'true' for excluding empty elements @@ -237,7 +238,7 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { protected Collection conditionalParseCollection(String param, ActionInvocation invocation, boolean excludeEmptyElements) { if (parse && param != null && invocation != null) { return TextParseUtil.translateVariablesCollection( - param, + param, invocation.getStack(), excludeEmptyElements, new EncodingParsedValueEvaluator()); @@ -251,9 +252,10 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { /** * {@link com.opensymphony.xwork2.util.TextParseUtil.ParsedValueEvaluator} to do URL encoding for found values. To be * used for single strings or collections. - * + * */ private final class EncodingParsedValueEvaluator implements TextParseUtil.ParsedValueEvaluator { + @Override public Object evaluate(String parsedValue) { if (encode) { if (parsedValue != null) { @@ -269,6 +271,13 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { } } + /** + * @deprecated since 6.7.0, override {@link #doExecute(String, org.apache.struts2.ActionInvocation)} instead. + */ + @Deprecated + protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { + } + /** * Executes the result given a final location (jsp page, action, etc) and the action invocation * (the state in which the action was executed). Subclasses must implement this class to handle @@ -278,5 +287,7 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics { * @param invocation the execution state of the action. * @throws Exception if an error occurs while executing the result. */ - protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception; + protected void doExecute(String finalLocation, org.apache.struts2.ActionInvocation invocation) throws Exception { + doExecute(finalLocation, ActionInvocation.adapt(invocation)); + } } diff --git a/core/src/main/java/org/apache/struts2/util/ValueStack.java b/core/src/main/java/org/apache/struts2/util/ValueStack.java new file mode 100644 index 000000000..f7d70a35d --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/ValueStack.java @@ -0,0 +1,167 @@ +/* + * 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.util; + +import com.opensymphony.xwork2.util.CompoundRoot; +import org.apache.struts2.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). + */ +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(); + + 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); + + /** + * 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. + *

    + * + * @param overrides overrides map. + */ + void setExprOverrides(Map overrides); + + /** + * Gets the override map if anyone exists. + * + * @return the override map, null if not set. + */ + Map getExprOverrides(); + + /** + * Get the CompoundRoot which holds the objects pushed onto the stack + * + * @return the root + */ + CompoundRoot getRoot(); + + /** + * Attempts to set a property on a bean in the stack with the given expression using the default search order. + * + * @param expr the expression defining the path to the property to be set. + * @param value the value to be set into the named property + */ + void setValue(String expr, Object value); + + /** + * Attempts to set a property on a bean in the stack with the given expression using the default search order. + * N.B.: unlike #setValue(String,Object) it doesn't allow eval expression. + * @param expr the expression defining the path to the property to be set. + * @param value the value to be set into the named property + */ + void setParameter(String expr, Object value); + + /** + * Attempts to set a property on a bean in the stack with the given expression using the default search order. + * + * @param expr the expression defining the path to the property to be set. + * @param value the value to be set into the named property + * @param throwExceptionOnFailure a flag to tell whether an exception should be thrown if there is no property with + * the given name. + */ + void setValue(String expr, Object value, boolean throwExceptionOnFailure); + + String findString(String expr); + String findString(String expr, boolean throwExceptionOnFailure); + + /** + * Find a value by evaluating the given expression against the stack in the default search order. + * + * @param expr the expression giving the path of properties to navigate to find the property value to return + * @return the result of evaluating the expression + */ + Object findValue(String expr); + + Object findValue(String expr, boolean throwExceptionOnFailure); + + /** + * Find a value by evaluating the given expression against the stack in the default search order. + * + * @param expr the expression giving the path of properties to navigate to find the property value to return + * @param asType the type to convert the return value to + * @return the result of evaluating the expression + */ + Object findValue(String expr, Class asType); + Object findValue(String expr, Class asType, boolean throwExceptionOnFailure); + + /** + * Get the object on the top of the stack without changing the stack. + * + * @return the object on the top. + * @see CompoundRoot#peek() + */ + Object peek(); + + /** + * Get the object on the top of the stack and remove it from the stack. + * + * @return the object on the top of the stack + * @see CompoundRoot#pop() + */ + Object pop(); + + /** + * Put this object onto the top of the stack + * + * @param o the object to be pushed onto the stack + * @see CompoundRoot#push(Object) + */ + void push(Object o); + + /** + * Sets an object on the stack with the given key + * so it is retrievable by {@link #findValue(String)}, {@link #findValue(String, Class)} + * + * @param key the key + * @param o the object + */ + void set(String key, Object o); + + /** + * Get the number of objects in the stack + * + * @return the number of objects in the stack + */ + int size(); + +} diff --git a/core/src/main/java/org/apache/struts2/views/freemarker/ScopesHashModel.java b/core/src/main/java/org/apache/struts2/views/freemarker/ScopesHashModel.java index b2c27497c..ac49c8326 100644 --- a/core/src/main/java/org/apache/struts2/views/freemarker/ScopesHashModel.java +++ b/core/src/main/java/org/apache/struts2/views/freemarker/ScopesHashModel.java @@ -61,7 +61,7 @@ public class ScopesHashModel extends SimpleHash implements TemplateModel { private final ServletContext servletContext; private ValueStack stack; private final Map unlistedModels = new HashMap<>(); - private volatile Object parametersCache; + private volatile Object attributesCache; public ScopesHashModel(ObjectWrapper objectWrapper, ServletContext context, HttpServletRequest request, ValueStack stack) { super(objectWrapper); @@ -155,12 +155,12 @@ public class ScopesHashModel extends SimpleHash implements TemplateModel { private Object findValueOnStack(final String key) { if (TAG_ATTRIBUTES.equals(key)) { - if (parametersCache != null) { - return parametersCache; + if (attributesCache != null) { + return attributesCache; } - Object parametersLocal = stack.findValue(key); - parametersCache = parametersLocal; - return parametersLocal; + Object attributesLocal = stack.findValue(key); + attributesCache = attributesLocal; + return attributesLocal; } return stack.findValue(key); } 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 d2c8bc3fe..f2fa9f700 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 @@ -50,6 +50,7 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase Class.forName("java.io.Serializable"), Class.forName("com.opensymphony.xwork2.mock.MockResult"), Class.forName("com.opensymphony.xwork2.interceptor.ConditionalInterceptor"), + Class.forName("org.apache.struts2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionChainResult"), Class.forName("com.opensymphony.xwork2.TextProvider"), @@ -60,8 +61,15 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase Class.forName("com.opensymphony.xwork2.mock.MockInterceptor"), Class.forName("com.opensymphony.xwork2.Action"), Class.forName("com.opensymphony.xwork2.interceptor.AbstractInterceptor"), + Class.forName("org.apache.struts2.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.interceptor.ConditionalInterceptor"), + Class.forName("org.apache.struts2.Result"), + Class.forName("org.apache.struts2.Action"), + Class.forName("org.apache.struts2.Validateable"), + Class.forName("org.apache.struts2.interceptor.ValidationAware") ); } @@ -76,6 +84,7 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase Class.forName("java.io.Serializable"), Class.forName("com.opensymphony.xwork2.mock.MockResult"), Class.forName("com.opensymphony.xwork2.interceptor.ConditionalInterceptor"), + Class.forName("org.apache.struts2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionSupport"), Class.forName("com.opensymphony.xwork2.TextProvider"), Class.forName("com.opensymphony.xwork2.interceptor.Interceptor"), @@ -84,8 +93,15 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase Class.forName("com.opensymphony.xwork2.mock.MockInterceptor"), Class.forName("com.opensymphony.xwork2.Action"), Class.forName("com.opensymphony.xwork2.interceptor.AbstractInterceptor"), + Class.forName("org.apache.struts2.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.interceptor.ConditionalInterceptor"), + Class.forName("org.apache.struts2.Result"), + Class.forName("org.apache.struts2.Action"), + Class.forName("org.apache.struts2.Validateable"), + Class.forName("org.apache.struts2.interceptor.ValidationAware") ); } @@ -99,6 +115,7 @@ public class ConfigurationProviderOgnlAllowlistTest extends XWorkJUnit4TestCase Class.forName("com.opensymphony.xwork2.LocaleProvider"), Class.forName("java.io.Serializable"), Class.forName("com.opensymphony.xwork2.interceptor.ConditionalInterceptor"), + Class.forName("org.apache.struts2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionChainResult"), Class.forName("com.opensymphony.xwork2.TextProvider"), @@ -108,7 +125,14 @@ 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("org.apache.struts2.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"), + Class.forName("org.apache.struts2.Validateable"), + Class.forName("org.apache.struts2.interceptor.ValidationAware") ); } } 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 178d6bfc0..8e9e7bd4d 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/ActionFileUploadInterceptorTest.java @@ -25,6 +25,7 @@ import com.opensymphony.xwork2.ValidationAwareSupport; import com.opensymphony.xwork2.mock.MockActionInvocation; import com.opensymphony.xwork2.mock.MockActionProxy; import com.opensymphony.xwork2.util.ClassLoaderUtil; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload; import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; @@ -268,12 +269,10 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase { } public void testNoContentMultipartRequest() throws Exception { - MockHttpServletRequest req = new MockHttpServletRequest(); - - req.setCharacterEncoding(StandardCharsets.UTF_8.name()); - req.setMethod("post"); - req.addHeader("Content-type", "multipart/form-data"); - req.setContent(null); // there is no content + request.setCharacterEncoding(StandardCharsets.UTF_8.name()); + request.setMethod("post"); + request.addHeader("Content-type", "multipart/form-data"); + request.setContent(null); // there is no content MyFileUploadAction action = container.inject(MyFileUploadAction.class); MockActionInvocation mai = new MockActionInvocation(); @@ -324,6 +323,145 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase { assertNotNull("deleteme.txt", files.get(0).getOriginalName()); } + public void testSuccessUploadOfATextFileMultipartRequestNoMaxParamsSet() throws Exception { + request.setCharacterEncoding(StandardCharsets.UTF_8.name()); + request.setMethod("post"); + request.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"); + request.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()); + + 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 { + request.setCharacterEncoding(StandardCharsets.UTF_8.name()); + request.setMethod("post"); + request.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"); + request.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(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 { + request.setCharacterEncoding(StandardCharsets.UTF_8.name()); + request.setMethod("post"); + request.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"); + request.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()); + + 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. @@ -614,6 +752,11 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase { return new MultiPartRequestWrapper(jak, request, tempDir.getAbsolutePath(), new DefaultLocaleProvider()); } + private MultiPartRequestWrapper createMultipartRequestNoMaxParamsSet() { + JakartaMultiPartRequest jak = new JakartaMultiPartRequest(); + return new MultiPartRequestWrapper(jak, request, tempDir.getAbsolutePath(), new DefaultLocaleProvider()); + } + protected void setUp() throws Exception { super.setUp(); request = new MockHttpServletRequest(); @@ -632,6 +775,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/views/jsp/ActionTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ActionTagTest.java index 8864a1edd..6e4884442 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(); diff --git a/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java b/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java index fd14205e3..cc95dfef0 100644 --- a/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java +++ b/plugins/bean-validation/src/main/java/org/apache/struts/beanvalidation/validation/interceptor/BeanValidationInterceptor.java @@ -20,7 +20,6 @@ package org.apache.struts.beanvalidation.validation.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.TextProviderFactory; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; @@ -33,6 +32,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts.beanvalidation.constraints.ValidationGroup; import org.apache.struts.beanvalidation.validation.constant.ValidatorConstants; +import org.apache.struts2.ModelDriven; import org.apache.struts2.interceptor.validation.SkipValidation; import jakarta.validation.ConstraintViolation; diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java index e466b0cef..9e473807e 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONResult.java @@ -20,7 +20,6 @@ package org.apache.struts2.json; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.Result; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.ValueStack; @@ -29,6 +28,7 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.ModelDriven; import org.apache.struts2.StrutsConstants; import org.apache.struts2.json.smd.SMDGenerator; diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONValidationInterceptor.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONValidationInterceptor.java index 912a110b5..fa6863662 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/JSONValidationInterceptor.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONValidationInterceptor.java @@ -20,13 +20,13 @@ package org.apache.struts2.json; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ModelDriven; -import com.opensymphony.xwork2.interceptor.ValidationAware; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; import org.apache.commons.text.StringEscapeUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ModelDriven; import org.apache.struts2.ServletActionContext; +import org.apache.struts2.interceptor.ValidationAware; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsJUnit4TestCase.java b/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsJUnit4TestCase.java index f57c985a2..4fa6f10ae 100644 --- a/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsJUnit4TestCase.java +++ b/plugins/junit/src/main/java/org/apache/struts2/junit/StrutsJUnit4TestCase.java @@ -22,7 +22,6 @@ import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionProxy; import com.opensymphony.xwork2.ActionProxyFactory; import com.opensymphony.xwork2.config.Configuration; -import com.opensymphony.xwork2.interceptor.ValidationAware; import com.opensymphony.xwork2.interceptor.annotations.After; import com.opensymphony.xwork2.interceptor.annotations.Before; import jakarta.servlet.ServletException; @@ -35,6 +34,7 @@ import org.apache.struts2.dispatcher.Dispatcher; import org.apache.struts2.dispatcher.HttpParameters; import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; +import org.apache.struts2.interceptor.ValidationAware; import org.apache.struts2.util.StrutsTestCaseHelper; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.mock.web.MockHttpServletRequest; diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/ContentTypeInterceptor.java b/plugins/rest/src/main/java/org/apache/struts2/rest/ContentTypeInterceptor.java index 17195ddd9..4093fc901 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/ContentTypeInterceptor.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/ContentTypeInterceptor.java @@ -19,9 +19,9 @@ package org.apache.struts2.rest; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import org.apache.struts2.ModelDriven; import org.apache.struts2.ServletActionContext; import org.apache.struts2.rest.handler.ContentTypeHandler; diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionInvocation.java b/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionInvocation.java index 850bdfc4f..4c76f7b37 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionInvocation.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionInvocation.java @@ -18,19 +18,23 @@ */ package org.apache.struts2.rest; -import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.DefaultActionInvocation; +import com.opensymphony.xwork2.Result; import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.inject.Inject; -import com.opensymphony.xwork2.interceptor.ValidationAware; import org.apache.commons.lang3.BooleanUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.ModelDriven; import org.apache.struts2.ServletActionContext; -import org.apache.struts2.result.HttpHeaderResult; +import org.apache.struts2.interceptor.ValidationAware; import org.apache.struts2.rest.handler.ContentTypeHandler; import org.apache.struts2.rest.handler.HtmlHandler; +import org.apache.struts2.result.HttpHeaderResult; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -74,7 +78,7 @@ public class RestActionInvocation extends DefaultActionInvocation { /** * If set to true (by default) blocks returning content from any other methods than GET, * if set to false, the content can be returned for any kind of method - * + * * @param restrictToGet true or false */ @Inject(value = RestConstants.REST_CONTENT_RESTRICT_TO_GET, required = false) diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/RestWorkflowInterceptor.java b/plugins/rest/src/main/java/org/apache/struts2/rest/RestWorkflowInterceptor.java index 54220c647..dfe04f993 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/RestWorkflowInterceptor.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/RestWorkflowInterceptor.java @@ -23,10 +23,10 @@ import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; -import com.opensymphony.xwork2.interceptor.ValidationAware; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.dispatcher.mapper.ActionMapping; +import org.apache.struts2.interceptor.ValidationAware; import java.util.HashMap; import java.util.Map; diff --git a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java index f1107b15e..f03005863 100644 --- a/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java +++ b/plugins/rest/src/main/java/org/apache/struts2/rest/handler/XStreamHandler.java @@ -19,7 +19,6 @@ package org.apache.struts2.rest.handler; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ModelDriven; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.security.ArrayTypePermission; @@ -29,6 +28,7 @@ import com.thoughtworks.xstream.security.PrimitiveTypePermission; import com.thoughtworks.xstream.security.TypePermission; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.ModelDriven; import org.apache.struts2.rest.handler.xstream.XStreamAllowedClassNames; import org.apache.struts2.rest.handler.xstream.XStreamAllowedClasses; import org.apache.struts2.rest.handler.xstream.XStreamPermissionProvider; diff --git a/pom.xml b/pom.xml index d2544f4db..5da04c34d 100644 --- a/pom.xml +++ b/pom.xml @@ -110,13 +110,13 @@ 17 - 9.7 + 9.7.1 1.14.11 2.3.33 8.0.1.Final 2.18.0 2.24.1 - 3.5.0 + 3.5.1 5.8.0 3.3.5 2.0.16 @@ -412,7 +412,7 @@ org.apache.maven.doxia doxia-core - 1.12.0 + 2.0.0 org.apache.maven.doxia @@ -805,7 +805,7 @@ org.apache.commons commons-lang3 - 3.15.0 + 3.17.0 org.apache.commons