Skip to content

Commit

Permalink
WW-4224 finishes adding support for collection of parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszlenart committed Mar 22, 2014
2 parents 49ecb5f + 01255fa commit da6ed91
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,9 @@ protected void doExecute(String finalLocation, ActionInvocation invocation) thro
List<String> prohibitedResultParams = getProhibitedResultParams();
for (Map.Entry<String, String> e : resultConfigParams.entrySet()) {
if (!prohibitedResultParams.contains(e.getKey())) {
String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue(), invocation);
if (!suppressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0))) {
requestParameters.put(e.getKey(), potentialValue);
Collection<String> values = conditionalParseCollection(e.getValue(), invocation, suppressEmptyParameters);
if (!suppressEmptyParameters || !values.isEmpty()) {
requestParameters.put(e.getKey(), values);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;

import org.apache.struts2.StrutsStatics;

Expand Down Expand Up @@ -195,31 +197,64 @@ public void execute(ActionInvocation invocation) throws Exception {
*/
protected String conditionalParse(String param, ActionInvocation invocation) {
if (parse && param != null && invocation != null) {
return TextParseUtil.translateVariables(param, invocation.getStack(),
new TextParseUtil.ParsedValueEvaluator() {
public Object evaluate(String parsedValue) {
if (encode) {
if (parsedValue != null) {
try {
// use UTF-8 as this is the recommended encoding by W3C to
// avoid incompatibilities.
return URLEncoder.encode(parsedValue, "UTF-8");
}
catch(UnsupportedEncodingException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("error while trying to encode ["+parsedValue+"]", e);
}
}
}
}
return parsedValue;
}
});
return TextParseUtil.translateVariables(
param,
invocation.getStack(),
new EncodingParsedValueEvaluator());
} else {
return param;
}
}

/**
* As {@link #conditionalParse(String, ActionInvocation)} but does not
* convert found object into String. If found object is a collection it is
* returned if found object is not a collection it is wrapped in one.
*
* @param param
* @param invocation
* @param excludeEmptyElements
* @return
*/
protected Collection<String> conditionalParseCollection(String param, ActionInvocation invocation, boolean excludeEmptyElements) {
if (parse && param != null && invocation != null) {
return TextParseUtil.translateVariablesCollection(
param,
invocation.getStack(),
excludeEmptyElements,
new EncodingParsedValueEvaluator());
} else {
Collection<String> collection = new ArrayList<String>(1);
collection.add(param);
return collection;
}
}

/**
* {@link com.opensymphony.xwork2.util.TextParseUtil.ParsedValueEvaluator} to do URL encoding for found values. To be
* used for single strings or collections.
*
*/
private final class EncodingParsedValueEvaluator implements TextParseUtil.ParsedValueEvaluator {
public Object evaluate(String parsedValue) {
if (encode) {
if (parsedValue != null) {
try {
// use UTF-8 as this is the recommended encoding by W3C to
// avoid incompatibilities.
return URLEncoder.encode(parsedValue, "UTF-8");
}
catch(UnsupportedEncodingException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("error while trying to encode ["+parsedValue+"]", e);
}
}
}
}
return parsedValue;
}
}

/**
* Executes the result given a final location (jsp page, action, etc) and the action invocation
* (the state in which the action was executed). Subclasses must implement this class to handle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,24 @@

package org.apache.struts2.dispatcher;

import com.mockobjects.dynamic.C;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import static javax.servlet.http.HttpServletResponse.SC_SEE_OTHER;
import static org.easymock.EasyMock.createControl;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import ognl.Ognl;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.StrutsStatics;
Expand All @@ -40,18 +48,16 @@
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import static javax.servlet.http.HttpServletResponse.SC_SEE_OTHER;
import static org.easymock.EasyMock.createControl;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import com.mockobjects.dynamic.C;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.util.ValueStack;


/**
Expand Down Expand Up @@ -225,6 +231,67 @@ public void testIncludeParameterInResult() throws Exception {
control.verify();
}

public void testIncludeCollectionParameterInResult() throws Exception {
List<String> paramValues = new ArrayList<String>();
paramValues.add("value 1");
paramValues.add("");
paramValues.add("value 2");
paramValues.add(null);

ResultConfig resultConfig = new ResultConfig.Builder("", "")
.addParam("namespace", "someNamespace")
.addParam("param", "${list}")
.build();

ActionContext context = ActionContext.getContext();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
context.put(ServletActionContext.HTTP_REQUEST, req);
context.put(ServletActionContext.HTTP_RESPONSE, res);

Map<String, ResultConfig> results= new HashMap<String, ResultConfig>();
results.put("myResult", resultConfig);

ActionConfig actionConfig = new ActionConfig.Builder("", "", "")
.addResultConfigs(results).build();

ServletRedirectResult result = new ServletRedirectResult();
result.setLocation("/myNamespace/myAction.action");
result.setParse(true);
result.setEncode(false);
result.setPrependServletContext(false);
result.setUrlHelper(new DefaultUrlHelper());
result.setSuppressEmptyParameters(true);

IMocksControl control = createControl();
ActionProxy mockActionProxy = control.createMock(ActionProxy.class);
ActionInvocation mockInvocation = control.createMock(ActionInvocation.class);

ValueStack mockValueStack = control.createMock(ValueStack.class);
Map<String, Object> mockContext = new HashMap<String, Object>();
mockContext.put(ActionContext.CONTAINER, container);

expect(mockInvocation.getStack()).andReturn(mockValueStack);
expect(mockValueStack.getContext()).andReturn(mockContext);

expect(mockInvocation.getStack()).andReturn(mockValueStack);

expect(mockValueStack.findValue("list")).andReturn(paramValues); // no asType !!!

expect(mockInvocation.getProxy()).andReturn(mockActionProxy);
expect(mockInvocation.getResultCode()).andReturn("myResult");
expect(mockActionProxy.getConfig()).andReturn(actionConfig);
expect(mockInvocation.getInvocationContext()).andReturn(context);

expect(mockValueStack.getContext()).andReturn(mockContext);

control.replay();
result.setActionMapper(container.getInstance(ActionMapper.class));
result.execute(mockInvocation);
assertEquals("/myNamespace/myAction.action?param=value+1&param=value+2", res.getRedirectedUrl());
control.verify();
}

protected void setUp() throws Exception {
super.setUp();
configurationManager.getConfiguration().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

package org.apache.struts2.dispatcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.struts2.StrutsInternalTestCase;
import org.easymock.EasyMock;

Expand Down Expand Up @@ -109,6 +112,34 @@ public String getMyLocation() {
EasyMock.verify(mockActionInvocation);
}

public void testConditionalParseCollection() throws Exception {
ValueStack stack = ActionContext.getContext().getValueStack();
stack.push(new ActionSupport() {
public List<String> getList() {
return new ArrayList<String>(){{
add("val 1");
add("val 2");
}};
}
});

ActionInvocation mockActionInvocation = EasyMock.createNiceMock(ActionInvocation.class);
mockActionInvocation.getStack();
EasyMock.expectLastCall().andReturn(stack);
EasyMock.replay(mockActionInvocation);

InternalStrutsResultSupport result = new InternalStrutsResultSupport();
result.setParse(true);
result.setEncode(true);

Collection<String> collection = result.conditionalParseCollection("${list}", mockActionInvocation, true);

assertNotNull(collection);
assertEquals(2, collection.size());
assertTrue(collection.contains("val+1"));
assertTrue(collection.contains("val+2"));
EasyMock.verify(mockActionInvocation);
}

public static class InternalStrutsResultSupport extends StrutsResultSupport {
private String _internalLocation = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
package com.opensymphony.xwork2.util;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


Expand Down Expand Up @@ -167,6 +171,89 @@ public Object evaluate(String parsedValue) {
return parser.evaluate(openChars, expression, ognlEval, maxLoopCount);
}

/**
* @see #translateVariablesCollection(char[], String, ValueStack, boolean, ParsedValueEvaluator, int)
*
* @param expression
* @param stack
* @param excludeEmptyElements
* @param evaluator
* @return
*/
public static Collection<String> translateVariablesCollection(String expression, ValueStack stack, boolean excludeEmptyElements, ParsedValueEvaluator evaluator) {
return translateVariablesCollection(new char[]{'$', '%'}, expression, stack, excludeEmptyElements, evaluator, MAX_RECURSION);
}

/**
* Resolves given expression on given ValueStack. If found element is a
* collection each element will be converted to String. If just a single
* object is found it is converted to String and wrapped in a collection.
*
* @param openChars
* @param expression
* @param stack
* @param excludeEmptyElements
* @param evaluator
* @param maxLoopCount
* @return
*/
public static Collection<String> translateVariablesCollection(
char[] openChars, String expression, final ValueStack stack, boolean excludeEmptyElements,
final ParsedValueEvaluator evaluator, int maxLoopCount) {

ParsedValueEvaluator ognlEval = new ParsedValueEvaluator() {
public Object evaluate(String parsedValue) {
return stack.findValue(parsedValue); // no asType !!!
}
};

Map<String, Object> context = stack.getContext();
TextParser parser = ((Container)context.get(ActionContext.CONTAINER)).getInstance(TextParser.class);

Object result = parser.evaluate(openChars, expression, ognlEval, maxLoopCount);

XWorkConverter conv = ((Container)context.get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class);

Collection<String> resultCol;
if (result instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Object> casted = (Collection<Object>)result;
resultCol = new ArrayList<String>(casted.size());
for (Object element : casted) {
String stringElement = (String)conv.convertValue(context, element, String.class);
if (shallBeIncluded(stringElement, excludeEmptyElements)) {
if (evaluator != null) {
stringElement = evaluator.evaluate(stringElement).toString();
}
resultCol.add(stringElement);
}
}
} else {
resultCol = new ArrayList<String>(1);
String stringResult = (String)conv.convertValue(context, result, String.class);
if (shallBeIncluded(stringResult, excludeEmptyElements)) {
if (evaluator != null) {
stringResult = evaluator.evaluate(stringResult).toString();
}
resultCol.add(stringResult);
}
}

return resultCol;
}

/**
* Tests if given string is not null and not empty when excluding of empty
* elements is requested.
*
* @param str String to check.
* @param excludeEmptyElements Whether empty elements shall be excluded.
* @return True if given string can be included in collection.
*/
private static boolean shallBeIncluded(String str, boolean excludeEmptyElements) {
return !excludeEmptyElements || ((str != null) && (str.length() > 0));
}

/**
* Returns a set from comma delimted Strings.
* @param s The String to parse.
Expand Down
Loading

0 comments on commit da6ed91

Please sign in to comment.