-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25300 from karesti/infinispan-caching-annotations
Infinispan - Support caching annotations
- Loading branch information
Showing
20 changed files
with
890 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/CacheInvalidate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package io.quarkus.infinispan.client; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Repeatable; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import javax.enterprise.util.Nonbinding; | ||
import javax.interceptor.InterceptorBinding; | ||
|
||
import io.quarkus.infinispan.client.CacheInvalidate.List; | ||
|
||
/** | ||
* When a method annotated with {@link CacheInvalidate} is invoked, Quarkus will use the method argument as key to try to | ||
* remove an existing entry from the Infinispan cache. If the key does not identify any cache entry, nothing will happen. | ||
* <p> | ||
* This annotation can be combined with {@link CacheResult} annotation on a single method. Caching operations will always | ||
* be executed in the same order: {@link CacheInvalidateAll} first, then {@link CacheInvalidate} and finally | ||
* {@link CacheResult}. | ||
*/ | ||
@InterceptorBinding | ||
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Repeatable(List.class) | ||
public @interface CacheInvalidate { | ||
|
||
/** | ||
* The name of the cache. | ||
*/ | ||
@Nonbinding | ||
String cacheName(); | ||
|
||
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@interface List { | ||
CacheInvalidate[] value(); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...inispan-client/runtime/src/main/java/io/quarkus/infinispan/client/CacheInvalidateAll.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package io.quarkus.infinispan.client; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Repeatable; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import javax.enterprise.util.Nonbinding; | ||
import javax.interceptor.InterceptorBinding; | ||
|
||
import io.quarkus.infinispan.client.CacheInvalidateAll.List; | ||
|
||
/** | ||
* When a method annotated with {@link CacheInvalidateAll} is invoked, Quarkus will remove all entries from the Infinispan | ||
* cache. | ||
* <p> | ||
* This annotation can be combined with {@link CacheResult} annotation on a single method. Caching operations will always | ||
* be executed in the same order: {@link CacheInvalidateAll} first, then {@link CacheInvalidate} and finally | ||
* {@link CacheResult}. | ||
*/ | ||
@InterceptorBinding | ||
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Repeatable(List.class) | ||
public @interface CacheInvalidateAll { | ||
|
||
/** | ||
* The name of the cache. | ||
*/ | ||
@Nonbinding | ||
String cacheName(); | ||
|
||
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@interface List { | ||
CacheInvalidateAll[] value(); | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
...ons/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/CacheResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package io.quarkus.infinispan.client; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import javax.enterprise.util.Nonbinding; | ||
import javax.interceptor.InterceptorBinding; | ||
|
||
/** | ||
* When a method annotated with {@link CacheResult} is invoked, Quarkus will use the method argument as key and use it to check | ||
* in the | ||
* Infinispan cache if the method has been already invoked. If a value is found in the cache, it is returned and the | ||
* annotated method is never actually | ||
* executed. If no value is found, the annotated method is invoked and the returned value is stored in the cache using the | ||
* computed key. | ||
* <p> | ||
* A method annotated with {@link CacheResult} is protected by a lock on cache miss mechanism. If several concurrent | ||
* invocations try to retrieve a cache value from the same missing key, the method will only be invoked once. The first | ||
* concurrent invocation will trigger the method invocation while the subsequent concurrent invocations will wait for the end | ||
* of the method invocation to get the cached result. The {@code lockTimeout} parameter can be used to interrupt the lock after | ||
* a given delay. The lock timeout is disabled by default, meaning the lock is never interrupted. See the parameter Javadoc for | ||
* more details. | ||
* <p> | ||
* This annotation cannot be used on a method returning {@code void}. It can be combined with {@link CacheInvalidate} and | ||
* {@link CacheInvalidateAll} | ||
* annotations on a single method. Caching operations will always be executed in the same order: {@link CacheInvalidateAll} | ||
* first, then {@link CacheInvalidate} and finally {@link CacheResult}. | ||
* <p> | ||
*/ | ||
@InterceptorBinding | ||
@Target({ ElementType.TYPE, ElementType.METHOD }) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface CacheResult { | ||
|
||
/** | ||
* The name of the cache. | ||
*/ | ||
@Nonbinding | ||
String cacheName(); | ||
|
||
/** | ||
* Delay in milliseconds before the lock on cache miss is interrupted. If such interruption happens, the cached method will | ||
* be invoked and its result will be returned without being cached. A value of {@code 0} (which is the default one) means | ||
* that the lock timeout is disabled. | ||
*/ | ||
@Nonbinding | ||
long lockTimeout() default 0; | ||
} |
21 changes: 21 additions & 0 deletions
21
...me/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInterceptionContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package io.quarkus.infinispan.client.runtime.cache; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class CacheInterceptionContext<T extends Annotation> { | ||
|
||
private final List<T> interceptorBindings; | ||
|
||
public CacheInterceptionContext(List<T> interceptorBindings) { | ||
Objects.requireNonNull(interceptorBindings); | ||
this.interceptorBindings = Collections.unmodifiableList(interceptorBindings); | ||
} | ||
|
||
public List<T> getInterceptorBindings() { | ||
return interceptorBindings; | ||
} | ||
|
||
} |
132 changes: 132 additions & 0 deletions
132
...nt/runtime/src/main/java/io/quarkus/infinispan/client/runtime/cache/CacheInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package io.quarkus.infinispan.client.runtime.cache; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.concurrent.CompletionStage; | ||
import java.util.function.Supplier; | ||
|
||
import javax.inject.Inject; | ||
import javax.interceptor.Interceptor.Priority; | ||
import javax.interceptor.InvocationContext; | ||
|
||
import org.infinispan.client.hotrod.RemoteCacheManager; | ||
import org.infinispan.commons.CacheException; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.quarkus.arc.runtime.InterceptorBindings; | ||
import io.smallrye.mutiny.Uni; | ||
|
||
public abstract class CacheInterceptor { | ||
|
||
public static final int BASE_PRIORITY = Priority.PLATFORM_BEFORE; | ||
protected static final String UNHANDLED_ASYNC_RETURN_TYPE_MSG = "Unhandled async return type"; | ||
|
||
private static final Logger LOGGER = Logger.getLogger(CacheInterceptor.class); | ||
|
||
@Inject | ||
RemoteCacheManager cacheManager; | ||
|
||
/* | ||
* The interception is almost always managed by Arc in a Quarkus application. In such a case, we want to retrieve the | ||
* interceptor bindings stored by Arc in the invocation context data (very good performance-wise). But sometimes the | ||
* interception is managed by another CDI interceptors implementation. It can happen for example while using caching | ||
* annotations on a MicroProfile REST Client method. In that case, we have no other choice but to rely on reflection (with | ||
* underlying synchronized blocks which are bad for performances) to retrieve the interceptor bindings. | ||
*/ | ||
protected <T extends Annotation> CacheInterceptionContext<T> getInterceptionContext(InvocationContext invocationContext, | ||
Class<T> interceptorBindingClass) { | ||
return getArcCacheInterceptionContext(invocationContext, interceptorBindingClass) | ||
.orElseGet(new Supplier<CacheInterceptionContext<T>>() { | ||
@Override | ||
public CacheInterceptionContext<T> get() { | ||
return getNonArcCacheInterceptionContext(invocationContext, interceptorBindingClass); | ||
} | ||
}); | ||
} | ||
|
||
private <T extends Annotation> Optional<CacheInterceptionContext<T>> getArcCacheInterceptionContext( | ||
InvocationContext invocationContext, Class<T> interceptorBindingClass) { | ||
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(invocationContext); | ||
if (bindings == null) { | ||
LOGGER.trace("Interceptor bindings not found in ArC"); | ||
// This should only happen when the interception is not managed by Arc. | ||
return Optional.empty(); | ||
} | ||
List<T> interceptorBindings = new ArrayList<>(); | ||
for (Annotation binding : bindings) { | ||
if (interceptorBindingClass.isInstance(binding)) { | ||
interceptorBindings.add((T) binding); | ||
} | ||
} | ||
return Optional.of(new CacheInterceptionContext<>(interceptorBindings)); | ||
} | ||
|
||
private <T extends Annotation> CacheInterceptionContext<T> getNonArcCacheInterceptionContext( | ||
InvocationContext invocationContext, Class<T> interceptorBindingClass) { | ||
LOGGER.trace("Retrieving interceptor bindings using reflection"); | ||
List<T> interceptorBindings = new ArrayList<>(); | ||
for (Annotation annotation : invocationContext.getMethod().getAnnotations()) { | ||
if (interceptorBindingClass.isInstance(annotation)) { | ||
interceptorBindings.add((T) annotation); | ||
} | ||
} | ||
return new CacheInterceptionContext<>(interceptorBindings); | ||
} | ||
|
||
protected Object getCacheKey(Object[] methodParameterValues) { | ||
if (methodParameterValues == null || methodParameterValues.length == 0) { | ||
// If the intercepted method doesn't have any parameter, raise an exception. | ||
throw new CacheException("Unable to cache without a key"); | ||
} else if (methodParameterValues.length == 1) { | ||
// If the intercepted method has exactly one parameter, then this parameter will be used as the cache key. | ||
return methodParameterValues[0]; | ||
} else { | ||
// Protobuf type must be used | ||
return new RuntimeException("A single parameter is needed. Create a Protobuf schema to create a Composite Key."); | ||
} | ||
} | ||
|
||
protected static ReturnType determineReturnType(Class<?> returnType) { | ||
if (Uni.class.isAssignableFrom(returnType)) { | ||
return ReturnType.Uni; | ||
} | ||
if (CompletionStage.class.isAssignableFrom(returnType)) { | ||
return ReturnType.CompletionStage; | ||
} | ||
return ReturnType.NonAsync; | ||
} | ||
|
||
protected Uni<?> asyncInvocationResultToUni(Object invocationResult, ReturnType returnType) { | ||
if (returnType == ReturnType.Uni) { | ||
return (Uni<?>) invocationResult; | ||
} else if (returnType == ReturnType.CompletionStage) { | ||
return Uni.createFrom().completionStage(new Supplier<>() { | ||
@Override | ||
public CompletionStage<?> get() { | ||
return (CompletionStage<?>) invocationResult; | ||
} | ||
}); | ||
} else { | ||
throw new CacheException(new IllegalStateException(UNHANDLED_ASYNC_RETURN_TYPE_MSG)); | ||
} | ||
} | ||
|
||
protected Object createAsyncResult(Uni<Object> cacheValue, ReturnType returnType) { | ||
if (returnType == ReturnType.Uni) { | ||
return cacheValue; | ||
} | ||
if (returnType == ReturnType.CompletionStage) { | ||
return cacheValue.subscribeAsCompletionStage(); | ||
} | ||
throw new CacheException(new IllegalStateException(UNHANDLED_ASYNC_RETURN_TYPE_MSG)); | ||
} | ||
|
||
protected enum ReturnType { | ||
NonAsync, | ||
Uni, | ||
CompletionStage | ||
} | ||
} |
Oops, something went wrong.