Skip to content

Commit

Permalink
Infinispan Caching annotations @invalidate @InvalidateAll @CacheResult
Browse files Browse the repository at this point in the history
  • Loading branch information
karesti committed May 3, 2022
1 parent e86c493 commit 7e2d9d5
Show file tree
Hide file tree
Showing 24 changed files with 961 additions and 9 deletions.
37 changes: 37 additions & 0 deletions docs/src/main/asciidoc/infinispan-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,43 @@ for Kubernetes deployments, Infinispan Console,
https://infinispan.org/docs/stable/titles/rest/rest.html#rest_v2_protobuf_schemas[REST API] or the
https://infinispan.org/docs/stable/titles/encoding/encoding.html#registering-sci-remote-caches_marshalling[Hot Rod Java Client].

[#annotations-api]
== Caching using annotations

The Infinispan Client extension offers a set of annotations that can be used in a CDI managed bean to enable caching abilities with Infinispan.

[WARNING]
====
Caching annotations are not allowed on private methods.
They will work fine with any other access modifier including package-private (no explicit modifier).
====

=== @CacheResult

Loads a method result from the cache without executing the method body whenever possible.

When a method annotated with `@CacheResult` is invoked, Quarkus will compute a cache key and use it to check in the cache whether the method has been already invoked.
Methods with multiple parameters are not allowed. For composite keys, define a Protobuf schema that will hold multiple values.
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.
This annotation cannot be used on a method returning `void`.

[NOTE]
====
Infinispan Client extension is not able yet to cache `null` values unlike the Quarkus-Cache extension.
====

=== @CacheInvalidate

Removes an entry from the cache.

When a method annotated with `@CacheInvalidate` is invoked, Infinispan will use the method argument as a cache key to try to remove an existing entry from the cache.
If the key does not identify any cache entry, nothing will happen.

=== @CacheInvalidateAll

When a method annotated with `@CacheInvalidateAll` is invoked, Infinispan will remove all entries from the cache.


== Querying

Expand Down
4 changes: 4 additions & 0 deletions extensions/infinispan-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jsonp-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mutiny-deployment</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.infinispan.client.deployment;

import java.util.Set;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* This build item is used to pass the full list of cache names from the validation step to the recording step.
*/
public final class CacheNamesBuildItem extends SimpleBuildItem {

private final Set<String> names;

public CacheNamesBuildItem(Set<String> names) {
this.names = names;
}

public Set<String> getNames() {
return names;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
import io.quarkus.infinispan.client.runtime.InfinispanClientBuildTimeConfig;
import io.quarkus.infinispan.client.runtime.InfinispanClientProducer;
import io.quarkus.infinispan.client.runtime.InfinispanRecorder;
import io.quarkus.infinispan.client.runtime.cache.CacheInvalidateAllInterceptor;
import io.quarkus.infinispan.client.runtime.cache.CacheInvalidateInterceptor;
import io.quarkus.infinispan.client.runtime.cache.CacheResultInterceptor;
import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem;

class InfinispanClientProcessor {
Expand Down Expand Up @@ -85,6 +88,9 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch

feature.produce(new FeatureBuildItem(Feature.INFINISPAN_CLIENT));
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(InfinispanClientProducer.class));
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CacheInvalidateAllInterceptor.class));
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CacheResultInterceptor.class));
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(CacheInvalidateInterceptor.class));
systemProperties.produce(new SystemPropertyBuildItem("io.netty.noUnsafe", "true"));
hotDeployment.produce(new HotDeploymentWatchedFileBuildItem(META_INF + File.separator + HOTROD_CLIENT_PROPERTIES));

Expand Down Expand Up @@ -235,7 +241,8 @@ BeanContainerListenerBuildItem build(InfinispanRecorder recorder, InfinispanProp
@BuildStep
UnremovableBeanBuildItem ensureBeanLookupAvailable() {
return UnremovableBeanBuildItem.beanTypes(BaseMarshaller.class, EnumMarshaller.class, MessageMarshaller.class,
RawProtobufMarshaller.class, FileDescriptorSource.class);
RawProtobufMarshaller.class, FileDescriptorSource.class, CacheResultInterceptor.class,
CacheInvalidateAllInterceptor.class);
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.quarkus.infinispan.client.deployment.cache;

import java.util.Arrays;
import java.util.List;

import org.jboss.jandex.DotName;

import io.quarkus.infinispan.client.CacheInvalidate;
import io.quarkus.infinispan.client.CacheInvalidateAll;
import io.quarkus.infinispan.client.CacheResult;
import io.quarkus.infinispan.client.runtime.cache.CacheResultInterceptor;
import io.smallrye.mutiny.Multi;

public class CacheDeploymentConstants {

// API annotations names.
public static final DotName CACHE_INVALIDATE_ALL = dotName(CacheInvalidateAll.class);
public static final DotName CACHE_INVALIDATE_ALL_LIST = dotName(CacheInvalidateAll.List.class);
public static final DotName CACHE_INVALIDATE = dotName(CacheInvalidate.class);
public static final DotName CACHE_INVALIDATE_LIST = dotName(CacheInvalidate.List.class);
public static final DotName CACHE_RESULT = dotName(CacheResult.class);
public static final List<DotName> INTERCEPTOR_BINDINGS = Arrays.asList(CACHE_RESULT, CACHE_INVALIDATE,
CACHE_INVALIDATE_ALL);
public static final List<DotName> INTERCEPTOR_BINDING_CONTAINERS = Arrays.asList(CACHE_INVALIDATE_LIST,
CACHE_INVALIDATE_ALL_LIST);
public static final List<DotName> INTERCEPTORS = Arrays.asList(dotName(CacheResultInterceptor.class));

// MicroProfile REST Client.
public static final DotName REGISTER_REST_CLIENT = DotName
.createSimple("org.eclipse.microprofile.rest.client.inject.RegisterRestClient");

// Mutiny.
public static final DotName MULTI = dotName(Multi.class);

// Annotations parameters.
public static final String CACHE_NAME_PARAM = "cacheName";

private static DotName dotName(Class<?> annotationClass) {
return DotName.createSimple(annotationClass.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.infinispan.client.deployment.cache.exception;

import org.jboss.jandex.DotName;

import io.quarkus.infinispan.client.CacheInvalidate;
import io.quarkus.infinispan.client.CacheInvalidateAll;
import io.quarkus.infinispan.client.CacheResult;

/**
* This exception is thrown at build time during the validation phase if a class is annotated with
* {@link CacheInvalidate @CacheInvalidate}, {@link CacheInvalidateAll @CacheInvalidateAll} or
* {@link CacheResult @CacheResult}. These annotations are only allowed at type level for the caching
* interceptors from this extension.
*/
@SuppressWarnings("serial")
public class ClassTargetException extends RuntimeException {

private final DotName className;

public ClassTargetException(DotName className, DotName annotationName) {
super("Caching annotations are not allowed on a class [class=" + className + ", annotation=" + annotationName + "]");
this.className = className;
}

public DotName getClassName() {
return className;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.infinispan.client.deployment.cache.exception;

import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;

import io.quarkus.infinispan.client.CacheInvalidate;
import io.quarkus.infinispan.client.CacheInvalidateAll;
import io.quarkus.infinispan.client.CacheResult;

/**
* This exception is thrown at build time during the validation phase if a private method is annotated with
* {@link CacheInvalidate @CacheInvalidate}, {@link CacheInvalidateAll @CacheInvalidateAll} or
* {@link CacheResult @CacheResult}.
*/
@SuppressWarnings("serial")
public class PrivateMethodTargetException extends RuntimeException {

private final MethodInfo methodInfo;

public PrivateMethodTargetException(MethodInfo methodInfo, DotName annotationName) {
super("Caching annotations are not allowed on a private method [class=" + methodInfo.declaringClass().name()
+ ", method=" + methodInfo.name() + ", annotation=" + annotationName + "]");
this.methodInfo = methodInfo;
}

public MethodInfo getMethodInfo() {
return methodInfo;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.infinispan.client.deployment.cache.exception;

import org.jboss.jandex.MethodInfo;

import io.quarkus.infinispan.client.CacheResult;

/**
* This exception is thrown at build time during the validation phase if a method returning void is annotated with
* {@link CacheResult @CacheResult}.
*/
@SuppressWarnings("serial")
public class VoidReturnTypeTargetException extends RuntimeException {

private final MethodInfo methodInfo;

public VoidReturnTypeTargetException(MethodInfo methodInfo) {
super("@CacheResult is not allowed on a method returning void [class=" + methodInfo.declaringClass().name()
+ ", method=" + methodInfo.name() + "]");
this.methodInfo = methodInfo;
}

public MethodInfo getMethodInfo() {
return methodInfo;
}
}
4 changes: 4 additions & 0 deletions extensions/infinispan-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-caffeine</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mutiny</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty</artifactId>
Expand Down
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 multiple other caching 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}.
*/
@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();
}
}
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 multiple other caching 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}.
*/
@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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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 whether 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 multiple other caching
* 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;
}
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;
}

}
Loading

0 comments on commit 7e2d9d5

Please sign in to comment.