WW-4687 Allows define interceptors with dynamic parameters
Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/2b12f06b Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/2b12f06b Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/2b12f06b Branch: refs/heads/master Commit: 2b12f06b01f01fb02f4fae0370ae1be48bf81be3 Parents: b9c05a7 Author: Lukasz Lenart <[email protected]> Authored: Mon Jan 9 10:07:20 2017 +0100 Committer: Lukasz Lenart <[email protected]> Committed: Mon Jan 9 10:07:20 2017 +0100 ---------------------------------------------------------------------- .../xwork2/DefaultActionInvocation.java | 20 ++++++- .../config/entities/InterceptorMapping.java | 14 ++++- .../config/providers/InterceptorBuilder.java | 10 ++-- .../factory/DefaultInterceptorFactory.java | 12 +++- .../xwork2/interceptor/WithLazyParams.java | 63 ++++++++++++++++++++ .../xwork2/DefaultActionInvocationTest.java | 34 +++++++++++ .../xwork2/mock/MockLazyInterceptor.java | 39 ++++++++++++ core/src/test/resources/xwork-sample.xml | 8 +++ core/src/test/resources/xwork-test-default.xml | 3 +- 9 files changed, 191 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java index 639bf95..754ae5a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java +++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -21,7 +21,9 @@ import com.opensymphony.xwork2.config.entities.InterceptorMapping; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.interceptor.Interceptor; import com.opensymphony.xwork2.interceptor.PreResultListener; +import com.opensymphony.xwork2.interceptor.WithLazyParams; import com.opensymphony.xwork2.ognl.OgnlUtil; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.ValueStackFactory; @@ -67,6 +69,7 @@ public class DefaultActionInvocation implements ActionInvocation { protected Container container; protected UnknownHandlerManager unknownHandlerManager; protected OgnlUtil ognlUtil; + protected WithLazyParams.LazyParamInjector lazyParamInjector; public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) { this.extraContext = extraContext; @@ -233,11 +236,15 @@ public class DefaultActionInvocation implements ActionInvocation { } if (interceptors.hasNext()) { - final InterceptorMapping interceptor = interceptors.next(); - String interceptorMsg = "interceptor: " + interceptor.getName(); + final InterceptorMapping interceptorMapping = interceptors.next(); + String interceptorMsg = "interceptorMapping: " + interceptorMapping.getName(); UtilTimerStack.push(interceptorMsg); try { - resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); + Interceptor interceptor = interceptorMapping.getInterceptor(); + if (interceptor instanceof WithLazyParams) { + interceptor = lazyParamInjector.injectParams(interceptor, interceptorMapping.getParams(), invocationContext); + } + resultCode = interceptor.intercept(DefaultActionInvocation.this); } finally { UtilTimerStack.pop(interceptorMsg); } @@ -400,6 +407,13 @@ public class DefaultActionInvocation implements ActionInvocation { invocationContext.setName(proxy.getActionName()); createInterceptors(proxy); + + prepareLazyParamInjector(invocationContext.getValueStack()); + } + + protected void prepareLazyParamInjector(ValueStack valueStack) { + lazyParamInjector = new WithLazyParams.LazyParamInjector(valueStack); + container.inject(lazyParamInjector); } protected void createInterceptors(ActionProxy proxy) { http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java index 846575e..36ba95c 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java @@ -19,6 +19,8 @@ package com.opensymphony.xwork2.config.entities; import com.opensymphony.xwork2.interceptor.Interceptor; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; /** * <code>InterceptorMapping</code> @@ -30,10 +32,16 @@ public class InterceptorMapping implements Serializable { private String name; private Interceptor interceptor; + private final Map<String, String> params; public InterceptorMapping(String name, Interceptor interceptor) { + this(name, interceptor, new HashMap<String, String>()); + } + + public InterceptorMapping(String name, Interceptor interceptor, Map<String, String> params) { this.name = name; this.interceptor = interceptor; + this.params = params; } public String getName() { @@ -44,6 +52,10 @@ public class InterceptorMapping implements Serializable { return interceptor; } + public Map<String, String> getParams() { + return params; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -65,7 +77,7 @@ public class InterceptorMapping implements Serializable { @Override public String toString() { - return "InterceptorMapping: [" + name + "] => [" + interceptor.getClass().getName() + ']'; + return "InterceptorMapping: [" + name + "] => [" + interceptor.getClass().getName() + "] with params [" + params + "]" ; } } http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java index fcf2484..8044c1a 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/InterceptorBuilder.java @@ -25,6 +25,7 @@ import com.opensymphony.xwork2.interceptor.Interceptor; import com.opensymphony.xwork2.util.location.Location; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -43,7 +44,6 @@ public class InterceptorBuilder { private static final Logger LOG = LogManager.getLogger(InterceptorBuilder.class); - /** * Builds a list of interceptors referenced by the refName in the supplied PackageConfig (InterceptorMapping object). * @@ -67,13 +67,11 @@ public class InterceptorBuilder { InterceptorConfig config = (InterceptorConfig) referencedConfig; Interceptor inter; try { - inter = objectFactory.buildInterceptor(config, refParams); - result.add(new InterceptorMapping(refName, inter)); + result.add(new InterceptorMapping(refName, inter, refParams)); } catch (ConfigurationException ex) { - LOG.warn("Unable to load config class {} at {} probably due to a missing jar, which might be fine if you never plan to use the {} interceptor", - config.getClassName(), ex.getLocation(), config.getName()); - LOG.error("Unable to load config class {}", config.getClassName(), ex); + LOG.warn(new ParameterizedMessage("Unable to load config class {} at {} probably due to a missing jar, which might be fine if you never plan to use the {} interceptor", + config.getClassName(), ex.getLocation(), config.getName()), ex); } } else if (referencedConfig instanceof InterceptorStackConfig) { http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java index 407aa35..3cdf6b5 100644 --- a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java +++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java @@ -5,7 +5,10 @@ import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.config.entities.InterceptorConfig; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.Interceptor; +import com.opensymphony.xwork2.interceptor.WithLazyParams; import com.opensymphony.xwork2.util.reflection.ReflectionProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.HashMap; import java.util.Map; @@ -15,6 +18,8 @@ import java.util.Map; */ public class DefaultInterceptorFactory implements InterceptorFactory { + private static final Logger LOG = LogManager.getLogger(DefaultInterceptorFactory.class); + private ObjectFactory objectFactory; private ReflectionProvider reflectionProvider; @@ -40,7 +45,12 @@ public class DefaultInterceptorFactory implements InterceptorFactory { try { // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context Object o = objectFactory.buildBean(interceptorClassName, null); - reflectionProvider.setProperties(params, o); + if (o instanceof WithLazyParams) { + LOG.debug("Interceptor {} is marked with interface {} and params will be set during action invocation", + interceptorClassName, WithLazyParams.class.getName()); + } else { + reflectionProvider.setProperties(params, o); + } if (o instanceof Interceptor) { Interceptor interceptor = (Interceptor) o; http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java new file mode 100644 index 0000000..e54cc73 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/WithLazyParams.java @@ -0,0 +1,63 @@ +package com.opensymphony.xwork2.interceptor; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.util.TextParseUtil; +import com.opensymphony.xwork2.util.TextParser; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + +import java.util.Map; + +/** + * Interceptors marked with this interface won't be fully initialised during initialisation. + * Appropriated params will be injected just before usage of the interceptor. + * + * Please be aware that in such case {@link Interceptor#init()} method must be prepared for this. + * + * @since 2.5.9 + */ +public interface WithLazyParams { + + class LazyParamInjector { + + protected OgnlUtil ognlUtil; + protected TextParser textParser; + protected ReflectionProvider reflectionProvider; + + private final TextParseUtil.ParsedValueEvaluator valueEvaluator; + + public LazyParamInjector(final ValueStack valueStack) { + valueEvaluator = new TextParseUtil.ParsedValueEvaluator() { + public Object evaluate(String parsedValue) { + return valueStack.findValue(parsedValue); // no asType !!! + } + }; + } + + @Inject + public void setTextParser(TextParser textParser) { + this.textParser = textParser; + } + + @Inject + public void setReflectionProvider(ReflectionProvider reflectionProvider) { + this.reflectionProvider = reflectionProvider; + } + + @Inject + public void setOgnlUtil(OgnlUtil ognlUtil) { + this.ognlUtil = ognlUtil; + } + + public Interceptor injectParams(Interceptor interceptor, Map<String, String> params, ActionContext invocationContext) { + for (Map.Entry<String, String> entry : params.entrySet()) { + Object paramValue = textParser.evaluate(new char[]{ '$' }, entry.getValue(), valueEvaluator, TextParser.DEFAULT_LOOP_COUNT); + ognlUtil.setProperty(entry.getKey(), paramValue, interceptor, invocationContext.getContextMap()); + } + + return interceptor; + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java b/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java index 7b7b3fe..98e3b1f 100644 --- a/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/DefaultActionInvocationTest.java @@ -3,16 +3,20 @@ package com.opensymphony.xwork2; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.InterceptorMapping; import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; import com.opensymphony.xwork2.mock.MockActionProxy; import com.opensymphony.xwork2.mock.MockContainer; import com.opensymphony.xwork2.mock.MockInterceptor; +import com.opensymphony.xwork2.mock.MockLazyInterceptor; import com.opensymphony.xwork2.ognl.OgnlUtil; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.ValueStackFactory; +import org.apache.struts2.dispatcher.HttpParameters; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** @@ -298,6 +302,36 @@ public class DefaultActionInvocationTest extends XWorkTestCase { assertEquals("success", result); } + public void testInvokeWithLazyParams() throws Exception { + HashMap<String, Object> params = new HashMap<>(); + params.put("blah", "this is blah"); + + HashMap<String, Object> extraContext = new HashMap<>(); + extraContext.put(ActionContext.PARAMETERS, HttpParameters.create(params).build()); + + DefaultActionInvocation defaultActionInvocation = new DefaultActionInvocation(extraContext, true); + container.inject(defaultActionInvocation); + + ActionProxy actionProxy = actionProxyFactory.createActionProxy( "", "LazyFoo", null, extraContext); + defaultActionInvocation.init(actionProxy); + defaultActionInvocation.invoke(); + + SimpleAction action = (SimpleAction) defaultActionInvocation.getAction(); + + assertEquals("this is blah", action.getBlah()); + assertEquals("this is blah", action.getName()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // ensure we're using the default configuration, not simple config + XmlConfigurationProvider configurationProvider = new XmlConfigurationProvider("xwork-sample.xml"); + container.inject(configurationProvider); + loadConfigurationProviders(configurationProvider); + } + } class DefaultActionInvocationTester extends DefaultActionInvocation { http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/test/java/com/opensymphony/xwork2/mock/MockLazyInterceptor.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/com/opensymphony/xwork2/mock/MockLazyInterceptor.java b/core/src/test/java/com/opensymphony/xwork2/mock/MockLazyInterceptor.java new file mode 100644 index 0000000..b43ee66 --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/mock/MockLazyInterceptor.java @@ -0,0 +1,39 @@ +/* + * 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.mock; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.SimpleAction; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.interceptor.Interceptor; +import com.opensymphony.xwork2.interceptor.WithLazyParams; +import org.junit.Assert; + +public class MockLazyInterceptor extends AbstractInterceptor implements WithLazyParams { + + private String foo = ""; + + public void setFoo(String foo) { + this.foo = foo; + } + + public String intercept(ActionInvocation invocation) throws Exception { + if (invocation.getAction() instanceof SimpleAction) { + ((SimpleAction) invocation.getAction()).setName(foo); + } + return invocation.invoke(); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/test/resources/xwork-sample.xml ---------------------------------------------------------------------- diff --git a/core/src/test/resources/xwork-sample.xml b/core/src/test/resources/xwork-sample.xml index 7f5e547..2cdee20 100644 --- a/core/src/test/resources/xwork-sample.xml +++ b/core/src/test/resources/xwork-sample.xml @@ -26,6 +26,14 @@ <interceptor-ref name="defaultStack"/> </action> + <action name="LazyFoo" class="com.opensymphony.xwork2.SimpleAction"> + <result name="error" type="void" /> + <interceptor-ref name="params"/> + <interceptor-ref name="lazy"> + <param name="foo">${blah}</param> + </interceptor-ref> + </action> + <action name="WildCard" class="com.opensymphony.xwork2.SimpleAction"> <param name="foo">17</param> <param name="bar">23</param> http://git-wip-us.apache.org/repos/asf/struts/blob/2b12f06b/core/src/test/resources/xwork-test-default.xml ---------------------------------------------------------------------- diff --git a/core/src/test/resources/xwork-test-default.xml b/core/src/test/resources/xwork-test-default.xml index bceeb32..fdfc609 100644 --- a/core/src/test/resources/xwork-test-default.xml +++ b/core/src/test/resources/xwork-test-default.xml @@ -19,10 +19,11 @@ <interceptor name="staticParams" class="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"/> <interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/> <interceptor name="validation" class="com.opensymphony.xwork2.validator.ValidationInterceptor"/> - <interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/> + <interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/> <interceptor name="test" class="com.opensymphony.xwork2.mock.MockInterceptor"> <param name="foo">expectedFoo</param> </interceptor> + <interceptor name="lazy" class="com.opensymphony.xwork2.mock.MockLazyInterceptor"/> <interceptor-stack name="defaultStack"> <interceptor-ref name="staticParams"/>
