package net.sourceforge.stripes.controller; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.exception.StripesRuntimeException; import net.sourceforge.stripes.util.Log; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; /** * Used by asynchrounous event handlers. * Instances of this class are passed by Stripes to asynchronous event handlers, * and allow to complete the asynchronous processing. * * Needs an abstract + concrete implementation because we * do not want to depend on Servlet3 APIs at runtime, so that * Stripes continues to run in Servlet2 containers. */ public abstract class AsyncResponse implements Resolution { private static final Log log = Log.getInstance(AsyncResponse.class); private static final String REQ_ATTR_NAME = "__Stripes_Async_Resolution"; private final HttpServletRequest request; private final HttpServletResponse response; private final ActionBean bean; private final Method handler; private final List<AsyncListener> listeners = new ArrayList<AsyncListener>(); // store static reference to impl constructor // in order to avoid useless lookups private static Constructor<?> ctor = null; private boolean handlerInvoked = false; static { try { HttpServletRequest.class.getMethod("startAsync"); Class<?> impl = Class.forName("net.sourceforge.stripes.controller.AsyncResponseServlet3"); ctor = impl.getDeclaredConstructor( HttpServletRequest.class, HttpServletResponse.class, ActionBean.class, Method.class); } catch (NoSuchMethodException e) { // servlet3 not available log.info("Container is not using Servlet3 : Async event handlers will throw runtime exceptions."); } catch (Exception e) { // should not happen unless we break internals (bad refactor etc). log.error("Exception while initializing AsyncResponse implementation class.", e); throw new RuntimeException(e); } } private Runnable cleanupCallback; AsyncResponse(HttpServletRequest request, HttpServletResponse response, ActionBean bean, Method handler) { this.request = request; this.response = response; this.bean = bean; this.handler = handler; // bind to request so that Resolutions can access request.setAttribute(REQ_ATTR_NAME, this); } /** * Return the AsyncResponse bound to the request, if any. * Primarily used by Resolutions in order to complete processing * accordingly when async is started. * @param request the request * @return the AsyncResponse or null */ public static AsyncResponse get(HttpServletRequest request) { return (AsyncResponse)request.getAttribute(REQ_ATTR_NAME); } void setCleanupCallback(Runnable cleanupCallback) { this.cleanupCallback = cleanupCallback; } void cleanup() { if (cleanupCallback != null) { cleanupCallback.run(); } } /** * Return the request associated to asychronous processing. * @return the http request */ public HttpServletRequest getRequest() { return request; } /** * Return the response associated to asynchronous processing. * @return the http response */ public HttpServletResponse getResponse() { return response; } /** * Called by Stripes internally in order to execute the asynchonous event. * You should neved need to invoke this method yourself. * @param request the current HttpServletRequest * @param response the current HttpServletResponse * @throws Exception if the event handler throws an Exception */ @Override public final void execute(HttpServletRequest request, HttpServletResponse response) throws Exception { // invoke the handler (start async has been done already) and let it complete... if (handlerInvoked) { throw new StripesRuntimeException("Handler already invoked."); } handlerInvoked = true; log.debug("Invoking async event ", handler.getName(), " on bean ", bean); handler.invoke(bean, this); } /** * Adds a listener to this async response. Listeners can be used to * be notified of async lifecycle events. * @param listener the listener to add. */ public void addListener(AsyncListener listener) { listeners.add(listener); } void notifyListenersComplete() { AsyncEvent event = new AsyncEvent(this, null); for (AsyncListener listener : listeners) { try { listener.onComplete(event); } catch (Exception e) { log.error("Error notifying listener " + listener, e); } } } // void notifyListenersStartAsync() { // AsyncEvent event = new AsyncEvent(this, null); // for (AsyncListener listener : listeners) { // try { // listener.onStartAsync(event); // } catch (Exception e) { // log.error("Error notifying listener " + listener, e); // } // } // } void notifyListenersError(Throwable error) { AsyncEvent event = new AsyncEvent(this, error); for (AsyncListener listener : listeners) { try { listener.onError(event); } catch (Exception e) { log.error("Error notifying listener " + listener, e); } } } void notifyListenersTimeout() { AsyncEvent event = new AsyncEvent(this, null); for (AsyncListener listener : listeners) { try { listener.onTimeout(event); } catch (Exception e) { log.error("Error notifying listener " + listener, e); } } } /** * Completes asynchronous processing. */ public abstract void complete(); /** * Executes passed resolution, and completes asynchronous processing. */ public abstract void complete(Resolution resolution); /** * Dispatches to a web application resource * @param path the path to dispatch to */ public abstract void dispatch(String path); /** * Return the timeout for async requests * @return the timeout in milliseconds */ public abstract long getTimeout(); /** * Set the timeout for async requests * @param timeout the timout in milliseconds */ public abstract void setTimeout(long timeout); static AsyncResponse newInstance(HttpServletRequest request, HttpServletResponse response, ActionBean bean, Method handler) { if (ctor == null) { throw new StripesRuntimeException("Async events are not available in your container (requires Servlet3+)."); } try { Object o = ctor.newInstance(request, response, bean, handler); return (AsyncResponse)o; } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }