001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.jcs3.jcache.cdi;
020
021import java.lang.annotation.Annotation;
022import java.lang.reflect.Method;
023import java.lang.reflect.Proxy;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.HashSet;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Set;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ConcurrentMap;
033import java.util.logging.Logger;
034
035import javax.annotation.PreDestroy;
036import javax.cache.annotation.CacheDefaults;
037import javax.cache.annotation.CacheKey;
038import javax.cache.annotation.CacheKeyGenerator;
039import javax.cache.annotation.CachePut;
040import javax.cache.annotation.CacheRemove;
041import javax.cache.annotation.CacheRemoveAll;
042import javax.cache.annotation.CacheResolverFactory;
043import javax.cache.annotation.CacheResult;
044import javax.cache.annotation.CacheValue;
045import javax.enterprise.context.ApplicationScoped;
046import javax.enterprise.context.spi.CreationalContext;
047import javax.enterprise.inject.spi.Bean;
048import javax.enterprise.inject.spi.BeanManager;
049import javax.inject.Inject;
050import javax.interceptor.InvocationContext;
051
052@ApplicationScoped
053public class CDIJCacheHelper
054{
055    private static final Logger LOGGER = Logger.getLogger(CDIJCacheHelper.class.getName());
056    private static final boolean CLOSE_CACHE = !Boolean.getBoolean("org.apache.commons.jcs3.jcache.cdi.skip-close");
057
058    private volatile CacheResolverFactoryImpl defaultCacheResolverFactory; // lazy to not create any cache if not needed
059    private final CacheKeyGeneratorImpl defaultCacheKeyGenerator = new CacheKeyGeneratorImpl();
060
061    private final Collection<CreationalContext<?>> toRelease = new ArrayList<>();
062    private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<>();
063
064    @Inject
065    private BeanManager beanManager;
066
067    @PreDestroy
068    private void release() {
069        if (CLOSE_CACHE && defaultCacheResolverFactory != null)
070        {
071            defaultCacheResolverFactory.release();
072        }
073        for (final CreationalContext<?> cc : toRelease)
074        {
075            try
076            {
077                cc.release();
078            }
079            catch (final RuntimeException re)
080            {
081                LOGGER.warning(re.getMessage());
082            }
083        }
084    }
085
086    public MethodMeta findMeta(final InvocationContext ic)
087    {
088        final Method mtd = ic.getMethod();
089        final Class<?> refType = findKeyType(ic.getTarget());
090        final MethodKey key = new MethodKey(refType, mtd);
091        MethodMeta methodMeta = methods.get(key);
092        if (methodMeta == null)
093        {
094            synchronized (this)
095            {
096                methodMeta = methods.get(key);
097                if (methodMeta == null)
098                {
099                    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}