Skip to content

Instantly share code, notes, and snippets.

@mp911de
Created August 26, 2014 07:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mp911de/1b068edb6613a809a239 to your computer and use it in GitHub Desktop.
Save mp911de/1b068edb6613a809a239 to your computer and use it in GitHub Desktop.
import javax.interceptor.InterceptorBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a method as Cacheable. The outcome of the method will be cached. A key is used to distinguish between different
* invocations/requests. Use @CacheKey to mark an argument as part of a cache key. Every outcome will be cached, even
* null values.
*
*/
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface Cacheable {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marker for a method argument to use it as part of the cache key (multiple arguments can be are annotated with @CacheKey)
*
*/
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheKey {
}
import static com.google.common.base.Preconditions.*;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.naming.InitialContext;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.infinispan.Cache;
import org.infinispan.manager.CacheContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Caching interceptor to perform caching on @Cacheable methods.
*
* Use @Cacheable on any method you want to cache (for it's output). Add @CacheKey annotations to every parameter,
* that is relevant for the caching key.
*
* {@code
* @Cacheable
* public int[] fibonacci(@CacheKey int base){...}
* }
*
* The values are cached per class, method and per cacheKey. So you can use your backing cache to store data from multiple, @Cacheable methods.
* Currently @UseInfinispan is supported as backing cache implementation.
*
*/
@Interceptor
@Cacheable
public class CachingInterceptor implements Serializable {
private static final Logger LOGGER = LoggerFactory.getLogger(CachingInterceptor.class);
@AroundInvoke
public Object perform(InvocationContext ctx) throws Exception {
LOGGER.debug("perform invocation for " + ctx.getMethod().getDeclaringClass().getSimpleName() + "#"
+ ctx.getMethod().getName());
CompositeCacheKey cacheKey = getCacheKeys(ctx.getMethod(), ctx.getParameters());
Cache<CompositeCacheKey, CacheEntry> cache = lookupCache(ctx.getMethod());
CacheEntry entry = cache.get(cacheKey);
if (entry == null) {
LOGGER.debug("perform|no cached value, performing invocation");
Object value = ctx.proceed();
entry = new CacheEntry(value);
cache.put(cacheKey, entry);
} else {
LOGGER.debug("perform|using cached value");
}
return entry.getValue();
}
private Cache<CompositeCacheKey, CacheEntry> lookupCache(Method method) throws Exception{
UseInfinispan useInfinispan = method.getAnnotation(UseInfinispan.class);
if (useInfinispan == null) {
useInfinispan = method.getDeclaringClass().getAnnotation(UseInfinispan.class);
}
checkState(useInfinispan != null, "@" + UseInfinispan.class.getSimpleName() + " is not declared on " + method
+ " or its class.");
String jndiName = useInfinispan.value();
if (StringUtils.isEmpty(jndiName)) {
jndiName = useInfinispan.mappedName();
}
if (StringUtils.isEmpty(jndiName)) {
checkArgument(StringUtils.isNotEmpty(useInfinispan.value()), "@" + UseInfinispan.class.getSimpleName()
+ ".mappedName is empty");
}
LOGGER.debug("lookupCache|infinispanJdniName=" + jndiName);
CacheContainer cacheContainer = InitialContext.doLookup(jndiName);
if (StringUtils.isEmpty(useInfinispan.cacheName())) {
return cacheContainer.getCache();
}
return cacheContainer.getCache(useInfinispan.cacheName());
}
private CompositeCacheKey getCacheKeys(Method method, Object[] parameters) {
checkState(method.getParameterTypes().length == parameters.length,
"Method-Parameter and Invocation-Parameter-Count do not match.");
List<Object> result = Lists.newArrayList();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
result.add(method.getDeclaringClass().getName());
result.add(method.getName());
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
if (isCacheKey(annotations)) {
result.add(parameters[i]);
}
}
LOGGER.debug("getCacheKeys|cacheKeys=" + result);
return new CompositeCacheKey(result);
}
private boolean isCacheKey(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(CacheKey.class)) {
return true;
}
}
return false;
}
private class CacheEntry implements Serializable {
private Object value;
private CacheEntry(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
private class CompositeCacheKey implements Serializable {
private List<Object> objects;
private CompositeCacheKey(List<Object> objects) {
this.objects = objects;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CompositeCacheKey)) {
return false;
}
CompositeCacheKey that = (CompositeCacheKey) o;
if (objects != null ? !objects.equals(that.objects) : that.objects != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return objects != null ? objects.hashCode() : 0;
}
}
}
@ApplicationScoped
@UseInfinispan("java:/cache/infinispan")
public class Example {
@Cacheable
public int[] fibonacci(@CacheKey int base){
return ...
}
@Cacheable
public int logN(@CacheKey int base){
return ...
}
@UseInfinispan(mappedName = "java:/cache/different/infinispan", cacheName = "otherCache")
@Cacheable
public String calculateSomething(@CacheKey String inputParameter, String notCacheRelevantParameter){
return ...
}
}
import javax.interceptor.InterceptorBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Value is a substitute for mappedName. If cacheName is empty, the default cache is used.
*
*/
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface UseInfinispan {
public String value() default "";
public String mappedName() default "";
public String cacheName() default "";
}
@jtktam
Copy link

jtktam commented Mar 14, 2018

thank you very much for this gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment