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); + } + +}
