View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.jcs3.jcache.cdi;
20  
21  import java.lang.annotation.Annotation;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Proxy;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.HashSet;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.concurrent.ConcurrentHashMap;
32  import java.util.concurrent.ConcurrentMap;
33  import java.util.logging.Logger;
34  
35  import javax.annotation.PreDestroy;
36  import javax.cache.annotation.CacheDefaults;
37  import javax.cache.annotation.CacheKey;
38  import javax.cache.annotation.CacheKeyGenerator;
39  import javax.cache.annotation.CachePut;
40  import javax.cache.annotation.CacheRemove;
41  import javax.cache.annotation.CacheRemoveAll;
42  import javax.cache.annotation.CacheResolverFactory;
43  import javax.cache.annotation.CacheResult;
44  import javax.cache.annotation.CacheValue;
45  import javax.enterprise.context.ApplicationScoped;
46  import javax.enterprise.context.spi.CreationalContext;
47  import javax.enterprise.inject.spi.Bean;
48  import javax.enterprise.inject.spi.BeanManager;
49  import javax.inject.Inject;
50  import javax.interceptor.InvocationContext;
51  
52  @ApplicationScoped
53  public class CDIJCacheHelper
54  {
55      private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName());
56      private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.commons.jcs3.jcache.cdi.skip-close");
57  
58      private volatile CacheResolverFactoryImpl defaultCacheResolverFactory; // lazy to not create any cache if not needed
59      private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl();
60  
61      private final Collection<CreationalContext<?>> toRelease = new ArrayList<>();
62      private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<>();
63  
64      @Inject
65      private BeanManager beanManager;
66  
67      @PreDestroy
68      private void release() {
69          if (CLOSE_CACHE && defaultCacheResolverFactory != null)
70          {
71              defaultCacheResolverFactory.release();
72          }
73          for (final CreationalContext<?> cc : toRelease)
74          {
75              try
76              {
77                  cc.release();
78              }
79              catch (final RuntimeException re)
80              {
81                  LOGGER.warning(re.getMessage());
82              }
83          }
84      }
85  
86      public MethodMeta findMeta(final InvocationContext ic)
87      {
88          final Method mtd = ic.getMethod();
89          final Class<?> refType = findKeyType(ic.getTarget());
90          final MethodKey key = new MethodKey(refType, mtd);
91          MethodMeta methodMeta = methods.get(key);
92          if (methodMeta == null)
93          {
94              synchronized (this)
95              {
96                  methodMeta = methods.get(key);
97                  if (methodMeta == null)
98                  {
99                      methodMeta = createMeta(ic);
100                     methods.put(key, methodMeta);
101                 }
102             }
103         }
104         return methodMeta;
105     }
106 
107     private static Class<?> findKeyType(final Object target)
108     {
109         if (null == target)
110         {
111             return null;
112         }
113         return target.getClass();
114     }
115 
116     // it is unlikely we have all annotations but for now we have a single meta model
117     private MethodMeta createMeta(final InvocationContext ic)
118     {
119         final CacheDefaults defaults = findDefaults(ic.getTarget() == null ? null : ic.getTarget()
120                                                       .getClass(), ic.getMethod());
121 
122         final Class<?>[] parameterTypes = ic.getMethod().getParameterTypes();
123         final Annotation[][] parameterAnnotations = ic.getMethod().getParameterAnnotations();
124         final List<Set<Annotation>> annotations = new ArrayList<>();
125         for (final Annotation[] parameterAnnotation : parameterAnnotations)
126         {
127             final Set<Annotation> set = new HashSet<>(parameterAnnotation.length);
128             set.addAll(Arrays.asList(parameterAnnotation));
129             annotations.add(set);
130         }
131 
132         final Set<Annotation> mtdAnnotations = new HashSet<>(Arrays.asList(ic.getMethod().getAnnotations()));
133         final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class);
134         final String cacheResultCacheResultName = cacheResult == null ? null : defaultName(ic.getMethod(), defaults, cacheResult.cacheName());
135         final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ?
136                 null : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory());
137         final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ?
138                 null : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator());
139 
140         final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class);
141         final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName());
142         final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ?
143                 null : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory());
144         final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ?
145                 null : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator());
146 
147         final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class);
148         final String cacheRemoveCacheRemoveName = cacheRemove == null ? null : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName());
149         final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ?
150                 null : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory());
151         final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ?
152                 null : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator());
153 
154         final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class);
155         final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName());
156         final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ?
157                 null : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory());
158 
159         return new MethodMeta(
160                 parameterTypes,
161                 annotations,
162                 mtdAnnotations,
163                 keyParameterIndexes(ic.getMethod()),
164                 getValueParameter(annotations),
165                 getKeyParameters(annotations),
166                 cacheResultCacheResultName,
167                 cacheResultCacheResolverFactory,
168                 cacheResultCacheKeyGenerator,
169                 cacheResult,
170                 cachePutCachePutName,
171                 cachePutCacheResolverFactory,
172                 cachePutCacheKeyGenerator,
173                 cachePut != null && cachePut.afterInvocation(),
174                 cachePut,
175                 cacheRemoveCacheRemoveName,
176                 cacheRemoveCacheResolverFactory,
177                 cacheRemoveCacheKeyGenerator,
178                 cacheRemove != null && cacheRemove.afterInvocation(),
179                 cacheRemove,
180                 cacheRemoveAllCacheRemoveAllName,
181                 cacheRemoveAllCacheResolverFactory,
182                 cacheRemoveAll != null && cacheRemoveAll.afterInvocation(),
183                 cacheRemoveAll);
184     }
185 
186     private static Integer[] getKeyParameters(final List<Set<Annotation>> annotations)
187     {
188         final Collection<Integer> list = new ArrayList<>();
189         int idx = 0;
190         for (final Set<Annotation> set : annotations)
191         {
192             for (final Annotation a : set)
193             {
194                 if (a.annotationType() == CacheKey.class)
195                 {
196                     list.add(idx);
197                 }
198             }
199             idx++;
200         }
201         if (list.isEmpty())
202         {
203             for (int i = 0; i < annotations.size(); i++)
204             {
205                 list.add(i);
206             }
207         }
208         return list.toArray(new Integer[0]);
209     }
210 
211     private static Integer getValueParameter(final List<Set<Annotation>> annotations)
212     {
213         final int idx = 0;
214         for (final Set<Annotation> set : annotations)
215         {
216             for (final Annotation a : set)
217             {
218                 if (a.annotationType() == CacheValue.class)
219                 {
220                     return idx;
221                 }
222             }
223         }
224         return -1;
225     }
226 
227     private static String defaultName(final Method method, final CacheDefaults defaults, final String cacheName)
228     {
229         if (!cacheName.isEmpty())
230         {
231             return cacheName;
232         }
233         if (defaults != null)
234         {
235             final String name = defaults.cacheName();
236             if (!name.isEmpty())
237             {
238                 return name;
239             }
240         }
241 
242         final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName());
243         name.append(".");
244         name.append(method.getName());
245         name.append("(");
246         final Class<?>[] parameterTypes = method.getParameterTypes();
247         for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++)
248         {
249             name.append(parameterTypes[pIdx].getName());
250             if ((pIdx + 1) < parameterTypes.length)
251             {
252                 name.append(",");
253             }
254         }
255         name.append(")");
256         return name.toString();
257     }
258 
259     private static CacheDefaults findDefaults(final Class<?> targetType, final Method method)
260     {
261         if (Proxy.isProxyClass(targetType)) // target doesn't hold annotations
262         {
263             final Class<?> api = method.getDeclaringClass();
264             for (final Class<?> type : targetType
265                                          .getInterfaces())
266             {
267                 if (!api.isAssignableFrom(type))
268                 {
269                     continue;
270                 }
271                 return extractDefaults(type);
272             }
273         }
274         return extractDefaults(targetType);
275     }
276 
277     private static CacheDefaults extractDefaults(final Class<?> type)
278     {
279         CacheDefaults annotation = null;
280         Class<?> clazz = type;
281         while (clazz != null && clazz != Object.class)
282         {
283             annotation = clazz.getAnnotation(CacheDefaults.class);
284             if (annotation != null)
285             {
286                 break;
287             }
288             clazz = clazz.getSuperclass();
289         }
290         return annotation;
291     }
292 
293     public boolean isIncluded(final Class<?> aClass, final Class<?>[] in, final Class<?>[] out)
294     {
295         if (in.length == 0 && out.length == 0)
296         {
297             return false;
298         }
299         for (final Class<?> potentialIn : in)
300         {
301             if (potentialIn.isAssignableFrom(aClass))
302             {
303                 for (final Class<?> potentialOut : out)
304                 {
305                     if (potentialOut.isAssignableFrom(aClass))
306                     {
307                         return false;
308                     }
309                 }
310                 return true;
311             }
312         }
313         return false;
314     }
315 
316     private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults, final Class<? extends CacheKeyGenerator> cacheKeyGenerator)
317     {
318         if (!CacheKeyGenerator.class.equals(cacheKeyGenerator))
319         {
320             return instance(cacheKeyGenerator);
321         }
322         if (defaults != null)
323         {
324             final Class<? extends CacheKeyGenerator> defaultCacheKeyGenerator = defaults.cacheKeyGenerator();
325             if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator))
326             {
327                 return instance(defaultCacheKeyGenerator);
328             }
329         }
330         return defaultCacheKeyGenerator;
331     }
332 
333     private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults, final Class<? extends CacheResolverFactory> cacheResolverFactory)
334     {
335         if (!CacheResolverFactory.class.equals(cacheResolverFactory))
336         {
337             return instance(cacheResolverFactory);
338         }
339         if (defaults != null)
340         {
341             final Class<? extends CacheResolverFactory> defaultCacheResolverFactory = defaults.cacheResolverFactory();
342             if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory))
343             {
344                 return instance(defaultCacheResolverFactory);
345             }
346         }
347         return defaultCacheResolverFactory();
348     }
349 
350     @SuppressWarnings("unchecked")
351     private <T> T instance(final Class<T> type)
352     {
353         final Set<Bean<?>> beans = beanManager.getBeans(type);
354         if (beans.isEmpty())
355         {
356             if (CacheKeyGenerator.class == type) {
357                 return (T) defaultCacheKeyGenerator;
358             }
359             if (CacheResolverFactory.class == type) {
360                 return (T) defaultCacheResolverFactory();
361             }
362             return null;
363         }
364         final Bean<?> bean = beanManager.resolve(beans);
365         final CreationalContext<?> context = beanManager.createCreationalContext(bean);
366         final Class<? extends Annotation> scope = bean.getScope();
367         final boolean normalScope = beanManager.isNormalScope(scope);
368         try
369         {
370             final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context);
371             if (!normalScope)
372             {
373                 toRelease.add(context);
374             }
375             return (T) reference;
376         }
377         finally
378         {
379             if (normalScope)
380             { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe?
381                 context.release();
382             }
383         }
384     }
385 
386     private CacheResolverFactoryImpl defaultCacheResolverFactory()
387     {
388         if (defaultCacheResolverFactory != null) {
389             return defaultCacheResolverFactory;
390         }
391         synchronized (this) {
392             if (defaultCacheResolverFactory != null) {
393                 return defaultCacheResolverFactory;
394             }
395             defaultCacheResolverFactory = new CacheResolverFactoryImpl();
396         }
397         return defaultCacheResolverFactory;
398     }
399 
400     private static Integer[] keyParameterIndexes(final Method method)
401     {
402         final List<Integer> keys = new LinkedList<>();
403         final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
404 
405         // first check if keys are specified explicitly
406         for (int i = 0; i < method.getParameterTypes().length; i++)
407         {
408             final Annotation[] annotations = parameterAnnotations[i];
409             for (final Annotation a : annotations)
410             {
411                 if (a.annotationType().equals(CacheKey.class))
412                 {
413                     keys.add(i);
414                     break;
415                 }
416             }
417         }
418 
419         // if not then use all parameters but value ones
420         if (keys.isEmpty())
421         {
422             for (int i = 0; i < method.getParameterTypes().length; i++)
423             {
424                 final Annotation[] annotations = parameterAnnotations[i];
425                 boolean value = false;
426                 for (final Annotation a : annotations)
427                 {
428                     if (a.annotationType().equals(CacheValue.class))
429                     {
430                         value = true;
431                         break;
432                     }
433                 }
434                 if (!value) {
435                     keys.add(i);
436                 }
437             }
438         }
439         return keys.toArray(new Integer[0]);
440     }
441 
442     private static final class MethodKey
443     {
444         private final Class<?> base;
445         private final Method delegate;
446         private final int hash;
447 
448         private MethodKey(final Class<?> base, final Method delegate)
449         {
450             this.base = base; // we need a class to ensure inheritance don't fall in the same key
451             this.delegate = delegate;
452             this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode());
453         }
454 
455         @Override
456         public boolean equals(final Object o)
457         {
458             if (this == o)
459             {
460                 return true;
461             }
462             if (o == null || getClass() != o.getClass())
463             {
464                 return false;
465             }
466             final MethodKey classKey = MethodKey.class.cast(o);
467             return delegate.equals(classKey.delegate) &&
468                 (base == null && classKey.base == null || base != null && base.equals(classKey.base));
469         }
470 
471         @Override
472         public int hashCode()
473         {
474             return hash;
475         }
476     }
477 
478     // TODO: split it in 5?
479     public static class MethodMeta
480     {
481         private final Class<?>[] parameterTypes;
482         private final List<Set<Annotation>> parameterAnnotations;
483         private final Set<Annotation> annotations;
484         private final Integer[] keysIndices;
485         private final Integer valueIndex;
486         private final Integer[] parameterIndices;
487 
488         private final String cacheResultCacheName;
489         private final CacheResolverFactory cacheResultResolverFactory;
490         private final CacheKeyGenerator cacheResultKeyGenerator;
491         private final CacheResult cacheResult;
492 
493         private final String cachePutCacheName;
494         private final CacheResolverFactory cachePutResolverFactory;
495         private final CacheKeyGenerator cachePutKeyGenerator;
496         private final boolean cachePutAfter;
497         private final CachePut cachePut;
498 
499         private final String cacheRemoveCacheName;
500         private final CacheResolverFactory cacheRemoveResolverFactory;
501         private final CacheKeyGenerator cacheRemoveKeyGenerator;
502         private final boolean cacheRemoveAfter;
503         private final CacheRemove cacheRemove;
504 
505         private final String cacheRemoveAllCacheName;
506         private final CacheResolverFactory cacheRemoveAllResolverFactory;
507         private final boolean cacheRemoveAllAfter;
508         private final CacheRemoveAll cacheRemoveAll;
509 
510         public MethodMeta(final Class<?>[] parameterTypes, final List<Set<Annotation>> parameterAnnotations, final Set<Annotation>
511                 annotations, final Integer[] keysIndices, final Integer valueIndex, final Integer[] parameterIndices, final String
512                 cacheResultCacheName, final CacheResolverFactory cacheResultResolverFactory, final CacheKeyGenerator
513                 cacheResultKeyGenerator, final CacheResult cacheResult, final String cachePutCacheName, final CacheResolverFactory
514                 cachePutResolverFactory, final CacheKeyGenerator cachePutKeyGenerator, final boolean cachePutAfter, final CachePut cachePut, final String
515                 cacheRemoveCacheName, final CacheResolverFactory cacheRemoveResolverFactory, final CacheKeyGenerator
516                 cacheRemoveKeyGenerator, final boolean cacheRemoveAfter, final CacheRemove cacheRemove, final String cacheRemoveAllCacheName,
517                           final CacheResolverFactory cacheRemoveAllResolverFactory, final boolean
518                                   cacheRemoveAllAfter, final CacheRemoveAll cacheRemoveAll)
519         {
520             this.parameterTypes = parameterTypes;
521             this.parameterAnnotations = parameterAnnotations;
522             this.annotations = annotations;
523             this.keysIndices = keysIndices;
524             this.valueIndex = valueIndex;
525             this.parameterIndices = parameterIndices;
526             this.cacheResultCacheName = cacheResultCacheName;
527             this.cacheResultResolverFactory = cacheResultResolverFactory;
528             this.cacheResultKeyGenerator = cacheResultKeyGenerator;
529             this.cacheResult = cacheResult;
530             this.cachePutCacheName = cachePutCacheName;
531             this.cachePutResolverFactory = cachePutResolverFactory;
532             this.cachePutKeyGenerator = cachePutKeyGenerator;
533             this.cachePutAfter = cachePutAfter;
534             this.cachePut = cachePut;
535             this.cacheRemoveCacheName = cacheRemoveCacheName;
536             this.cacheRemoveResolverFactory = cacheRemoveResolverFactory;
537             this.cacheRemoveKeyGenerator = cacheRemoveKeyGenerator;
538             this.cacheRemoveAfter = cacheRemoveAfter;
539             this.cacheRemove = cacheRemove;
540             this.cacheRemoveAllCacheName = cacheRemoveAllCacheName;
541             this.cacheRemoveAllResolverFactory = cacheRemoveAllResolverFactory;
542             this.cacheRemoveAllAfter = cacheRemoveAllAfter;
543             this.cacheRemoveAll = cacheRemoveAll;
544         }
545 
546         public boolean isCacheRemoveAfter()
547         {
548             return cacheRemoveAfter;
549         }
550 
551         public boolean isCachePutAfter()
552         {
553             return cachePutAfter;
554         }
555 
556         public Class<?>[] getParameterTypes()
557         {
558             return parameterTypes;
559         }
560 
561         public List<Set<Annotation>> getParameterAnnotations()
562         {
563             return parameterAnnotations;
564         }
565 
566         public String getCacheResultCacheName()
567         {
568             return cacheResultCacheName;
569         }
570 
571         public CacheResolverFactory getCacheResultResolverFactory()
572         {
573             return cacheResultResolverFactory;
574         }
575 
576         public CacheKeyGenerator getCacheResultKeyGenerator()
577         {
578             return cacheResultKeyGenerator;
579         }
580 
581         public CacheResult getCacheResult() {
582             return cacheResult;
583         }
584 
585         public Integer[] getParameterIndices()
586         {
587             return parameterIndices;
588         }
589 
590         public Set<Annotation> getAnnotations()
591         {
592             return annotations;
593         }
594 
595         public Integer[] getKeysIndices()
596         {
597             return keysIndices;
598         }
599 
600         public Integer getValuesIndex()
601         {
602             return valueIndex;
603         }
604 
605         public Integer getValueIndex()
606         {
607             return valueIndex;
608         }
609 
610         public String getCachePutCacheName()
611         {
612             return cachePutCacheName;
613         }
614 
615         public CacheResolverFactory getCachePutResolverFactory()
616         {
617             return cachePutResolverFactory;
618         }
619 
620         public CacheKeyGenerator getCachePutKeyGenerator()
621         {
622             return cachePutKeyGenerator;
623         }
624 
625         public CachePut getCachePut()
626         {
627             return cachePut;
628         }
629 
630         public String getCacheRemoveCacheName()
631         {
632             return cacheRemoveCacheName;
633         }
634 
635         public CacheResolverFactory getCacheRemoveResolverFactory()
636         {
637             return cacheRemoveResolverFactory;
638         }
639 
640         public CacheKeyGenerator getCacheRemoveKeyGenerator()
641         {
642             return cacheRemoveKeyGenerator;
643         }
644 
645         public CacheRemove getCacheRemove()
646         {
647             return cacheRemove;
648         }
649 
650         public String getCacheRemoveAllCacheName()
651         {
652             return cacheRemoveAllCacheName;
653         }
654 
655         public CacheResolverFactory getCacheRemoveAllResolverFactory()
656         {
657             return cacheRemoveAllResolverFactory;
658         }
659 
660         public boolean isCacheRemoveAllAfter()
661         {
662             return cacheRemoveAllAfter;
663         }
664 
665         public CacheRemoveAll getCacheRemoveAll()
666         {
667             return cacheRemoveAll;
668         }
669     }
670 }