diff --git a/core/src/main/java/org/apache/struts2/util/ValueStackProvider.java b/core/src/main/java/org/apache/struts2/util/ValueStackProvider.java new file mode 100644 index 0000000000..2a6d1bf0af --- /dev/null +++ b/core/src/main/java/org/apache/struts2/util/ValueStackProvider.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.util; + +import com.opensymphony.xwork2.util.ValueStack; + +/** + * @since 6.4.0 + */ +public interface ValueStackProvider { + + ValueStack getValueStack(); + +} diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityContext.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityContext.java index 4241f6ead4..d18ca6bcf4 100644 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityContext.java +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityContext.java @@ -19,6 +19,7 @@ package org.apache.struts2.views.velocity; import com.opensymphony.xwork2.util.ValueStack; +import org.apache.struts2.util.ValueStackProvider; import org.apache.velocity.VelocityContext; import java.util.ArrayList; @@ -26,7 +27,7 @@ import java.util.List; import java.util.function.Function; -public class StrutsVelocityContext extends VelocityContext { +public class StrutsVelocityContext extends VelocityContext implements ValueStackProvider { private final ValueStack stack; private final List chainedContexts; @@ -77,10 +78,10 @@ public Object internalGet(String key) { } protected List> contextGetterList() { - return Arrays.asList(this::superGet, this::chainedContextGet, this::stackGet); + return Arrays.asList(this::superInternalGet, this::chainedContextGet, this::stackGet); } - protected Object superGet(String key) { + protected Object superInternalGet(String key) { return super.internalGet(key); } @@ -96,11 +97,16 @@ protected Object chainedContextGet(String key) { return null; } for (VelocityContext chainedContext : chainedContexts) { - Object val = chainedContext.internalGet(key); + Object val = chainedContext.get(key); if (val != null) { return val; } } return null; } + + @Override + public ValueStack getValueStack() { + return stack; + } } diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/components/AbstractDirective.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/components/AbstractDirective.java index e955b32aa3..539f64bdd9 100644 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/components/AbstractDirective.java +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/components/AbstractDirective.java @@ -18,26 +18,28 @@ */ package org.apache.struts2.views.velocity.components; -import java.io.IOException; -import java.io.Writer; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.util.ValueStack; import org.apache.struts2.ServletActionContext; import org.apache.struts2.components.Component; +import org.apache.struts2.util.ValueStackProvider; +import org.apache.struts2.views.util.ContextUtil; +import org.apache.velocity.context.AbstractContext; +import org.apache.velocity.context.Context; import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.context.InternalWrapperContext; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.directive.Directive; import org.apache.velocity.runtime.parser.node.Node; -import com.opensymphony.xwork2.ActionContext; -import com.opensymphony.xwork2.inject.Container; -import com.opensymphony.xwork2.util.ValueStack; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; public abstract class AbstractDirective extends Directive { public String getName() { @@ -57,8 +59,11 @@ public int getType() { protected abstract Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res); public boolean render(InternalContextAdapter ctx, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { - // get the bean - ValueStack stack = (ValueStack) ctx.get("stack"); + ValueStack stack = extractValueStack(ctx); + if (stack == null) { + // Fallback to assuming the ValueStack was put into the Velocity context (as is by default) + stack = (ValueStack) ctx.get(ContextUtil.STACK); + } HttpServletRequest req = (HttpServletRequest) stack.getContext().get(ServletActionContext.HTTP_REQUEST); HttpServletResponse res = (HttpServletResponse) stack.getContext().get(ServletActionContext.HTTP_RESPONSE); Component bean = getBean(stack, req, res); @@ -79,6 +84,27 @@ public boolean render(InternalContextAdapter ctx, Writer writer, Node node) thro return true; } + private ValueStack extractValueStack(Context context) { + do { + if (context instanceof ValueStackProvider) { + return ((ValueStackProvider) context).getValueStack(); + } + context = extractContext(context); + } while (context != null); + + return null; + } + + private Context extractContext(Context context) { + if (context instanceof InternalWrapperContext) { + return ((InternalWrapperContext) context).getInternalUserContext(); + } + if (context instanceof AbstractContext) { + return ((AbstractContext) context).getChainedContext(); + } + return null; + } + /** *

* Create a Map of properties that the user has passed in. For example: diff --git a/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityContextTest.java b/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityContextTest.java index 6cd38c8aa6..60490c6aff 100644 --- a/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityContextTest.java +++ b/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityContextTest.java @@ -54,7 +54,7 @@ public void setUp() throws Exception { @Test public void getChainedValue() { - when(chainedContext.internalGet("foo")).thenReturn("bar"); + when(chainedContext.get("foo")).thenReturn("bar"); assertEquals("bar", strutsVelocityContext.internalGet("foo")); } @@ -75,7 +75,7 @@ public void getValuePrecedence() { when(stack.findValue("foo")).thenReturn("qux"); assertEquals("qux", strutsVelocityContext.internalGet("foo")); - when(chainedContext.internalGet("foo")).thenReturn("baz"); + when(chainedContext.get("foo")).thenReturn("baz"); assertEquals("baz", strutsVelocityContext.internalGet("foo")); strutsVelocityContext.put("foo", "bar");