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 ([email protected]) + */ +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 ([email protected]) + */ +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(); +}
