WW-5333 Refactors AttributeMap

This commit is contained in:
Lukasz Lenart
2023-11-04 15:50:59 +01:00
parent f511034acd
commit f326ce3277
10 changed files with 475 additions and 62 deletions
@@ -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);
}
@@ -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,23 @@ 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 value;
}
return null;
@@ -135,21 +137,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 +165,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);
}
}
@@ -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;
}
@@ -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() {
}
}
@@ -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.
*/
@@ -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<>());
@@ -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;
@@ -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";
@@ -0,0 +1,363 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
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 shouldPutAttribute() {
// given
PageContext pc = new MockPageContext();
Map<String, Object> context = new HashMap<String, Object>() {{
put(StrutsStatics.PAGE_CONTEXT, pc);
}};
// when
AttributeMap am = new AttributeMap(context);
Object value = am.put("attr", "value");
// then
assertEquals("value", value);
assertEquals("value", pc.getAttribute("attr"));
}
@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
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);
assertThrows(UnsupportedOperationException.class, () -> am.remove("attr"));
}
@Test
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
assertThrows(UnsupportedOperationException.class, am::clear);
}
@Test
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
assertThrows(UnsupportedOperationException.class, am::isEmpty);
}
@Test
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
assertThrows(UnsupportedOperationException.class, () -> am.containsValue("attr"));
}
@Test
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);
}};
Map<String, Object> values = Collections.emptyMap();
// when
AttributeMap am = new AttributeMap(context);
Object value = am.get("attr");
// then
assertEquals("value", value);
// when
assertThrows(UnsupportedOperationException.class, () -> am.putAll(values));
}
@Test
public void shouldThrowExceptionOnKeySet() {
// 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
assertEquals(am.keySet(), context.keySet());
}
@Test
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
assertThrows(UnsupportedOperationException.class, 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));
}
@Test
public void shouldGetEntrySet() {
// 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);
// then
assertEquals(context.entrySet(), am.entrySet());
}
@Test
public void shouldContainsKey() {
// 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
assertTrue(am.containsKey("attr"));
Object value = am.get("attr");
assertEquals("value", value);
}
}
@@ -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)