diff --git a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
index 7e78edf755..babc7ddafc 100644
--- a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
+++ b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/ValueStackShadowMap.java
@@ -53,10 +53,11 @@ public ValueStackShadowMap(ValueStack valueStack) {
* @return true, if contains key, false otherwise.
* @see java.util.HashMap#containsKey
*/
- public boolean containsKey(String key) {
+ @Override
+ public boolean containsKey(Object key) {
boolean hasKey = super.containsKey(key);
- if (!hasKey && valueStack.findValue(key) != null) {
+ if (!hasKey && key != null && valueStack.findValue(key.toString()) != null) {
hasKey = true;
}
@@ -67,16 +68,30 @@ public boolean containsKey(String key) {
* Implementation of get(), overriding HashMap implementation.
*
* @param key - The key to get in HashMap and if not found there from the valueStack.
- * @return value - The object from HashMap or if null, from the valueStack.
+ * @return value - The object from HashMap or, if the key is absent, from the valueStack.
* @see java.util.HashMap#get
*/
- public Object get(String key) {
- Object value = super.get(key);
-
- if ((value == null)) {
- value = valueStack.findValue((String) key);
+ @Override
+ public Object get(Object key) {
+ if (key == null || super.containsKey(key)) {
+ return super.get(key);
}
+ return valueStack.findValue(key.toString());
+ }
+
+ /**
+ * @deprecated since 7.4.0, use {@link #containsKey(Object)}
+ */
+ @Deprecated(since = "7.4.0", forRemoval = true)
+ public boolean containsKey(String key) {
+ return containsKey((Object) key);
+ }
- return value;
+ /**
+ * @deprecated since 7.4.0, use {@link #get(Object)}
+ */
+ @Deprecated(since = "7.4.0", forRemoval = true)
+ public Object get(String key) {
+ return get((Object) key);
}
}
diff --git a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
index beb563c122..82eb41d1fa 100644
--- a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
+++ b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java
@@ -171,6 +171,39 @@ public void testReportParametersExpressionAccepted() throws Exception {
assertTrue(response.getContentAsString().contains("Qux Report"));
}
+ public void testDeclaredParameterResolvedFromValueStack() throws Exception {
+ result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
+ stack.push(new Object() {
+ public String getTitle() {
+ return "Shadow";
+ }
+ });
+
+ result.execute(this.invocation);
+
+ assertTrue(response.getContentAsString().contains("Shadow Report"));
+ }
+
+ public void testExplicitNullParameterIsNotShadowedByValueStack() throws Exception {
+ result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
+ stack.push(new Object() {
+ public String getTitle() {
+ return "Shadow";
+ }
+
+ public Map getReportParameters() {
+ Map params = new HashMap<>();
+ params.put("title", null);
+ return params;
+ }
+ });
+ result.setReportParameters("reportParameters");
+
+ result.execute(this.invocation);
+
+ assertTrue(response.getContentAsString().contains("null Report"));
+ }
+
public void testFillFromReportParametersWithoutDataSourceOrConnection() throws Exception {
stack.push(new Object() {
public Map getReportParameters() {
diff --git a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/ValueStackShadowMapTest.java b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/ValueStackShadowMapTest.java
new file mode 100644
index 0000000000..b6c905ec71
--- /dev/null
+++ b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/ValueStackShadowMapTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.views.jasperreports;
+
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.junit.StrutsTestCase;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Map;
+
+public class ValueStackShadowMapTest extends StrutsTestCase {
+
+ private ValueStackShadowMap map;
+
+ public void testFallsBackToValueStackThroughMapInterface() {
+ Map asMap = map;
+
+ assertTrue(asMap.containsKey("title"));
+ assertEquals("Shadow", asMap.get("title"));
+ assertFalse(asMap.containsKey("missing"));
+ assertNull(asMap.get("missing"));
+ }
+
+ public void testExplicitEntryWinsOverValueStack() {
+ map.put("title", "Explicit");
+ map.put("other", null);
+
+ assertEquals("Explicit", map.get((Object) "title"));
+ assertTrue(map.containsKey((Object) "other"));
+ assertNull(map.get((Object) "other"));
+ }
+
+ public void testNullKeyDoesNotReachValueStack() {
+ assertFalse(map.containsKey((Object) null));
+ assertNull(map.get((Object) null));
+ }
+
+ @SuppressWarnings("removal")
+ public void testStringOverloadsDelegateToOverrides() {
+ assertTrue(map.containsKey("title"));
+ assertEquals("Shadow", map.get("title"));
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ValueStack stack = ActionContext.getContext().getValueStack();
+ stack.push(new Object() {
+ public String getTitle() {
+ return "Shadow";
+ }
+ });
+ map = new ValueStackShadowMap(stack);
+ }
+}
diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
index 12aa3c871b..1022a64078 100644
--- a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
+++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java
@@ -66,17 +66,14 @@ public boolean containsKey(Object key) {
* Implementation of get(), overriding HashMap implementation.
*
* @param key - The key to get in HashMap and if not found there from the valueStack.
- * @return value - The object from HashMap or if null, from the valueStack.
+ * @return value - The object from HashMap or, if the key is absent, from the valueStack.
* @see java.util.HashMap#get
*/
@Override
public Object get(Object key) {
- Object value = super.get(key);
-
- if (key != null && value == null) {
- value = valueStack.findValue(key.toString());
+ if (key == null || super.containsKey(key)) {
+ return super.get(key);
}
-
- return value;
+ return valueStack.findValue(key.toString());
}
}
diff --git a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
index d1f82d5dc0..c334dd6e8f 100644
--- a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
+++ b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
@@ -197,6 +197,29 @@ public Map getReportParameters() {
assertThat(response.getContentAsString()).contains("Baz Report");
}
+ public void testExplicitNullParameterIsNotShadowedByValueStack() throws Exception {
+ // given
+ result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
+ stack.push(new Object() {
+ public String getTitle() {
+ return "Shadow";
+ }
+
+ public Map getReportParameters() {
+ Map params = new HashMap<>();
+ params.put("title", null);
+ return params;
+ }
+ });
+ result.setReportParameters("reportParameters");
+
+ // when
+ result.execute(this.invocation);
+
+ // then
+ assertThat(response.getContentAsString()).contains("null Report");
+ }
+
public void testFillFromReportParametersWithoutDataSourceOrConnection() throws Exception {
// given
stack.push(new Object() {