diff --git a/README.md b/README.md index 3190962d8..dc1411ac2 100644 --- a/README.md +++ b/README.md @@ -1139,6 +1139,12 @@ It covers different usages: 3. from a blocking endpoint 4. from a reactive endpoint +### `cache/caffeine-resteasy` + +Verifies the `quarkus-cache` extension with RESTEasy. +It covers different usages: +1. `@CacheResult`, `@CacheInvalidate` and `@CacheInvalidateAll` set in RESTEasy client + ### `cache/infinispan` Verifies the `quarkus-infinispan-cache` extension using `@CacheResult`, `@CacheInvalidate`, `@CacheInvalidateAll` and `@CacheKey`. It covers different usages: diff --git a/cache/caffeine-resteasy/pom.xml b/cache/caffeine-resteasy/pom.xml new file mode 100644 index 000000000..66032613b --- /dev/null +++ b/cache/caffeine-resteasy/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + io.quarkus.ts.qe + parent + 1.0.0-SNAPSHOT + ../.. + + cache-caffeine-resteasy + jar + Quarkus QE TS: Cache: Caffeine Resteasy + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-client-jackson + + + io.quarkus + quarkus-resteasy-client-jaxb + + + io.quarkus + quarkus-cache + + + diff --git a/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/ClientBookResource.java b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/ClientBookResource.java new file mode 100644 index 000000000..4be4cd190 --- /dev/null +++ b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/ClientBookResource.java @@ -0,0 +1,45 @@ +package io.quarkus.ts.cache.caffeine.restclient; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +import io.quarkus.ts.cache.caffeine.restclient.types.Book; + +@Path("/client/book") +public class ClientBookResource { + + @Inject + @RestClient + RestInterface restInterface; + + @GET + @Path("/xml-cache") + @Produces(MediaType.APPLICATION_XML) + public Book getAsXmlWithCache() { + return restInterface.getAsXmlWithCache(); + } + + @GET + @Path("/json-cache") + @Produces(MediaType.APPLICATION_JSON) + public Book getAsJsonWithCache() { + return restInterface.getAsJsonWithCache(); + } + + @GET + @Path("/invalidate-xml") + public String invalidateXml() { + return restInterface.invalidateXml(); + } + + @GET + @Path("/invalidate-json") + public String invalidateJson() { + return restInterface.invalidateJson(); + } +} diff --git a/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/RestInterface.java b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/RestInterface.java new file mode 100644 index 000000000..d0ec2b8f6 --- /dev/null +++ b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/RestInterface.java @@ -0,0 +1,43 @@ +package io.quarkus.ts.cache.caffeine.restclient; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.quarkus.cache.CacheInvalidate; +import io.quarkus.cache.CacheInvalidateAll; +import io.quarkus.cache.CacheResult; +import io.quarkus.ts.cache.caffeine.restclient.types.Book; + +@RegisterRestClient +@Path("/book") +@RegisterClientHeaders +public interface RestInterface { + + @GET + @Path("/xml-cache") + @CacheResult(cacheName = "xml") + @Produces(MediaType.APPLICATION_XML) + Book getAsXmlWithCache(); + + @GET + @Path("/json-cache") + @CacheResult(cacheName = "json") + @Produces(MediaType.APPLICATION_JSON) + Book getAsJsonWithCache(); + + @GET + @Path("/xml-cache-invalidate") + @Produces(MediaType.TEXT_PLAIN) + @CacheInvalidateAll(cacheName = "xml") + String invalidateXml(); + + @GET + @Path("/json-cache-invalidate") + @CacheInvalidate(cacheName = "json") + String invalidateJson(); +} diff --git a/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/Book.java b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/Book.java new file mode 100644 index 000000000..c8b1f10ff --- /dev/null +++ b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/Book.java @@ -0,0 +1,24 @@ +package io.quarkus.ts.cache.caffeine.restclient.types; + +import jakarta.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class Book { + + private String title; + + public Book() { + } + + public Book(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/BookAsJsonResource.java b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/BookAsJsonResource.java new file mode 100644 index 000000000..ba3fa23b6 --- /dev/null +++ b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/BookAsJsonResource.java @@ -0,0 +1,33 @@ +package io.quarkus.ts.cache.caffeine.restclient.types; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/book") +public class BookAsJsonResource { + + public static int counter = 0; + + @GET + @Path("/json-cache") + @Produces(MediaType.APPLICATION_JSON) + public String getCache() throws InterruptedException { + counter++; + return "{\"title\":\"Title in Json with counter equal to " + counter + "\"}"; + } + + @GET + @Path("/json-cache-invalidate") + public String invalidateCache() { + return "json cache was invalidated"; + } + + @GET + @Path("/reset-counter-json") + public String resetCounter() { + counter = 0; + return "Counter reset"; + } +} diff --git a/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/BookAsXmlResource.java b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/BookAsXmlResource.java new file mode 100644 index 000000000..eb3299007 --- /dev/null +++ b/cache/caffeine-resteasy/src/main/java/io/quarkus/ts/cache/caffeine/restclient/types/BookAsXmlResource.java @@ -0,0 +1,34 @@ +package io.quarkus.ts.cache.caffeine.restclient.types; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/book") +public class BookAsXmlResource { + + private static int counter = 0; + + @GET + @Path("/xml-cache") + @Produces(MediaType.APPLICATION_XML) + public String helloCache() throws InterruptedException { + counter++; + return "Title in Xml with counter equal to " + counter + ""; + } + + @GET + @Path("/xml-cache-invalidate") + @Produces(MediaType.TEXT_PLAIN) + public String invalidateCache() { + return "xml cache was invalidated"; + } + + @GET + @Path("/reset-counter-xml") + public String resetCounter() { + counter = 0; + return "Counter reset"; + } +} diff --git a/cache/caffeine-resteasy/src/main/resources/application.properties b/cache/caffeine-resteasy/src/main/resources/application.properties new file mode 100644 index 000000000..02bf13d3b --- /dev/null +++ b/cache/caffeine-resteasy/src/main/resources/application.properties @@ -0,0 +1,5 @@ +io.quarkus.ts.cache.caffeine.restclient.RestInterface/mp-rest/url=http://localhost:${quarkus.http.port} +cache.expire.time=4000 +cache.expire.json.time=2000 +quarkus.cache.caffeine.expire-after-write=${cache.expire.time}ms +quarkus.cache.caffeine."json".expire-after-write=${cache.expire.json.time}ms diff --git a/cache/caffeine-resteasy/src/test/java/io/quarkus/ts/cache/caffeine/restclient/OpenShiftRestClientWithCacheIT.java b/cache/caffeine-resteasy/src/test/java/io/quarkus/ts/cache/caffeine/restclient/OpenShiftRestClientWithCacheIT.java new file mode 100644 index 000000000..1a6af4372 --- /dev/null +++ b/cache/caffeine-resteasy/src/test/java/io/quarkus/ts/cache/caffeine/restclient/OpenShiftRestClientWithCacheIT.java @@ -0,0 +1,7 @@ +package io.quarkus.ts.cache.caffeine.restclient; + +import io.quarkus.test.scenarios.OpenShiftScenario; + +@OpenShiftScenario +public class OpenShiftRestClientWithCacheIT extends RestClientWithCacheIT { +} diff --git a/cache/caffeine-resteasy/src/test/java/io/quarkus/ts/cache/caffeine/restclient/RestClientWithCacheIT.java b/cache/caffeine-resteasy/src/test/java/io/quarkus/ts/cache/caffeine/restclient/RestClientWithCacheIT.java new file mode 100644 index 000000000..792c389ff --- /dev/null +++ b/cache/caffeine-resteasy/src/test/java/io/quarkus/ts/cache/caffeine/restclient/RestClientWithCacheIT.java @@ -0,0 +1,115 @@ +package io.quarkus.ts.cache.caffeine.restclient; + +import static org.hamcrest.CoreMatchers.is; + +import org.apache.http.HttpStatus; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.services.QuarkusApplication; + +@QuarkusScenario +public class RestClientWithCacheIT { + + @QuarkusApplication + static RestService app = new RestService(); + + @BeforeEach + public void resetCounterAndInvalidateCache() { + invalidateCache("xml"); + invalidateCache("json"); + resetCounter("xml"); + resetCounter("json"); + } + + @Test + @Tag("QUARKUS-5068") + public void shouldGetBookFromRestClientXmlWithCache() { + // Check if request is cached + sendRequestAndReturnResponseTime("/client/book/xml-cache", + "Title in Xml with counter equal to 1"); + sendRequestAndReturnResponseTime("/client/book/xml-cache", + "Title in Xml with counter equal to 1"); + + // Invalidate cache + invalidateCache("xml"); + + // The request shouldn't be cached and counter should be different then previous requests + sendRequestAndReturnResponseTime("/client/book/xml-cache", + "Title in Xml with counter equal to 2"); + } + + @Test + @Tag("QUARKUS-5068") + public void shouldGetBookFromRestClientJsonWithCache() { + sendRequestAndReturnResponseTime("/client/book/json-cache", + "{\"title\":\"Title in Json with counter equal to 1\"}"); + sendRequestAndReturnResponseTime("/client/book/json-cache", + "{\"title\":\"Title in Json with counter equal to 1\"}"); + + // Invalidate cache + invalidateCache("json"); + + // The request shouldn't be cached and counter should be different then previous requests + sendRequestAndReturnResponseTime("/client/book/json-cache", + "{\"title\":\"Title in Json with counter equal to 2\"}"); + } + + @Test + public void testExpireAfterWritePropertyWithSpecificName() throws InterruptedException { + sendRequestAndReturnResponseTime("/client/book/json-cache", + "{\"title\":\"Title in Json with counter equal to 1\"}"); + sendRequestAndReturnResponseTime("/client/book/json-cache", + "{\"title\":\"Title in Json with counter equal to 1\"}"); + + // Sleep same time as the cache expiration time + adding 100ms as safe buffer + Thread.sleep(ConfigProvider.getConfig().getValue("cache.expire.json.time", Integer.class) + 100); + + // The request shouldn't be cached and counter should be different then previous requests + sendRequestAndReturnResponseTime("/client/book/json-cache", + "{\"title\":\"Title in Json with counter equal to 2\"}"); + } + + @Test + public void testExpireAfterWritePropertyWith() throws InterruptedException { + sendRequestAndReturnResponseTime("/client/book/xml-cache", + "Title in Xml with counter equal to 1"); + sendRequestAndReturnResponseTime("/client/book/xml-cache", + "Title in Xml with counter equal to 1"); + + // Sleep same time as the cache expiration time + adding 100ms as safe buffer + Thread.sleep(ConfigProvider.getConfig().getValue("cache.expire.time", Integer.class) + 100); + + // The request shouldn't be cached and counter should be different then previous requests + sendRequestAndReturnResponseTime("/client/book/xml-cache", + "Title in Xml with counter equal to 2"); + } + + public void sendRequestAndReturnResponseTime(String path, String expectedBody) { + app.given() + .get(path) + .then() + .statusCode(HttpStatus.SC_OK) + .body(is(expectedBody)); + } + + public void invalidateCache(String cacheName) { + app.given() + .get("/client/book/invalidate-" + cacheName) + .then() + .statusCode(HttpStatus.SC_OK) + .body(is(cacheName + " cache was invalidated")); + } + + public void resetCounter(String fileType) { + app.given() + .get("/book/reset-counter-" + fileType) + .then() + .statusCode(HttpStatus.SC_OK) + .body(is("Counter reset")); + } +} diff --git a/pom.xml b/pom.xml index fd6de6a70..7e5b26297 100644 --- a/pom.xml +++ b/pom.xml @@ -449,6 +449,7 @@ env-info cache/caffeine + cache/caffeine-resteasy cache/redis cache/infinispan infinispan-client