You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2021/07/22 23:54:45 UTC
[cxf] 01/04: Run Async systests with no CDI or MP Config
This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch 3.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 0ef8490104abdea02f756557b2abaa35eac3fe70
Author: Andy McCright <j....@gmail.com>
AuthorDate: Tue Jun 1 13:13:45 2021 -0500
Run Async systests with no CDI or MP Config
Signed-off-by: Andy McCright <j....@gmail.com>
---
systests/microprofile/client/nocdi/pom.xml | 136 ++++++++++
.../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, 902 insertions(+)
diff --git a/systests/microprofile/client/nocdi/pom.xml b/systests/microprofile/client/nocdi/pom.xml
new file mode 100644
index 0000000..00df626
--- /dev/null
+++ b/systests/microprofile/client/nocdi/pom.xml
@@ -0,0 +1,136 @@
+<?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.5.0-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>jakarta.json.bind</groupId>
+ <artifactId>jakarta.json.bind-api</artifactId>
+ <version>${cxf.json.bind-api.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.json</groupId>
+ <artifactId>jakarta.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);
+ }
+
+}