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.jcs.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.jcs.jcache.cdi.skip-close");
57  
58      private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // 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<CreationalContext<?>>();
62      private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<MethodKey, MethodMeta>();
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 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<Set<Annotation>>();
125         for (final Annotation[] parameterAnnotation : parameterAnnotations)
126         {
127             final Set<Annotation> set = new HashSet<Annotation>(parameterAnnotation.length);
128             set.addAll(Arrays.asList(parameterAnnotation));
129             annotations.add(set);
130         }
131 
132         final Set<Annotation> mtdAnnotations = new HashSet<Annotation>();
133         mtdAnnotations.addAll(Arrays.asList(ic.getMethod().getAnnotations()));
134 
135         final CacheResult cacheResult = ic.getMethod().getAnnotation(CacheResult.class);
136         final String cacheResultCacheResultName = cacheResult == null ? null : defaultName(ic.getMethod(), defaults, cacheResult.cacheName());
137         final CacheResolverFactory cacheResultCacheResolverFactory = cacheResult == null ?
138                 null : cacheResolverFactoryFor(defaults, cacheResult.cacheResolverFactory());
139         final CacheKeyGenerator cacheResultCacheKeyGenerator = cacheResult == null ?
140                 null : cacheKeyGeneratorFor(defaults, cacheResult.cacheKeyGenerator());
141 
142         final CachePut cachePut = ic.getMethod().getAnnotation(CachePut.class);
143         final String cachePutCachePutName = cachePut == null ? null : defaultName(ic.getMethod(), defaults, cachePut.cacheName());
144         final CacheResolverFactory cachePutCacheResolverFactory = cachePut == null ?
145                 null : cacheResolverFactoryFor(defaults, cachePut.cacheResolverFactory());
146         final CacheKeyGenerator cachePutCacheKeyGenerator = cachePut == null ?
147                 null : cacheKeyGeneratorFor(defaults, cachePut.cacheKeyGenerator());
148 
149         final CacheRemove cacheRemove = ic.getMethod().getAnnotation(CacheRemove.class);
150         final String cacheRemoveCacheRemoveName = cacheRemove == null ? null : defaultName(ic.getMethod(), defaults, cacheRemove.cacheName());
151         final CacheResolverFactory cacheRemoveCacheResolverFactory = cacheRemove == null ?
152                 null : cacheResolverFactoryFor(defaults, cacheRemove.cacheResolverFactory());
153         final CacheKeyGenerator cacheRemoveCacheKeyGenerator = cacheRemove == null ?
154                 null : cacheKeyGeneratorFor(defaults, cacheRemove.cacheKeyGenerator());
155 
156         final CacheRemoveAll cacheRemoveAll = ic.getMethod().getAnnotation(CacheRemoveAll.class);
157         final String cacheRemoveAllCacheRemoveAllName = cacheRemoveAll == null ? null : defaultName(ic.getMethod(), defaults, cacheRemoveAll.cacheName());
158         final CacheResolverFactory cacheRemoveAllCacheResolverFactory = cacheRemoveAll == null ?
159                 null : cacheResolverFactoryFor(defaults, cacheRemoveAll.cacheResolverFactory());
160 
161         return new MethodMeta(
162                 parameterTypes,
163                 annotations,
164                 mtdAnnotations,
165                 keyParameterIndexes(ic.getMethod()),
166                 getValueParameter(annotations),
167                 getKeyParameters(annotations),
168                 cacheResultCacheResultName,
169                 cacheResultCacheResolverFactory,
170                 cacheResultCacheKeyGenerator,
171                 cacheResult,
172                 cachePutCachePutName,
173                 cachePutCacheResolverFactory,
174                 cachePutCacheKeyGenerator,
175                 cachePut != null && cachePut.afterInvocation(),
176                 cachePut,
177                 cacheRemoveCacheRemoveName,
178                 cacheRemoveCacheResolverFactory,
179                 cacheRemoveCacheKeyGenerator,
180                 cacheRemove != null && cacheRemove.afterInvocation(),
181                 cacheRemove,
182                 cacheRemoveAllCacheRemoveAllName,
183                 cacheRemoveAllCacheResolverFactory,
184                 cacheRemoveAll != null && cacheRemoveAll.afterInvocation(),
185                 cacheRemoveAll);
186     }
187 
188     private Integer[] getKeyParameters(final List<Set<Annotation>> annotations)
189     {
190         final Collection<Integer> list = new ArrayList<Integer>();
191         int idx = 0;
192         for (final Set<Annotation> set : annotations)
193         {
194             for (final Annotation a : set)
195             {
196                 if (a.annotationType() == CacheKey.class)
197                 {
198                     list.add(idx);
199                 }
200             }
201             idx++;
202         }
203         if (list.isEmpty())
204         {
205             for (int i = 0; i < annotations.size(); i++)
206             {
207                 list.add(i);
208             }
209         }
210         return list.toArray(new Integer[list.size()]);
211     }
212 
213     private Integer getValueParameter(final List<Set<Annotation>> annotations)
214     {
215         int idx = 0;
216         for (final Set<Annotation> set : annotations)
217         {
218             for (final Annotation a : set)
219             {
220                 if (a.annotationType() == CacheValue.class)
221                 {
222                     return idx;
223                 }
224             }
225         }
226         return -1;
227     }
228 
229     private String defaultName(final Method method, final CacheDefaults defaults, final String cacheName)
230     {
231         if (!cacheName.isEmpty())
232         {
233             return cacheName;
234         }
235         if (defaults != null)
236         {
237             final String name = defaults.cacheName();
238             if (!name.isEmpty())
239             {
240                 return name;
241             }
242         }
243 
244         final StringBuilder name = new StringBuilder(method.getDeclaringClass().getName());
245         name.append(".");
246         name.append(method.getName());
247         name.append("(");
248         final Class<?>[] parameterTypes = method.getParameterTypes();
249         for (int pIdx = 0; pIdx < parameterTypes.length; pIdx++)
250         {
251             name.append(parameterTypes[pIdx].getName());
252             if ((pIdx + 1) < parameterTypes.length)
253             {
254                 name.append(",");
255             }
256         }
257         name.append(")");
258         return name.toString();
259     }
260 
261     private CacheDefaults findDefaults(final Class<?> targetType, final Method method)
262     {
263         if (Proxy.isProxyClass(targetType)) // target doesnt hold annotations
264         {
265             final Class<?> api = method.getDeclaringClass();
266             for (final Class<?> type : targetType
267                                          .getInterfaces())
268             {
269                 if (!api.isAssignableFrom(type))
270                 {
271                     continue;
272                 }
273                 return extractDefaults(type);
274             }
275         }
276         return extractDefaults(targetType);
277     }
278 
279     private CacheDefaults extractDefaults(final Class<?> type)
280     {
281         CacheDefaults annotation = null;
282         Class<?> clazz = type;
283         while (clazz != null && clazz != Object.class)
284         {
285             annotation = clazz.getAnnotation(CacheDefaults.class);
286             if (annotation != null)
287             {
288                 break;
289             }
290             clazz = clazz.getSuperclass();
291         }
292         return annotation;
293     }
294 
295     public boolean isIncluded(final Class<?> aClass, final Class<?>[] in, final Class<?>[] out)
296     {
297         if (in.length == 0 && out.length == 0)
298         {
299             return false;
300         }
301         for (final Class<?> potentialIn : in)
302         {
303             if (potentialIn.isAssignableFrom(aClass))
304             {
305                 for (final Class<?> potentialOut : out)
306                 {
307                     if (potentialOut.isAssignableFrom(aClass))
308                     {
309                         return false;
310                     }
311                 }
312                 return true;
313             }
314         }
315         return false;
316     }
317 
318     private CacheKeyGenerator cacheKeyGeneratorFor(final CacheDefaults defaults, final Class<? extends CacheKeyGenerator> cacheKeyGenerator)
319     {
320         if (!CacheKeyGenerator.class.equals(cacheKeyGenerator))
321         {
322             return instance(cacheKeyGenerator);
323         }
324         if (defaults != null)
325         {
326             final Class<? extends CacheKeyGenerator> defaultCacheKeyGenerator = defaults.cacheKeyGenerator();
327             if (!CacheKeyGenerator.class.equals(defaultCacheKeyGenerator))
328             {
329                 return instance(defaultCacheKeyGenerator);
330             }
331         }
332         return defaultCacheKeyGenerator;
333     }
334 
335     private CacheResolverFactory cacheResolverFactoryFor(final CacheDefaults defaults, final Class<? extends CacheResolverFactory> cacheResolverFactory)
336     {
337         if (!CacheResolverFactory.class.equals(cacheResolverFactory))
338         {
339             return instance(cacheResolverFactory);
340         }
341         if (defaults != null)
342         {
343             final Class<? extends CacheResolverFactory> defaultCacheResolverFactory = defaults.cacheResolverFactory();
344             if (!CacheResolverFactory.class.equals(defaultCacheResolverFactory))
345             {
346                 return instance(defaultCacheResolverFactory);
347             }
348         }
349         return defaultCacheResolverFactory();
350     }
351 
352     private <T> T instance(final Class<T> type)
353     {
354         final Set<Bean<?>> beans = beanManager.getBeans(type);
355         if (beans.isEmpty())
356         {
357             if (CacheKeyGenerator.class == type) {
358                 return (T) defaultCacheKeyGenerator;
359             }
360             if (CacheResolverFactory.class == type) {
361                 return (T) defaultCacheResolverFactory();
362             }
363             return null;
364         }
365         final Bean<?> bean = beanManager.resolve(beans);
366         final CreationalContext<?> context = beanManager.createCreationalContext(bean);
367         final Class<? extends Annotation> scope = bean.getScope();
368         final boolean normalScope = beanManager.isNormalScope(scope);
369         try
370         {
371             final Object reference = beanManager.getReference(bean, bean.getBeanClass(), context);
372             if (!normalScope)
373             {
374                 toRelease.add(context);
375             }
376             return (T) reference;
377         }
378         finally
379         {
380             if (normalScope)
381             { // TODO: release at the right moment, @PreDestroy? question is: do we assume it is thread safe?
382                 context.release();
383             }
384         }
385     }
386 
387     private CacheResolverFactoryImpl defaultCacheResolverFactory()
388     {
389         if (defaultCacheResolverFactory != null) {
390             return defaultCacheResolverFactory;
391         }
392         synchronized (this) {
393             if (defaultCacheResolverFactory != null) {
394                 return defaultCacheResolverFactory;
395             }
396             defaultCacheResolverFactory = new CacheResolverFactoryImpl();
397         }
398         return defaultCacheResolverFactory;
399     }
400 
401     private Integer[] keyParameterIndexes(final Method method)
402     {
403         final List<Integer> keys = new LinkedList<Integer>();
404         final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
405 
406         // first check if keys are specified explicitely
407         for (int i = 0; i < method.getParameterTypes().length; i++)
408         {
409             final Annotation[] annotations = parameterAnnotations[i];
410             for (final Annotation a : annotations)
411             {
412                 if (a.annotationType().equals(CacheKey.class))
413                 {
414                     keys.add(i);
415                     break;
416                 }
417             }
418         }
419 
420         // if not then use all parameters but value ones
421         if (keys.isEmpty())
422         {
423             for (int i = 0; i < method.getParameterTypes().length; i++)
424             {
425                 final Annotation[] annotations = parameterAnnotations[i];
426                 boolean value = false;
427                 for (final Annotation a : annotations)
428                 {
429                     if (a.annotationType().equals(CacheValue.class))
430                     {
431                         value = true;
432                         break;
433                     }
434                 }
435                 if (!value) {
436                     keys.add(i);
437                 }
438             }
439         }
440         return keys.toArray(new Integer[keys.size()]);
441     }
442 
443     private static final class MethodKey
444     {
445         private final Class<?> base;
446         private final Method delegate;
447         private final int hash;
448 
449         private MethodKey(final Class<?> base, final Method delegate)
450         {
451             this.base = base; // we need a class to ensure inheritance don't fall in the same key
452             this.delegate = delegate;
453             this.hash = 31 * delegate.hashCode() + (base == null ? 0 : base.hashCode());
454         }
455 
456         @Override
457         public boolean equals(final Object o)
458         {
459             if (this == o)
460             {
461                 return true;
462             }
463             if (o == null || getClass() != o.getClass())
464             {
465                 return false;
466             }
467             final MethodKey classKey = MethodKey.class.cast(o);
468             return delegate.equals(classKey.delegate) && ((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(Class<?>[] parameterTypes, List<Set<Annotation>> parameterAnnotations, Set<Annotation> 
511                 annotations, Integer[] keysIndices, Integer valueIndex, Integer[] parameterIndices, String 
512                 cacheResultCacheName, CacheResolverFactory cacheResultResolverFactory, CacheKeyGenerator 
513                 cacheResultKeyGenerator, CacheResult cacheResult, String cachePutCacheName, CacheResolverFactory 
514                 cachePutResolverFactory, CacheKeyGenerator cachePutKeyGenerator, boolean cachePutAfter, CachePut cachePut, String
515                 cacheRemoveCacheName, CacheResolverFactory cacheRemoveResolverFactory, CacheKeyGenerator 
516                 cacheRemoveKeyGenerator, boolean cacheRemoveAfter, CacheRemove cacheRemove, String cacheRemoveAllCacheName,
517                           CacheResolverFactory cacheRemoveAllResolverFactory, boolean
518                                   cacheRemoveAllAfter, 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 }