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.jcs.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.jcs.jcache.cdi.skip-close");
057
058    private volatile CacheResolverFactoryImpl defaultCacheResolverFactory = null; // 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<CreationalContext<?>>();
062    private final ConcurrentMap<MethodKey, MethodMeta> methods = new ConcurrentHashMap<MethodKey, MethodMeta>();
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 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}