From a623842bcee720e48c2f142d9854048f39b3ba4a Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Thu, 17 Oct 2024 16:19:33 +1100 Subject: [PATCH 1/6] WW-3714 Deprecate and migrate ActionSupport --- .../opensymphony/xwork2/ActionSupport.java | 339 +--------------- .../org/apache/struts2/ActionSupport.java | 371 ++++++++++++++++++ ...onfigurationProviderOgnlAllowlistTest.java | 3 + 3 files changed, 377 insertions(+), 336 deletions(-) create mode 100644 core/src/main/java/org/apache/struts2/ActionSupport.java diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java index ab1a18099..a775c9bb7 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java @@ -18,342 +18,9 @@ */ 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.*; - /** - * 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, Arrays.asList(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 { } 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..3f2715731 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ActionSupport.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.LocaleProvider; +import com.opensymphony.xwork2.LocaleProviderFactory; +import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.TextProviderFactory; +import com.opensymphony.xwork2.Validateable; +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.interceptor.ValidationAware; +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.Serializable; +import java.util.Arrays; +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 com.opensymphony.xwork2.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 = com.opensymphony.xwork2.ActionContext.getContext().getConversionErrors(); + if (conversionErrors.containsKey(expr)) { + String[] vals = (String[]) conversionErrors.get(expr).getValue(); + return vals[0]; + } else { + final ValueStack valueStack = com.opensymphony.xwork2.ActionContext.getContext().getValueStack(); + final Object val = valueStack.findValue(expr); + return getText(key, Arrays.asList(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/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java index 2379216bc..b3f65973d 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"), @@ -82,6 +83,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"), @@ -111,6 +113,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"), From 9e23fbe665540a050b64975c690196dd291339ac Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Thu, 17 Oct 2024 16:26:01 +1100 Subject: [PATCH 2/6] WW-3714 Deprecate and migrate AbstractInterceptor and MethodFilterInterceptor --- .../interceptor/AbstractInterceptor.java | 32 +--- .../interceptor/MethodFilterInterceptor.java | 45 +++--- .../MethodFilterInterceptorUtil.java | 128 +-------------- .../interceptor/AbstractInterceptor.java | 61 ++++++++ .../interceptor/MethodFilterInterceptor.java | 123 +++++++++++++++ .../MethodFilterInterceptorUtil.java | 148 ++++++++++++++++++ ...onfigurationProviderOgnlAllowlistTest.java | 3 + 7 files changed, 369 insertions(+), 171 deletions(-) create mode 100644 core/src/main/java/org/apache/struts2/interceptor/AbstractInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptorUtil.java 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 21e459c29..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,41 +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 - */ - public void init() { - } - - /** - * Does nothing - */ - public void destroy() { - } +@Deprecated +public abstract class AbstractInterceptor extends org.apache.struts2.interceptor.AbstractInterceptor implements ConditionalInterceptor { /** * Override to handle interception */ 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/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 beacb8784..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,131 +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; - /** - * 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.size() == 0) ) { - if (excludeMethods != null - && excludeMethods.contains(method) - && !includeMethods.contains(method) ) { - return false; - } - } - - // test the methods using pattern matching - WildcardHelper wildcard = new WildcardHelper(); - String methodCopy ; - if (method == null ) { // no method specified - methodCopy = ""; - } - else { - methodCopy = new String(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.size() == 0 || 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/org/apache/struts2/interceptor/AbstractInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AbstractInterceptor.java new file mode 100644 index 000000000..ddb48a0d7 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/AbstractInterceptor.java @@ -0,0 +1,61 @@ +/* + * 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 + */ + public void init() { + } + + /** + * Does nothing + */ + public void destroy() { + } + + /** + * Override to handle interception + */ + 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/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..2a43ba2ff --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/MethodFilterInterceptorUtil.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.TextParseUtil; +import com.opensymphony.xwork2.util.WildcardHelper; + +import java.util.HashMap; +import java.util.Set; + +/** + * 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.size() == 0) ) { + if (excludeMethods != null + && excludeMethods.contains(method) + && !includeMethods.contains(method) ) { + return false; + } + } + + // test the methods using pattern matching + WildcardHelper wildcard = new WildcardHelper(); + String methodCopy ; + if (method == null ) { // no method specified + methodCopy = ""; + } + else { + methodCopy = new String(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.size() == 0 || 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/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java index b3f65973d..51d2f96f2 100644 --- a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java @@ -61,6 +61,7 @@ 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("org.apache.struts2.interceptor.Interceptor"), @@ -92,6 +93,7 @@ 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("org.apache.struts2.interceptor.Interceptor"), @@ -123,6 +125,7 @@ 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("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"), From f95f9a7cd3ff0710cd7d4e0d2054fbf562fbf7e9 Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Tue, 22 Oct 2024 10:57:57 +1100 Subject: [PATCH 3/6] WW-3714 Add alternative constructors in InterceptorMapping --- .../xwork2/config/entities/InterceptorMapping.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 260ae325b..6625bc7a1 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 @@ -36,8 +36,16 @@ public class InterceptorMapping implements Serializable { private 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) { From deb6c09bce253f25f8d47a5eac2552b05ba38d71 Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Tue, 22 Oct 2024 10:58:50 +1100 Subject: [PATCH 4/6] WW-3714 Replace deprecated APIs in new ActionSupport --- .../java/com/opensymphony/xwork2/ActionSupport.java | 4 +++- .../main/java/org/apache/struts2/ActionSupport.java | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java index a775c9bb7..be9cc29ca 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java @@ -18,9 +18,11 @@ */ package com.opensymphony.xwork2; +import com.opensymphony.xwork2.interceptor.ValidationAware; + /** * @deprecated since 6.7.0, use {@link org.apache.struts2.ActionSupport} instead. */ @Deprecated -public class ActionSupport extends org.apache.struts2.ActionSupport { +public class ActionSupport extends org.apache.struts2.ActionSupport implements Action, Validateable, ValidationAware { } diff --git a/core/src/main/java/org/apache/struts2/ActionSupport.java b/core/src/main/java/org/apache/struts2/ActionSupport.java index 3f2715731..04c513f3a 100644 --- a/core/src/main/java/org/apache/struts2/ActionSupport.java +++ b/core/src/main/java/org/apache/struts2/ActionSupport.java @@ -18,21 +18,18 @@ */ package org.apache.struts2; -import com.opensymphony.xwork2.Action; -import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.LocaleProvider; import com.opensymphony.xwork2.LocaleProviderFactory; import com.opensymphony.xwork2.TextProvider; import com.opensymphony.xwork2.TextProviderFactory; -import com.opensymphony.xwork2.Validateable; 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.interceptor.ValidationAware; 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.Arrays; @@ -46,7 +43,7 @@ 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 com.opensymphony.xwork2.Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable { +public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable { private static final Logger LOG = LogManager.getLogger(ActionSupport.class); @@ -165,12 +162,12 @@ public class ActionSupport implements com.opensymphony.xwork2.Action, Validateab * @return formatted expr with format specified by key */ public String getFormatted(String key, String expr) { - Map conversionErrors = com.opensymphony.xwork2.ActionContext.getContext().getConversionErrors(); + Map conversionErrors = ActionContext.getContext().getConversionErrors(); if (conversionErrors.containsKey(expr)) { String[] vals = (String[]) conversionErrors.get(expr).getValue(); return vals[0]; } else { - final ValueStack valueStack = com.opensymphony.xwork2.ActionContext.getContext().getValueStack(); + final ValueStack valueStack = ValueStack.adapt(ActionContext.getContext().getValueStack()); final Object val = valueStack.findValue(expr); return getText(key, Arrays.asList(val)); } From 45a1f5efc6e5997e7f1e3106dfd03a52f6091c7f Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Thu, 17 Oct 2024 17:53:30 +1100 Subject: [PATCH 5/6] WW-3714 Deprecate and migrate assorted Interceptors --- .../xwork2/interceptor/AliasInterceptor.java | 3 + .../interceptor/ChainingInterceptor.java | 3 + .../ConversionErrorInterceptor.java | 7 +- .../DefaultWorkflowInterceptor.java | 5 +- .../ExceptionMappingInterceptor.java | 5 +- .../interceptor/LoggingInterceptor.java | 3 + .../interceptor/ModelDrivenInterceptor.java | 5 +- .../ParameterRemoverInterceptor.java | 3 + .../PrefixMethodInvocationUtil.java | 51 +-- .../interceptor/PrepareInterceptor.java | 3 + .../ScopedModelDrivenInterceptor.java | 25 +- .../StaticParametersInterceptor.java | 3 + .../struts2/interceptor/AliasInterceptor.java | 293 ++++++++++++++++ .../interceptor/ChainingInterceptor.java | 275 +++++++++++++++ .../ConversionErrorInterceptor.java | 149 ++++++++ .../DefaultWorkflowInterceptor.java | 245 +++++++++++++ .../ExceptionMappingInterceptor.java | 324 ++++++++++++++++++ .../interceptor/LoggingInterceptor.java | 90 +++++ .../interceptor/ModelDrivenInterceptor.java | 148 ++++++++ .../ParameterRemoverInterceptor.java | 124 +++++++ .../interceptor/PrepareInterceptor.java | 177 ++++++++++ .../ScopedModelDrivenInterceptor.java | 165 +++++++++ .../StaticParametersInterceptor.java | 242 +++++++++++++ 23 files changed, 2308 insertions(+), 40 deletions(-) create mode 100644 core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java create mode 100644 core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java 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 334525d27..943aaacf4 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java @@ -91,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 7284dc037..a21d18c56 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java @@ -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/ConversionErrorInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java index b549cc019..21e3d9f65 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java @@ -42,13 +42,13 @@ import java.util.Map; * 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:

@@ -85,7 +85,10 @@ import java.util.Map; * * * @author Jason Carreira + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ConversionErrorInterceptor} instead. */ +@Deprecated public class ConversionErrorInterceptor extends MethodFilterInterceptor { public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override"; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java index 05749ae19..d238e1ceb 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java @@ -32,7 +32,7 @@ import org.apache.struts2.interceptor.ValidationWorkflowAware; /** * *

- * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue. + * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue. * If a single FieldError or ActionError (including the ones replicated by the Message Store Interceptor in a redirection) is found, the INPUT result will be triggered. * This interceptor does not perform any validation. *

@@ -132,7 +132,10 @@ import org.apache.struts2.interceptor.ValidationWorkflowAware; * @author Alexandru Popescu * @author Philip Luppens * @author tm_jee + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.DefaultWorkflowInterceptor} instead. */ +@Deprecated public class DefaultWorkflowInterceptor extends MethodFilterInterceptor { private static final long serialVersionUID = 7563014655616490865L; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java index e60550ca6..3bb70bcb8 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java @@ -20,8 +20,8 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.struts2.dispatcher.HttpParameters; import java.util.List; @@ -153,7 +153,10 @@ import java.util.Map; * * @author Matthew E. Porter (matthew dot porter at metissian dot com) * @author Claus Ibsen + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ExceptionMappingInterceptor} instead. */ +@Deprecated public class ExceptionMappingInterceptor extends AbstractInterceptor { private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class); diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java index 6ba498b3c..3f012288c 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java @@ -59,7 +59,10 @@ import org.apache.logging.log4j.Logger; * * * @author Jason Carreira + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.LoggingInterceptor} instead. */ +@Deprecated public class LoggingInterceptor extends AbstractInterceptor { private static final Logger LOG = LogManager.getLogger(LoggingInterceptor.class); private static final String FINISH_MESSAGE = "Finishing execution stack for action "; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java index f513deb1c..84170b669 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java @@ -71,10 +71,13 @@ import org.apache.struts2.ModelDriven; * </action> * * - * + * * @author tm_jee * @version $Date$ $Id$ + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ModelDrivenInterceptor} instead. */ +@Deprecated public class ModelDrivenInterceptor extends AbstractInterceptor { protected boolean refreshModelBeforeResult = false; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java index c0f83765c..f33ebf6e2 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java @@ -66,7 +66,10 @@ import java.util.Set; * ... * </action> * + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ParameterRemoverInterceptor} instead. */ +@Deprecated public class ParameterRemoverInterceptor extends AbstractInterceptor { private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class); diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java index 040080824..0ac840c7a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java @@ -19,8 +19,8 @@ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -28,7 +28,7 @@ import java.lang.reflect.Method; /** *

* A utility class for invoking prefixed methods in action class. - * + * * Interceptors that made use of this class are: *

*
    @@ -37,7 +37,7 @@ import java.lang.reflect.Method; *
* * * - * + * * In DefaultWorkflowInterceptor *

applies only when action implements {@link com.opensymphony.xwork2.Validateable}

*
    @@ -45,12 +45,12 @@ import java.lang.reflect.Method; *
  1. else if the action class have validateDo{MethodName}(), it will be invoked
  2. *
  3. no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is "true" (which is by default "true"), validate() will be invoked.
  4. *
- * + * * - * - * + * + * * - * + * * In PrepareInterceptor *

Applies only when action implements Preparable

*
    @@ -58,14 +58,14 @@ import java.lang.reflect.Method; *
  1. else if the action class have prepareDo(MethodName()}(), it will be invoked
  2. *
  3. 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.
  4. *
- * + * * - * + * * @author Philip Luppens * @author tm_jee */ public class PrefixMethodInvocationUtil { - + private static final Logger LOG = LogManager.getLogger(PrefixMethodInvocationUtil.class); private static final String DEFAULT_INVOCATION_METHODNAME = "execute"; @@ -76,7 +76,7 @@ public class PrefixMethodInvocationUtil { *

* This method will prefix actionInvocation's ActionProxy's * method with prefixes before invoking the prefixed method. - * Order of the prefixes is important, as this method will return once + * Order of the prefixes is important, as this method will return once * a prefixed method is found in the action class. *

* @@ -89,7 +89,7 @@ public class PrefixMethodInvocationUtil { * * *

- * Assuming actionInvocation.getProxy(),getMethod() returns "submit", + * Assuming actionInvocation.getProxy(),getMethod() returns "submit", * the order of invocation would be as follows:- *

* @@ -99,12 +99,12 @@ public class PrefixMethodInvocationUtil { * * *

- * If prepareSubmit() exists, it will be invoked and this method - * will return, prepareDoSubmit() will NOT be invoked. + * If prepareSubmit() exists, it will be invoked and this method + * will return, prepareDoSubmit() will NOT be invoked. *

* *

- * On the other hand, if prepareDoSubmit() does not exists, and + * On the other hand, if prepareDoSubmit() does not exists, and * prepareDoSubmit() exists, it will be invoked. *

* @@ -119,29 +119,32 @@ public class PrefixMethodInvocationUtil { */ public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException { Object action = actionInvocation.getAction(); - + String methodName = actionInvocation.getProxy().getMethod(); - + if (methodName == null) { - // if null returns (possible according to the docs), use the default execute + // if null returns (possible according to the docs), use the default execute methodName = DEFAULT_INVOCATION_METHODNAME; } - + Method method = getPrefixedMethod(prefixes, methodName, action); if (method != null) { method.invoke(action, new Object[0]); } } - - + + public static void invokePrefixMethod(org.apache.struts2.ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException { + invokePrefixMethod(ActionInvocation.adapt(actionInvocation), prefixes); + } + /** - * This method returns a {@link Method} in action. The method + * This method returns a {@link Method} in action. The method * returned is found by searching for method in action whose method name * is equals to the result of appending each prefixes * to methodName. Only the first method found will be returned, hence * the order of prefixes is important. If none is found this method * will return null. - * + * * @param prefixes the prefixes to prefix the methodName * @param methodName the method name to be prefixed with prefixes * @param action the action class of which the prefixed method is to be search for. @@ -162,7 +165,7 @@ public class PrefixMethodInvocationUtil { } return null; } - + /** *

* This method capitalized the first character of methodName. diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java index e4d5af634..43bb12c2b 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java @@ -96,7 +96,10 @@ import java.lang.reflect.InvocationTargetException; * @author Philip Luppens * @author tm_jee * @see com.opensymphony.xwork2.Preparable + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.PrepareInterceptor} instead. */ +@Deprecated public class PrepareInterceptor extends MethodFilterInterceptor { private static final long serialVersionUID = -5216969014510719786L; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java index ae2266be0..03473034d 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java @@ -36,7 +36,7 @@ import java.util.Map; * *

This interceptor only activates on actions that implement the {@link ScopedModelDriven} interface. If * detected, it will retrieve the model class from the configured scope, then provide it to the Action.

- * + * * * *

Interceptor parameters:

@@ -46,7 +46,7 @@ import java.util.Map; *
    * *
  • className - The model class name. Defaults to the class name of the object returned by the getModel() method.
  • - * + * *
  • name - The key to use when storing or retrieving the instance in a scope. Defaults to the model * class name.
  • * @@ -67,42 +67,45 @@ import java.util.Map; * *
      * 
    - * 
    + *
      * <-- Basic usage -->
      * <interceptor name="scopedModelDriven" class="com.opensymphony.interceptor.ScopedModelDrivenInterceptor" />
    - * 
    + *
      * <-- Using all available parameters -->
      * <interceptor name="gangsterForm" class="com.opensymphony.interceptor.ScopedModelDrivenInterceptor">
      *      <param name="scope">session</param>
      *      <param name="name">gangsterForm</param>
      *      <param name="className">com.opensymphony.example.GangsterForm</param>
      *  </interceptor>
    - * 
    + *
      * 
      * 
    + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ScopedModelDrivenInterceptor} instead. */ +@Deprecated public class ScopedModelDrivenInterceptor extends AbstractInterceptor { private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - + private static final String GET_MODEL = "getModel"; private String scope; private String name; private String className; private ObjectFactory objectFactory; - + @Inject public void setObjectFactory(ObjectFactory factory) { this.objectFactory = factory; } - + protected Object resolveModel(ObjectFactory factory, ActionContext actionContext, String modelClassName, String modelScope, String modelName) throws Exception { Object model; Map scopeMap = actionContext.getContextMap(); if ("session".equals(modelScope)) { scopeMap = actionContext.getSession(); } - + model = scopeMap.get(modelName); if (model == null) { model = factory.buildBean(modelClassName, null); @@ -120,7 +123,7 @@ public class ScopedModelDrivenInterceptor extends AbstractInterceptor { if (modelDriven.getModel() == null) { ActionContext ctx = ActionContext.getContext(); ActionConfig config = invocation.getProxy().getConfig(); - + String cName = className; if (cName == null) { try { @@ -162,5 +165,5 @@ public class ScopedModelDrivenInterceptor extends AbstractInterceptor { */ public void setScope(String scope) { this.scope = scope; - } + } } diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java index d560e1dd4..f5d4382ae 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java @@ -85,7 +85,10 @@ import java.util.Map; * * * @author Patrick Lightbody + * + * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.StaticParametersInterceptor} instead. */ +@Deprecated public class StaticParametersInterceptor extends AbstractInterceptor { private boolean parse; diff --git a/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java new file mode 100644 index 000000000..c5aa9fb3c --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java @@ -0,0 +1,293 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor; + +import com.opensymphony.xwork2.LocalizedTextProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.ParametersInterceptor; +import com.opensymphony.xwork2.security.AcceptedPatternsChecker; +import com.opensymphony.xwork2.security.ExcludedPatternsChecker; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.Evaluated; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionContext; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.dispatcher.HttpParameters; +import org.apache.struts2.dispatcher.Parameter; +import org.apache.struts2.util.ValueStack; + +import java.util.Map; + + +/** + * + * + * The aim of this Interceptor is to alias a named parameter to a different named parameter. By acting as the glue + * between actions sharing similar parameters (but with different names), it can help greatly with action chaining. + * + *

    Action's alias expressions should be in the form of #{ "name1" : "alias1", "name2" : "alias2" }. + * This means that assuming an action (or something else in the stack) has a value for the expression named name1 and the + * action this interceptor is applied to has a setter named alias1, alias1 will be set with the value from + * name1. + *

    + * + * + * + *

    Interceptor parameters:

    + * + * + * + *
      + * + *
    • aliasesKey (optional) - the name of the action parameter to look for the alias map (by default this is + * aliases).
    • + * + *
    + * + * + * + *

    Extending the interceptor:

    + * + * + * + * This interceptor does not have any known extension points. + * + * + * + *

    Example code:

    + * + *
    + * 
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <!-- The value for the foo parameter will be applied as if it were named bar -->
    + *     <param name="aliases">#{ 'foo' : 'bar' }</param>
    + *
    + *     <interceptor-ref name="alias"/>
    + *     <interceptor-ref name="basicStack"/>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + * 
    + * 
    + * + * @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/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/LoggingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java new file mode 100644 index 000000000..4536d462b --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; + + +/** + * + *

    + * This interceptor logs the start and end of the execution an action (in English-only, not internationalized). + *
    + * Note:: This interceptor will log at INFO level. + *

    + * + * + * + * There are no parameters for this interceptor. + * + * + * + * There are no obvious extensions to the existing interceptor. + * + * + *
    + * 
    + * <!-- prints out a message before and after the immediate action execution -->
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <interceptor-ref name="completeStack"/>
    + *     <interceptor-ref name="logger"/>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + *
    + * <!-- prints out a message before any more interceptors continue and after they have finished -->
    + * <action name="someAction" class="com.examples.SomeAction">
    + *     <interceptor-ref name="logger"/>
    + *     <interceptor-ref name="completeStack"/>
    + *     <result name="success">good_result.ftl</result>
    + * </action>
    + * 
    + * 
    + * + * @author Jason Carreira + */ +public class LoggingInterceptor extends AbstractInterceptor { + private static final Logger LOG = LogManager.getLogger(LoggingInterceptor.class); + private static final String FINISH_MESSAGE = "Finishing execution stack for action "; + private static final String START_MESSAGE = "Starting execution stack for action "; + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + logMessage(invocation, START_MESSAGE); + String result = invocation.invoke(); + logMessage(invocation, FINISH_MESSAGE); + return result; + } + + private void logMessage(ActionInvocation invocation, String baseMessage) { + if (LOG.isInfoEnabled()) { + StringBuilder message = new StringBuilder(baseMessage); + String namespace = invocation.getProxy().getNamespace(); + + if ((namespace != null) && (namespace.trim().length() > 0)) { + message.append(namespace).append("/"); + } + + message.append(invocation.getProxy().getActionName()); + LOG.info(message.toString()); + } + } + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java new file mode 100644 index 000000000..d2679ebd6 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor; + +import com.opensymphony.xwork2.util.CompoundRoot; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.ModelDriven; +import org.apache.struts2.interceptor.parameter.ParametersInterceptor; +import org.apache.struts2.util.ValueStack; + +/** + * + * + * Watches for {@link ModelDriven} actions and adds the action's model on to the value stack. + * + *

    Note: The ModelDrivenInterceptor must come before the both {@link StaticParametersInterceptor} and + * {@link ParametersInterceptor} if you want the parameters to be applied to the model. + *

    + *

    Note: The ModelDrivenInterceptor will only push the model into the stack when the + * model is not null, else it will be ignored. + *

    + * + * + * + *

    Interceptor parameters:

    + * + * + * + *
      + * + *
    • refreshModelBeforeResult - set to true if you want the model to be refreshed on the value stack after action + * execution and before result execution. The setting is useful if you want to change the model instance during the + * action execution phase, like when loading it from the data layer. This will result in getModel() being called at + * least twice.
    • + * + *
    + * + * + * + *

    Extending the interceptor:

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

    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/PrepareInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java new file mode 100644 index 000000000..a410c3b12 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor; + +import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.Preparable; + +import java.lang.reflect.InvocationTargetException; + +/** + * + * + * This interceptor calls prepare() on actions which implement + * {@link Preparable}. This interceptor is very useful for any situation where + * you need to ensure some logic runs before the actual execute method runs. + * + *

    + * A typical use of this is to run some logic to load an object from the + * database so that when parameters are set they can be set on this object. For + * example, suppose you have a User object with two properties: id and + * name. Provided that the params interceptor is called twice (once + * before and once after this interceptor), you can load the User object using + * the id property, and then when the second params interceptor is called the + * parameter user.name will be set, as desired, on the actual object + * loaded from the database. See the example for more info. + *

    + *

    + * Note: Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being + * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor] + * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature. + *

    + * + *

    + * Update: Added logic to execute a prepare{MethodName} and conditionally + * the a general prepare() Method, depending on the 'alwaysInvokePrepare' parameter/property + * which is by default true. This allows us to run some logic based on the method + * name we specify in the {@link org.apache.struts2.ActionProxy}. For example, you can specify a + * prepareInput() method that will be run before the invocation of the input method. + *

    + * + * + * + *

    Interceptor parameters:

    + * + * + * + *
      + * + *
    • alwaysInvokePrepare - Default to true. If true, prepare will always be invoked, + * otherwise it will not.
    • + * + *
    + * + * + * + *

    Extending the interceptor:

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

    Example code:

    + * + *
    + * 
    + * <!-- Calls the params interceptor twice, allowing you to
    + *       pre-load data for the second time parameters are set -->
    + *  <action name="someAction" class="com.examples.SomeAction">
    + *      <interceptor-ref name="params"/>
    + *      <interceptor-ref name="prepare"/>
    + *      <interceptor-ref name="basicStack"/>
    + *      <result name="success">good_result.ftl</result>
    + *  </action>
    + * 
    + * 
    + * + * @author Jason Carreira + * @author Philip Luppens + * @author tm_jee + * @see Preparable + */ +public class PrepareInterceptor extends MethodFilterInterceptor { + + private static final long serialVersionUID = -5216969014510719786L; + + private final static String PREPARE_PREFIX = "prepare"; + private final static String ALT_PREPARE_PREFIX = "prepareDo"; + + private boolean alwaysInvokePrepare = true; + private boolean firstCallPrepareDo = false; + + /** + * Sets if the prepare method should always be executed. + *

    + * Default is true. + *

    + * + * @param alwaysInvokePrepare if prepare should always be executed or not. + */ + public void setAlwaysInvokePrepare(String alwaysInvokePrepare) { + this.alwaysInvokePrepare = Boolean.parseBoolean(alwaysInvokePrepare); + } + + /** + * Sets if the prepareDoXXX method should be called first + *

    + * Default is false for backward compatibility + *

    + * @param firstCallPrepareDo if prepareDoXXX should be called first + */ + public void setFirstCallPrepareDo(String firstCallPrepareDo) { + this.firstCallPrepareDo = Boolean.parseBoolean(firstCallPrepareDo); + } + + @Override + public String doIntercept(ActionInvocation invocation) throws Exception { + Object action = invocation.getAction(); + + if (action instanceof Preparable) { + try { + String[] prefixes; + if (firstCallPrepareDo) { + prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX}; + } else { + prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX}; + } + PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes); + } + catch (InvocationTargetException e) { + /* + * The invoked method threw an exception and reflection wrapped it + * in an InvocationTargetException. + * If possible re-throw the original exception so that normal + * exception handling will take place. + */ + Throwable cause = e.getCause(); + if (cause instanceof Exception) { + throw (Exception) cause; + } else if(cause instanceof Error) { + throw (Error) cause; + } else { + /* + * The cause is not an Exception or Error (must be Throwable) so + * just re-throw the wrapped exception. + */ + throw e; + } + } + + if (alwaysInvokePrepare) { + ((Preparable) action).prepare(); + } + } + + return invocation.invoke(); + } + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java new file mode 100644 index 000000000..ddf426519 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.inject.Inject; +import org.apache.struts2.ActionContext; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * + * + * An interceptor that enables scoped model-driven actions. + * + *

    This interceptor only activates on actions that implement the {@link ScopedModelDriven} interface. If + * detected, it will retrieve the model class from the configured scope, then provide it to the Action.

    + * + * + * + *

    Interceptor parameters:

    + * + * + * + *
      + * + *
    • className - The model class name. Defaults to the class name of the object returned by the getModel() method.
    • + * + *
    • name - The key to use when storing or retrieving the instance in a scope. Defaults to the model + * class name.
    • + * + *
    • scope - The scope to store and retrieve the model. Defaults to 'request' but can also be 'session'.
    • + *
    + * + * + * + *

    Extending the interceptor:

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

    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..5b00ffca6 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor; + +import com.opensymphony.xwork2.LocalizedTextProvider; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.Parameterizable; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.ParametersInterceptor; +import com.opensymphony.xwork2.util.ClearableValueStack; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionContext; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.dispatcher.HttpParameters; +import org.apache.struts2.util.ValueStack; + +import java.util.Collections; +import java.util.Map; + +/** + * + * + * This interceptor populates the action with the static parameters defined in the action configuration. If the action + * implements {@link Parameterizable}, a map of the static parameters will be also be passed directly to the action. + * The static params will be added to the request params map, unless "merge" is set to false. + * + *

    Parameters are typically defined with <param> elements within xwork.xml.

    + * + * + * + *

    Interceptor parameters:

    + * + * + * + *
      + * + *
    • None
    • + * + *
    + * + * + * + *

    Extending the interceptor:

    + * + * + * + *

    There are no extension points to this interceptor.

    + * + * + * + *

    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()); + } +} From 243244997590e0c1bcca8de3db82dfa1d7933ec7 Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Mon, 21 Oct 2024 18:51:15 +1100 Subject: [PATCH 6/6] WW-3714 Update StrutsResultSupport to allow overriding new signature --- .../struts2/result/StrutsResultSupport.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) 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)); + } }