This is an automated email from the ASF dual-hosted git repository.

thiagohp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git


The following commit(s) were added to refs/heads/master by this push:
     new 3c83ffa  TAP5-2698: Support for Servlet API 3.0+ asynchronous requests
3c83ffa is described below

commit 3c83ffa5735ca8b5bad8624e517b7b89615802c8
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Sat Dec 11 17:06:27 2021 -0300

    TAP5-2698: Support for Servlet API 3.0+ asynchronous requests
    
    TAP5-2698: Adding support for async timeout
---
 tapestry-core/src/test/app1/WEB-INF/web.xml        |  12 +-
 .../apache/tapestry5/http/AsyncRequestHandler.java |  53 +++++++
 .../http/AsyncRequestHandlerResponse.java          | 171 +++++++++++++++++++++
 .../org/apache/tapestry5/http/TapestryFilter.java  |  71 ++++++++-
 .../http/internal/AsyncRequestService.java         |  33 ++++
 .../internal/services/AsyncRequestServiceImpl.java |  54 +++++++
 .../tapestry5/http/modules/TapestryHttpModule.java |   4 +
 7 files changed, 391 insertions(+), 7 deletions(-)

diff --git a/tapestry-core/src/test/app1/WEB-INF/web.xml 
b/tapestry-core/src/test/app1/WEB-INF/web.xml
index bd71436..c4c2489 100644
--- a/tapestry-core/src/test/app1/WEB-INF/web.xml
+++ b/tapestry-core/src/test/app1/WEB-INF/web.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 
-   Copyright 2006, 2010 The Apache Software Foundation
+   Copyright 2006, 2010, 2021 The Apache Software Foundation
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 -->
-
-<!DOCTYPE web-app
-        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
-        "http://java.sun.com/dtd/web-app_2_3.dtd";>
-<web-app>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd";
+       version="3.0">
     <display-name>Integration Test App 1</display-name>
     <context-param>
         <param-name>tapestry.app-package</param-name>
@@ -35,6 +34,7 @@
     <filter>
         <filter-name>app</filter-name>
         <filter-class>org.apache.tapestry5.TapestryFilter</filter-class>
+        <async-supported>true</async-supported>
     </filter>
     <filter-mapping>
         <filter-name>app</filter-name>
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandler.java
 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandler.java
new file mode 100644
index 0000000..9271375
--- /dev/null
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandler.java
@@ -0,0 +1,53 @@
+// Licensed 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.tapestry5.http;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.http.internal.AsyncRequestService;
+
+/**
+ * <p>
+ * Service whose implementations define whether a given request should be 
handled
+ * asynchronously or not and, if yes, which {@link Executor} (usually, a 
thread pool,
+ * but not necessarily) should handle it, possibly different {@link 
HttpServletRequest}
+ * and {@link HttpServletResponse} objects to be used when calling 
+ * {@linkplain} HttpServletRequest#startAsync()} and an optional {@linkplain 
AsyncListener}.
+ * <p>
+ * <p>
+ * If one {@link AsyncRequestHandler} doesn't tells the request should be 
asynchronous,
+ * the next one contributed to {@link AsyncRequestService} will be called
+ * and so on until one says the request should be asynchronous or all of them
+ * were called and the request will be synchronous.
+ * </p>
+ * @see AsyncRequestService
+ * @see Executor
+ * @see ExecutorService
+ * @see Executors
+ */
+public interface AsyncRequestHandler 
+{
+    
+    /**
+     * Returns whether this request is handled by this handler. If not,
+     * it should return {@link AsyncRequestHandlerResponse#notHandled()}. 
+     * @return a non-null {@linkplain AsyncRequestHandlerResponse}.
+     */
+    AsyncRequestHandlerResponse handle(HttpServletRequest request, 
HttpServletResponse response);
+
+}
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
new file mode 100644
index 0000000..aec82c1
--- /dev/null
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/AsyncRequestHandlerResponse.java
@@ -0,0 +1,171 @@
+// Licensed 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.tapestry5.http;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Class used by {@linkplain AsyncRequestHandler} to return information on how 
to handle
+ * a request.
+ * @see AsyncRequestHandler
+ */
+public class AsyncRequestHandlerResponse 
+{
+    
+    private static final AsyncRequestHandlerResponse NOT_HANDLED = 
+            new AsyncRequestHandlerResponse(false);
+    
+    final private boolean async;
+    
+    final private Executor executor;
+    
+    private HttpServletRequest request;
+    
+    private HttpServletResponse response;
+    
+    private AsyncListener listener;
+    
+    private long timeout;
+    
+    /**
+     * Creates an instance with a given {@link Executor}. It cannot be null.
+     * If you want an instance with a non-async response, use {@link 
#notHandled()} instead.
+     * @param executor a non-null {@link Executor}.
+     */
+    public AsyncRequestHandlerResponse(Executor executor)
+    {
+        this(true, executor);
+    }
+    
+    private AsyncRequestHandlerResponse(boolean async, Executor executor)
+    {
+        Objects.requireNonNull(executor, "Parameter executor cannot be null");
+        this.async = async;
+        this.executor = executor;
+    }
+    
+    private AsyncRequestHandlerResponse(boolean async)
+    {
+        this.async = async;
+        executor = null;
+    }
+
+    /**
+     * Defines a different request and response to be passed to {@link 
HttpServletRequest#startAsync(javax.servlet.ServletRequest, 
javax.servlet.ServletResponse)}.
+     * Both cannot be null.
+     */
+    public AsyncRequestHandlerResponse with(HttpServletRequest request, 
HttpServletResponse response)
+    {
+        Objects.requireNonNull(request, "Parameter request cannot be null");
+        Objects.requireNonNull(response, "Parameter response cannot be null");
+        this.request = request;
+        this.response = response;
+        return this;
+    }
+
+    /**
+     * Defines a listener to be added to the asynchronous request. It cannot 
be null.
+     */
+    public AsyncRequestHandlerResponse with(AsyncListener listener)
+    {
+        Objects.requireNonNull(listener, "Parameter listener cannot be null");
+        this.listener = listener;
+        return this;
+    }
+    
+    /**
+     * Sets the timeout for this asynchronous request in milliseconds.
+     */
+    public AsyncRequestHandlerResponse withTimeout(long timeout)
+    {
+        this.timeout = timeout;
+        return this;
+    }
+    
+    /**
+     * Returns a response saying this {@linkplain AsyncRequestHandler} doesn't 
handle this request.
+     * @return an {@link AsyncRequestHandlerResponse}.
+     */
+    public static AsyncRequestHandlerResponse notHandled()
+    {
+        return NOT_HANDLED;
+    }
+
+    /**
+     * Returns whether the request should be processed asynchronously or not.
+     */
+    public boolean isAsync() 
+    {
+        return async;
+    }
+
+    /**    
+     * Returns the {@link Executor} to be used to process the request.
+     */
+    public Executor getExecutor() 
+    {
+        return executor;
+    }
+
+    /**
+     * Returns the request to be used with {@link 
HttpServletRequest#startAsync()} or null.
+     */
+    public HttpServletRequest getRequest() 
+    {
+        return request;
+    }
+
+    /**
+     * Returns the response to be used with {@link 
HttpServletRequest#startAsync()} or null.
+     */
+    public HttpServletResponse getResponse() 
+    {
+        return response;
+    }
+
+    /**
+     * Returns the listener to be added to the asynchronous request or null.
+     */
+    public AsyncListener getListener() 
+    {
+        return listener;
+    }
+    
+    /**
+     * Returns whether a request and a response were set in this object.
+     */
+    public boolean isHasRequestAndResponse()
+    {
+        return request != null && response != null;
+    }
+
+    /**
+     * Returns the timeout, in milliseconds, for the asynchronous request. Any 
value
+     * less than or equal zero is considered not having set a timeout.
+     */
+    public long getTimeout() {
+        return timeout;
+    }
+
+    @Override
+    public String toString() 
+    {
+        return "AsyncRequestHandlerResponse [async=" + async + ", executor=" + 
executor + ", request=" + request + ", response=" + response + ", listener="
+                + listener + "]";
+    }
+    
+}
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
index 7aea81f..3fdb98d 100644
--- a/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
+++ b/tapestry-http/src/main/java/org/apache/tapestry5/http/TapestryFilter.java
@@ -14,6 +14,7 @@ package org.apache.tapestry5.http;
 
 import java.io.IOException;
 
+import javax.servlet.AsyncContext;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -24,6 +25,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.tapestry5.http.internal.AsyncRequestService;
 import org.apache.tapestry5.http.internal.ServletContextSymbolProvider;
 import org.apache.tapestry5.http.internal.SingleKeySymbolProvider;
 import org.apache.tapestry5.http.internal.TapestryAppInitializer;
@@ -67,6 +69,8 @@ public class TapestryFilter implements Filter
     private Registry registry;
 
     private HttpServletRequestHandler handler;
+    
+    private AsyncRequestService asyncRequestService;
 
     /**
      * Key under which the Tapestry IoC {@link 
org.apache.tapestry5.ioc.Registry} is stored in the
@@ -117,6 +121,7 @@ public class TapestryFilter implements Filter
         registry.performRegistryStartup();
 
         handler = registry.getService("HttpServletRequestHandler", 
HttpServletRequestHandler.class);
+        asyncRequestService = registry.getService("AsyncRequestService", 
AsyncRequestService.class);
 
         init(registry);
 
@@ -165,7 +170,7 @@ public class TapestryFilter implements Filter
         return new Class[0];
     }
 
-    public final void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain)
+    public final void runFilter(ServletRequest request, ServletResponse 
response, FilterChain chain)
             throws IOException, ServletException
     {
         try
@@ -182,6 +187,70 @@ public class TapestryFilter implements Filter
             registry.cleanupThread();
         }
     }
+    
+    public final void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain)
+            throws IOException, ServletException
+    {
+
+        AsyncRequestHandlerResponse handlerResponse = 
asyncRequestService.handle(
+                (HttpServletRequest) request, (HttpServletResponse) response);
+        
+        if (handlerResponse.isAsync())
+        {
+            AsyncContext asyncContext;
+            if (handlerResponse.isHasRequestAndResponse())
+            {
+                asyncContext = 
request.startAsync(handlerResponse.getRequest(), handlerResponse.getResponse());
+            }
+            else
+            {
+                asyncContext = request.startAsync();
+            }
+            if (handlerResponse.getListener() != null)
+            {
+                asyncContext.addListener(handlerResponse.getListener());
+            }
+            if (handlerResponse.getTimeout() > 0)
+            {
+                asyncContext.setTimeout(handlerResponse.getTimeout());
+            }
+            handlerResponse.getExecutor().execute(
+                    new ExceptionCatchingRunnable(() -> {
+                        runFilter(request, response, chain);
+                        asyncContext.complete();
+                    }));
+        }
+        else
+        {
+            runFilter(request, response, chain);
+        }
+    }
+    
+    private static interface ExceptionRunnable
+    {
+        void run() throws Exception;
+    }
+    
+    private final class ExceptionCatchingRunnable implements Runnable
+    {
+        private final ExceptionRunnable runnable;
+        
+        public ExceptionCatchingRunnable(ExceptionRunnable runnable) 
+        {
+            this.runnable = runnable;
+        }
+        public void run() 
+        {
+            try 
+            {
+                runnable.run();
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
 
     /**
      * Shuts down and discards the registry. Invokes
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/AsyncRequestService.java
 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/AsyncRequestService.java
new file mode 100644
index 0000000..a7f4d52
--- /dev/null
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/AsyncRequestService.java
@@ -0,0 +1,33 @@
+// Licensed 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.tapestry5.http.internal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.http.AsyncRequestHandler;
+import org.apache.tapestry5.http.AsyncRequestHandlerResponse;
+import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
+
+/**
+ * Service that handles Tapestry's support for asynchronous Servlet API 
requests.
+ * @see AsyncRequestHandler
+ */
+@UsesOrderedConfiguration(AsyncRequestHandler.class)
+public interface AsyncRequestService 
+{
+    /**
+     * Returns an {@linkplain AsyncRequestHandlerResponse} describing how to 
handle this
+     * request concering being async or not.
+     */
+    AsyncRequestHandlerResponse handle(HttpServletRequest request, 
HttpServletResponse response);
+}
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/AsyncRequestServiceImpl.java
 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/AsyncRequestServiceImpl.java
new file mode 100644
index 0000000..964aa60
--- /dev/null
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/AsyncRequestServiceImpl.java
@@ -0,0 +1,54 @@
+// Licensed 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.tapestry5.http.internal.services;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.http.AsyncRequestHandler;
+import org.apache.tapestry5.http.AsyncRequestHandlerResponse;
+import org.apache.tapestry5.http.internal.AsyncRequestService;
+
+/**
+ * Service that handles Tapestry's support for asynchronous Servlet API 
requests.
+ */
+public class AsyncRequestServiceImpl implements AsyncRequestService
+{
+    
+    final private List<AsyncRequestHandler> handlers;
+
+    public AsyncRequestServiceImpl(List<AsyncRequestHandler> handlers) 
+    {
+        super();
+        this.handlers = handlers;
+    }
+
+    public AsyncRequestHandlerResponse handle(HttpServletRequest request, 
HttpServletResponse response)  
+    {
+        
+        AsyncRequestHandlerResponse handlerResponse = 
AsyncRequestHandlerResponse.notHandled();
+        
+        for (AsyncRequestHandler asyncRequestHandler : handlers) 
+        {
+            handlerResponse = asyncRequestHandler.handle(request, response);
+            if (handlerResponse.isAsync())
+            {
+                break;
+            }
+        }
+        
+        return handlerResponse;
+    }
+    
+}
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
index 87f500a..8e39574 100644
--- 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/modules/TapestryHttpModule.java
@@ -30,9 +30,11 @@ import 
org.apache.tapestry5.commons.internal.util.TapestryException;
 import org.apache.tapestry5.commons.services.CoercionTuple;
 import org.apache.tapestry5.http.OptimizedSessionPersistedObject;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
+import org.apache.tapestry5.http.internal.AsyncRequestService;
 import org.apache.tapestry5.http.internal.TypeCoercerHttpRequestBodyConverter;
 import org.apache.tapestry5.http.internal.gzip.GZipFilter;
 import org.apache.tapestry5.http.internal.services.ApplicationGlobalsImpl;
+import org.apache.tapestry5.http.internal.services.AsyncRequestServiceImpl;
 import org.apache.tapestry5.http.internal.services.BaseURLSourceImpl;
 import org.apache.tapestry5.http.internal.services.ContextImpl;
 import 
org.apache.tapestry5.http.internal.services.DefaultSessionPersistedObjectAnalyzer;
@@ -102,6 +104,7 @@ public final class TapestryHttpModule {
         binder.bind(BaseURLSource.class, BaseURLSourceImpl.class);
         binder.bind(ResponseCompressionAnalyzer.class, 
ResponseCompressionAnalyzerImpl.class);
         binder.bind(RestSupport.class, RestSupportImpl.class);
+        binder.bind(AsyncRequestService.class, AsyncRequestServiceImpl.class);
     }
     
     /**
@@ -316,6 +319,7 @@ public final class TapestryHttpModule {
         configuration.addInstance("TypeCoercer", 
TypeCoercerHttpRequestBodyConverter.class);
     }
     
+    @SuppressWarnings("rawtypes")
     public static void 
contributeTypeCoercer(MappedConfiguration<CoercionTuple.Key, CoercionTuple> 
configuration)
     {
         CoercionTuple.add(configuration, HttpServletRequest.class, 
String.class, TapestryHttpModule::toString);

Reply via email to