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

reta pushed a commit to branch 3.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit b9ea2fb23b3542bc4d006f5124e4c4ee69de97b2
Author: Andy McCright <[email protected]>
AuthorDate: Tue Jun 1 13:13:45 2021 -0500

    Run Async systests with no CDI or MP Config
    
    Signed-off-by: Andy McCright <[email protected]>
---
 systests/microprofile/client/nocdi/pom.xml         | 130 +++++++++
 .../microprofile/rest/client/AsyncMethodTest.java  | 239 +++++++++++++++++
 .../rest/client/AsyncThreadingTest.java            | 296 +++++++++++++++++++++
 .../mock/AsyncClientWithCompletionStage.java       |  48 ++++
 .../AsyncInvocationInterceptorFactoryTestImpl.java |  70 +++++
 ...AsyncInvocationInterceptorFactoryTestImpl2.java |  65 +++++
 .../rest/client/mock/ThreadLocalClientFilter.java  |  48 ++++
 7 files changed, 896 insertions(+)

diff --git a/systests/microprofile/client/nocdi/pom.xml 
b/systests/microprofile/client/nocdi/pom.xml
new file mode 100644
index 0000000..54358e4
--- /dev/null
+++ b/systests/microprofile/client/nocdi/pom.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0"?>
+<!--
+  ~  Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <parent>
+        <artifactId>cxf-microprofile-tck</artifactId>
+        <groupId>org.apache.cxf.systests</groupId>
+        <version>3.3.12-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.cxf.systests</groupId>
+    <artifactId>cxf-systests-microprofile-async</artifactId>
+    <name>Apache CXF MicroProfile No-CDI/Config Sys Tests</name>
+    <description>Apache CXF System Tests - MicroProfile Rest Client 
No-CDI/Config Tests</description>
+    <url>https://cxf.apache.org</url>
+    <properties>
+        
<cxf.module.name>org.apache.cxf.systests.microprofile.nocdi</cxf.module.name>
+        <cxf.wiremock.params>--port=8765</cxf.wiremock.params>
+    </properties>
+        <dependencies>
+            <dependency>
+                <groupId>org.hamcrest</groupId>
+                <artifactId>hamcrest-all</artifactId>
+                <version>1.3</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>commons-logging</groupId>
+                <artifactId>commons-logging</artifactId>
+                <version>${cxf.commons-logging.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>jakarta.ws.rs</groupId>
+                <artifactId>jakarta.ws.rs-api</artifactId>
+                <version>${cxf.javax.ws.rs.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.microprofile.rest.client</groupId>
+                <artifactId>microprofile-rest-client-api</artifactId>
+                <version>${cxf.microprofile.rest.client.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.johnzon</groupId>
+                <artifactId>johnzon-core</artifactId>
+                <version>${cxf.johnzon.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>javax.json</groupId>
+                <artifactId>javax.json-api</artifactId>
+                <version>${cxf.json.api.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-jdk14</artifactId>
+                <version>${cxf.slf4j.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.geronimo.config</groupId>
+                <artifactId>geronimo-config-impl</artifactId>
+                <version>${cxf.geronimo.config.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.cxf</groupId>
+                <artifactId>cxf-rt-rs-mp-client</artifactId>
+                <version>${project.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.johnzon</groupId>
+                <artifactId>johnzon-jsonb</artifactId>
+                <version>${cxf.johnzon.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.microprofile.rest.client</groupId>
+                <artifactId>microprofile-rest-client-tck</artifactId>
+                <version>${cxf.microprofile.rest.client.version}</version>
+                <scope>test</scope>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.testng</groupId>
+                        <artifactId>testng</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.jboss.arquillian.testng</groupId>
+                        <artifactId>arquillian-testng-container</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.reactivestreams</groupId>
+                        <artifactId>reactive-streams-tck</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <dependency>
+                <groupId>com.github.tomakehurst</groupId>
+                <artifactId>wiremock</artifactId>
+                <version>${cxf.wiremock.version}</version>
+                <scope>test</scope>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.xmlunit</groupId>
+                        <artifactId>xmlunit-core</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+</project>
diff --git 
a/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/AsyncMethodTest.java
 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/AsyncMethodTest.java
new file mode 100644
index 0000000..601291c
--- /dev/null
+++ 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/AsyncMethodTest.java
@@ -0,0 +1,239 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cxf.systest.microprofile.rest.client;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonStructure;
+import javax.ws.rs.core.Response;
+
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+
+import org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider;
+import 
org.apache.cxf.systest.microprofile.rest.client.mock.AsyncClientWithCompletionStage;
+import 
org.apache.cxf.systest.microprofile.rest.client.mock.AsyncInvocationInterceptorFactoryTestImpl;
+import 
org.apache.cxf.systest.microprofile.rest.client.mock.AsyncInvocationInterceptorFactoryTestImpl2;
+import 
org.apache.cxf.systest.microprofile.rest.client.mock.ThreadLocalClientFilter;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestClientRequestFilter;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestClientResponseFilter;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestMessageBodyReader;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestMessageBodyWriter;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestParamConverterProvider;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestReaderInterceptor;
+import 
org.eclipse.microprofile.rest.client.tck.providers.TestWriterInterceptor;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+//CHECKSTYLE:OFF
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+//CHECKSTYLE:ON
+
+public class AsyncMethodTest {
+
+    @Rule
+    public WireMockRule wireMockRule = new 
WireMockRule(WireMockConfiguration.options().dynamicPort());
+
+    @Test
+    public void verifyNoCDIOrMPConfigInClassPath() {
+        try {
+            Class.forName("org.eclipse.microprofile.config.ConfigProvider");
+            fail("ConfigProvider API class is on the classpath - results from 
this test project are not valid");
+        } catch (Throwable t) {
+            // expected
+        }
+        try {
+            Class.forName("javax.enterprise.inject.spi.BeanManager");
+            fail("BeanManager API class is on the classpath - results from 
this test project are not valid");
+        } catch (Throwable t) {
+            //expected
+        }
+    }
+
+    @Test
+    public void 
testInvokesPostOperationWithRegisteredProvidersAsyncCompletionStage() throws 
Exception {
+        wireMockRule.stubFor(put(urlEqualTo("/echo/test"))
+                                .willReturn(aResponse()
+                                .withBody("this is the replaced writer input 
body will be removed")));
+        String inputBody = "input body will be removed";
+        String expectedResponseBody = TestMessageBodyReader.REPLACED_BODY;
+
+        AsyncClientWithCompletionStage api = RestClientBuilder.newBuilder()
+                .register(TestClientRequestFilter.class)
+                .register(TestClientResponseFilter.class)
+                .register(TestMessageBodyReader.class, 3)
+                .register(TestMessageBodyWriter.class)
+                .register(TestParamConverterProvider.class)
+                .register(TestReaderInterceptor.class)
+                .register(TestWriterInterceptor.class)
+                .baseUri(getBaseUri())
+                .build(AsyncClientWithCompletionStage.class);
+
+        CompletionStage<Response> cs = api.put(inputBody);
+
+        // should need <1 second, but 20s timeout in case something goes wrong
+        Response response = cs.toCompletableFuture().get(20, TimeUnit.SECONDS);
+        String actualResponseBody = response.readEntity(String.class);
+
+        assertEquals(expectedResponseBody, actualResponseBody);
+
+        assertEquals(TestClientResponseFilter.getAndResetValue(), 1);
+        assertEquals(TestClientRequestFilter.getAndResetValue(), 1);
+        assertEquals(TestReaderInterceptor.getAndResetValue(), 1);
+    }
+
+    @Test
+    public void 
testInvokesPostOperationWithRegisteredProvidersAsyncCompletionStageWithExecutor()
 throws Exception {
+        final String inputBody = "input body will be ignored";
+        wireMockRule.stubFor(put(urlEqualTo("/echo/test"))
+                                .willReturn(aResponse()
+                                .withBody(inputBody)));
+        AsyncInvocationInterceptorFactoryTestImpl.INBOUND.remove();
+        AsyncInvocationInterceptorFactoryTestImpl.OUTBOUND.remove();
+        try {
+            final String asyncThreadName = "CXF-MPRestClientThread-2";
+
+            AsyncClientWithCompletionStage api = RestClientBuilder.newBuilder()
+                .register(AsyncInvocationInterceptorFactoryTestImpl.class)
+                .register(AsyncInvocationInterceptorFactoryTestImpl2.class)
+                .register(ThreadLocalClientFilter.class)
+                .baseUri(getBaseUri())
+                .executorService(Executors.newSingleThreadExecutor(new 
ThreadFactory() {
+
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        return new Thread(r, asyncThreadName);
+                    }
+                }))
+                .build(AsyncClientWithCompletionStage.class);
+
+            CompletionStage<Response> cs = api.put(inputBody);
+            List<String> outboundList = 
AsyncInvocationInterceptorFactoryTestImpl.OUTBOUND.get();
+            assertEquals(4, outboundList.size());
+
+            // ensure filters and asyncInvocationInterceptors are executed in 
the correct order and the correct thread
+            // outbound:
+            assertEquals(ThreadLocalClientFilter.class.getSimpleName(), 
outboundList.get(0));
+            
assertEquals(AsyncInvocationInterceptorFactoryTestImpl.class.getSimpleName(), 
outboundList.get(1));
+            
assertEquals(AsyncInvocationInterceptorFactoryTestImpl2.class.getSimpleName(), 
outboundList.get(2));
+            assertEquals(Thread.currentThread().getName(), 
outboundList.get(3));
+
+            // inbound:
+            // should need <1 second, but 20s timeout in case something goes 
wrong
+            Response response = cs.toCompletableFuture().get(20, 
TimeUnit.SECONDS);
+
+            List<String> responseList = 
response.getStringHeaders().get("CXFTestResponse");
+            assertEquals(4, responseList.size());
+
+            assertEquals(asyncThreadName, responseList.get(0));
+            
assertEquals(AsyncInvocationInterceptorFactoryTestImpl2.class.getSimpleName(), 
responseList.get(1));
+            
assertEquals(AsyncInvocationInterceptorFactoryTestImpl.class.getSimpleName(), 
responseList.get(2));
+            assertEquals(ThreadLocalClientFilter.class.getSimpleName(), 
responseList.get(3));
+        } finally {
+            AsyncInvocationInterceptorFactoryTestImpl.INBOUND.remove();
+            AsyncInvocationInterceptorFactoryTestImpl.OUTBOUND.remove();
+        }
+    }
+
+    @Test
+    public void 
testInvokesGetOperationWithRegisteredProvidersAsyncCompletionStage() throws 
Exception {
+        wireMockRule.stubFor(get(urlEqualTo("/echo/test2"))
+                                .willReturn(aResponse()
+                                .withBody("{\"name\": \"test\"}")));
+
+        AsyncClientWithCompletionStage api = RestClientBuilder.newBuilder()
+                .register(TestClientRequestFilter.class)
+                .register(TestClientResponseFilter.class)
+                .register(TestMessageBodyReader.class, 3)
+                .register(TestMessageBodyWriter.class)
+                .register(TestParamConverterProvider.class)
+                .register(TestReaderInterceptor.class)
+                .register(TestWriterInterceptor.class)
+                .register(JsrJsonpProvider.class)
+                .baseUri(getBaseUri())
+                .build(AsyncClientWithCompletionStage.class);
+
+        CompletionStage<JsonStructure> cs = api.get();
+
+        // should need <1 second, but 20s timeout in case something goes wrong
+        JsonStructure response = cs.toCompletableFuture().get(20, 
TimeUnit.SECONDS);
+        assertThat(response, instanceOf(JsonObject.class));
+        
+        final JsonObject jsonObject = (JsonObject)response;
+        assertThat(jsonObject.get("name"), instanceOf(JsonString.class));
+        assertThat(((JsonString)jsonObject.get("name")).getString(), 
equalTo("test"));
+
+        assertEquals(TestClientResponseFilter.getAndResetValue(), 1);
+        assertEquals(TestClientRequestFilter.getAndResetValue(), 1);
+        assertEquals(TestReaderInterceptor.getAndResetValue(), 1);
+    }
+
+    @Test
+    public void 
testInvokesGetAllOperationWithRegisteredProvidersAsyncCompletionStage() throws 
Exception {
+        wireMockRule.stubFor(get(urlEqualTo("/echo/test3"))
+                                .willReturn(aResponse()
+                                .withBody("[{\"name\": \"test\"}]")));
+
+        AsyncClientWithCompletionStage api = RestClientBuilder.newBuilder()
+                .register(TestClientRequestFilter.class)
+                .register(TestClientResponseFilter.class)
+                .register(TestMessageBodyReader.class, 3)
+                .register(TestMessageBodyWriter.class)
+                .register(TestParamConverterProvider.class)
+                .register(TestReaderInterceptor.class)
+                .register(TestWriterInterceptor.class)
+                .register(JsrJsonpProvider.class)
+                .baseUri(getBaseUri())
+                .build(AsyncClientWithCompletionStage.class);
+
+        CompletionStage<Collection<JsonObject>> cs = api.getAll();
+
+        // should need <1 second, but 20s timeout in case something goes wrong
+        Collection<JsonObject> response = cs.toCompletableFuture().get(20, 
TimeUnit.SECONDS);
+        assertEquals(1, response.size());
+        
+        final JsonObject jsonObject = response.iterator().next();
+        assertThat(jsonObject.get("name"), instanceOf(JsonString.class));
+        assertThat(((JsonString)jsonObject.get("name")).getString(), 
equalTo("test"));
+
+        assertEquals(TestClientResponseFilter.getAndResetValue(), 1);
+        assertEquals(TestClientRequestFilter.getAndResetValue(), 1);
+        assertEquals(TestReaderInterceptor.getAndResetValue(), 1);
+    }
+
+    private URI getBaseUri() {
+        return URI.create("http://localhost:"; + wireMockRule.port() + "/echo");
+    }
+
+}
\ No newline at end of file
diff --git 
a/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/AsyncThreadingTest.java
 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/AsyncThreadingTest.java
new file mode 100644
index 0000000..17ead61
--- /dev/null
+++ 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/AsyncThreadingTest.java
@@ -0,0 +1,296 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cxf.systest.microprofile.rest.client;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+
+import org.apache.johnzon.jaxrs.jsonb.jaxrs.JsonbJaxrsProvider;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import 
org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
+
+@RunWith(Parameterized.class)
+public class AsyncThreadingTest {
+    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
+    
+    @Rule
+    public WireMockRule wireMockRule = new 
WireMockRule(WireMockConfiguration.options().dynamicPort());
+    
+    private final ExecutorService executorService;
+    private final String prefix;
+    private EchoResource echo;
+    
+    public AsyncThreadingTest(final ExecutorService executorService, final 
String prefix) {
+        this.executorService = executorService;
+        this.prefix = prefix;
+    }
+
+    @Parameters(name = "Using pool: {1}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] 
+        {
+            {cachedExecutor(), "mp-async-"}, 
+            {null, "ForkJoinPool.commonPool-worker-"}
+        });
+    }
+        
+    @Before
+    public void setUp() {
+        final RestClientBuilder builder = RestClientBuilder
+            .newBuilder()
+            .register(JsonbJaxrsProvider.class)
+            .register(AsyncInvocationInterceptorFactoryImpl.class)
+            .baseUri(getBaseUri());
+        
+        if (executorService == null /* use default one */) {
+            echo = builder.build(EchoResource.class);
+        } else {
+            echo = 
builder.executorService(executorService).build(EchoResource.class);
+        }
+    }
+    
+    @After
+    public void tearDown() {
+        CONTEXT.remove();
+    }
+
+    @Test
+    public void testAsynchronousNotFoundCall() throws Exception {
+        wireMockRule.stubFor(get(urlEqualTo("/echo"))
+            .willReturn(aResponse()
+            .withStatus(404)));
+
+        final CompletableFuture<Echo> future = echo
+            .getAsync()
+            .toCompletableFuture()
+            .handle((r, ex) -> {
+                try {
+                    Thread.sleep(500);
+                    assertThat(Thread.currentThread().getName(), 
startsWith(prefix));
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+
+                if (ex instanceof CompletionException) {
+                    throw (CompletionException)ex;
+                } else {
+                    return r;
+                }
+            });
+
+        // Simulate some processing pause
+        assertNull(future.getNow(null));
+        
+        final ExecutionException ex = assertThrows(ExecutionException.class, 
() -> future.get(5L, TimeUnit.SECONDS));
+        assertEquals(WebApplicationException.class, ex.getCause().getClass());
+    }
+
+    @Test
+    public void testAsynchronousCall() throws Exception {
+        wireMockRule.stubFor(get(urlEqualTo("/echo"))
+            .willReturn(aResponse()
+            .withHeader("Content-Type", MediaType.APPLICATION_JSON)
+            .withBody("{ \"message\": \"echo\" }")));
+
+        final CompletableFuture<Echo> future = echo
+            .getAsync()
+            .toCompletableFuture()
+            .thenApply(s -> {
+                try {
+                    Thread.sleep(500);
+                    assertThat(Thread.currentThread().getName(), 
startsWith(prefix));
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+                
+                return s;
+            });
+        
+        assertNull(future.getNow(null));
+        
+        final Echo result = future.get(5L, TimeUnit.SECONDS);
+        assertThat(result.getMessage(), equalTo("echo"));
+    }
+
+    @Test
+    public void testAsynchronousCallAndContextPropagation() throws Exception {
+        wireMockRule.stubFor(get(urlEqualTo("/echo"))
+            .willReturn(aResponse()
+            .withHeader("Content-Type", MediaType.APPLICATION_JSON)
+            .withBody("{ \"message\": \"echo\" }")));
+
+        CONTEXT.set("context-value");
+        
+        final CompletableFuture<Echo> future = echo
+            .getAsync()
+            .toCompletableFuture()
+            .thenApply(s -> {
+                try {
+                    Thread.sleep(500);
+                    assertThat(Thread.currentThread().getName(), 
startsWith(prefix));
+                    assertThat(CONTEXT.get(), equalTo("context-value"));
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+
+                return s;
+            });
+
+        final Echo result = future.get(5L, TimeUnit.SECONDS);
+        assertThat(result.getMessage(), equalTo("echo"));
+    }
+
+    @Test
+    public void testAsynchronousCallMany() throws InterruptedException, 
ExecutionException, TimeoutException {
+        wireMockRule.stubFor(get(urlEqualTo("/echo"))
+            .willReturn(aResponse()
+            .withHeader("Content-Type", MediaType.APPLICATION_JSON)
+            .withBody("{ \"message\": \"echo\" }")));
+
+        final Collection<CompletableFuture<Echo>> futures = new ArrayList<>();
+        for (int i = 0; i < 20; ++i) {
+            futures.add(
+                echo
+                    .getAsync()
+                    .toCompletableFuture()
+                    .thenApply(s -> {
+                        try {
+                            Thread.sleep(500);
+                            assertThat(Thread.currentThread().getName(), 
startsWith(prefix));
+                        } catch (InterruptedException e) {
+                            Thread.currentThread().interrupt();
+                        }
+    
+                        return s;
+                    })
+            );
+        }
+
+        CompletableFuture
+            .allOf(futures.toArray(new CompletableFuture[0]))
+            .join();
+        
+        for (final CompletableFuture<Echo> future: futures) {
+            assertThat(future.get().getMessage(), equalTo("echo"));
+        }
+    }
+    
+    private URI getBaseUri() {
+        return URI.create("http://localhost:"; + wireMockRule.port() + "/echo");
+    }
+      
+    public static class Echo {
+        private String message;
+          
+        public String getMessage() {
+            return message;
+        }
+          
+        public void setMessage(String message) {
+            this.message = message;
+        }
+    }
+      
+    @Path("/")
+    public interface EchoResource {
+        @GET
+        @Produces(MediaType.APPLICATION_JSON)
+        @Consumes(MediaType.APPLICATION_JSON)
+        CompletionStage<Echo> getAsync();
+    }
+      
+    public static class AsyncInvocationInterceptorFactoryImpl implements 
AsyncInvocationInterceptorFactory {
+        @Override
+        public AsyncInvocationInterceptor newInterceptor() {
+            return new AsyncInvocationInterceptorImpl();
+        }
+    }
+
+    public static class AsyncInvocationInterceptorImpl implements 
AsyncInvocationInterceptor {
+        private String context;
+
+        @Override
+        public void prepareContext() {
+            context = CONTEXT.get();
+        }
+
+        @Override
+        public void applyContext() {
+            CONTEXT.set(context);
+        }
+
+        @Override
+        public void removeContext() {
+            CONTEXT.remove();
+        }
+    }
+    
+    private static ExecutorService cachedExecutor() {
+        return Executors.newCachedThreadPool(new ThreadFactory() {
+            private AtomicInteger counter = new AtomicInteger();
+             
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(r, "mp-async-" + counter.incrementAndGet());
+            }
+        });
+    }
+}
diff --git 
a/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncClientWithCompletionStage.java
 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncClientWithCompletionStage.java
new file mode 100644
index 0000000..d5b57ed
--- /dev/null
+++ 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncClientWithCompletionStage.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cxf.systest.microprofile.rest.client.mock;
+
+import java.util.Collection;
+import java.util.concurrent.CompletionStage;
+
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+public interface AsyncClientWithCompletionStage {
+
+    @PUT
+    @Path("/test")
+    CompletionStage<Response> put(String text);
+    
+    @GET
+    @Path("/test2")
+    @Produces("application/json")
+    CompletionStage<JsonStructure> get();
+    
+    @GET
+    @Path("/test3")
+    @Produces("application/json")
+    CompletionStage<Collection<JsonObject>> getAll();
+}
diff --git 
a/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncInvocationInterceptorFactoryTestImpl.java
 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncInvocationInterceptorFactoryTestImpl.java
new file mode 100644
index 0000000..75b8634
--- /dev/null
+++ 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncInvocationInterceptorFactoryTestImpl.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cxf.systest.microprofile.rest.client.mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Priority;
+
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import 
org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory;
+
+@Priority(3500)
+public class AsyncInvocationInterceptorFactoryTestImpl implements 
AsyncInvocationInterceptorFactory {
+
+    //CHECKSTYLE:OFF
+    public static ThreadLocal<List<String>> OUTBOUND = 
ThreadLocal.withInitial(() -> {return new ArrayList<>();});
+    public static ThreadLocal<List<String>> INBOUND = 
ThreadLocal.withInitial(() -> {return new ArrayList<>();});
+    //CHECKSTYLE:ON
+
+    static class AsyncInvocationInterceptorTestImpl implements 
AsyncInvocationInterceptor {
+
+        /** {@inheritDoc}*/
+        @Override
+        public void prepareContext() {
+            List<String> list = OUTBOUND.get();
+            
list.add(AsyncInvocationInterceptorFactoryTestImpl.class.getSimpleName());
+        }
+
+        /** {@inheritDoc}*/
+        @Override
+        public void applyContext() {
+            List<String> list = INBOUND.get();
+            list.add(Thread.currentThread().getName());
+            
list.add(AsyncInvocationInterceptorFactoryTestImpl.class.getSimpleName());
+        }
+
+        /** {@inheritDoc}*/
+        @Override
+        public void removeContext() {
+            List<String> list = INBOUND.get();
+            list.add("REMOVE-" + Thread.currentThread().getName());
+            list.add("REMOVE-" + 
AsyncInvocationInterceptorFactoryTestImpl.class.getSimpleName());
+        }
+    }
+
+    /** {@inheritDoc}*/
+    @Override
+    public AsyncInvocationInterceptor newInterceptor() {
+        return new AsyncInvocationInterceptorTestImpl();
+    }
+
+}
diff --git 
a/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncInvocationInterceptorFactoryTestImpl2.java
 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncInvocationInterceptorFactoryTestImpl2.java
new file mode 100644
index 0000000..2576cb4
--- /dev/null
+++ 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/AsyncInvocationInterceptorFactoryTestImpl2.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cxf.systest.microprofile.rest.client.mock;
+
+import java.util.List;
+
+import javax.annotation.Priority;
+
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import 
org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory;
+
+@Priority(3700)
+public class AsyncInvocationInterceptorFactoryTestImpl2 implements 
AsyncInvocationInterceptorFactory {
+
+    static class AsyncInvocationInterceptorTestImpl2 implements 
AsyncInvocationInterceptor {
+
+        /** {@inheritDoc}*/
+        @Override
+        public void prepareContext() {
+            List<String> list = 
AsyncInvocationInterceptorFactoryTestImpl.OUTBOUND.get();
+            
list.add(AsyncInvocationInterceptorFactoryTestImpl2.class.getSimpleName());
+            list.add(Thread.currentThread().getName());
+        }
+
+        /** {@inheritDoc}*/
+        @Override
+        public void applyContext() {
+            List<String> list = 
AsyncInvocationInterceptorFactoryTestImpl.INBOUND.get();
+            list.add(Thread.currentThread().getName());
+            
list.add(AsyncInvocationInterceptorFactoryTestImpl2.class.getSimpleName());
+        }
+
+        /** {@inheritDoc}*/
+        @Override
+        public void removeContext() {
+            List<String> list = 
AsyncInvocationInterceptorFactoryTestImpl.INBOUND.get();
+            list.add("REMOVE-" + Thread.currentThread().getName());
+            list.add("REMOVE-" + 
AsyncInvocationInterceptorFactoryTestImpl.class.getSimpleName());
+        }
+    }
+
+    /** {@inheritDoc}*/
+    @Override
+    public AsyncInvocationInterceptor newInterceptor() {
+        return new AsyncInvocationInterceptorTestImpl2();
+    }
+
+}
diff --git 
a/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/ThreadLocalClientFilter.java
 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/ThreadLocalClientFilter.java
new file mode 100644
index 0000000..bd83d64
--- /dev/null
+++ 
b/systests/microprofile/client/nocdi/src/test/java/org/apache/cxf/systest/microprofile/rest/client/mock/ThreadLocalClientFilter.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cxf.systest.microprofile.rest.client.mock;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+public class ThreadLocalClientFilter implements ClientRequestFilter, 
ClientResponseFilter {
+
+    /** {@inheritDoc}*/
+    @Override
+    public void filter(ClientRequestContext requestContext) throws IOException 
{
+        List<String> list = 
AsyncInvocationInterceptorFactoryTestImpl.OUTBOUND.get();
+        list.add(ThreadLocalClientFilter.class.getSimpleName());
+    }
+
+    /** {@inheritDoc}*/
+    @Override
+    public void filter(ClientRequestContext requestContext, 
ClientResponseContext responseContext)
+        throws IOException {
+        List<String> list = 
AsyncInvocationInterceptorFactoryTestImpl.INBOUND.get();
+        list.add(ThreadLocalClientFilter.class.getSimpleName());
+        responseContext.getHeaders().put("CXFTestResponse", list);
+    }
+
+}

Reply via email to