diff --git a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ApplicationScopeService.java b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ApplicationScopeService.java index baf9fbebb3..04076d2c7e 100644 --- a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ApplicationScopeService.java +++ b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ApplicationScopeService.java @@ -1,4 +1,4 @@ -package io.quarkus.ts.cache.caffeine; +package io.quarkus.ts.cache.caffeine.cache.caffeine; import jakarta.enterprise.context.ApplicationScoped; diff --git a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BaseServiceWithCache.java b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BaseServiceWithCache.java index c93a2e55d1..8117f1727c 100644 --- a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BaseServiceWithCache.java +++ b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BaseServiceWithCache.java @@ -1,4 +1,4 @@ -package io.quarkus.ts.cache.caffeine; +package io.quarkus.ts.cache.caffeine.cache.caffeine; import io.quarkus.cache.CacheInvalidate; import io.quarkus.cache.CacheInvalidateAll; diff --git a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BlockingWithCacheResource.java b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BlockingWithCacheResource.java index c8f56a7b99..77ec1a3d02 100644 --- a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BlockingWithCacheResource.java +++ b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/BlockingWithCacheResource.java @@ -1,4 +1,4 @@ -package io.quarkus.ts.cache.caffeine; +package io.quarkus.ts.cache.caffeine.cache.caffeine; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; diff --git a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ReactiveWithCacheResource.java b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ReactiveWithCacheResource.java index 76bb2c9094..386005afb1 100644 --- a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ReactiveWithCacheResource.java +++ b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ReactiveWithCacheResource.java @@ -1,4 +1,4 @@ -package io.quarkus.ts.cache.caffeine; +package io.quarkus.ts.cache.caffeine.cache.caffeine; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; diff --git a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/RequestScopeService.java b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/RequestScopeService.java index 8e12392b55..f3cd82c569 100644 --- a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/RequestScopeService.java +++ b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/RequestScopeService.java @@ -1,4 +1,4 @@ -package io.quarkus.ts.cache.caffeine; +package io.quarkus.ts.cache.caffeine.cache.caffeine; import jakarta.enterprise.context.RequestScoped; diff --git a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ServiceWithCacheResource.java b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ServiceWithCacheResource.java index e71870276a..5d39d87cfb 100644 --- a/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ServiceWithCacheResource.java +++ b/cache/caffeine/src/main/java/io/quarkus/ts/cache/caffeine/ServiceWithCacheResource.java @@ -1,4 +1,4 @@ -package io.quarkus.ts.cache.caffeine; +package io.quarkus.ts.cache.caffeine.cache.caffeine; import jakarta.inject.Inject; import jakarta.ws.rs.GET; diff --git a/cache/caffeine/src/test/java/io/quarkus/ts/cache/caffeine/cache/caffeine/CaffeineCacheIT.java b/cache/caffeine/src/test/java/io/quarkus/ts/cache/caffeine/cache/caffeine/CaffeineCacheIT.java index 33b9b368dc..e318300d78 100644 --- a/cache/caffeine/src/test/java/io/quarkus/ts/cache/caffeine/cache/caffeine/CaffeineCacheIT.java +++ b/cache/caffeine/src/test/java/io/quarkus/ts/cache/caffeine/cache/caffeine/CaffeineCacheIT.java @@ -1,7 +1,7 @@ package io.quarkus.ts.cache.caffeine.cache.caffeine; -import static io.quarkus.ts.cache.caffeine.ServiceWithCacheResource.APPLICATION_SCOPE_SERVICE_PATH; -import static io.quarkus.ts.cache.caffeine.ServiceWithCacheResource.REQUEST_SCOPE_SERVICE_PATH; +import static io.quarkus.ts.cache.caffeine.cache.caffeine.ServiceWithCacheResource.APPLICATION_SCOPE_SERVICE_PATH; +import static io.quarkus.ts.cache.caffeine.cache.caffeine.ServiceWithCacheResource.REQUEST_SCOPE_SERVICE_PATH; import static io.restassured.RestAssured.given; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; diff --git a/cache/redis/pom.xml b/cache/redis/pom.xml new file mode 100644 index 0000000000..51f1e140ea --- /dev/null +++ b/cache/redis/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + io.quarkus.ts.qe + parent + 1.0.0-SNAPSHOT + ../.. + + cache-redis + jar + Quarkus QE TS: Cache: Redis + + + io.quarkus + quarkus-cache + + + io.quarkus + quarkus-redis-cache + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-qute + + Added dependency to check https://github.com/quarkusio/quarkus/issues/35680 <--> + + io.quarkus + quarkus-mailer + + + diff --git a/cache/redis/src/main/java/io/quarkus/ts/cache/redis/ApplicationScopeService.java b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/ApplicationScopeService.java new file mode 100644 index 0000000000..baf9fbebb3 --- /dev/null +++ b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/ApplicationScopeService.java @@ -0,0 +1,7 @@ +package io.quarkus.ts.cache.caffeine; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class ApplicationScopeService extends BaseServiceWithCache { +} diff --git a/cache/redis/src/main/java/io/quarkus/ts/cache/redis/BaseServiceWithCache.java b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/BaseServiceWithCache.java new file mode 100644 index 0000000000..c93a2e55d1 --- /dev/null +++ b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/BaseServiceWithCache.java @@ -0,0 +1,38 @@ +package io.quarkus.ts.cache.caffeine; + +import io.quarkus.cache.CacheInvalidate; +import io.quarkus.cache.CacheInvalidateAll; +import io.quarkus.cache.CacheKey; +import io.quarkus.cache.CacheResult; + +public abstract class BaseServiceWithCache { + + private static final String CACHE_NAME = "service-cache"; + + private static int counter = 0; + + @CacheResult(cacheName = CACHE_NAME) + public String getValue() { + return "Value: " + counter++; + } + + @CacheInvalidate(cacheName = CACHE_NAME) + public void invalidate() { + // do nothing + } + + @CacheResult(cacheName = CACHE_NAME) + public String getValueWithPrefix(@CacheKey String prefix) { + return prefix + ": " + counter++; + } + + @CacheInvalidate(cacheName = CACHE_NAME) + public void invalidateWithPrefix(@CacheKey String prefix) { + // do nothing + } + + @CacheInvalidateAll(cacheName = CACHE_NAME) + public void invalidateAll() { + // do nothing + } +} diff --git a/cache/redis/src/main/java/io/quarkus/ts/cache/redis/RequestScopeService.java b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/RequestScopeService.java new file mode 100644 index 0000000000..8e12392b55 --- /dev/null +++ b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/RequestScopeService.java @@ -0,0 +1,7 @@ +package io.quarkus.ts.cache.caffeine; + +import jakarta.enterprise.context.RequestScoped; + +@RequestScoped +public class RequestScopeService extends BaseServiceWithCache { +} diff --git a/cache/redis/src/main/java/io/quarkus/ts/cache/redis/ServiceWithCacheResource.java b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/ServiceWithCacheResource.java new file mode 100644 index 0000000000..e71870276a --- /dev/null +++ b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/ServiceWithCacheResource.java @@ -0,0 +1,65 @@ +package io.quarkus.ts.cache.caffeine; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/services") +public class ServiceWithCacheResource { + + public static final String APPLICATION_SCOPE_SERVICE_PATH = "application-scope"; + public static final String REQUEST_SCOPE_SERVICE_PATH = "request-scope"; + + @Inject + ApplicationScopeService applicationScopeService; + + @Inject + RequestScopeService requestScopeService; + + @GET + @Path("/{service}") + @Produces(MediaType.TEXT_PLAIN) + public String getValueFromService(@PathParam("service") String service) { + return lookupServiceByPathParam(service).getValue(); + } + + @POST + @Path("/{service}/invalidate-cache") + public void invalidateCacheFromService(@PathParam("service") String service) { + lookupServiceByPathParam(service).invalidate(); + } + + @POST + @Path("/{service}/invalidate-cache-all") + public void invalidateCacheAllFromService(@PathParam("service") String service) { + lookupServiceByPathParam(service).invalidateAll(); + } + + @GET + @Path("/{service}/using-prefix/{prefix}") + @Produces(MediaType.TEXT_PLAIN) + public String getValueUsingPrefixFromService(@PathParam("service") String service, @PathParam("prefix") String prefix) { + return lookupServiceByPathParam(service).getValueWithPrefix(prefix); + } + + @POST + @Path("/{service}/using-prefix/{prefix}/invalidate-cache") + public void invalidateCacheUsingPrefixFromService(@PathParam("service") String service, + @PathParam("prefix") String prefix) { + lookupServiceByPathParam(service).invalidateWithPrefix(prefix); + } + + private BaseServiceWithCache lookupServiceByPathParam(String service) { + if (APPLICATION_SCOPE_SERVICE_PATH.equals(service)) { + return applicationScopeService; + } else if (REQUEST_SCOPE_SERVICE_PATH.equals(service)) { + return requestScopeService; + } + + throw new IllegalArgumentException("Service " + service + " is not recognised"); + } +} diff --git a/cache/redis/src/main/java/io/quarkus/ts/cache/redis/TemplateCacheResource.java b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/TemplateCacheResource.java new file mode 100644 index 0000000000..0e2bb2a76c --- /dev/null +++ b/cache/redis/src/main/java/io/quarkus/ts/cache/redis/TemplateCacheResource.java @@ -0,0 +1,31 @@ +package io.quarkus.ts.cache.caffeine; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import io.quarkus.qute.Template; + +@Path("template") +public class TemplateCacheResource { + + @Inject + Template cached; + + /** + * Check for remote cache with Qute template. Qute should not use remote cache. + * See https://github.com/quarkusio/quarkus/issues/35680#issuecomment-1711153725 + * + * @return Should return error contains `not supported for remote caches` + */ + @GET + @Path("error") + public String getQuteTemplate() { + try { + return cached.render(); + } catch (IllegalStateException e) { + return e.getMessage(); + } + } + +} diff --git a/cache/redis/src/main/resources/application.properties b/cache/redis/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cache/redis/src/main/resources/templates/cached.html b/cache/redis/src/main/resources/templates/cached.html new file mode 100644 index 0000000000..55584772d0 --- /dev/null +++ b/cache/redis/src/main/resources/templates/cached.html @@ -0,0 +1 @@ +{#cached}This cached template won't be working with remote cache like redis.{/cached} diff --git a/cache/redis/src/test/java/io/quarkus/ts/cache/redis/OpenShiftRedisCacheIT.java b/cache/redis/src/test/java/io/quarkus/ts/cache/redis/OpenShiftRedisCacheIT.java new file mode 100644 index 0000000000..e96cf029d8 --- /dev/null +++ b/cache/redis/src/test/java/io/quarkus/ts/cache/redis/OpenShiftRedisCacheIT.java @@ -0,0 +1,7 @@ +package io.quarkus.ts.cache.redis; + +import io.quarkus.test.scenarios.OpenShiftScenario; + +@OpenShiftScenario +public class OpenShiftRedisCacheIT extends RedisCacheIT { +} diff --git a/cache/redis/src/test/java/io/quarkus/ts/cache/redis/RedisCacheIT.java b/cache/redis/src/test/java/io/quarkus/ts/cache/redis/RedisCacheIT.java new file mode 100644 index 0000000000..75fa38fe97 --- /dev/null +++ b/cache/redis/src/test/java/io/quarkus/ts/cache/redis/RedisCacheIT.java @@ -0,0 +1,185 @@ +package io.quarkus.ts.cache.redis; + +import static io.quarkus.ts.cache.caffeine.ServiceWithCacheResource.APPLICATION_SCOPE_SERVICE_PATH; +import static io.quarkus.ts.cache.caffeine.ServiceWithCacheResource.REQUEST_SCOPE_SERVICE_PATH; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import io.quarkus.test.bootstrap.DefaultService; +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.services.Container; +import io.quarkus.test.services.QuarkusApplication; + +@QuarkusScenario +public class RedisCacheIT { + + private static final String SERVICE_APPLICATION_SCOPE_PATH = "/services/" + APPLICATION_SCOPE_SERVICE_PATH; + private static final String SERVICE_REQUEST_SCOPE_PATH = "/services/" + REQUEST_SCOPE_SERVICE_PATH; + + private static final String PREFIX_ONE = "prefix1"; + private static final String PREFIX_TWO = "prefix2"; + + private static final int REDIS_PORT = 6379; + + @Container(image = "${redis.image}", port = REDIS_PORT, expectedLog = "Ready to accept connections") + static DefaultService redis = new DefaultService().withProperty("ALLOW_EMPTY_PASSWORD", "YES"); + + @QuarkusApplication + static RestService app = new RestService() + .withProperty("quarkus.redis.hosts", + () -> { + String redisHost = redis.getURI().withScheme("redis").getRestAssuredStyleUri(); + return String.format("%s:%d", redisHost, redis.getURI().getPort()); + }); + + /** + * Check whether the `@CacheResult` annotation works when used in a service. + */ + @ParameterizedTest + @ValueSource(strings = { SERVICE_APPLICATION_SCOPE_PATH, SERVICE_REQUEST_SCOPE_PATH }) + public void shouldGetTheSameValueAlwaysWhenGettingValueFromPath(String path) { + // We call the service endpoint + String value = getFromPath(path); + + // At this point, the cache is populated and we should get the same value from the cache + assertEquals(value, getFromPath(path), "Value was different which means cache is not working"); + } + + /** + * Check whether the `@CacheInvalidate` annotation invalidates the cache when used in a service. + */ + @ParameterizedTest + @ValueSource(strings = { SERVICE_APPLICATION_SCOPE_PATH, SERVICE_REQUEST_SCOPE_PATH }) + public void shouldGetDifferentValueWhenInvalidateCacheFromPath(String path) { + // We call the service endpoint + String value = getFromPath(path); + + // invalidate the cache + invalidateCacheFromPath(path); + + // Then the value should be different as we have invalidated the cache. + assertNotEquals(value, getFromPath(path), "Value was equal which means cache invalidate didn't work"); + } + + /** + * Check whether the `@CacheResult` annotation works when used in a service. + */ + @ParameterizedTest + @ValueSource(strings = { SERVICE_APPLICATION_SCOPE_PATH, SERVICE_REQUEST_SCOPE_PATH }) + public void shouldGetTheSameValueForSamePrefixesWhenGettingValueFromPath(String path) { + // We call the service endpoint + String value = getValueFromPathUsingPrefix(path, PREFIX_ONE); + + // At this point, the cache is populated and we should get the same value from the cache + assertEquals(value, getValueFromPathUsingPrefix(path, PREFIX_ONE), + "Value was different which means cache is not working"); + // But different value using another prefix + assertNotEquals(value, getValueFromPathUsingPrefix(path, PREFIX_TWO), + "Value was equal which means @CacheKey didn't work"); + } + + /** + * Check whether the `@CacheInvalidate` annotation does not invalidate all the caches + */ + @ParameterizedTest + @ValueSource(strings = { SERVICE_APPLICATION_SCOPE_PATH, SERVICE_REQUEST_SCOPE_PATH }) + public void shouldGetTheSameValuesEvenAfterCallingToCacheInvalidateFromPath(String path) { + // We call the service endpoints + String valueOfPrefix1 = getValueFromPathUsingPrefix(path, PREFIX_ONE); + String valueOfPrefix2 = getValueFromPathUsingPrefix(path, PREFIX_TWO); + + // invalidate the cache: this should not invalidate all the keys + invalidateCacheFromPath(path); + + // At this point, the cache is populated and we should get the same value for both prefixes + assertEquals(valueOfPrefix1, getValueFromPathUsingPrefix(path, PREFIX_ONE)); + assertEquals(valueOfPrefix2, getValueFromPathUsingPrefix(path, PREFIX_TWO)); + } + + /** + * Check whether the `@CacheInvalidate` and `@CacheKey` annotations work as expected. + */ + @ParameterizedTest + @ValueSource(strings = { SERVICE_APPLICATION_SCOPE_PATH, SERVICE_REQUEST_SCOPE_PATH }) + public void shouldGetDifferentValueWhenInvalidateCacheOnlyForOnePrefixFromPath(String path) { + // We call the service endpoints + String valueOfPrefix1 = getValueFromPathUsingPrefix(path, PREFIX_ONE); + String valueOfPrefix2 = getValueFromPathUsingPrefix(path, PREFIX_TWO); + + // invalidate the cache: this should not invalidate all the keys + invalidateCacheWithPrefixFromPath(path, PREFIX_ONE); + + // The cache was invalidated only for prefix1, so the value should be different + assertNotEquals(valueOfPrefix1, getValueFromPathUsingPrefix(path, PREFIX_ONE)); + // The cache was not invalidated for prefix2, so the value should be the same + assertEquals(valueOfPrefix2, getValueFromPathUsingPrefix(path, PREFIX_TWO)); + } + + /** + * Check whether the `@CacheInvalidateAll` annotation works as expected. + */ + @ParameterizedTest + @ValueSource(strings = { SERVICE_APPLICATION_SCOPE_PATH, SERVICE_REQUEST_SCOPE_PATH }) + public void shouldGetDifferentValueWhenInvalidateAllTheCacheFromPath(String path) { + // We call the service endpoints + String value = getFromPath(path); + String valueOfPrefix1 = getValueFromPathUsingPrefix(path, PREFIX_ONE); + String valueOfPrefix2 = getValueFromPathUsingPrefix(path, PREFIX_TWO); + + // invalidate all the cache + invalidateCacheAllFromPath(path); + + // Then, all the values should be different: + assertNotEquals(value, getFromPath(path)); + assertNotEquals(valueOfPrefix1, getValueFromPathUsingPrefix(path, PREFIX_ONE)); + assertNotEquals(valueOfPrefix2, getValueFromPathUsingPrefix(path, PREFIX_TWO)); + } + + /** + * Check if the usage of Qute and redis throw expected error + */ + @Test + public void quteShouldThrowError() { + assertThat(getFromPath("/template/error"), containsString("not supported for remote caches")); + } + + private void invalidateCacheAllFromPath(String path) { + postFromPath(path + "/invalidate-cache-all"); + } + + private void invalidateCacheWithPrefixFromPath(String path, String prefix) { + postFromPath(path + "/using-prefix/" + prefix + "/invalidate-cache"); + } + + private void invalidateCacheFromPath(String path) { + postFromPath(path + "/invalidate-cache"); + } + + private String getValueFromPathUsingPrefix(String path, String prefix) { + return getFromPath(path + "/using-prefix/" + prefix); + } + + private String getFromPath(String path) { + return app.given() + .when().get(path) + .then() + .statusCode(HttpStatus.SC_OK) + .extract().asString(); + } + + private void postFromPath(String path) { + app.given() + .when().post(path) + .then() + .statusCode(HttpStatus.SC_NO_CONTENT); + } + +} diff --git a/cache/redis/src/test/resources/test.properties b/cache/redis/src/test/resources/test.properties new file mode 100644 index 0000000000..a72083e303 --- /dev/null +++ b/cache/redis/src/test/resources/test.properties @@ -0,0 +1 @@ +ts.redis.openshift.use-internal-service-as-url=true diff --git a/pom.xml b/pom.xml index bde6cded91..fa5a94bc29 100644 --- a/pom.xml +++ b/pom.xml @@ -415,6 +415,7 @@ quarkus-cli logging/jboss cache/caffeine + cache/redis qute/multimodule qute/synchronous qute/reactive