View Javadoc

1   package org.apache.onami.cache;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static java.lang.String.format;
23  import static java.util.Arrays.asList;
24  import static java.util.Collections.unmodifiableSet;
25  
26  import java.lang.annotation.Annotation;
27  import java.lang.reflect.Method;
28  import java.util.ArrayList;
29  import java.util.LinkedHashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  import javax.cache.annotation.CacheAnnotationConfigurationException;
34  import javax.cache.annotation.CacheDefaults;
35  import javax.cache.annotation.CacheInvocationContext;
36  import javax.cache.annotation.CacheInvocationParameter;
37  import javax.cache.annotation.CacheKeyGenerator;
38  import javax.cache.annotation.CacheKeyParam;
39  import javax.cache.annotation.CachePut;
40  import javax.cache.annotation.CacheRemoveAll;
41  import javax.cache.annotation.CacheRemoveEntry;
42  import javax.cache.annotation.CacheResolverFactory;
43  import javax.cache.annotation.CacheResult;
44  import javax.cache.annotation.CacheValue;
45  import javax.inject.Inject;
46  
47  import org.aopalliance.intercept.MethodInterceptor;
48  import org.aopalliance.intercept.MethodInvocation;
49  
50  import com.google.inject.Injector;
51  
52  abstract class CacheInterceptor<A extends Annotation>
53      implements MethodInterceptor
54  {
55  
56      protected static final Object NULL_PLACEHOLDER = new Object();
57  
58      @Inject
59      private Injector injector;
60  
61      @Inject
62      private CacheResolverFactory cacheResolverFactory;
63  
64      @Inject
65      private CacheKeyGenerator cacheKeyGenerator;
66  
67      public final void setInjector( Injector injector )
68      {
69          this.injector = injector;
70      }
71  
72      public final void setCacheResolverFactory( CacheResolverFactory cacheResolverFactory )
73      {
74          this.cacheResolverFactory = cacheResolverFactory;
75      }
76  
77      public final void setCacheKeyGenerator( CacheKeyGenerator cacheKeyGenerator )
78      {
79          this.cacheKeyGenerator = cacheKeyGenerator;
80      }
81  
82      public abstract Class<A> getInterceptedAnnotationType();
83  
84      public final Object invoke( MethodInvocation invocation )
85          throws Throwable
86      {
87          String cacheName = getCacheName( invocation.getMethod() );
88          Object target = invocation.getThis();
89          A annotation = invocation.getMethod().getAnnotation( getInterceptedAnnotationType() );
90          Set<Annotation> methodAnnotations = toAnnotationsSet( invocation.getMethod().getAnnotations() );
91  
92          boolean cacheValueAllowed = CachePut.class == getInterceptedAnnotationType();
93  
94          CacheInvocationParameter[] allParameters = new CacheInvocationParameter[invocation.getArguments().length];
95          List<CacheInvocationParameter> keyParametersList = new ArrayList<CacheInvocationParameter>( allParameters.length );
96          CacheInvocationParameter valueParameter = null;
97  
98          for ( int i = 0; i < invocation.getArguments().length; i++ )
99          {
100             Class<?> parameterType = invocation.getMethod().getParameterTypes()[i];
101 
102             CacheInvocationParameter parameter = new DefaultCacheInvocationParameter( parameterType,
103                                                                                       invocation.getArguments()[i],
104                                                                                       toAnnotationsSet( invocation.getMethod().getParameterAnnotations()[i] ),
105                                                                                       i );
106 
107             for ( Annotation parameterAnnotation : invocation.getMethod().getParameterAnnotations()[i] )
108             {
109                 if ( CacheKeyParam.class == parameterAnnotation.annotationType() )
110                 {
111                     keyParametersList.add( parameter );
112                 }
113                 else if ( CacheValue.class == parameterAnnotation.annotationType() )
114                 {
115                     if ( !cacheValueAllowed )
116                     {
117                         throw new CacheAnnotationConfigurationException( format( "CacheValue parameter annotation is not allowed on %s",
118                                                                                  invocation.getMethod() ) );
119                     }
120                     else if ( valueParameter != null )
121                     {
122                         throw new CacheAnnotationConfigurationException( format( "Multiple CacheValue parameter annotations are not allowed on %s",
123                                                                                  invocation.getMethod() ) );
124                     }
125                     else
126                     {
127                         valueParameter = parameter;
128                     }
129                 }
130             }
131 
132             allParameters[i] = parameter;
133         }
134 
135         CacheInvocationParameter[] keyParameters;
136 
137         if ( keyParametersList.isEmpty() )
138         {
139             keyParameters = allParameters;
140         }
141         else
142         {
143             keyParameters = keyParametersList.toArray( new CacheInvocationParameter[keyParametersList.size()] );
144         }
145 
146         return invoke( new DefaultCacheKeyInvocationContext<A>( injector,
147                                                                 cacheName,
148                                                                 target,
149                                                                 invocation.getMethod(),
150                                                                 allParameters,
151                                                                 keyParameters,
152                                                                 valueParameter,
153                                                                 methodAnnotations,
154                                                                 annotation ),
155                        invocation );
156     }
157 
158     protected abstract Object invoke( CacheInvocationContext<A> context, MethodInvocation invocation )
159         throws Throwable;
160 
161     protected final CacheResolverFactory getCacheResolverFactory( CacheInvocationContext<A> context )
162     {
163         Class<? extends CacheResolverFactory> cacheKeyGeneratorType = getCacheResolverFactoryType( context.getCacheAnnotation() );
164 
165         if ( CacheResolverFactory.class != cacheKeyGeneratorType )
166         {
167             return injector.getInstance( cacheKeyGeneratorType );
168         }
169 
170         CacheDefaults cacheDefaults = context.getTarget().getClass().getAnnotation( CacheDefaults.class );
171 
172         if ( cacheDefaults != null && cacheDefaults.cacheKeyGenerator().isAssignableFrom( CacheResolverFactory.class ) )
173         {
174             return injector.getInstance( cacheKeyGeneratorType );
175         }
176 
177         return cacheResolverFactory;
178     }
179 
180     protected final CacheKeyGenerator getCacheKeyGenerator( CacheInvocationContext<A> context )
181     {
182         Class<? extends CacheKeyGenerator> cacheKeyGeneratorType = getCacheKeyGeneratorType( context.getCacheAnnotation() );
183 
184         if ( CacheKeyGenerator.class != cacheKeyGeneratorType )
185         {
186             return injector.getInstance( cacheKeyGeneratorType );
187         }
188 
189         CacheDefaults cacheDefaults = context.getTarget().getClass().getAnnotation( CacheDefaults.class );
190 
191         if ( cacheDefaults != null && CacheKeyGenerator.class != cacheDefaults.cacheKeyGenerator() )
192         {
193             return injector.getInstance( cacheKeyGeneratorType );
194         }
195 
196         return cacheKeyGenerator;
197     }
198 
199     @SuppressWarnings( "unchecked" )
200     private static String getCacheName( Method method )
201     {
202         for ( Class<? extends Annotation> annotationType : asList( CachePut.class,
203                                                                    CacheRemoveAll.class,
204                                                                    CacheRemoveEntry.class,
205                                                                    CacheResult.class ) )
206         {
207             if ( method.isAnnotationPresent( annotationType ) )
208             {
209                 Annotation annotation = method.getAnnotation( annotationType );
210                 String cacheName;
211                 try
212                 {
213                     cacheName = (String) annotationType.getMethod( "cacheName" ).invoke( annotation );
214                 }
215                 catch ( Exception e )
216                 {
217                     // should not happen, all enlisted annotations have "cacheName()" method
218                     cacheName = null;
219                 }
220 
221                 if ( !isEmpty( cacheName ) )
222                 {
223                     return cacheName;
224                 }
225             }
226         }
227 
228         if ( method.getDeclaringClass().isAnnotationPresent( CacheDefaults.class ) )
229         {
230             CacheDefaults cacheDefaults = method.getDeclaringClass().getAnnotation( CacheDefaults.class );
231             if ( !isEmpty( cacheDefaults.cacheName() ) )
232             {
233                 return cacheDefaults.cacheName();
234             }
235         }
236 
237         return method.toGenericString();
238     }
239 
240     static <T extends Throwable> boolean include( T throwable,
241                                                   Class<? extends T>[] includes,
242                                                   Class<? extends T>[] excludes,
243                                                   boolean includeBothEmpty )
244     {
245         boolean includedEmpty = isEmpty( includes );
246         boolean excludedEmpty = isEmpty( excludes );
247 
248         if ( includedEmpty && excludedEmpty )
249         {
250             return includeBothEmpty;
251         }
252 
253         boolean isAssignableFromIncludes = isAssignable( throwable, includes );
254         boolean isAssignableFromExcludes = isAssignable( throwable, excludes );
255 
256         if ( includedEmpty )
257         {
258             return !isAssignableFromExcludes;
259         }
260 
261         if ( excludedEmpty )
262         {
263             return isAssignableFromIncludes;
264         }
265 
266         return isAssignableFromIncludes && !isAssignableFromExcludes;
267     }
268 
269     private static <T extends Throwable> boolean isAssignable( T target, Class<? extends T>[] from )
270     {
271         for ( final Class<? extends T> throwable : from )
272         {
273             if ( throwable.isAssignableFrom( target.getClass() ) )
274             {
275                 return true;
276             }
277         }
278 
279         return false;
280     }
281 
282     private static final <T extends Throwable> boolean isEmpty( Class<? extends T>...types )
283     {
284         return types == null || types.length == 0;
285     }
286 
287     private static boolean isEmpty( String value )
288     {
289         return value == null || value.length() == 0;
290     }
291 
292     private static Set<Annotation> toAnnotationsSet( Annotation...annotations )
293     {
294         return unmodifiableSet( new LinkedHashSet<Annotation>( asList( annotations ) ) );
295     }
296 
297     private static <A extends Annotation> Class<? extends CacheKeyGenerator> getCacheKeyGeneratorType( A annotation )
298     {
299         if ( CachePut.class.isInstance( annotation ) )
300         {
301             return ( (CachePut) annotation).cacheKeyGenerator();
302         }
303         else if ( CacheRemoveEntry.class.isInstance( annotation ) )
304         {
305             return ( (CacheRemoveEntry) annotation).cacheKeyGenerator();
306         }
307         else if ( CacheResult.class.isInstance( annotation ) )
308         {
309             return ( (CacheResult) annotation).cacheKeyGenerator();
310         }
311 
312         // doesn't happen
313         return null;
314     }
315 
316     private static <A extends Annotation> Class<? extends CacheResolverFactory> getCacheResolverFactoryType( A annotation )
317     {
318         if ( CachePut.class.isInstance( annotation ) )
319         {
320             return ( (CachePut) annotation).cacheResolverFactory();
321         }
322         else if ( CacheRemoveAll.class.isInstance( annotation ) )
323         {
324             return ( (CacheRemoveAll) annotation).cacheResolverFactory();
325         }
326         else if ( CacheRemoveEntry.class.isInstance( annotation ) )
327         {
328             return ( (CacheRemoveEntry) annotation).cacheResolverFactory();
329         }
330         else if ( CacheResult.class.isInstance( annotation ) )
331         {
332             return ( (CacheResult) annotation).cacheResolverFactory();
333         }
334 
335         // doesn't happen
336         return null;
337     }
338 
339 }