You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2015/06/17 23:09:40 UTC
[40/57] [partial] struts git commit: Merges xwork packages into struts
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
new file mode 100644
index 0000000..4266236
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/Interceptor.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+
+import java.io.Serializable;
+
+
+/**
+ * <!-- START SNIPPET: introduction -->
+ * <p/>
+ * An interceptor is a stateless class that follows the interceptor pattern, as
+ * found in {@link javax.servlet.Filter} and in AOP languages.
+ * <p/>
+ * <p/>
+ * <p/>
+ * Interceptors are objects that dynamically intercept Action invocations.
+ * They provide the developer with the opportunity to define code that can be executed
+ * before and/or after the execution of an action. They also have the ability
+ * to prevent an action from executing. Interceptors provide developers a way to
+ * encapulate common functionality in a re-usable form that can be applied to
+ * one or more Actions.
+ * <p/>
+ * <p/>
+ * <p/>
+ * Interceptors <b>must</b> be stateless and not assume that a new instance will be created for each request or Action.
+ * Interceptors may choose to either short-circuit the {@link ActionInvocation} execution and return a return code
+ * (such as {@link com.opensymphony.xwork2.Action#SUCCESS}), or it may choose to do some processing before
+ * and/or after delegating the rest of the procesing using {@link ActionInvocation#invoke()}.
+ * <p/>
+ * <!-- END SNIPPET: introduction -->
+ * <p/>
+ * <p/>
+ * <p/>
+ * <!-- START SNIPPET: parameterOverriding -->
+ * <p/>
+ * Interceptor's parameter could be overriden through the following ways :-
+ * <p/>
+ * <p/>
+ * <p/>
+ * <b>Method 1:</b>
+ * <pre>
+ * <action name="myAction" class="myActionClass">
+ * <interceptor-ref name="exception"/>
+ * <interceptor-ref name="alias"/>
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="servletConfig"/>
+ * <interceptor-ref name="prepare"/>
+ * <interceptor-ref name="i18n"/>
+ * <interceptor-ref name="chain"/>
+ * <interceptor-ref name="modelDriven"/>
+ * <interceptor-ref name="fileUpload"/>
+ * <interceptor-ref name="staticParams"/>
+ * <interceptor-ref name="params"/>
+ * <interceptor-ref name="conversionError"/>
+ * <interceptor-ref name="validation">
+ * <param name="excludeMethods">myValidationExcudeMethod</param>
+ * </interceptor-ref>
+ * <interceptor-ref name="workflow">
+ * <param name="excludeMethods">myWorkflowExcludeMethod</param>
+ * </interceptor-ref>
+ * </action>
+ * </pre>
+ * <p/>
+ * <b>Method 2:</b>
+ * <pre>
+ * <action name="myAction" class="myActionClass">
+ * <interceptor-ref name="defaultStack">
+ * <param name="validation.excludeMethods">myValidationExcludeMethod</param>
+ * <param name="workflow.excludeMethods">myWorkflowExcludeMethod</param>
+ * </interceptor-ref>
+ * </action>
+ * </pre>
+ * <p/>
+ * <p/>
+ * <p/>
+ * In the first method, the whole default stack is copied and the parameter then
+ * changed accordingly.
+ * <p/>
+ * <p/>
+ * <p/>
+ * In the second method, the 'interceptor-ref' refer to an existing
+ * interceptor-stack, namely defaultStack in this example, and override the validator
+ * and workflow interceptor excludeMethods typically in this case. Note that in the
+ * 'param' tag, the name attribute contains a dot (.) the word before the dot(.)
+ * specifies the interceptor name whose parameter is to be overridden and the word after
+ * the dot (.) specifies the parameter itself. Essetially it is as follows :-
+ * <p/>
+ * <pre>
+ * <interceptor-name>.<parameter-name>
+ * </pre>
+ * <p/>
+ * <b>Note</b> also that in this case the 'interceptor-ref' name attribute
+ * is used to indicate an interceptor stack which makes sense as if it is referring
+ * to the interceptor itself it would be just using Method 1 describe above.
+ * <p/>
+ * <!-- END SNIPPET: parameterOverriding -->
+ * <p/>
+ * <p/>
+ * <b>Nested Interceptor param overriding</b>
+ * <p/>
+ * <!-- START SNIPPET: nestedParameterOverriding -->
+ * <p/>
+ * Interceptor stack parameter overriding could be nested into as many level as possible, though it would
+ * be advisable not to nest it too deep as to avoid confusion, For example,
+ * <pre>
+ * <interceptor name="interceptor1" class="foo.bar.Interceptor1" />
+ * <interceptor name="interceptor2" class="foo.bar.Interceptor2" />
+ * <interceptor name="interceptor3" class="foo.bar.Interceptor3" />
+ * <interceptor name="interceptor4" class="foo.bar.Interceptor4" />
+ * <interceptor-stack name="stack1">
+ * <interceptor-ref name="interceptor1" />
+ * </interceptor-stack>
+ * <interceptor-stack name="stack2">
+ * <interceptor-ref name="intercetor2" />
+ * <interceptor-ref name="stack1" />
+ * </interceptor-stack>
+ * <interceptor-stack name="stack3">
+ * <interceptor-ref name="interceptor3" />
+ * <interceptor-ref name="stack2" />
+ * </interceptor-stack>
+ * <interceptor-stack name="stack4">
+ * <interceptor-ref name="interceptor4" />
+ * <interceptor-ref name="stack3" />
+ * </interceptor-stack>
+ * </pre>
+ * Assuming the interceptor has the following properties
+ * <table border="1" width="100%">
+ * <tr>
+ * <td>Interceptor</td>
+ * <td>property</td>
+ * </tr>
+ * <tr>
+ * <td>Interceptor1</td>
+ * <td>param1</td>
+ * </tr>
+ * <tr>
+ * <td>Interceptor2</td>
+ * <td>param2</td>
+ * </tr>
+ * <tr>
+ * <td>Interceptor3</td>
+ * <td>param3</td>
+ * </tr>
+ * <tr>
+ * <td>Interceptor4</td>
+ * <td>param4</td>
+ * </tr>
+ * </table>
+ * We could override them as follows :-
+ * <pre>
+ * <action ... >
+ * <!-- to override parameters of interceptor located directly in the stack -->
+ * <interceptor-ref name="stack4">
+ * <param name="interceptor4.param4"> ... </param>
+ * </interceptor-ref>
+ * </action>
+ * <p/>
+ * <action ... >
+ * <!-- to override parameters of interceptor located under nested stack -->
+ * <interceptor-ref name="stack4">
+ * <param name="stack3.interceptor3.param3"> ... </param>
+ * <param name="stack3.stack2.interceptor2.param2"> ... </param>
+ * <param name="stack3.stack2.stack1.interceptor1.param1"> ... </param>
+ * </interceptor-ref>
+ * </action>
+ * </pre>
+ * <p/>
+ * <!-- END SNIPPET: nestedParameterOverriding -->
+ *
+ * @author Jason Carreira
+ * @author tmjee
+ * @version $Date$ $Id$
+ */
+public interface Interceptor extends Serializable {
+
+ /**
+ * Called to let an interceptor clean up any resources it has allocated.
+ */
+ void destroy();
+
+ /**
+ * Called after an interceptor is created, but before any requests are processed using
+ * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving
+ * the Interceptor a chance to initialize any needed resources.
+ */
+ void init();
+
+ /**
+ * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the
+ * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code.
+ *
+ * @param invocation the action invocation
+ * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself.
+ * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}.
+ */
+ String intercept(ActionInvocation invocation) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
new file mode 100644
index 0000000..c82532f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ * This interceptor logs the start and end of the execution an action (in English-only, not internationalized).
+ * <br/>
+ * <b>Note:</b>: This interceptor will log at <tt>INFO</tt> level.
+ * <p/>
+ * <!-- END SNIPPET: description -->
+ *
+ * <!-- START SNIPPET: parameters -->
+ * There are no parameters for this interceptor.
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <!-- START SNIPPET: extending -->
+ * There are no obvious extensions to the existing interceptor.
+ * <!-- END SNIPPET: extending -->
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * <!-- 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>
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @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());
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java
new file mode 100644
index 0000000..a46cf34
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptor.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Collections;
+import java.util.Set;
+
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ *
+ * MethodFilterInterceptor is an abstract <code>Interceptor</code> used as
+ * a base class for interceptors that will filter execution based on method
+ * names according to specified included/excluded method lists.
+ *
+ * <p/>
+ *
+ * Settable parameters are as follows:
+ *
+ * <ul>
+ * <li>excludeMethods - method names to be excluded from interceptor processing</li>
+ * <li>includeMethods - method names to be included in interceptor processing</li>
+ * </ul>
+ *
+ * <p/>
+ *
+ * <b>NOTE:</b> If method name are available in both includeMethods and
+ * excludeMethods, it will be considered as an included method:
+ * includeMethods takes precedence over excludeMethods.
+ *
+ * <p/>
+ *
+ * Interceptors that extends this capability include:
+ *
+ * <ul>
+ * <li>TokenInterceptor</li>
+ * <li>TokenSessionStoreInterceptor</li>
+ * <li>DefaultWorkflowInterceptor</li>
+ * <li>ValidationInterceptor</li>
+ * </ul>
+ *
+ * <!-- END SNIPPET: javadoc -->
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ * @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
+ *
+ * @version $Date$ $Id$
+ */
+public abstract class MethodFilterInterceptor extends AbstractInterceptor {
+ protected transient Logger log = LogManager.getLogger(getClass());
+
+ protected Set<String> excludeMethods = Collections.emptySet();
+ protected Set<String> includeMethods = Collections.emptySet();
+
+ public void setExcludeMethods(String excludeMethods) {
+ this.excludeMethods = TextParseUtil.commaDelimitedStringToSet(excludeMethods);
+ }
+
+ public Set<String> getExcludeMethodsSet() {
+ return excludeMethods;
+ }
+
+ public void setIncludeMethods(String includeMethods) {
+ this.includeMethods = TextParseUtil.commaDelimitedStringToSet(includeMethods);
+ }
+
+ public Set<String> 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
+ */
+ protected abstract String doIntercept(ActionInvocation invocation) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java
new file mode 100644
index 0000000..987d782
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/MethodFilterInterceptorUtil.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 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
+ */
+public class MethodFilterInterceptorUtil {
+
+ /**
+ * Static method to decide if the specified <code>method</code> should be
+ * apply (not filtered) depending on the set of <code>excludeMethods</code> and
+ * <code>includeMethods</code>.
+ *
+ * <ul>
+ * <li>
+ * <code>includeMethods</code> takes precedence over <code>excludeMethods</code>
+ * </li>
+ * </ul>
+ * <b>Note:</b> 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 <tt>true</tt> if the method should be applied.
+ */
+ public static boolean applyMethod(Set<String> excludeMethods, Set<String> 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<String, String> 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<String, String> 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 <code>excludeMethods</code>
+ * and <code>includeMethods</code> 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 <tt>true</tt> if the method should be applied.
+ */
+ public static boolean applyMethod(String excludeMethods, String includeMethods, String method) {
+ Set<String> includeMethodsSet = TextParseUtil.commaDelimitedStringToSet(includeMethods == null? "" : includeMethods);
+ Set<String> excludeMethodsSet = TextParseUtil.commaDelimitedStringToSet(excludeMethods == null? "" : excludeMethods);
+
+ return applyMethod(excludeMethodsSet, includeMethodsSet, method);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
new file mode 100644
index 0000000..06a91b5
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ModelDriven;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.ValueStack;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * Watches for {@link ModelDriven} actions and adds the action's model on to the value stack.
+ *
+ * <p/> <b>Note:</b> The ModelDrivenInterceptor must come before the both {@link StaticParametersInterceptor} and
+ * {@link ParametersInterceptor} if you want the parameters to be applied to the model.
+ *
+ * <p/> <b>Note:</b> The ModelDrivenInterceptor will only push the model into the stack when the
+ * model is not null, else it will be ignored.
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Interceptor parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>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.</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Extending the interceptor:</u>
+ *
+ * <p/>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * There are no known extension points to this interceptor.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p/> <u>Example code:</u>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="modelDriven"/>
+ * <interceptor-ref name="basicStack"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @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 = null;
+ 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
+ for (Object item : root) {
+ if (item.equals(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);
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java
new file mode 100644
index 0000000..8db8fbc
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/NoParameters.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+
+/**
+ * Marker interface to incidate no auto setting of parameters.
+ * <p/>
+ * This marker interface should be implemented by actions that do not want any
+ * request parameters set on them automatically (by the ParametersInterceptor).
+ * This may be useful if one is using the action tag and want to supply
+ * the parameters to the action manually using the param tag.
+ * It may also be useful if one for security reasons wants to make sure that
+ * parameters cannot be set by malicious users.
+ *
+ * @author Dick Zetterberg (dick@transitor.se)
+ */
+public interface NoParameters {
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java
new file mode 100644
index 0000000..46767d5
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterFilterInterceptor.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * The Parameter Filter Interceptor blocks parameters from getting
+ * to the rest of the stack or your action. You can use multiple
+ * parameter filter interceptors for a given action, so, for example,
+ * you could use one in your default stack that filtered parameters
+ * you wanted blocked from every action and those you wanted blocked
+ * from an individual action you could add an additional interceptor
+ * for each action.
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ * <li>allowed - a comma delimited list of parameter prefixes
+ * that are allowed to pass to the action</li>
+ * <li>blocked - a comma delimited list of parameter prefixes
+ * that are not allowed to pass to the action</li>
+ * <li>defaultBlock - boolean (default to false) whether by
+ * default a given parameter is blocked. If true, then a parameter
+ * must have a prefix in the allowed list in order to be able
+ * to pass to the action
+ * </ul>
+ *
+ * <p>The way parameters are filtered for the least configuration is that
+ * if a string is in the allowed or blocked lists, then any parameter
+ * that is a member of the object represented by the parameter is allowed
+ * or blocked respectively.</p>
+ *
+ * <p>For example, if the parameters are:
+ * <ul>
+ * <li>blocked: person,person.address.createDate,personDao</li>
+ * <li>allowed: person.address</li>
+ * <li>defaultBlock: false</li>
+ * </ul>
+ * <br>
+ * The parameters person.name, person.phoneNum etc would be blocked
+ * because 'person' is in the blocked list. However, person.address.street
+ * and person.address.city would be allowed because person.address is
+ * in the allowed list (the longer string determines permissions).</p>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <!-- START SNIPPET: extending -->
+ * There are no known extension points to this interceptor.
+ * <!-- END SNIPPET: extending -->
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * <interceptors>
+ * ...
+ * <interceptor name="parameterFilter" class="com.opensymphony.xwork2.interceptor.ParameterFilterInterceptor"/>
+ * ...
+ * </interceptors>
+ *
+ * <action ....>
+ * ...
+ * <interceptor-ref name="parameterFilter">
+ * <param name="blocked">person,person.address.createDate,personDao</param>
+ * </interceptor-ref>
+ * ...
+ * </action>
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Gabe
+ */
+public class ParameterFilterInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ParameterFilterInterceptor.class);
+
+ private Collection<String> allowed;
+ private Collection<String> blocked;
+ private Map<String, Boolean> includesExcludesMap;
+ private boolean defaultBlock = false;
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+
+ Map<String, Object> parameters = invocation.getInvocationContext().getParameters();
+ HashSet<String> paramsToRemove = new HashSet<>();
+
+ Map<String, Boolean> includesExcludesMap = getIncludesExcludesMap();
+
+ for (String param : parameters.keySet()) {
+ boolean currentAllowed = !isDefaultBlock();
+
+ for (String currRule : includesExcludesMap.keySet()) {
+ if (param.startsWith(currRule)
+ && (param.length() == currRule.length()
+ || isPropertySeparator(param.charAt(currRule.length())))) {
+ currentAllowed = includesExcludesMap.get(currRule).booleanValue();
+ }
+ }
+ if (!currentAllowed) {
+ paramsToRemove.add(param);
+ }
+ }
+
+ LOG.debug("Params to remove: {}", paramsToRemove);
+
+ for (Object aParamsToRemove : paramsToRemove) {
+ parameters.remove(aParamsToRemove);
+ }
+
+ return invocation.invoke();
+ }
+
+ /**
+ * Tests if the given char is a property separator char <code>.([</code>.
+ *
+ * @param c the char
+ * @return <tt>true</tt>, if char is property separator, <tt>false</tt> otherwise.
+ */
+ private static boolean isPropertySeparator(char c) {
+ return c == '.' || c == '(' || c == '[';
+ }
+
+ private Map<String, Boolean> getIncludesExcludesMap() {
+ if (this.includesExcludesMap == null) {
+ this.includesExcludesMap = new TreeMap<>();
+
+ if (getAllowedCollection() != null) {
+ for (String e : getAllowedCollection()) {
+ this.includesExcludesMap.put(e, Boolean.TRUE);
+ }
+ }
+ if (getBlockedCollection() != null) {
+ for (String b : getBlockedCollection()) {
+ this.includesExcludesMap.put(b, Boolean.FALSE);
+ }
+ }
+ }
+
+ return this.includesExcludesMap;
+ }
+
+ /**
+ * @return Returns the defaultBlock.
+ */
+ public boolean isDefaultBlock() {
+ return defaultBlock;
+ }
+
+ /**
+ * @param defaultExclude The defaultExclude to set.
+ */
+ public void setDefaultBlock(boolean defaultExclude) {
+ this.defaultBlock = defaultExclude;
+ }
+
+ /**
+ * @return Returns the blocked.
+ */
+ public Collection<String> getBlockedCollection() {
+ return blocked;
+ }
+
+ /**
+ * @param blocked The blocked to set.
+ */
+ public void setBlockedCollection(Collection<String> blocked) {
+ this.blocked = blocked;
+ }
+
+ /**
+ * @param blocked The blocked paramters as comma separated String.
+ */
+ public void setBlocked(String blocked) {
+ setBlockedCollection(asCollection(blocked));
+ }
+
+ /**
+ * @return Returns the allowed.
+ */
+ public Collection<String> getAllowedCollection() {
+ return allowed;
+ }
+
+ /**
+ * @param allowed The allowed to set.
+ */
+ public void setAllowedCollection(Collection<String> allowed) {
+ this.allowed = allowed;
+ }
+
+ /**
+ * @param allowed The allowed paramters as comma separated String.
+ */
+ public void setAllowed(String allowed) {
+ setAllowedCollection(asCollection(allowed));
+ }
+
+ /**
+ * Return a collection from the comma delimited String.
+ *
+ * @param commaDelim the comma delimited String.
+ * @return A collection from the comma delimited String. Returns <tt>null</tt> if the string is empty.
+ */
+ private Collection<String> asCollection(String commaDelim) {
+ if (StringUtils.isBlank(commaDelim)) {
+ return null;
+ }
+ return TextParseUtil.commaDelimitedStringToSet(commaDelim);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java
new file mode 100644
index 0000000..46ba188
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterNameAware.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ * This interface is implemented by actions that want to declare acceptable parameters. Works in conjunction with {@link
+ * ParametersInterceptor}. For example, actions may want to create a whitelist of parameters they will accept or a
+ * blacklist of paramters they will reject to prevent clients from setting other unexpected (and possibly dangerous)
+ * parameters.
+ * <!-- END SNIPPET: javadoc -->
+ *
+ * @author Bob Lee (crazybob@google.com)
+ */
+public interface ParameterNameAware {
+
+ /**
+ * Tests if the the action will accept the parameter with the given name.
+ *
+ * @param parameterName the parameter name
+ * @return <tt> if accepted, <tt>false</tt> otherwise
+ */
+ boolean acceptableParameterName(String parameterName);
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
new file mode 100644
index 0000000..68bb154
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * 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.
+ * <!-- END SNIPPET: description -->
+ *
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <ul>
+ * <li>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</li>
+ * <li>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</li>
+ * </ul>
+ * <!-- END SNIPPET: parameters -->
+ *
+ *
+ * <!-- START SNIPPET: extending -->
+ * No intended extension point
+ * <!-- END SNIPPET: extending -->
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ *
+ * <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>
+ *
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ *
+ * @author martin.gilday
+ */
+public class ParameterRemoverInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class);
+
+ private static final long serialVersionUID = 1;
+
+ private Set<String> paramNames = Collections.emptySet();
+ private Set<String> paramValues = Collections.emptySet();
+
+
+ /**
+ * Decide if the parameter should be removed from the parameter map based on
+ * <code>paramNames</code> and <code>paramValues</code>.
+ *
+ * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor
+ */
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ if (!(invocation.getAction() instanceof NoParameters)
+ && (null != this.paramNames)) {
+ ActionContext ac = invocation.getInvocationContext();
+ final Map<String, Object> parameters = ac.getParameters();
+
+ if (parameters != null) {
+ for (String removeName : paramNames) {
+ // see if the field is in the parameter map
+ if (parameters.containsKey(removeName)) {
+
+ try {
+ String[] values = (String[]) parameters.get(removeName);
+ String value = values[0];
+ if (null != value && this.paramValues.contains(value)) {
+ parameters.remove(removeName);
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to convert parameter to string", e);
+ }
+ }
+ }
+ }
+ }
+ return invocation.invoke();
+ }
+
+ /**
+ * Allows <code>paramNames</code> 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 <code>paramValues</code> 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);
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
new file mode 100644
index 0000000..8496610
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.ValidationAware;
+import com.opensymphony.xwork2.XWorkConstants;
+import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
+import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
+import com.opensymphony.xwork2.util.*;
+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 java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ * This interceptor sets all parameters on the value stack.
+ *
+ * This interceptor gets all parameters from {@link ActionContext#getParameters()} and sets them on the value stack by
+ * calling {@link ValueStack#setValue(String, Object)}, typically resulting in the values submitted in a form
+ * request being applied to an action in the value stack. Note that the parameter map must contain a String key and
+ * often containers a String[] for the value.
+ *
+ * The interceptor takes one parameter named 'ordered'. When set to true action properties are guaranteed to be
+ * set top-down which means that top action's properties are set first. Then it's subcomponents properties are set.
+ * The reason for this order is to enable a 'factory' pattern. For example, let's assume that one has an action
+ * that contains a property named 'modelClass' that allows to choose what is the underlying implementation of model.
+ * By assuring that modelClass property is set before any model properties are set, it's possible to choose model
+ * implementation during action.setModelClass() call. Similiarily it's possible to use action.setPrimaryKey()
+ * property set call to actually load the model class from persistent storage. Without any assumption on parameter
+ * order you have to use patterns like 'Preparable'.
+ *
+ * Because parameter names are effectively OGNL statements, it is important that security be taken in to account.
+ * This interceptor will not apply any values in the parameters map if the expression contains an assignment (=),
+ * multiple expressions (,), or references any objects in the context (#). This is all done in the {@link
+ * #acceptableName(String)} method. In addition to this method, if the action being invoked implements the {@link
+ * ParameterNameAware} interface, the action will be consulted to determine if the parameter should be set.
+ *
+ * In addition to these restrictions, a flag ({@link ReflectionContextState#DENY_METHOD_EXECUTION}) is set such that
+ * no methods are allowed to be invoked. That means that any expression such as <i>person.doSomething()</i> or
+ * <i>person.getName()</i> will be explicitely forbidden. This is needed to make sure that your application is not
+ * exposed to attacks by malicious users.
+ *
+ * While this interceptor is being invoked, a flag ({@link ReflectionContextState#CREATE_NULL_OBJECTS}) is turned
+ * on to ensure that any null reference is automatically created - if possible. See the type conversion documentation
+ * and the {@link InstantiatingNullHandler} javadocs for more information.
+ *
+ * Finally, a third flag ({@link XWorkConverter#REPORT_CONVERSION_ERRORS}) is set that indicates any errors when
+ * converting the the values to their final data type (String[] -> int) an unrecoverable error occured. With this
+ * flag set, the type conversion errors will be reported in the action context. See the type conversion documentation
+ * and the {@link XWorkConverter} javadocs for more information.
+ *
+ * If you are looking for detailed logging information about your parameters, turn on DEBUG level logging for this
+ * interceptor. A detailed log of all the parameter keys and values will be reported.
+ *
+ * <b>Note:</b> 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.
+ * <!-- END SNIPPET: description -->
+ *
+ * <u>Interceptor parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ * <li>ordered - set to true if you want the top-down property setter behaviour</li>
+ * <li>acceptParamNames - a comma delimited list of regular expressions to describe a whitelist of accepted parameter names.
+ * Don't change the default unless you know what you are doing in terms of security implications</li>
+ * <li>excludeParams - a comma delimited list of regular expressions to describe a blacklist of not allowed parameter names</li>
+ * <li>paramNameMaxLength - the maximum length of parameter names; parameters with longer names will be ignored; the default is 100 characters</li>
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <u>Extending the interceptor:</u>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your
+ * actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend
+ * this interceptor and override the {@link #acceptableName(String)} method.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ *
+ * <!-- START SNIPPET: extending-warning -->
+ * Using {@link ParameterNameAware} could be dangerous as {@link ParameterNameAware#acceptableParameterName(String)} takes precedence
+ * over ParametersInterceptor which means if ParametersInterceptor excluded given parameter name you can accept it with
+ * {@link ParameterNameAware#acceptableParameterName(String)}.
+ *
+ * The best idea is to define very tight restrictions with ParametersInterceptor and relax them per action with
+ * {@link ParameterNameAware#acceptableParameterName(String)}
+ * <!-- END SNIPPET: extending-warning -->
+ *
+ *
+ * <u>Example code:</u>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <interceptor-ref name="params"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Patrick Lightbody
+ */
+public class ParametersInterceptor extends MethodFilterInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ParametersInterceptor.class);
+
+ protected static final int PARAM_NAME_MAX_LENGTH = 100;
+
+ private int paramNameMaxLength = PARAM_NAME_MAX_LENGTH;
+ private boolean devMode = false;
+
+ protected boolean ordered = false;
+
+ private ValueStackFactory valueStackFactory;
+ private ExcludedPatternsChecker excludedPatterns;
+ private AcceptedPatternsChecker acceptedPatterns;
+
+ @Inject
+ public void setValueStackFactory(ValueStackFactory valueStackFactory) {
+ this.valueStackFactory = valueStackFactory;
+ }
+
+ @Inject(XWorkConstants.DEV_MODE)
+ public void setDevMode(String mode) {
+ this.devMode = BooleanUtils.toBoolean(mode);
+ }
+
+ @Inject
+ public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
+ this.excludedPatterns = excludedPatterns;
+ }
+
+ @Inject
+ public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
+ this.acceptedPatterns = acceptedPatterns;
+ }
+
+ /**
+ * If the param name exceeds the configured maximum length it will not be
+ * accepted.
+ *
+ * @param paramNameMaxLength Maximum length of param names
+ */
+ public void setParamNameMaxLength(int paramNameMaxLength) {
+ this.paramNameMaxLength = paramNameMaxLength;
+ }
+
+ static private int countOGNLCharacters(String s) {
+ int count = 0;
+ for (int i = s.length() - 1; i >= 0; i--) {
+ char c = s.charAt(i);
+ if (c == '.' || c == '[') count++;
+ }
+ return count;
+ }
+
+ /**
+ * Compares based on number of '.' and '[' characters (fewer is higher)
+ */
+ static final Comparator<String> rbCollator = new Comparator<String>() {
+ public int compare(String s1, String s2) {
+ int l1 = countOGNLCharacters(s1),
+ l2 = countOGNLCharacters(s2);
+ return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2));
+ }
+
+ };
+
+ @Override
+ public String doIntercept(ActionInvocation invocation) throws Exception {
+ Object action = invocation.getAction();
+ if (!(action instanceof NoParameters)) {
+ ActionContext ac = invocation.getInvocationContext();
+ final Map<String, Object> parameters = retrieveParameters(ac);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Setting params {}", getParameterLogMap(parameters));
+ }
+
+ if (parameters != null) {
+ Map<String, Object> contextMap = ac.getContextMap();
+ try {
+ ReflectionContextState.setCreatingNullObjects(contextMap, true);
+ ReflectionContextState.setDenyMethodExecution(contextMap, true);
+ ReflectionContextState.setReportingConversionErrors(contextMap, true);
+
+ ValueStack stack = ac.getValueStack();
+ setParameters(action, stack, parameters);
+ } finally {
+ ReflectionContextState.setCreatingNullObjects(contextMap, false);
+ ReflectionContextState.setDenyMethodExecution(contextMap, false);
+ ReflectionContextState.setReportingConversionErrors(contextMap, false);
+ }
+ }
+ }
+ return invocation.invoke();
+ }
+
+ /**
+ * Gets the parameter map to apply from wherever appropriate
+ *
+ * @param ac The action context
+ * @return The parameter map to apply
+ */
+ protected Map<String, Object> retrieveParameters(ActionContext ac) {
+ return ac.getParameters();
+ }
+
+
+ /**
+ * Adds the parameters into context's ParameterMap
+ *
+ * @param ac The action context
+ * @param newParams The parameter map to apply
+ * <p/>
+ * In this class this is a no-op, since the parameters were fetched from the same location.
+ * In subclasses both retrieveParameters() and addParametersToContext() should be overridden.
+ */
+ protected void addParametersToContext(ActionContext ac, Map<String, Object> newParams) {
+ }
+
+ protected void setParameters(final Object action, ValueStack stack, final Map<String, Object> parameters) {
+ Map<String, Object> params;
+ Map<String, Object> acceptableParameters;
+ if (ordered) {
+ params = new TreeMap<>(getOrderedComparator());
+ acceptableParameters = new TreeMap<>(getOrderedComparator());
+ params.putAll(parameters);
+ } else {
+ params = new TreeMap<>(parameters);
+ acceptableParameters = new TreeMap<>();
+ }
+
+ for (Map.Entry<String, Object> entry : params.entrySet()) {
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ if (isAcceptableParameter(name, action) && isAcceptableValue(value)) {
+ acceptableParameters.put(name, entry.getValue());
+ }
+ }
+
+ ValueStack newStack = valueStackFactory.createValueStack(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<String, Object> context = newStack.getContext();
+ ReflectionContextState.setCreatingNullObjects(context, true);
+ ReflectionContextState.setDenyMethodExecution(context, true);
+ ReflectionContextState.setReportingConversionErrors(context, true);
+
+ //keep locale from original context
+ context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE));
+ }
+
+ boolean memberAccessStack = newStack instanceof MemberAccessValueStack;
+ if (memberAccessStack) {
+ //block or allow access to properties
+ //see WW-2761 for more details
+ MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack;
+ accessValueStack.setAcceptProperties(acceptedPatterns.getAcceptedPatterns());
+ accessValueStack.setExcludeProperties(excludedPatterns.getExcludedPatterns());
+ }
+
+ for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) {
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ try {
+ newStack.setParameter(name, value);
+ } catch (RuntimeException e) {
+ if (devMode) {
+ notifyDeveloperParameterException(action, name, e.getMessage());
+ }
+ }
+ }
+
+ if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null))
+ stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS));
+
+ addParametersToContext(ActionContext.getContext(), acceptableParameters);
+ }
+
+ protected void notifyDeveloperParameterException(Object action, String property, String message) {
+ String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification",
+ ActionContext.getContext().getLocale(), "Developer Notification:\n{0}",
+ new Object[]{
+ "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message
+ }
+ );
+ LOG.error(developerNotification);
+ // see https://issues.apache.org/jira/browse/WW-4066
+ if (action instanceof ValidationAware) {
+ Collection<String> messages = ((ValidationAware) action).getActionMessages();
+ messages.add(message);
+ ((ValidationAware) action).setActionMessages(messages);
+ }
+ }
+
+ /**
+ * Checks if name of parameter can be accepted or thrown away
+ *
+ * @param name parameter name
+ * @param action current action
+ * @return true if parameter is accepted
+ */
+ protected boolean isAcceptableParameter(String name, Object action) {
+ ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware) ? (ParameterNameAware) action : null;
+ return acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name));
+ }
+
+ /**
+ * Checks if given value doesn't match global excluded patterns to avoid passing malicious code
+ *
+ * @param value incoming parameter's value
+ * @return true if value is safe
+ *
+ * FIXME: can be removed when parameters won't be represented as simple Strings
+ */
+ protected boolean isAcceptableValue(Object value) {
+ if (value == null) {
+ return true;
+ }
+ Object[] values;
+ if (value.getClass().isArray()) {
+ values = (Object[]) value;
+ } else {
+ values = new Object[] { value };
+ }
+ boolean result = true;
+ for (Object obj : values) {
+ if (isExcluded(String.valueOf(obj))) {
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets an instance of the comparator to use for the ordered sorting. Override this
+ * method to customize the ordering of the parameters as they are set to the
+ * action.
+ *
+ * @return A comparator to sort the parameters
+ */
+ protected Comparator<String> getOrderedComparator() {
+ return rbCollator;
+ }
+
+ protected String getParameterLogMap(Map<String, Object> parameters) {
+ if (parameters == null) {
+ return "NONE";
+ }
+
+ StringBuilder logEntry = new StringBuilder();
+ for (Map.Entry entry : parameters.entrySet()) {
+ logEntry.append(String.valueOf(entry.getKey()));
+ logEntry.append(" => ");
+ if (entry.getValue() instanceof Object[]) {
+ Object[] valueArray = (Object[]) entry.getValue();
+ logEntry.append("[ ");
+ if (valueArray.length > 0 ) {
+ for (int indexA = 0; indexA < (valueArray.length - 1); indexA++) {
+ Object valueAtIndex = valueArray[indexA];
+ logEntry.append(String.valueOf(valueAtIndex));
+ logEntry.append(", ");
+ }
+ logEntry.append(String.valueOf(valueArray[valueArray.length - 1]));
+ }
+ logEntry.append(" ] ");
+ } else {
+ logEntry.append(String.valueOf(entry.getValue()));
+ }
+ }
+
+ return logEntry.toString();
+ }
+
+ protected boolean acceptableName(String name) {
+ boolean accepted = isWithinLengthLimit(name) && !isExcluded(name) && isAccepted(name);
+ if (devMode && accepted) { // notify only when in devMode
+ LOG.debug("Parameter [{}] was accepted and will be appended to action!", name);
+ }
+ return accepted;
+ }
+
+ protected boolean isWithinLengthLimit( String name ) {
+ boolean matchLength = name.length() <= paramNameMaxLength;
+ if (!matchLength) {
+ notifyDeveloper("Parameter [{}] is too long, allowed length is [{}]", name, String.valueOf(paramNameMaxLength));
+ }
+ return matchLength;
+ }
+
+ protected boolean isAccepted(String paramName) {
+ AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
+ if (result.isAccepted()) {
+ return true;
+ }
+ notifyDeveloper("Parameter [{}] didn't match accepted pattern [{}]!", paramName, result.getAcceptedPattern());
+ return false;
+ }
+
+ protected boolean isExcluded(String paramName) {
+ ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
+ if (result.isExcluded()) {
+ notifyDeveloper("Parameter [{}] matches excluded pattern [{}]!", paramName, result.getExcludedPattern());
+ return true;
+ }
+ return false;
+ }
+
+ private void notifyDeveloper(String message, String... parameters) {
+ if (devMode) {
+ LOG.warn(message, parameters);
+ } else {
+ LOG.debug(message, parameters);
+ }
+ }
+
+ /**
+ * Whether to order the parameters or not
+ *
+ * @return True to order
+ */
+ public boolean isOrdered() {
+ return ordered;
+ }
+
+ /**
+ * Set whether to order the parameters by object depth or not
+ *
+ * @param ordered True to order them
+ */
+ public void setOrdered(boolean ordered) {
+ this.ordered = ordered;
+ }
+
+ /**
+ * Sets a comma-delimited list of regular expressions to match
+ * parameters that are allowed in the parameter map (aka whitelist).
+ * <p/>
+ * 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);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java
new file mode 100644
index 0000000..12643a3
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PreResultListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+
+
+/**
+ * PreResultListeners may be registered with an {@link ActionInvocation} to get a callback after the
+ * {@link com.opensymphony.xwork2.Action} has been executed but before the {@link com.opensymphony.xwork2.Result}
+ * is executed.
+ *
+ * @author Jason Carreira
+ */
+public interface PreResultListener {
+
+ /**
+ * This callback method will be called after the {@link com.opensymphony.xwork2.Action} execution and
+ * before the {@link com.opensymphony.xwork2.Result} execution.
+ *
+ * @param invocation the action invocation
+ * @param resultCode the result code returned by the action (eg. <code>success</code>).
+ */
+ void beforeResult(ActionInvocation invocation, String resultCode);
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
new file mode 100644
index 0000000..5a6e5a1
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A utility class for invoking prefixed methods in action class.
+ *
+ * Interceptors that made use of this class are:
+ * <ul>
+ * <li>DefaultWorkflowInterceptor</li>
+ * <li>PrepareInterceptor</li>
+ * </ul>
+ *
+ * <p/>
+ *
+ * <!-- START SNIPPET: javadocDefaultWorkflowInterceptor -->
+ *
+ * <b>In DefaultWorkflowInterceptor</b>
+ * <p>applies only when action implements {@link com.opensymphony.xwork2.Validateable}</p>
+ * <ol>
+ * <li>if the action class have validate{MethodName}(), it will be invoked</li>
+ * <li>else if the action class have validateDo{MethodName}(), it will be invoked</li>
+ * <li>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.</li>
+ * </ol>
+ *
+ * <!-- END SNIPPET: javadocDefaultWorkflowInterceptor -->
+ *
+ *
+ * <!-- START SNIPPET: javadocPrepareInterceptor -->
+ *
+ * <b>In PrepareInterceptor</b>
+ * <p>Applies only when action implements Preparable</p>
+ * <ol>
+ * <li>if the action class have prepare{MethodName}(), it will be invoked</li>
+ * <li>else if the action class have prepareDo(MethodName()}(), it will be invoked</li>
+ * <li>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.</li>
+ * </ol>
+ *
+ * <!-- END SNIPPET: javadocPrepareInterceptor -->
+ *
+ * @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";
+
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ /**
+ * This method will prefix <code>actionInvocation</code>'s <code>ActionProxy</code>'s
+ * <code>method</code> with <code>prefixes</code> before invoking the prefixed method.
+ * Order of the <code>prefixes</code> is important, as this method will return once
+ * a prefixed method is found in the action class.
+ *
+ * <p/>
+ *
+ * For example, with
+ * <pre>
+ * invokePrefixMethod(actionInvocation, new String[] { "prepare", "prepareDo" });
+ * </pre>
+ *
+ * Assuming <code>actionInvocation.getProxy(),getMethod()</code> returns "submit",
+ * the order of invocation would be as follows:-
+ * <ol>
+ * <li>prepareSubmit()</li>
+ * <li>prepareDoSubmit()</li>
+ * </ol>
+ *
+ * If <code>prepareSubmit()</code> exists, it will be invoked and this method
+ * will return, <code>prepareDoSubmit()</code> will NOT be invoked.
+ *
+ * <p/>
+ *
+ * On the other hand, if <code>prepareDoSubmit()</code> does not exists, and
+ * <code>prepareDoSubmit()</code> exists, it will be invoked.
+ *
+ * <p/>
+ *
+ * If none of those two methods exists, nothing will be invoked.
+ *
+ * @param actionInvocation the action invocation
+ * @param prefixes prefixes for method names
+ * @throws InvocationTargetException is thrown if invocation of a method failed.
+ * @throws IllegalAccessException is thrown if invocation of a method failed.
+ */
+ 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
+ methodName = DEFAULT_INVOCATION_METHODNAME;
+ }
+
+ Method method = getPrefixedMethod(prefixes, methodName, action);
+ if (method != null) {
+ method.invoke(action, new Object[0]);
+ }
+ }
+
+
+ /**
+ * This method returns a {@link Method} in <code>action</code>. The method
+ * returned is found by searching for method in <code>action</code> whose method name
+ * is equals to the result of appending each <code>prefixes</code>
+ * to <code>methodName</code>. Only the first method found will be returned, hence
+ * the order of <code>prefixes</code> is important. If none is found this method
+ * will return null.
+ *
+ * @param prefixes the prefixes to prefix the <code>methodName</code>
+ * @param methodName the method name to be prefixed with <code>prefixes</code>
+ * @param action the action class of which the prefixed method is to be search for.
+ * @return a {@link Method} if one is found, else <tt>null</tt>.
+ */
+ public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
+ assert(prefixes != null);
+ String capitalizedMethodName = capitalizeMethodName(methodName);
+ for (String prefixe : prefixes) {
+ String prefixedMethodName = prefixe + capitalizedMethodName;
+ try {
+ return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
+ }
+ catch (NoSuchMethodException e) {
+ // hmm -- OK, try next prefix
+ LOG.debug("Cannot find method [{}] in action [{}]", prefixedMethodName, action);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method capitalized the first character of <code>methodName</code>.
+ * <br/>
+ * eg. <code>capitalizeMethodName("someMethod");</code> will return <code>"SomeMethod"</code>.
+ *
+ * @param methodName the method name
+ * @return capitalized method name
+ */
+ public static String capitalizeMethodName(String methodName) {
+ assert(methodName != null);
+ return methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
new file mode 100644
index 0000000..6a327c3
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
@@ -0,0 +1,174 @@
+/*
+ * $Id$
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.Preparable;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * This interceptor calls <code>prepare()</code> 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.
+ *
+ * <p/> 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: <i>id</i> and
+ * <i>name</i>. 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 <i>user.name</i> will be set, as desired, on the actual object
+ * loaded from the database. See the example for more info.
+ *
+ * <p/>
+ * <b>Note:</b> 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.
+ *
+ * <p/><b>Update</b>: 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 com.opensymphony.xwork2.ActionProxy}. For example, you can specify a
+ * prepareInput() method that will be run before the invocation of the input method.
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Interceptor parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>alwaysInvokePrepare - Default to true. If true, prepare will always be invoked,
+ * otherwise it will not.</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Extending the interceptor:</u>
+ *
+ * <p/>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * There are no known extension points to this interceptor.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p/> <u>Example code:</u>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * <!-- 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>
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Jason Carreira
+ * @author Philip Luppens
+ * @author tm_jee
+ * @see com.opensymphony.xwork2.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 <code>preapare</code> method should always be executed.
+ * <p/>
+ * Default is <tt>true</tt>.
+ *
+ * @param alwaysInvokePrepare if <code>prepare</code> should always be executed or not.
+ */
+ public void setAlwaysInvokePrepare(String alwaysInvokePrepare) {
+ this.alwaysInvokePrepare = Boolean.parseBoolean(alwaysInvokePrepare);
+ }
+
+ /**
+ * Sets if the <code>prepareDoXXX</code> method should be called first
+ * <p/>
+ * Default is <tt>false</tt> for backward compatibility
+ *
+ * @param firstCallPrepareDo if <code>prepareDoXXX</code> 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();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java
new file mode 100644
index 0000000..e381373
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDriven.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.opensymphony.xwork2.interceptor;
+
+import com.opensymphony.xwork2.ModelDriven;
+
+/**
+ * Adds the ability to set a model, probably retrieved from a given state.
+ */
+public interface ScopedModelDriven<T> extends ModelDriven<T> {
+
+ /**
+ * Sets the model
+ */
+ void setModel(T model);
+
+ /**
+ * Sets the key under which the model is stored
+ * @param key The model key
+ */
+ void setScopeKey(String key);
+
+ /**
+ * Gets the key under which the model is stored
+ */
+ String getScopeKey();
+}