| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.commons.jcs3.jcache.cdi; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.logging.Logger; |
| |
| import javax.annotation.PreDestroy; |
| import javax.cache.annotation.CacheDefaults; |
| import javax.cache.annotation.CacheKey; |
| import javax.cache.annotation.CacheKeyGenerator; |
| import javax.cache.annotation.CachePut; |
| import javax.cache.annotation.CacheRemove; |
| import javax.cache.annotation.CacheRemoveAll; |
| import javax.cache.annotation.CacheResolverFactory; |
| import javax.cache.annotation.CacheResult; |
| import javax.cache.annotation.CacheValue; |
| import javax.enterprise.context.ApplicationScoped; |
| import javax.enterprise.context.spi.CreationalContext; |
| import javax.enterprise.inject.spi.Bean; |
| import javax.enterprise.inject.spi.BeanManager; |
| import javax.inject.Inject; |
| import javax.interceptor.InvocationContext; |
| |
| @ApplicationScoped |
| public class CDIJCacheHelper |
| { |
| private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName()); |
| private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.commons.jcs3.jcache.cdi.skip-close"); |
| |
| private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // lazy to not create any cache if not needed |
| private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl(); |
| |
| private final Collection<CreationalContext<?>> toRelease = new ArrayList<>(); |
| private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<>(); |
| |
| @Inject |
| private BeanManager beanManager; |
| |
| @PreDestroy |
| private void release() { |
| if (CLOSE_CACHE && defaultCacheResolverFactory != null) |
| { |
| defaultCacheResolverFactory.release(); |
| } |
| for (final CreationalContext<?> cc : toRelease) |
| { |
| try |
| { |
| cc.release(); |
| } |
| catch (final RuntimeException re) |
| { |
| LOGGER.warning(re.getMessage()); |
| } |
| } |
| } |
| |
| public MethodMeta findMeta(final InvocationContext ic) |
| { |
| final Method mtd = ic.getMethod(); |
| final Class<?> refType = findKeyType(ic.getTarget()); |
| final MethodKey key = new MethodKey(refType, mtd); |
| MethodMeta methodMeta = methods.get(key); |
| if (methodMeta == null) |
| { |
| synchronized (this) |
| { |
| methodMeta = methods.get(key); |
| if (methodMeta == null) |
| { |
| methodMeta = createMeta(ic); |
| methods.put(key, methodMeta); |
| } |
| } |
| } |
| return methodMeta; |
| } |
| |
| private Class<?> findKeyType(final Object target) |
| { |
| if (null == target) |
| { |
| return null; |
| } |
| return target.getClass(); |
| } |
| |
| // it is unlikely we have all annotations but for now we have a single meta model |
| private MethodMeta createMeta(final InvocationContext ic) |
| { |
| final CacheDefaults defaults = findDefaults(ic.getTarget() == null ? null : ic.getTarget() |
| .getClass(), ic.getMethod()); |
| |
| final Class<?>[] parameterTypes = ic.getMethod().getParameterTypes(); |
| final Annotation[][] parameterAnnotations = ic.getMethod().getParameterAnnotations(); |
| final List<Set<Annotation>> annotations = new ArrayList<>(); |
| for (final Annotation[] parameterAnnotation : parameterAnnotations) |
| { |
| final Set<Annotation> set = new HashSet<>(parameterAnnotation.length); |
| set.addAll(Arrays.asList(parameterAnnotation)); |
| annotations.add(set); |
| } |
| |
| final Set<Annotation> mtdAnnotations = new HashSet<>(); |
| mtdAnnotations.addAll(Arrays.asList(ic.getMethod().getAnnotations())); |
| |
| final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class); |
| final String cacheResultCacheResultName = cacheResult == null ? null : defaultName(ic.getMethod(), defaults, cacheResult.cacheName()); |
| final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ? |
| null : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory()); |
| final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ? |
| null : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator()); |
| |
| final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class); |
| final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName()); |
| final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ? |
| null : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory()); |
| final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ? |
| null : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator()); |
| |
| final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class); |
| final String cacheRemoveCacheRemoveName = cacheRemove == null ? null : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName()); |
| final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ? |
| null : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory()); |
| final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ? |
| null : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator()); |
| |
| final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class); |
| final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName()); |
| final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ? |
| null : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory()); |
| |
| return new MethodMeta( |
| parameterTypes, |
| annotations, |
| mtdAnnotations, |
| keyParameterIndexes(ic.getMethod()), |
| getValueParameter(annotations), |
| getKeyParameters(annotations), |
| cacheResultCacheResultName, |
| cacheResultCacheResolverFactory, |
| cacheResultCacheKeyGenerator, |
| cacheResult, |
| cachePutCachePutName, |
| cachePutCacheResolverFactory, |
| cachePutCacheKeyGenerator, |
| cachePut != null && cachePut.afterInvocation(), |
| cachePut, |
| cacheRemoveCacheRemoveName, |
| cacheRemoveCacheResolverFactory, |
| cacheRemoveCacheKeyGenerator, |
| cacheRemove != null && cacheRemove.afterInvocation(), |
| cacheRemove, |
| cacheRemoveAllCacheRemoveAllName, |
| cacheRemoveAllCacheResolverFactory, |
| cacheRemoveAll != null && cacheRemoveAll.afterInvocation(), |
| cacheRemoveAll); |
| } |
| |
| private Integer[] getKeyParameters(final List<Set<Annotation>> annotations) |
| { |
| final Collection<Integer> list = new ArrayList<>(); |
| int idx = 0; |
| for (final Set<Annotation> set : annotations) |
| { |
| for (final Annotation a : set) |
| { |
| if (a.annotationType() == CacheKey.class) |
| { |
| list.add(idx); |
| } |
| } |
| idx++; |
| } |
| if (list.isEmpty()) |
| { |
| for (int i = 0; i < annotations.size(); i++) |
| { |
| list.add(i); |
| } |
| } |
| return list.toArray(new Integer[list.size()]); |
| } |
| |
| private Integer getValueParameter(final List<Set<Annotation>> annotations) |
| { |
| int idx = 0; |
| for (final Set<Annotation> set : annotations) |
| { |
| for (final Annotation a : set) |
| { |
| if (a.annotationType() == CacheValue.class) |
| { |
| return idx; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| private String defaultName(final Method method, final CacheDefaults defaults, final String cacheName) |
| { |
| if (!cacheName.isEmpty()) |
| { |
| return cacheName; |
| } |
| if (defaults != null) |
| { |
| final String name = defaults.cacheName(); |
| if (!name.isEmpty()) |
| { |
| return name; |
| } |
| } |
| |
| final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName()); |
| name.append("."); |
| name.append(method.getName()); |
| name.append("("); |
| final Class<?>[] parameterTypes = method.getParameterTypes(); |
| for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++) |
| { |
| name.append(parameterTypes[pIdx].getName()); |
| if ((pIdx + 1) < parameterTypes.length) |
| { |
| name.append(","); |
| } |
| } |
| name.append(")"); |
| return name.toString(); |
| } |
| |
| private CacheDefaults findDefaults(final Class<?> targetType, final Method method) |
| { |
| if (Proxy.isProxyClass(targetType)) // target doesnt hold annotations |
| { |
| final Class<?> api = method.getDeclaringClass(); |
| for (final Class<?> type : targetType |
| .getInterfaces()) |
| { |
| if (!api.isAssignableFrom(type)) |
| { |
| continue; |
| } |
| return extractDefaults(type); |
| } |
| } |
| return extractDefaults(targetType); |
| } |
| |
| private CacheDefaults extractDefaults(final Class<?> type) |
| { |
| CacheDefaults annotation = null; |
| Class<?> clazz = type; |
| while (clazz != null && clazz != Object.class) |
| { |
| annotation = clazz.getAnnotation(CacheDefaults.class); |
| if (annotation != null) |
| { |
| break; |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| return annotation; |
| } |
| |
| public boolean isIncluded(final Class<?> aClass, final Class<?>[] in, final Class<?>[] out) |
| { |
| if (in.length == 0 && out.length == 0) |
| { |
| return false; |
| } |
| for (final Class<?> potentialIn : in) |
| { |
| if (potentialIn.isAssignableFrom(aClass)) |
| { |
| for (final Class<?> potentialOut : out) |
| { |
| if (potentialOut.isAssignableFrom(aClass)) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults, final Class<? extends CacheKeyGenerator> cacheKeyGenerator) |
| { |
| if (!CacheKeyGenerator.class.equals(cacheKeyGenerator)) |
| { |
| return instance(cacheKeyGenerator); |
| } |
| if (defaults != null) |
| { |
| final Class<? extends CacheKeyGenerator> defaultCacheKeyGenerator = defaults.cacheKeyGenerator(); |
| if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator)) |
| { |
| return instance(defaultCacheKeyGenerator); |
| } |
| } |
| return defaultCacheKeyGenerator; |
| } |
| |
| private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults, final Class<? extends CacheResolverFactory> cacheResolverFactory) |
| { |
| if (!CacheResolverFactory.class.equals(cacheResolverFactory)) |
| { |
| return instance(cacheResolverFactory); |
| } |
| if (defaults != null) |
| { |
| final Class<? extends CacheResolverFactory> defaultCacheResolverFactory = defaults.cacheResolverFactory(); |
| if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory)) |
| { |
| return instance(defaultCacheResolverFactory); |
| } |
| } |
| return defaultCacheResolverFactory(); |
| } |
| |
| private <T> T instance(final Class<T> type) |
| { |
| final Set<Bean<?>> beans = beanManager.getBeans(type); |
| if (beans.isEmpty()) |
| { |
| if (CacheKeyGenerator.class == type) { |
| return (T) defaultCacheKeyGenerator; |
| } |
| if (CacheResolverFactory.class == type) { |
| return (T) defaultCacheResolverFactory(); |
| } |
| return null; |
| } |
| final Bean<?> bean = beanManager.resolve(beans); |
| final CreationalContext<?> context = beanManager.createCreationalContext(bean); |
| final Class<? extends Annotation> scope = bean.getScope(); |
| final boolean normalScope = beanManager.isNormalScope(scope); |
| try |
| { |
| final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context); |
| if (!normalScope) |
| { |
| toRelease.add(context); |
| } |
| return (T) reference; |
| } |
| finally |
| { |
| if (normalScope) |
| { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe? |
| context.release(); |
| } |
| } |
| } |
| |
| private CacheResolverFactoryImpl defaultCacheResolverFactory() |
| { |
| if (defaultCacheResolverFactory != null) { |
| return defaultCacheResolverFactory; |
| } |
| synchronized (this) { |
| if (defaultCacheResolverFactory != null) { |
| return defaultCacheResolverFactory; |
| } |
| defaultCacheResolverFactory = new CacheResolverFactoryImpl(); |
| } |
| return defaultCacheResolverFactory; |
| } |
| |
| private Integer[] keyParameterIndexes(final Method method) |
| { |
| final List<Integer> keys = new LinkedList<>(); |
| final Annotation[][] parameterAnnotations = method.getParameterAnnotations(); |
| |
| // first check if keys are specified explicitely |
| for (int i = 0; i < method.getParameterTypes().length; i++) |
| { |
| final Annotation[] annotations = parameterAnnotations[i]; |
| for (final Annotation a : annotations) |
| { |
| if (a.annotationType().equals(CacheKey.class)) |
| { |
| keys.add(i); |
| break; |
| } |
| } |
| } |
| |
| // if not then use all parameters but value ones |
| if (keys.isEmpty()) |
| { |
| for (int i = 0; i < method.getParameterTypes().length; i++) |
| { |
| final Annotation[] annotations = parameterAnnotations[i]; |
| boolean value = false; |
| for (final Annotation a : annotations) |
| { |
| if (a.annotationType().equals(CacheValue.class)) |
| { |
| value = true; |
| break; |
| } |
| } |
| if (!value) { |
| keys.add(i); |
| } |
| } |
| } |
| return keys.toArray(new Integer[keys.size()]); |
| } |
| |
| private static final class MethodKey |
| { |
| private final Class<?> base; |
| private final Method delegate; |
| private final int hash; |
| |
| private MethodKey(final Class<?> base, final Method delegate) |
| { |
| this.base = base; // we need a class to ensure inheritance don't fall in the same key |
| this.delegate = delegate; |
| this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode()); |
| } |
| |
| @Override |
| public boolean equals(final Object o) |
| { |
| if (this == o) |
| { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) |
| { |
| return false; |
| } |
| final MethodKey classKey = MethodKey.class.cast(o); |
| return delegate.equals(classKey.delegate) && ((base == null && classKey.base == null) || (base != null && base.equals(classKey.base))); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return hash; |
| } |
| } |
| |
| // TODO: split it in 5? |
| public static class MethodMeta |
| { |
| private final Class<?>[] parameterTypes; |
| private final List<Set<Annotation>> parameterAnnotations; |
| private final Set<Annotation> annotations; |
| private final Integer[] keysIndices; |
| private final Integer valueIndex; |
| private final Integer[] parameterIndices; |
| |
| private final String cacheResultCacheName; |
| private final CacheResolverFactory cacheResultResolverFactory; |
| private final CacheKeyGenerator cacheResultKeyGenerator; |
| private final CacheResult cacheResult; |
| |
| private final String cachePutCacheName; |
| private final CacheResolverFactory cachePutResolverFactory; |
| private final CacheKeyGenerator cachePutKeyGenerator; |
| private final boolean cachePutAfter; |
| private final CachePut cachePut; |
| |
| private final String cacheRemoveCacheName; |
| private final CacheResolverFactory cacheRemoveResolverFactory; |
| private final CacheKeyGenerator cacheRemoveKeyGenerator; |
| private final boolean cacheRemoveAfter; |
| private final CacheRemove cacheRemove; |
| |
| private final String cacheRemoveAllCacheName; |
| private final CacheResolverFactory cacheRemoveAllResolverFactory; |
| private final boolean cacheRemoveAllAfter; |
| private final CacheRemoveAll cacheRemoveAll; |
| |
| public MethodMeta(Class<?>[] parameterTypes, List<Set<Annotation>> parameterAnnotations, Set<Annotation> |
| annotations, Integer[] keysIndices, Integer valueIndex, Integer[] parameterIndices, String |
| cacheResultCacheName, CacheResolverFactory cacheResultResolverFactory, CacheKeyGenerator |
| cacheResultKeyGenerator, CacheResult cacheResult, String cachePutCacheName, CacheResolverFactory |
| cachePutResolverFactory, CacheKeyGenerator cachePutKeyGenerator, boolean cachePutAfter, CachePut cachePut, String |
| cacheRemoveCacheName, CacheResolverFactory cacheRemoveResolverFactory, CacheKeyGenerator |
| cacheRemoveKeyGenerator, boolean cacheRemoveAfter, CacheRemove cacheRemove, String cacheRemoveAllCacheName, |
| CacheResolverFactory cacheRemoveAllResolverFactory, boolean |
| cacheRemoveAllAfter, CacheRemoveAll cacheRemoveAll) |
| { |
| this.parameterTypes = parameterTypes; |
| this.parameterAnnotations = parameterAnnotations; |
| this.annotations = annotations; |
| this.keysIndices = keysIndices; |
| this.valueIndex = valueIndex; |
| this.parameterIndices = parameterIndices; |
| this.cacheResultCacheName = cacheResultCacheName; |
| this.cacheResultResolverFactory = cacheResultResolverFactory; |
| this.cacheResultKeyGenerator = cacheResultKeyGenerator; |
| this.cacheResult = cacheResult; |
| this.cachePutCacheName = cachePutCacheName; |
| this.cachePutResolverFactory = cachePutResolverFactory; |
| this.cachePutKeyGenerator = cachePutKeyGenerator; |
| this.cachePutAfter = cachePutAfter; |
| this.cachePut = cachePut; |
| this.cacheRemoveCacheName = cacheRemoveCacheName; |
| this.cacheRemoveResolverFactory = cacheRemoveResolverFactory; |
| this.cacheRemoveKeyGenerator = cacheRemoveKeyGenerator; |
| this.cacheRemoveAfter = cacheRemoveAfter; |
| this.cacheRemove = cacheRemove; |
| this.cacheRemoveAllCacheName = cacheRemoveAllCacheName; |
| this.cacheRemoveAllResolverFactory = cacheRemoveAllResolverFactory; |
| this.cacheRemoveAllAfter = cacheRemoveAllAfter; |
| this.cacheRemoveAll = cacheRemoveAll; |
| } |
| |
| public boolean isCacheRemoveAfter() |
| { |
| return cacheRemoveAfter; |
| } |
| |
| public boolean isCachePutAfter() |
| { |
| return cachePutAfter; |
| } |
| |
| public Class<?>[] getParameterTypes() |
| { |
| return parameterTypes; |
| } |
| |
| public List<Set<Annotation>> getParameterAnnotations() |
| { |
| return parameterAnnotations; |
| } |
| |
| public String getCacheResultCacheName() |
| { |
| return cacheResultCacheName; |
| } |
| |
| public CacheResolverFactory getCacheResultResolverFactory() |
| { |
| return cacheResultResolverFactory; |
| } |
| |
| public CacheKeyGenerator getCacheResultKeyGenerator() |
| { |
| return cacheResultKeyGenerator; |
| } |
| |
| public CacheResult getCacheResult() { |
| return cacheResult; |
| } |
| |
| public Integer[] getParameterIndices() |
| { |
| return parameterIndices; |
| } |
| |
| public Set<Annotation> getAnnotations() |
| { |
| return annotations; |
| } |
| |
| public Integer[] getKeysIndices() |
| { |
| return keysIndices; |
| } |
| |
| public Integer getValuesIndex() |
| { |
| return valueIndex; |
| } |
| |
| public Integer getValueIndex() |
| { |
| return valueIndex; |
| } |
| |
| public String getCachePutCacheName() |
| { |
| return cachePutCacheName; |
| } |
| |
| public CacheResolverFactory getCachePutResolverFactory() |
| { |
| return cachePutResolverFactory; |
| } |
| |
| public CacheKeyGenerator getCachePutKeyGenerator() |
| { |
| return cachePutKeyGenerator; |
| } |
| |
| public CachePut getCachePut() |
| { |
| return cachePut; |
| } |
| |
| public String getCacheRemoveCacheName() |
| { |
| return cacheRemoveCacheName; |
| } |
| |
| public CacheResolverFactory getCacheRemoveResolverFactory() |
| { |
| return cacheRemoveResolverFactory; |
| } |
| |
| public CacheKeyGenerator getCacheRemoveKeyGenerator() |
| { |
| return cacheRemoveKeyGenerator; |
| } |
| |
| public CacheRemove getCacheRemove() |
| { |
| return cacheRemove; |
| } |
| |
| public String getCacheRemoveAllCacheName() |
| { |
| return cacheRemoveAllCacheName; |
| } |
| |
| public CacheResolverFactory getCacheRemoveAllResolverFactory() |
| { |
| return cacheRemoveAllResolverFactory; |
| } |
| |
| public boolean isCacheRemoveAllAfter() |
| { |
| return cacheRemoveAllAfter; |
| } |
| |
| public CacheRemoveAll getCacheRemoveAll() |
| { |
| return cacheRemoveAll; |
| } |
| } |
| } |