Created
August 26, 2014 07:41
-
-
Save mp911de/1b068edb6613a809a239 to your computer and use it in GitHub Desktop.
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
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 { | |
} |
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
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 { | |
} |
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
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; | |
} | |
} | |
} |
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
@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 ... | |
} | |
} |
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
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 ""; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thank you very much for this gist