This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch feature/WW-5333-attribute-map
in repository https://gitbox.apache.org/repos/asf/struts.git

commit e8c2b27e189f1b3e0dfa607d98b7faf1a947a502
Author: Lukasz Lenart <lukaszlen...@apache.org>
AuthorDate: Sat Nov 4 15:50:59 2023 +0100

    WW-5333 Refactors AttributeMap
---
 .../java/org/apache/struts2/components/Set.java    |  16 +-
 .../struts2/{util => dispatcher}/AttributeMap.java |  71 +++---
 .../org/apache/struts2/dispatcher/Dispatcher.java  |  11 +-
 .../struts2/dispatcher/DispatcherConstants.java    |  33 +++
 .../org/apache/struts2/dispatcher/RequestMap.java  |   4 +-
 .../debugging/DebuggingInterceptor.java            |  16 +-
 .../org/apache/struts2/views/jsp/TagUtils.java     |   2 +-
 .../org/apache/struts2/views/util/ContextUtil.java |   7 +-
 .../struts2/dispatcher/AttributeMapTest.java       | 279 +++++++++++++++++++++
 .../portlet/dispatcher/Jsr168Dispatcher.java       |  13 +-
 10 files changed, 390 insertions(+), 62 deletions(-)

diff --git a/core/src/main/java/org/apache/struts2/components/Set.java 
b/core/src/main/java/org/apache/struts2/components/Set.java
index 8cb1ca461..cca990ea8 100644
--- a/core/src/main/java/org/apache/struts2/components/Set.java
+++ b/core/src/main/java/org/apache/struts2/components/Set.java
@@ -20,6 +20,7 @@ package org.apache.struts2.components;
 
 import java.io.Writer;
 
+import org.apache.struts2.dispatcher.DispatcherConstants;
 import org.apache.struts2.views.annotations.StrutsTag;
 import org.apache.struts2.views.annotations.StrutsTagAttribute;
 
@@ -53,16 +54,11 @@ import com.opensymphony.xwork2.util.ValueStack;
  * <!-- START SNIPPET: params -->
  *
  * <ul>
- *
  * <li>var* (String): The name of the new variable that is assigned the value 
of <i>value</i></li>
- *
  * <li>value (Object): The value that is assigned to the variable named 
<i>name</i></li>
- *
  * <li>scope (String): The scope in which to assign the variable. Can be 
<b>application</b>, <b>session</b>,
  * <b>request</b>, <b>page</b>, or <b>action</b>. By default it is 
<b>action</b>.</li>
- * 
  * <li>Note: With the <b>action</b> scope, the variable is <em>also</em> 
assigned to the <b>page</b> scope.
- *
  * </ul>
  *
  * <!-- END SNIPPET: params -->
@@ -107,16 +103,16 @@ public class Set extends ContextBean {
 
         body="";
 
-        if ("application".equalsIgnoreCase(scope)) {
+        if (DispatcherConstants.APPLICATION.equalsIgnoreCase(scope)) {
             stack.setValue("#application['" + getVar() + "']", o);
-        } else if ("session".equalsIgnoreCase(scope)) {
+        } else if (DispatcherConstants.SESSION.equalsIgnoreCase(scope)) {
             stack.setValue("#session['" + getVar() + "']", o);
-        } else if ("request".equalsIgnoreCase(scope)) {
+        } else if (DispatcherConstants.REQUEST.equalsIgnoreCase(scope)) {
             stack.setValue("#request['" + getVar() + "']", o);
-        } else if ("page".equalsIgnoreCase(scope)) {
+        } else if (DispatcherConstants.PAGE.equalsIgnoreCase(scope)) {
             stack.setValue("#attr['" + getVar() + "']", o, false);
         } else {
-            // Default scope is action.  Note: The action acope handling also 
adds the var to the page scope.
+            // Default scope is action. Note: The action scope handling also 
adds the var to the page scope.
             stack.getContext().put(getVar(), o);
             stack.setValue("#attr['" + getVar() + "']", o, false);
         }
diff --git a/core/src/main/java/org/apache/struts2/util/AttributeMap.java 
b/core/src/main/java/org/apache/struts2/dispatcher/AttributeMap.java
similarity index 64%
rename from core/src/main/java/org/apache/struts2/util/AttributeMap.java
rename to core/src/main/java/org/apache/struts2/dispatcher/AttributeMap.java
index ea088b4af..01855ab4b 100644
--- a/core/src/main/java/org/apache/struts2/util/AttributeMap.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/AttributeMap.java
@@ -16,14 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.struts2.util;
+package org.apache.struts2.dispatcher;
 
-import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.StrutsStatics;
 
 import javax.servlet.jsp.PageContext;
+import java.util.AbstractMap;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -38,17 +40,16 @@ import java.util.Set;
  *   <li>Session scope</li>
  *   <li>Application scope</li>
  * </ul>
- *
+ * <p>
  * A object is searched in the order above, starting from page and ending at 
application scope.
- *
  */
-public class AttributeMap implements Map {
+public class AttributeMap extends AbstractMap<String, Object> {
 
     protected static final String UNSUPPORTED = "method makes no sense for a 
simplified map";
 
-    Map context;
+    private final Map<String, Object> context;
 
-    public AttributeMap(Map context) {
+    public AttributeMap(Map<String, Object> context) {
         this.context = context;
     }
 
@@ -73,18 +74,22 @@ public class AttributeMap implements Map {
     }
 
     @Override
-    public Set entrySet() {
-        return Collections.EMPTY_SET;
+    public Set<Map.Entry<String, Object>> entrySet() {
+        return Collections.unmodifiableSet(this.context.entrySet());
     }
 
     @Override
     public Object get(Object key) {
+        if (key == null) {
+            return null;
+        }
+
         PageContext pc = getPageContext();
 
         if (pc == null) {
-            Map request = (Map) context.get("request");
-            Map session = (Map) context.get("session");
-            Map application = (Map) context.get("application");
+            RequestMap request = (RequestMap) 
context.get(DispatcherConstants.REQUEST);
+            SessionMap session = (SessionMap) 
context.get(DispatcherConstants.SESSION);
+            ApplicationMap application = (ApplicationMap) 
context.get(DispatcherConstants.APPLICATION);
 
             if ((request != null) && (request.get(key) != null)) {
                 return request.get(key);
@@ -94,26 +99,22 @@ public class AttributeMap implements Map {
                 return application.get(key);
             }
         } else {
-            try {
-                return pc.findAttribute(key.toString());
-            } catch (NullPointerException npe) {
-                return null;
-            }
+            return pc.findAttribute(key.toString());
         }
 
         return null;
     }
 
     @Override
-    public Set keySet() {
-        return Collections.EMPTY_SET;
+    public Set<String> keySet() {
+        return Collections.unmodifiableSet(this.context.keySet());
     }
 
     @Override
-    public Object put(Object key, Object value) {
+    public Object put(String key, Object value) {
         PageContext pc = getPageContext();
         if (pc != null) {
-            pc.setAttribute(key.toString(), value);
+            pc.setAttribute(key, value);
         }
 
         return null;
@@ -135,21 +136,21 @@ public class AttributeMap implements Map {
     }
 
     @Override
-    public Collection values() {
-        return Collections.EMPTY_SET;
+    public Collection<Object> values() {
+        return Collections.unmodifiableCollection(this.context.values());
     }
 
     private PageContext getPageContext() {
-        return (PageContext) context.get(ServletActionContext.PAGE_CONTEXT);
+        return (PageContext) context.get(StrutsStatics.PAGE_CONTEXT);
     }
 
     @Override
     public String toString() {
         return "AttributeMap {" +
-                "request=" + toStringSafe(context.get("request")) +
-                ", session=" +  toStringSafe(context.get("session")) +
-                ", application=" + toStringSafe(context.get("application")) +
-                '}';
+            "request=" + 
toStringSafe(context.get(DispatcherConstants.REQUEST)) +
+            ", session=" + 
toStringSafe(context.get(DispatcherConstants.SESSION)) +
+            ", application=" + 
toStringSafe(context.get(DispatcherConstants.APPLICATION)) +
+            '}';
     }
 
     private String toStringSafe(Object obj) {
@@ -163,4 +164,18 @@ public class AttributeMap implements Map {
         }
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof AttributeMap)) return false;
+        if (!super.equals(o)) return false;
+        AttributeMap that = (AttributeMap) o;
+        return Objects.equals(context, that.context);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), context);
+    }
+
 }
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java 
b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
index 51ae95d27..e378633e2 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
@@ -67,7 +67,6 @@ import 
org.apache.struts2.config.StrutsXmlConfigurationProvider;
 import org.apache.struts2.dispatcher.mapper.ActionMapping;
 import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
 import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
-import org.apache.struts2.util.AttributeMap;
 import org.apache.struts2.util.ObjectFactoryDestroyable;
 import org.apache.struts2.util.fs.JBossFileManager;
 
@@ -779,14 +778,14 @@ public class Dispatcher {
             .withServletResponse(response)
             .withServletContext(servletContext)
             // helpers to get access to request/session/application scope
-            .with("request", requestMap)
-            .with("session", sessionMap)
-            .with("application", applicationMap)
-            .with("parameters", parameters)
+            .with(DispatcherConstants.REQUEST, requestMap)
+            .with(DispatcherConstants.SESSION, sessionMap)
+            .with(DispatcherConstants.APPLICATION, applicationMap)
+            .with(DispatcherConstants.PARAMETERS, parameters)
             .getContextMap();
 
         AttributeMap attrMap = new AttributeMap(extraContext);
-        extraContext.put("attr", attrMap);
+        extraContext.put(DispatcherConstants.ATTRIBUTES, attrMap);
 
         return extraContext;
     }
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/DispatcherConstants.java 
b/core/src/main/java/org/apache/struts2/dispatcher/DispatcherConstants.java
new file mode 100644
index 000000000..7ccd220cb
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/dispatcher/DispatcherConstants.java
@@ -0,0 +1,33 @@
+/*
+ * 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.dispatcher;
+
+public final class DispatcherConstants {
+
+    public static final String REQUEST = "request";
+    public static final String RESPONSE = "response";
+    public static final String SESSION = "session";
+    public static final String APPLICATION = "application";
+    public static final String PARAMETERS = "parameters";
+    public static final String ATTRIBUTES = "attr";
+    public static final String PAGE = "page";
+
+    private DispatcherConstants() {
+    }
+}
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/RequestMap.java 
b/core/src/main/java/org/apache/struts2/dispatcher/RequestMap.java
index a75dffb75..b8e5b8e67 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/RequestMap.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/RequestMap.java
@@ -32,8 +32,9 @@ public class RequestMap extends AbstractMap<String, Object> 
implements Serializa
 
     private static final long serialVersionUID = -7675640869293787926L;
 
+    private final HttpServletRequest request;
+
     private Set<Entry<String, Object>> entries;
-    private HttpServletRequest request;
 
     /**
      * Saves the request to use as the backing for getting and setting values
@@ -44,7 +45,6 @@ public class RequestMap extends AbstractMap<String, Object> 
implements Serializa
         this.request = request;
     }
 
-
     /**
      * Removes all attributes from the request as well as clears entries in 
this map.
      */
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
index 62f871690..7f116829e 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/debugging/DebuggingInterceptor.java
@@ -29,8 +29,10 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.struts2.ServletActionContext;
 import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.DispatcherConstants;
 import org.apache.struts2.dispatcher.Parameter;
 import org.apache.struts2.dispatcher.PrepareOperations;
+import org.apache.struts2.dispatcher.RequestMap;
 import org.apache.struts2.views.freemarker.FreemarkerManager;
 import org.apache.struts2.views.freemarker.FreemarkerResult;
 
@@ -97,11 +99,13 @@ public class DebuggingInterceptor extends 
AbstractInterceptor {
 
     private final static Logger LOG = 
LogManager.getLogger(DebuggingInterceptor.class);
 
-    private String[] ignorePrefixes = new String[]{"org.apache.struts.",
-        "com.opensymphony.xwork2.", "xwork."};
-    private String[] _ignoreKeys = new String[]{"application", "session",
-        "parameters", "request"};
-    private HashSet<String> ignoreKeys = new 
HashSet<>(Arrays.asList(_ignoreKeys));
+    private final String[] ignorePrefixes = new String[]{"org.apache.struts.", 
"com.opensymphony.xwork2.", "xwork."};
+    private final HashSet<String> ignoreKeys = new HashSet<>(Arrays.asList(
+        DispatcherConstants.APPLICATION,
+        DispatcherConstants.SESSION,
+        DispatcherConstants.PARAMETERS,
+        DispatcherConstants.REQUEST
+    ));
 
     private final static String XML_MODE = "xml";
     private final static String CONSOLE_MODE = "console";
@@ -319,7 +323,7 @@ public class DebuggingInterceptor extends 
AbstractInterceptor {
             }
         }
         writer.endNode();
-        Map<String, Object> requestMap = (Map<String, Object>) 
ctx.get("request");
+        RequestMap requestMap = (RequestMap) 
ctx.get(DispatcherConstants.REQUEST);
         serializeIt(requestMap, "request", writer, 
filterValueStack(requestMap));
         serializeIt(ctx.getSession(), "session", writer, new ArrayList<>());
 
diff --git a/core/src/main/java/org/apache/struts2/views/jsp/TagUtils.java 
b/core/src/main/java/org/apache/struts2/views/jsp/TagUtils.java
index f813e00d1..044f5d1af 100644
--- a/core/src/main/java/org/apache/struts2/views/jsp/TagUtils.java
+++ b/core/src/main/java/org/apache/struts2/views/jsp/TagUtils.java
@@ -24,7 +24,7 @@ import com.opensymphony.xwork2.util.ValueStack;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.struts2.ServletActionContext;
-import org.apache.struts2.util.AttributeMap;
+import org.apache.struts2.dispatcher.AttributeMap;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.jsp.PageContext;
diff --git a/core/src/main/java/org/apache/struts2/views/util/ContextUtil.java 
b/core/src/main/java/org/apache/struts2/views/util/ContextUtil.java
index 4ab3c2786..0f1c85aae 100644
--- a/core/src/main/java/org/apache/struts2/views/util/ContextUtil.java
+++ b/core/src/main/java/org/apache/struts2/views/util/ContextUtil.java
@@ -20,6 +20,7 @@ package org.apache.struts2.views.util;
 
 import com.opensymphony.xwork2.ActionInvocation;
 import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.struts2.dispatcher.DispatcherConstants;
 import org.apache.struts2.util.StrutsUtil;
 
 import javax.servlet.http.HttpServletRequest;
@@ -31,9 +32,9 @@ import java.util.Map;
  * Value Stack's Context related Utilities.
  */
 public class ContextUtil {
-    public static final String REQUEST = "request";
-    public static final String RESPONSE = "response";
-    public static final String SESSION = "session";
+    public static final String REQUEST = DispatcherConstants.REQUEST;
+    public static final String RESPONSE = DispatcherConstants.RESPONSE;
+    public static final String SESSION = DispatcherConstants.SESSION;
     public static final String BASE = "base";
     public static final String STACK = "stack";
     public static final String STRUTS = "struts";
diff --git 
a/core/src/test/java/org/apache/struts2/dispatcher/AttributeMapTest.java 
b/core/src/test/java/org/apache/struts2/dispatcher/AttributeMapTest.java
new file mode 100644
index 000000000..eb003a1e1
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/dispatcher/AttributeMapTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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.dispatcher;
+
+import org.apache.struts2.StrutsStatics;
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.mock.web.MockPageContext;
+import org.springframework.mock.web.MockServletContext;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.PageContext;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.hasItem;
+
+public class AttributeMapTest {
+
+    @Test
+    public void shouldRetrievePageContextAttribute() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+    }
+
+    @Test
+    public void shouldRetrieveRequestAttribute() {
+        // given
+        HttpServletRequest request = new MockHttpServletRequest();
+        request.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(DispatcherConstants.REQUEST, new RequestMap(request));
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+    }
+
+    @Test
+    public void shouldRetrieveSessionAttribute() {
+        // given
+        HttpSession session = new MockHttpSession();
+        session.setAttribute("attr", "value");
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setSession(session);
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(DispatcherConstants.SESSION, new SessionMap(request));
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+    }
+
+    @Test
+    public void shouldRetrieveApplicationAttribute() {
+        // given
+        ServletContext sc = new MockServletContext();
+        sc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(DispatcherConstants.APPLICATION, new ApplicationMap(sc));
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+    }
+
+    @Test
+    public void shouldReturnNullIfKeyIsNull() {
+        // given
+        // when
+        AttributeMap am = new AttributeMap(new HashMap<>());
+        Object value = am.get(null);
+
+        // then
+        assertNull(value);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowExceptionOnRemove() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+
+        // then
+        Object value = am.get("attr");
+        assertEquals("value", value);
+
+        am.remove("attr");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowExceptionOnClear() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+
+        // when
+        am.clear();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowExceptionOnIsEmpty() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+
+        // when
+        am.isEmpty();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowExceptionOnContainsValue() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+
+        // when
+        am.containsValue("attr");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowExceptionOnPutAll() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+
+        // when
+        am.putAll(new HashMap<>());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void shouldThrowExceptionOnSize() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+
+        // when
+        am.size();
+    }
+
+    @Test
+    public void shouldGetAllValues() {
+        // given
+        PageContext pc = new MockPageContext();
+        pc.setAttribute("attr", "value");
+
+        Map<String, Object> context = new HashMap<String, Object>() {{
+            put(StrutsStatics.PAGE_CONTEXT, pc);
+        }};
+
+        // when
+        AttributeMap am = new AttributeMap(context);
+        Object value = am.get("attr");
+
+        // then
+        assertEquals("value", value);
+
+        // when
+        Collection<Object> values = am.values();
+
+        // then
+        assertThat(values, hasItem(pc));
+    }
+
+}
\ No newline at end of file
diff --git 
a/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
 
b/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
index cc7b00c5b..394ef6042 100644
--- 
a/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
+++ 
b/plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
@@ -33,6 +33,7 @@ import org.apache.struts2.StrutsException;
 import org.apache.struts2.StrutsStatics;
 import org.apache.struts2.dispatcher.ApplicationMap;
 import org.apache.struts2.dispatcher.Dispatcher;
+import org.apache.struts2.dispatcher.DispatcherConstants;
 import org.apache.struts2.dispatcher.HttpParameters;
 import org.apache.struts2.dispatcher.RequestMap;
 import org.apache.struts2.dispatcher.SessionMap;
@@ -48,7 +49,7 @@ import 
org.apache.struts2.portlet.context.PortletActionContext;
 import org.apache.struts2.portlet.servlet.PortletServletContext;
 import org.apache.struts2.portlet.servlet.PortletServletRequest;
 import org.apache.struts2.portlet.servlet.PortletServletResponse;
-import org.apache.struts2.util.AttributeMap;
+import org.apache.struts2.dispatcher.AttributeMap;
 
 import javax.portlet.ActionRequest;
 import javax.portlet.ActionResponse;
@@ -377,7 +378,7 @@ public class Jsr168Dispatcher extends GenericPortlet 
implements StrutsStatics {
         container.inject(servletRequest);
 
         // ServletActionContext
-        Map<String, Object> extraContext = ActionContext.of(new 
HashMap<String, Object>())
+        Map<String, Object> extraContext = ActionContext.of(new HashMap<>())
             .withServletRequest(servletRequest)
             .withServletResponse(servletResponse)
             .withServletContext(servletContext)
@@ -392,10 +393,10 @@ public class Jsr168Dispatcher extends GenericPortlet 
implements StrutsStatics {
             .with(PORTLET_NAMESPACE, portletNamespace)
             .with(DEFAULT_ACTION_FOR_MODE, 
actionMap.get(request.getPortletMode()))
             // helpers to get access to request/session/application scope
-            .with("request", requestMap)
-            .with("session", sessionMap)
-            .with("application", applicationMap)
-            .with("parameters", parameterMap)
+            .with(DispatcherConstants.REQUEST, requestMap)
+            .with(DispatcherConstants.SESSION, sessionMap)
+            .with(DispatcherConstants.APPLICATION, applicationMap)
+            .with(DispatcherConstants.PARAMETERS, parameterMap)
             .with(MODE_NAMESPACE_MAP, modeMap)
             .with(PortletConstants.DEFAULT_ACTION_MAP, actionMap)
             .with(PortletConstants.PHASE, phase)

Reply via email to