Solved WW-3494 - many improvments to REST plugin

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@1026675 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Lukasz Lenart
2010-10-23 20:19:47 +00:00
parent 8afe4c4b22
commit 753d6f5fde
11 changed files with 806 additions and 48 deletions
@@ -61,4 +61,12 @@ public interface ContentTypeHandlerManager {
*/
String handleResult(ActionConfig actionConfig, Object methodResult, Object target)
throws IOException;
/**
* Finds the extension in the url
*
* @param url The url
* @return The extension
*/
String findExtension(String url);
}
@@ -102,9 +102,6 @@ public class DefaultContentTypeHandlerManager implements ContentTypeHandlerManag
if (handler == null) {
String extension = findExtension(req.getRequestURI());
if (extension == null) {
extension = defaultExtension;
}
handler = handlersByExtension.get(extension);
}
return handler;
@@ -117,9 +114,6 @@ public class DefaultContentTypeHandlerManager implements ContentTypeHandlerManag
*/
public ContentTypeHandler getHandlerForResponse(HttpServletRequest req, HttpServletResponse res) {
String extension = findExtension(req.getRequestURI());
if (extension == null) {
extension = defaultExtension;
}
return handlersByExtension.get(extension);
}
@@ -137,33 +131,7 @@ public class DefaultContentTypeHandlerManager implements ContentTypeHandlerManag
String resultCode = null;
HttpServletRequest req = ServletActionContext.getRequest();
HttpServletResponse res = ServletActionContext.getResponse();
if (target instanceof ModelDriven) {
target = ((ModelDriven)target).getModel();
}
boolean statusNotOk = false;
if (methodResult instanceof HttpHeaders) {
HttpHeaders info = (HttpHeaders) methodResult;
resultCode = info.apply(req, res, target);
if (info.getStatus() != SC_OK) {
// Don't return content on a not modified
if (info.getStatus() == SC_NOT_MODIFIED) {
target = null;
} else {
statusNotOk = true;
}
}
} else {
resultCode = (String) methodResult;
}
// Don't return any content for PUT, DELETE, and POST where there are no errors
if (!statusNotOk && !"get".equalsIgnoreCase(req.getMethod())) {
target = null;
}
ContentTypeHandler handler = getHandlerForResponse(req, res);
if (handler != null) {
String extCode = resultCode+"-"+handler.getExtension();
@@ -192,12 +160,12 @@ public class DefaultContentTypeHandlerManager implements ContentTypeHandlerManag
* @param url The url
* @return The extension
*/
protected String findExtension(String url) {
public String findExtension(String url) {
int dotPos = url.lastIndexOf('.');
int slashPos = url.lastIndexOf('/');
if (dotPos > slashPos && dotPos > -1) {
return url.substring(dotPos+1);
}
return null;
return defaultExtension;
}
}
@@ -152,6 +152,14 @@ public class DefaultHttpHeaders implements HttpHeaders {
return status;
}
public void setStatus(int s) {
status = s;
}
public String getResultCode() {
return resultCode;
}
@@ -43,4 +43,15 @@ public interface HttpHeaders {
* The HTTP status code
*/
int getStatus();
/**
* The HTTP status code
*/
void setStatus(int status);
/**
* The result code to process
*/
String getResultCode();
}
@@ -24,17 +24,30 @@ package org.apache.struts2.rest;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.DefaultActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.UnknownHandlerManager;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.HttpHeaderResult;
import org.apache.struts2.rest.handler.ContentTypeHandler;
import org.apache.struts2.rest.handler.HtmlHandler;
/**
@@ -49,11 +62,28 @@ public class RestActionInvocation extends DefaultActionInvocation {
private static final Logger LOG = LoggerFactory.getLogger(RestActionInvocation.class);
private ContentTypeHandlerManager handlerSelector;
private boolean logger;
private String defaultErrorResultName;
protected HttpHeaders httpHeaders;
protected Object target;
protected boolean isFirstInterceptor = true;
protected boolean hasErrors;
protected RestActionInvocation(Map extraContext, boolean pushAction) {
super(extraContext, pushAction);
}
@Inject("struts.rest.logger")
public void setLogger(String value) {
logger = new Boolean(value);
}
@Inject("struts.rest.defaultErrorResultName")
public void setDefaultErrorResultName(String value) {
defaultErrorResultName = value;
}
@Inject
public void setMimeTypeHandlerSelector(ContentTypeHandlerManager sel) {
this.handlerSelector = sel;
@@ -100,7 +130,7 @@ public class RestActionInvocation extends DefaultActionInvocation {
methodResult = method.invoke(action, new Object[0]);
}
return processResult(actionConfig, methodResult);
return saveResult(actionConfig, methodResult);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (InvocationTargetException e) {
@@ -123,16 +153,301 @@ public class RestActionInvocation extends DefaultActionInvocation {
}
}
protected String processResult(ActionConfig actionConfig, Object methodResult) throws IOException {
if (methodResult instanceof Result) {
this.explicitResult = (Result) methodResult;
/**
* Save the result to be used later.
* @param actionConfig
* @param methodResult the result of the action.
* @return the result code to process.
*
* @throws ConfigurationException If it is an incorrect result.
*/
protected String saveResult(ActionConfig actionConfig, Object methodResult) {
if (methodResult instanceof Result) {
explicitResult = (Result) methodResult;
// Wire the result automatically
container.inject(explicitResult);
return null;
} else if (methodResult instanceof HttpHeaders) {
httpHeaders = (HttpHeaders) methodResult;
resultCode = httpHeaders.getResultCode();
} else if (methodResult instanceof String) {
resultCode = (String) methodResult;
} else if (methodResult != null) {
resultCode = handlerSelector.handleResult(actionConfig, methodResult, action);
throw new ConfigurationException("The result type " + methodResult.getClass()
+ " is not allowed. Use the type String, HttpHeaders or Result.");
}
return resultCode;
}
@Override
public String invoke() throws Exception {
long startTime = 0;
boolean executeResult = false;
if (isFirstInterceptor) {
startTime = System.currentTimeMillis();
executeResult = true;
isFirstInterceptor = false;
}
// Normal invoke without execute the result
proxy.setExecuteResult(false);
resultCode = super.invoke();
// Execute the result when the last interceptor has finished
if (executeResult) {
long middleTime = System.currentTimeMillis();
try {
processResult();
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
// Error proccesing the result
LOG.error("Exception processing the result.", e);
if (!ServletActionContext.getResponse().isCommitted()) {
ServletActionContext.getResponse()
.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
stack.set("exception", e);
result = null;
resultCode = null;
processResult();
}
}
// Log execution + result time
logger(startTime, middleTime);
}
return resultCode;
}
protected void processResult() throws Exception {
String timerKey = "processResult: " + getResultCode();
try {
UtilTimerStack.push(timerKey);
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
// Select the target
selectTarget();
// Get the httpHeaders
if (httpHeaders == null) {
httpHeaders = new DefaultHttpHeaders(resultCode);
}
// Apply headers
if (!hasErrors) {
httpHeaders.apply(request, response, target);
} else {
disableCatching(response);
}
// Don't return content on a not modified
if (httpHeaders.getStatus() != HttpServletResponse.SC_NOT_MODIFIED ) {
executeResult();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Result not processed because the status code is not modified.");
}
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
/**
* Execute the current result. If it is an error and no result is selected load
* the default error result (default-error).
*/
private void executeResult() throws Exception {
// Get handler by representation
ContentTypeHandler handler = handlerSelector.getHandlerForResponse(
ServletActionContext.getRequest(), ServletActionContext.getResponse());
// get the result
this.result = createResult();
if (this.result instanceof HttpHeaderResult) {
// execute the result to apply headers and status in every representations
this.result.execute(this);
updateStatusFromResult();
}
if (handler != null && !(handler instanceof HtmlHandler)) {
// Specific representation (json, xml...)
resultCode = handlerSelector.handleResult(
this.getProxy().getConfig(), httpHeaders, target);
} else {
// Normal struts execution (html o other struts result)
findResult();
if (result != null) {
this.result.execute(this);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No result returned for action " + getAction().getClass().getName()
+ " at " + proxy.getConfig().getLocation());
}
}
}
}
/**
* Get the status code from HttpHeaderResult
* and it is saved in the HttpHeaders object.
* @throws Exception
*/
private void updateStatusFromResult() {
if (this.result instanceof HttpHeaderResult) {
try {
Field field = result.getClass().getDeclaredField("status");
if (field != null) {
field.setAccessible(true);
int status = (Integer)field.get(result);
if (status != -1) {
this.httpHeaders.setStatus(status);
}
}
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(e.getMessage(), e);
}
}
}
}
/**
* Find the most appropriate result:
* - Find by result code.
* - If it is an error, find the default error result.
*
* @throws ConfigurationException If not result can be found
*/
private void findResult() throws Exception {
boolean isHttpHeaderResult = false;
if (result != null && this.result instanceof HttpHeaderResult) {
result = null;
isHttpHeaderResult = true;
}
if (result == null && resultCode != null && !Action.NONE.equals(resultCode)
&& unknownHandlerManager.hasUnknownHandlers()) {
// Find result by resultCode
this.result = unknownHandlerManager.handleUnknownResult(
invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
}
if (this.result == null && this.hasErrors && defaultErrorResultName != null) {
// Get default error result
ResultConfig resultConfig = this.proxy.getConfig().getResults()
.get(defaultErrorResultName);
if (resultConfig != null) {
this.result = objectFactory.buildResult(resultConfig,
invocationContext.getContextMap());
if (LOG.isDebugEnabled()) {
LOG.debug("Found default error result.");
}
}
}
if (result == null && resultCode != null &&
!Action.NONE.equals(resultCode) && !isHttpHeaderResult) {
throw new ConfigurationException("No result defined for action "
+ getAction().getClass().getName()
+ " and result " + getResultCode(), proxy.getConfig());
}
}
@SuppressWarnings("unchecked")
protected void selectTarget() {
// Select target (content to return)
Throwable e = (Throwable)stack.findValue("exception");
if (e != null) {
// Exception
target = e;
hasErrors = true;
} else if (action instanceof ValidationAware && ((ValidationAware)action).hasErrors()) {
// Error messages
ValidationAware validationAwareAction = ((ValidationAware)action);
Map errors = new HashMap();
if (validationAwareAction.getActionErrors().size() > 0) {
errors.put("actionErrors", validationAwareAction.getActionErrors());
}
if (validationAwareAction.getFieldErrors().size() > 0) {
errors.put("fieldErrors", validationAwareAction.getFieldErrors());
}
target = errors;
hasErrors = true;
} else if (action instanceof ModelDriven) {
// Model
target = ((ModelDriven)action).getModel();
} else {
target = action;
}
// don't return any content for PUT, DELETE, and POST where there are no errors
if (!hasErrors && !"get".equalsIgnoreCase(ServletActionContext.getRequest().getMethod())) {
target = null;
}
}
private void disableCatching(HttpServletResponse response) {
// No cache
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Last-Modified", 0);
response.setHeader("ETag", "-1");
}
private void logger(long startTime, long middleTime) {
if (logger && LOG.isInfoEnabled()) {
long endTime = System.currentTimeMillis();
long executionTime = middleTime - startTime;
long processResult = endTime - middleTime;
long total = endTime - startTime;
String message = "Executed action [/";
String namespace = getProxy().getNamespace();
if ((namespace != null) && (namespace.trim().length() > 1)) {
message += namespace + "/";
}
message += getProxy().getActionName() + "!" + getProxy().getMethod();
String extension = handlerSelector.findExtension(
ServletActionContext.getRequest().getRequestURI());
if (extension != null) {
message += "!" + extension;
}
if (httpHeaders != null) {
message += "!" + httpHeaders.getStatus();
}
message += "] took " + total + " ms (execution: " + executionTime
+ " ms, result: " + processResult + " ms)";
LOG.info(message);
}
}
}
@@ -106,6 +106,9 @@ public class RestActionMapper extends DefaultActionMapper {
private String newMethodName = "editNew";
private String deleteMethodName = "destroy";
private String putMethodName = "update";
private String optionsMethodName = "options";
private String postContinueMethodName = "createContinue";
private String putContinueMethodName = "updateContinue";
public RestActionMapper() {
}
@@ -154,6 +157,21 @@ public class RestActionMapper extends DefaultActionMapper {
this.putMethodName = putMethodName;
}
@Inject(required=false,value="struts.mapper.optionsMethodName")
public void setOptionsMethodName(String optionsMethodName) {
this.optionsMethodName = optionsMethodName;
}
@Inject(required=false,value="struts.mapper.postContinueMethodName")
public void setPostContinueMethodName(String postContinueMethodName) {
this.postContinueMethodName = postContinueMethodName;
}
@Inject(required=false,value="struts.mapper.putContinueMethodName")
public void setPutContinueMethodName(String putContinueMethodName) {
this.putContinueMethodName = putContinueMethodName;
}
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
@@ -209,8 +227,11 @@ public class RestActionMapper extends DefaultActionMapper {
// If a method hasn't been explicitly named, try to guess using ReST-style patterns
if (mapping.getMethod() == null) {
// Handle uris with no id, possibly ending in '/'
if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) {
if (isOptions(request)) {
mapping.setMethod(optionsMethodName);
// Handle uris with no id, possibly ending in '/'
} else if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) {
// Index e.g. foo
if (isGet(request)) {
@@ -218,7 +239,11 @@ public class RestActionMapper extends DefaultActionMapper {
// Creating a new entry on POST e.g. foo
} else if (isPost(request)) {
mapping.setMethod(postMethodName);
if (isExpectContinue(request)) {
mapping.setMethod(postContinueMethodName);
} else {
mapping.setMethod(postMethodName);
}
}
// Handle uris with an id at the end
@@ -243,7 +268,11 @@ public class RestActionMapper extends DefaultActionMapper {
// Updating an item e.g. foo/1
} else if (isPut(request)) {
mapping.setMethod(putMethodName);
if (isExpectContinue(request)) {
mapping.setMethod(putContinueMethodName);
} else {
mapping.setMethod(putMethodName);
}
}
}
}
@@ -334,4 +363,13 @@ public class RestActionMapper extends DefaultActionMapper {
}
}
protected boolean isOptions(HttpServletRequest request) {
return "options".equalsIgnoreCase(request.getMethod());
}
protected boolean isExpectContinue(HttpServletRequest request) {
String expect = request.getHeader("Expect");
return (expect != null && expect.toLowerCase().contains("100-continue"));
}
}
@@ -21,6 +21,13 @@
package org.apache.struts2.rest;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
@@ -29,7 +36,83 @@ import com.opensymphony.xwork2.ActionSupport;
*/
public class RestActionSupport extends ActionSupport {
public String index() throws Exception {
private static final long serialVersionUID = -889518620073576882L;
private static final String DELETE = "DELETE";
private static final String PUT = "PUT";
private static final String POST = "POST";
private static final String GET = "GET";
private static final String OPTIONS = "OPTIONS";
private static final String DIVIDER = ", ";
/**
* Default execution.
* @return object because it can return string, result or httpHeader.
* @throws Exception
*/
public Object index() throws Exception {
return execute();
}
/**
* Inspect the implemented methods to know the allowed http methods.
*
* @return Include the header "Allow" with the allowed http methods.
*/
public HttpHeaders options() {
String methods = OPTIONS;
Method[] meths = this.getClass().getDeclaredMethods();
for (Method m : meths) {
String methodName = m.getName();
if (!methods.contains(GET) &&
(methodName.equals("index")
|| methodName.equals("show")
|| methodName.equals("edit")
|| methodName.equals("editNew"))) {
methods += DIVIDER + GET;
} else if (methodName.equals("create")) {
methods += DIVIDER + POST;
} else if (methodName.equals("update")) {
methods += DIVIDER + PUT;
}else if (methodName.equals("destroy")) {
methods += DIVIDER + DELETE;
}
}
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
response.addHeader("Allow", methods);
DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
httpHeaders.apply(request, response, this);
httpHeaders.disableCaching().withStatus(HttpServletResponse.SC_OK);
return httpHeaders;
}
/**
* By default, return continue.
* Is possible override the method to return expectation failed.
*
* @return continue
*/
public HttpHeaders createContinue() {
return new DefaultHttpHeaders()
.disableCaching()
.withStatus(HttpServletResponse.SC_CONTINUE);
}
/**
* By default, return continue.
* Is possible override the method to return expectation failed.
*
* @return continue
*/
public HttpHeaders updateContinue() {
return new DefaultHttpHeaders()
.disableCaching()
.withStatus(HttpServletResponse.SC_CONTINUE);
}
}
@@ -40,6 +40,8 @@
<constant name="struts.actionProxyFactory" value="rest" />
<constant name="struts.rest.defaultExtension" value="xhtml" />
<constant name="struts.rest.logger" value="true" />
<constant name="struts.rest.defaultErrorResultName" value="default-error" />
<constant name="struts.mapper.class" value="rest" />
<constant name="struts.mapper.idParameterName" value="id" />
<constant name="struts.action.extension" value="xhtml,,xml,json" />
@@ -180,4 +180,20 @@ public class DefaultHttpHeadersTest extends TestCase {
assertEquals(SC_OK, mockResponse.getStatus());
}
public void testApplyOptions() {
String methods = "OPTIONS, GET, POST, PUT";
String allow = "Allow";
mockResponse.addHeader(allow, methods);
DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
httpHeaders.apply(mockRequest, mockResponse, this);
httpHeaders.disableCaching().withStatus(SC_OK);
assertEquals(methods, mockResponse.getHeader(allow));
assertEquals(SC_OK, mockResponse.getStatus());
}
}
@@ -0,0 +1,270 @@
package org.apache.struts2.rest;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import junit.framework.TestCase;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.HttpHeaderResult;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.DefaultUnknownHandlerManager;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.mock.MockActionProxy;
import com.opensymphony.xwork2.mock.MockInterceptor;
import com.opensymphony.xwork2.util.XWorkTestCaseHelper;
public class RestActionInvocationTest extends TestCase {
RestActionInvocation restActionInvocation;
MockHttpServletRequest request;
MockHttpServletResponse response;
@Override
protected void setUp() throws Exception {
super.setUp();
restActionInvocation = new RestActionInvocationTester();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
ServletActionContext.setRequest(request);
ServletActionContext.setResponse(response);
}
/**
* Test the correct action results: null, String, HttpHeaders, Result
* @throws Exception
*/
public void testSaveResult() throws Exception {
Object methodResult = "index";
ActionConfig actionConfig = restActionInvocation.getProxy().getConfig();
assertEquals("index", restActionInvocation.saveResult(actionConfig, methodResult));
setUp();
methodResult = new DefaultHttpHeaders("show");
assertEquals("show", restActionInvocation.saveResult(actionConfig, methodResult));
assertEquals(methodResult, restActionInvocation.httpHeaders);
setUp();
methodResult = new HttpHeaderResult(HttpServletResponse.SC_ACCEPTED);
assertEquals(null, restActionInvocation.saveResult(actionConfig, methodResult));
assertEquals(methodResult, restActionInvocation.createResult());
setUp();
try {
methodResult = new Object();
restActionInvocation.saveResult(actionConfig, methodResult);
// ko
assertFalse(true);
} catch (ConfigurationException c) {
// ok, object not allowed
}
}
/**
* Test the target selection: exception, error messages, model and null
* @throws Exception
*/
public void testSelectTarget() throws Exception {
// Exception
Exception e = new Exception();
restActionInvocation.getStack().set("exception", e);
restActionInvocation.selectTarget();
assertEquals(e, restActionInvocation.target);
// Error messages
setUp();
String actionMessage = "Error!";
RestActionSupport action = (RestActionSupport)restActionInvocation.getAction();
action.addActionError(actionMessage);
Map errors = new HashMap();
List<String> list = new ArrayList<String>();
list.add(actionMessage);
errors.put("actionErrors", list);
restActionInvocation.selectTarget();
assertEquals(errors, restActionInvocation.target);
// Model with get and no content in post, put, delete
setUp();
RestAction restAction = (RestAction)restActionInvocation.getAction();
List<String> model = new ArrayList<String>();
model.add("Item");
restAction.model = model;
request.setMethod("GET");
restActionInvocation.selectTarget();
assertEquals(model, restActionInvocation.target);
request.setMethod("POST");
restActionInvocation.selectTarget();
assertEquals(null, restActionInvocation.target);
request.setMethod("PUT");
restActionInvocation.selectTarget();
assertEquals(null, restActionInvocation.target);
request.setMethod("DELETE");
restActionInvocation.selectTarget();
assertEquals(null, restActionInvocation.target);
}
/**
* Test the not modified status code.
* @throws Exception
*/
public void testResultNotModified() throws Exception {
request.addHeader("If-None-Match", "123");
request.setMethod("GET");
RestAction restAction = (RestAction)restActionInvocation.getAction();
List<String> model = new ArrayList<String>() {
@Override
public int hashCode() {
return 123;
}
};
model.add("Item");
restAction.model = model;
restActionInvocation.processResult();
assertEquals(SC_NOT_MODIFIED, response.getStatus());
}
/**
* Test the default error result.
* @throws Exception
*/
public void testDefaultErrorResult() throws Exception {
// Exception
Exception e = new Exception();
restActionInvocation.getStack().set("exception", e);
request.setMethod("GET");
RestAction restAction = (RestAction)restActionInvocation.getAction();
List<String> model = new ArrayList<String>();
model.add("Item");
restAction.model = model;
restActionInvocation.setDefaultErrorResultName("default-error");
ResultConfig resultConfig = new ResultConfig.Builder("default-error",
"org.apache.struts2.dispatcher.HttpHeaderResult")
.addParam("status", "123").build();
ActionConfig actionConfig = new ActionConfig.Builder("org.apache.rest",
"RestAction", "org.apache.rest.RestAction")
.addResultConfig(resultConfig)
.build();
((MockActionProxy)restActionInvocation.getProxy()).setConfig(actionConfig);
restActionInvocation.processResult();
assertEquals(123, response.getStatus());
}
public void testNoResult() throws Exception {
RestAction restAction = (RestAction)restActionInvocation.getAction();
List<String> model = new ArrayList<String>();
model.add("Item");
restAction.model = model;
request.setMethod("GET");
restActionInvocation.setResultCode("index");
try {
restActionInvocation.processResult();
// ko
assertFalse(true);
} catch (ConfigurationException c) {
// ok, no result
}
}
/**
* Test the global execution
* @throws Exception
*/
public void testInvoke() throws Exception {
// Default index method return 'success'
((MockActionProxy)restActionInvocation.getProxy()).setMethod("index");
// Define result 'success'
ResultConfig resultConfig = new ResultConfig.Builder("success",
"org.apache.struts2.dispatcher.HttpHeaderResult")
.addParam("status", "123").build();
ActionConfig actionConfig = new ActionConfig.Builder("org.apache.rest",
"RestAction", "org.apache.rest.RestAction")
.addResultConfig(resultConfig)
.build();
((MockActionProxy)restActionInvocation.getProxy()).setConfig(actionConfig);
request.setMethod("GET");
restActionInvocation.invoke();
assertEquals(123, response.getStatus());
}
class RestActionInvocationTester extends RestActionInvocation {
RestActionInvocationTester() {
super(new HashMap<String,String>(), true);
List<InterceptorMapping> interceptorMappings = new ArrayList<InterceptorMapping>();
MockInterceptor mockInterceptor = new MockInterceptor();
mockInterceptor.setFoo("interceptor");
mockInterceptor.setExpectedFoo("interceptor");
interceptorMappings.add(new InterceptorMapping("interceptor", mockInterceptor));
interceptors = interceptorMappings.iterator();
MockActionProxy actionProxy = new MockActionProxy();
ActionConfig actionConfig = new ActionConfig.Builder("org.apache.rest",
"RestAction", "org.apache.rest.RestAction").build();
actionProxy.setConfig(actionConfig);
proxy = actionProxy;
action = new RestAction();
setMimeTypeHandlerSelector(new DefaultContentTypeHandlerManager());
unknownHandlerManager = new DefaultUnknownHandlerManager();
try {
XWorkTestCaseHelper.setUp();
} catch (Exception e) {
throw new RuntimeException(e);
}
invocationContext = ActionContext.getContext();
container = ActionContext.getContext().getContainer();
stack = ActionContext.getContext().getValueStack();
objectFactory = container.getInstance(ObjectFactory.class);
}
}
class RestAction extends RestActionSupport implements ModelDriven<List<String>> {
List<String> model;
public List<String> getModel() {
return model;
}
}
}
@@ -191,5 +191,44 @@ public class RestActionMapperTest extends TestCase {
assertEquals(expectedName, mapping.getName());
assertEquals(expectedNamespace, mapping.getNamespace());
}
public void testOptionsMapping() throws Exception {
req.setRequestURI("/myapp/animals/dog");
req.setServletPath("/animals/dog");
req.setMethod("OPTIONS");
ActionMapping mapping = mapper.getMapping(req, configManager);
assertEquals("/animals", mapping.getNamespace());
assertEquals("dog", mapping.getName());
assertEquals("options", mapping.getMethod());
}
public void testPostContinueMapping() throws Exception {
req.setRequestURI("/myapp/animals/dog");
req.setServletPath("/animals/dog");
req.setMethod("POST");
req.addHeader("Expect", "100-continue");
ActionMapping mapping = mapper.getMapping(req, configManager);
assertEquals("/animals", mapping.getNamespace());
assertEquals("dog", mapping.getName());
assertEquals("createContinue", mapping.getMethod());
}
public void testPutContinueMapping() throws Exception {
req.setRequestURI("/myapp/animals/dog/fido");
req.setServletPath("/animals/dog/fido");
req.setMethod("PUT");
req.addHeader("Expect", "100-continue");
ActionMapping mapping = mapper.getMapping(req, configManager);
assertEquals("/animals", mapping.getNamespace());
assertEquals("dog", mapping.getName());
assertEquals("updateContinue", mapping.getMethod());
assertEquals("fido", ((String[])mapping.getParams().get("id"))[0]);
}
}