Skip to content

Commit

Permalink
#37 : alternate signature for async event handlers : public void foo(…
Browse files Browse the repository at this point in the history
…AsyncResolution). AsyncResolution is no more a base class to extend but instead it's a param to the event handler that allows to complete() the async processing.
  • Loading branch information
vankeisb committed Jan 23, 2016
1 parent 95fdf51 commit 538c34f
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,81 +43,63 @@ public Resolution display() {
* asynchronously fetch data from a remote web service (github)
* and set instance fields for use in the view.
*/
public Resolution asyncEvent() {

// we return an AsyncResolution : this triggers the asynchronous servlet mode...
return new AsyncResolution() {

// only this method to implement. you must complete() or dispatch() yourself.
@Override
protected void executeAsync() throws Exception {

// we use an Async Http Client in order to call the github web service as a demo.
// the async http client calls on of the lambdas when he's done, and
// then we dispatch to a JSP, completing the async request.

HttpHost host = new HttpHost("api.github.com", 443, "https");
new AsyncHttpClient(host)
.buildRequest("/repos/StripesFramework/stripes/commits")
.completed(result -> {

// response is returned, deserialize result
status = result.getStatusLine().getStatusCode();
if (status == 200) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
result.getEntity().writeTo(bos);
bos.close();
ghResponse = bos.toString("UTF-8");
} catch (Exception e) {
clientException = e;
}
dispatch(JSP_PATH);
} else {
ghResponse = result.getStatusLine().getReasonPhrase();
dispatch(JSP_PATH);
}

})
.failed(ex -> {

// http client failure
clientException = ex;
dispatch(JSP_PATH);

})
.cancelled(() -> {

// just for demo, we never call it...
cancelled = true;
dispatch(JSP_PATH);

})
.get(); // trigger async request
}
};
public void asyncEvent(AsyncResolution async) {

// we use an Async Http Client in order to call the github web service as a demo.
// the async http client calls back one of the lambdas when it's done, and
// then we complete the async request.

final Resolution forwardResolution = new ForwardResolution(JSP_PATH);
HttpHost host = new HttpHost("api.github.com", 443, "https");
new AsyncHttpClient(host)
.buildRequest("/repos/StripesFramework/stripes/commits")
.completed(result -> {

// response is returned, deserialize result
status = result.getStatusLine().getStatusCode();
if (status == 200) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
result.getEntity().writeTo(bos);
bos.close();
ghResponse = bos.toString("UTF-8");
} catch (Exception e) {
clientException = e;
}
async.complete(forwardResolution);
} else {
ghResponse = result.getStatusLine().getReasonPhrase();
async.complete(forwardResolution);
}

})
.failed(ex -> {

// http client failure
clientException = ex;
async.complete(forwardResolution);

})
.cancelled(() -> {

// just for demo, we never call it...
cancelled = true;
async.complete(forwardResolution);

})
.get(); // trigger async request
}

@DontValidate
public Resolution asyncEventThatTimeouts() {
return new AsyncResolution() {
@Override
protected void executeAsync() throws Exception {
getAsyncContext().setTimeout(1000);
getResponse().getWriter().write("OK");
// never call complete/dispatch...
}
};
public void asyncEventThatTimeouts(AsyncResolution r) throws Exception {
r.getAsyncContext().setTimeout(1000);
r.getResponse().getWriter().write("OK");
// never call complete/dispatch...
}

@DontValidate
public Resolution asyncEventThatThrows() {
return new AsyncResolution() {
@Override
protected void executeAsync() throws Exception {
throw new RuntimeException("WTF");
}
};
public void asyncEventThatThrows(AsyncResolution r) {
throw new RuntimeException("BOOM");
}

// getters for instance fields that have been set by event method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.lang.reflect.Method;

public abstract class AsyncResolution implements Resolution {
public class AsyncResolution implements Resolution {

private final HttpServletRequest request;
private final HttpServletResponse response;
private final Object bean;
private final Method handler;
private AsyncContext asyncContext;

public AsyncResolution(HttpServletRequest request, HttpServletResponse response, Object bean, Method handler) {
this.request = request;
this.response = response;
this.bean = bean;
this.handler = handler;
}

public AsyncContext getAsyncContext() {
return asyncContext;
}
Expand All @@ -28,8 +38,6 @@ public void setContext(ActionBeanContext context) {
this.context = context;
}

private HttpServletRequest request;
private HttpServletResponse response;

public HttpServletRequest getRequest() {
return request;
Expand All @@ -40,29 +48,25 @@ public HttpServletResponse getResponse() {
}

@Override
public final void execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
this.request = request;
this.response = response;
executeAsync();
public void execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
// invoke the handler (start async has been done already) and let it complete...
handler.invoke(bean, this);
}

protected abstract void executeAsync() throws Exception;

protected void dispatch(String path) {
public void dispatch(String path) {
getAsyncContext().dispatch(path);
}

protected void complete() {
public void complete() {
getAsyncContext().complete();
}

protected void complete(Resolution resolution) {
public void complete(Resolution resolution) {
try {
resolution.execute(getRequest(), getResponse());
} catch (Exception e) {
throw new RuntimeException(e);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@
package net.sourceforge.stripes.controller;

import java.lang.annotation.Annotation;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DontBind;
import net.sourceforge.stripes.action.DontValidate;
import net.sourceforge.stripes.action.Resolution;

import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.util.HtmlUtil;
Expand Down Expand Up @@ -49,15 +46,6 @@
import java.util.TreeSet;
import java.util.WeakHashMap;
import javax.servlet.http.HttpServletResponse;
import net.sourceforge.stripes.action.DELETE;
import net.sourceforge.stripes.action.ErrorResolution;
import net.sourceforge.stripes.action.GET;
import net.sourceforge.stripes.action.HEAD;
import net.sourceforge.stripes.action.HttpRequestMethod;
import net.sourceforge.stripes.action.JsonBuilder;
import net.sourceforge.stripes.action.POST;
import net.sourceforge.stripes.action.PUT;
import net.sourceforge.stripes.action.RestActionBean;

/**
* Helper class that contains much of the logic used when dispatching requests
Expand Down Expand Up @@ -657,7 +645,17 @@ public Resolution intercept(ExecutionContext ctx) throws Exception {
// a exception to be handled differently for RestActionBeans that regular
// ActionBeans, they will need to write this code accordingly in their
// ExceptionHandler class.
Object returnValue = handler.invoke(bean);
final Object returnValue;
if (NameBasedActionResolver.isAsyncEventHandler(handler)) {
returnValue = new AsyncResolution(
ctx.getActionBeanContext().getRequest(),
ctx.getActionBeanContext().getResponse(),
bean,
handler
);
} else {
returnValue = handler.invoke(bean);
}

fillInValidationErrors(ctx);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ protected void service(final HttpServletRequest request, final HttpServletRespon
// start async processing
log.debug("Starting async processing from action ", ctx.getActionBean());
AsyncContext asyncContext = request.startAsync(request, response);
AsyncResolution asyncResolution = (AsyncResolution)resolution;
asyncResolution.setAsyncContext(asyncContext);
asyncResolution.setContext(ctx.getActionBeanContext());
final PageContext pc = pageContext;
// register listener for finalizing the async processing
asyncContext.addListener(new AsyncListener() {
Expand Down Expand Up @@ -237,9 +240,6 @@ public void onStartAsync(AsyncEvent event) throws IOException {
"response=", event.getSuppliedResponse());
}
});
AsyncResolution asyncResolution = (AsyncResolution)resolution;
asyncResolution.setAsyncContext(asyncContext);
asyncResolution.setContext(ctx.getActionBeanContext());
}
executeResolution(ctx, resolution);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
*/
package net.sourceforge.stripes.controller;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.util.Literal;
Expand All @@ -26,6 +23,7 @@
import javax.servlet.ServletContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -243,9 +241,24 @@ public String getHandledEvent(Method handler) {
name = handler.getName();
}

if (name == null && isAsyncEventHandler(handler)) {
name = handler.getName();
}

return name;
}

public static boolean isAsyncEventHandler(Method handler) {
if (!Modifier.isAbstract(handler.getModifiers())
&& handler.getReturnType().equals(Void.TYPE)
&& handler.getParameterCount() == 1) {
// look at arg type
Parameter p = handler.getParameters()[0];
return AsyncResolution.class.isAssignableFrom(p.getType());
}
return false;
}

/**
* <p>Overridden to trap the exception that is thrown when a URL cannot be mapped to an
* ActionBean and then attempt to construct a dummy ActionBean that will forward the
Expand Down
Loading

0 comments on commit 538c34f

Please sign in to comment.