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.onami.factoryannotation;
20  
21  import static com.google.common.base.Preconditions.checkNotNull;
22  import static com.google.common.base.Preconditions.checkState;
23  
24  import java.lang.annotation.Annotation;
25  import java.util.HashMap;
26  import java.util.Map;
27  
28  import org.apache.onami.factoryannotation.PrivilegedHelper.PrivilegedHelperCallback;
29  import org.apache.onami.factoryannotation.builder.BindFactoryAnnotationBindingBuilder;
30  import org.apache.onami.factoryannotation.builder.CombinedListenerCacheableBindingBuilder;
31  import org.apache.onami.factoryannotation.builder.FactoryAnnotationAnnotationBindingBuilder;
32  import org.apache.onami.factoryannotation.builder.FactoryAnnotationBaseProviderBindingBuilder;
33  
34  import com.google.common.base.Preconditions;
35  import com.google.inject.Binder;
36  import com.google.inject.BindingAnnotation;
37  import com.google.inject.Key;
38  import com.google.inject.Module;
39  import com.google.inject.Scope;
40  import com.google.inject.ScopeAnnotation;
41  import com.google.inject.TypeLiteral;
42  import com.google.inject.binder.ScopedBindingBuilder;
43  import com.google.inject.internal.Annotations;
44  import com.google.inject.internal.Errors;
45  import com.google.inject.internal.Scoping;
46  
47  /**
48   * A module builder for configuring annotation factory based injections. <h3>Creating a factory annotation</h3> Factory
49   * annotations used to create an factory annotation based binding for injections. These annotations itself must be
50   * annotated by Guice' own {@literal @}{@link BindingAnnotation} annotation to be recognized as injection point and have
51   * at least one method (or annotation property);
52   * 
53   * <pre>
54   * {@literal @}BindingAnnotation
55   * {@literal @}Retention(RetentionPolicy.RUNTIME)
56   * {@literal @}Target({ ElementType.FIELD, ElementType.PARAMETER })
57   * public {@literal @}interface User {
58   * 
59   *     int byId();
60   *     
61   * }
62   * </pre>
63   * 
64   * In this example we define a {@literal @}User annotation to hold the value of the userId to be retrieved from the
65   * datastore. <h3>Implementing a FactoryAnnotationProvider</h3> An {@link FactoryAnnotationProvider} is a special
66   * provider to return the actual instance of a injection type in combination with the actual factoey annotation at the
67   * injection point. At this point we omit the UserEntity class. It can be a simple POJO, a JPA entity or whatever class
68   * you can image.
69   * 
70   * <pre>
71   * public class UserEntityProvider implements FactoryAnnotationProvider<UserEntity, User> {
72   * 
73   *     {@literal @}Override
74   *     public UserEntity buildValue(final User annotation) {
75   *         return getRealEntityById(annotation.byId());
76   *     }
77   *     
78   *     {@literal @}Override
79   *     public Class<UserEntity> getInjectionType() {
80   *         return UserEntity.class;
81   *     }
82   * 
83   *     private UserEntity getRealEntityById(int id) {
84   *         ...
85   *     }
86   *     
87   * }
88   * </pre>
89   * 
90   * The provider implementation can get an instance from nearly everywhere. It can have injection points itself, for
91   * example an EntityManager for JPA based retrieving of the entities to be injected. <h3>Configure the factory
92   * annotation based injection</h3> For configuring an factory annotation based injection {@link FactoryAnnotationModule}
93   * class is used. Like the normal {@link Module} it is configured by using the {@link Module#configure()} method. A
94   * binding is a combination of the type to be injected, the annotation type the injection should handle (compared to
95   * Guice' normal injection mechanism this is just the type, no real annotation is bound here) and at least the
96   * {@link FactoryAnnotationProvider} implementation. Additionally a {@link FactoryAnnotationProvisionListener}
97   * implementation could be bound to be notified before or after a provision by this {@link FactoryAnnotationProvider}.
98   * 
99   * <pre>
100  * Injector injector = Guice.createInjector(new IdentityModule() {
101  *     
102  *     {@literal @}Override
103  *     public void configure() {
104  *         bindType(UserEntity.class).annotatedWith(User.class)
105  *                 .toAnnotationFactory(UserEntityProvider.class);
106  *                 
107  *         bindType(UserEntity.class).annotatedWith(ListenedUser.class)
108  *                 .applyListener(getFactoryAnnotationProvisionListener())
109  *                 .toAnnotationFactory(UserEntityProvider.class);
110  *     }
111  *     
112  * });
113  * </pre>
114  */
115 public abstract class FactoryAnnotationModule
116     implements Module
117 {
118 
119     private final Map<ScopedKey<?>, FactoryAnnotationProviderFactory<?, ?>> providerFactoryCache =
120         new HashMap<ScopedKey<?>, FactoryAnnotationProviderFactory<?, ?>>();
121 
122     private Binder binder;
123 
124     /**
125      * This method starts an actual binding process for a class type
126      * 
127      * @param injectionType The type of the class that should be bound
128      */
129     protected <T> FactoryAnnotationAnnotationBindingBuilder bindType( final Class<T> injectionType )
130     {
131 
132         return bindIdentity( TypeLiteral.get( injectionType ) );
133     }
134 
135     /**
136      * This method starts an actual binding process for a class type
137      * 
138      * @param injectionType The {@link TypeLiteral} of the class that should be bound
139      */
140     protected <T> FactoryAnnotationAnnotationBindingBuilder bindIdentity( final TypeLiteral<T> injectionType )
141     {
142 
143         return new IdentityAnnotationBindingBuilderImpl<T>( injectionType, null, null );
144 
145     }
146 
147     /**
148      * This scopes an {@link FactoryAnnotationProvider} to an annotation annotated by {@literal @}
149      * {@link ScopeAnnotation}.
150      * 
151      * @param scopeAnnotation Annotation type to use for scope binding
152      */
153     protected <A extends Annotation> BindFactoryAnnotationBindingBuilder in( final Class<A> scopeAnnotation )
154     {
155 
156         Preconditions.checkNotNull( scopeAnnotation, "scopeAnnotation" );
157 
158         return new BindIdentityBindingBuilderImpl( scopeAnnotation, null );
159     }
160 
161     /**
162      * This scopes an {@link FactoryAnnotationProvider} to the given {@link Scope} annotation.
163      * 
164      * @param scope {@link Scope} annotation to use for scope binding
165      */
166     protected BindFactoryAnnotationBindingBuilder in( final Scope scope )
167     {
168         Preconditions.checkNotNull( scope, "scope" );
169 
170         return new BindIdentityBindingBuilderImpl( null, scope );
171     }
172 
173     public void configure( final Binder binder )
174     {
175         checkState( this.binder == null, "Re-entry is not allowed." );
176 
177         this.binder = checkNotNull( binder, "binder" );
178         try
179         {
180             configure();
181         }
182         finally
183         {
184             this.binder = null;
185         }
186     }
187 
188     protected Binder binder()
189     {
190         return binder;
191     }
192 
193     protected abstract void configure();
194 
195     private final class BindIdentityBindingBuilderImpl
196         implements BindFactoryAnnotationBindingBuilder
197     {
198 
199         private final Scope scope;
200 
201         private final Class<? extends Annotation> scopeAnnotation;
202 
203         private BindIdentityBindingBuilderImpl( final Class<? extends Annotation> scopeAnnotation, final Scope scope )
204         {
205 
206             this.scopeAnnotation = scopeAnnotation;
207             this.scope = scope;
208         }
209 
210         public <T> FactoryAnnotationAnnotationBindingBuilder bindIdentity( final Class<T> injectionType )
211         {
212 
213             return new IdentityAnnotationBindingBuilderImpl<T>( TypeLiteral.get( injectionType ), scopeAnnotation,
214                                                                 scope );
215         }
216 
217         public <T> FactoryAnnotationAnnotationBindingBuilder bindIdentity( final TypeLiteral<T> injectionType )
218         {
219 
220             return new IdentityAnnotationBindingBuilderImpl<T>( injectionType, scopeAnnotation, scope );
221         }
222     }
223 
224     private final class IdentityAnnotationBindingBuilderImpl<T>
225         implements FactoryAnnotationAnnotationBindingBuilder
226     {
227 
228         private final TypeLiteral<T> injectionType;
229 
230         private final Scope scope;
231 
232         private final Class<? extends Annotation> scopeAnnotation;
233 
234         private IdentityAnnotationBindingBuilderImpl( final TypeLiteral<T> injectionType,
235                                                       final Class<? extends Annotation> scopeAnnotation,
236                                                       final Scope scope )
237         {
238 
239             Preconditions.checkNotNull( injectionType );
240 
241             this.injectionType = injectionType;
242             this.scopeAnnotation = scopeAnnotation;
243             this.scope = scope;
244         }
245 
246         public <I extends Annotation> CombinedListenerCacheableBindingBuilder annotatedWith( final Class<I> annotationType )
247         {
248 
249             Preconditions.checkNotNull( annotationType, "annotationType" );
250 
251             Preconditions.checkArgument( Annotations.isRetainedAtRuntime( annotationType ),
252                                          "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
253                                          annotationType.getName() );
254 
255             Preconditions.checkArgument( Annotations.isBindingAnnotation( annotationType ),
256                                          "%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
257                                          annotationType.getName() );
258 
259             final boolean cacheable = annotationType.isAnnotationPresent( FactoryAnnotationCacheable.class );
260 
261             return new IdentityProviderBindingBuilderImpl<T>( injectionType, scopeAnnotation, scope, null,
262                                                               annotationType, cacheable );
263         }
264     }
265 
266     private final class IdentityProviderBindingBuilderImpl<T>
267         implements CombinedListenerCacheableBindingBuilder
268     {
269 
270         private final TypeLiteral<T> injectionType;
271 
272         private final Scope scope;
273 
274         private final Class<? extends Annotation> scopeAnnotation;
275 
276         private final Class<? extends Annotation> annotationType;
277 
278         private final FactoryAnnotationProvisionListener<?, ?> provisionListener;
279 
280         private final boolean cacheable;
281 
282         private IdentityProviderBindingBuilderImpl( final TypeLiteral<T> injectionType,
283                                                     final Class<? extends Annotation> scopeAnnotation,
284                                                     final Scope scope,
285                                                     final FactoryAnnotationProvisionListener<?, ?> provisionListener,
286                                                     final Class<? extends Annotation> annotationType,
287                                                     final boolean cacheable )
288         {
289 
290             this.injectionType = injectionType;
291             this.scopeAnnotation = scopeAnnotation;
292             this.scope = scope;
293             this.provisionListener = provisionListener;
294             this.annotationType = annotationType;
295             this.cacheable = cacheable;
296         }
297 
298         @SuppressWarnings( "unchecked" )
299         public <I, A extends Annotation> void toAnnotationFactory( final FactoryAnnotationProvider<I, A> factoryAnnotationProvider )
300         {
301 
302             Preconditions.checkNotNull( factoryAnnotationProvider, "factoryAnnotationProvider" );
303 
304             final Class<I> keyType = factoryAnnotationProvider.getInjectionType();
305 
306             if ( !keyType.equals( injectionType.getRawType() ) )
307             {
308                 new Errors().cannotInjectTypeLiteralOf( keyType ).throwConfigurationExceptionIfErrorsExist();
309             }
310 
311             final ScopedKey<T> cacheKey =
312                 buildScopedKey( factoryAnnotationProvider.getClass(), annotationType, scopeAnnotation, scope );
313 
314             final FactoryAnnotationProviderFactory<I, A> provider;
315             if ( providerFactoryCache.containsKey( cacheKey ) )
316             {
317                 provider = (FactoryAnnotationProviderFactory<I, A>) providerFactoryCache.get( cacheKey );
318 
319             }
320             else
321             {
322                 provider =
323                     new FactoryAnnotationProviderFactory<I, A>(
324                                                                 binder(),
325                                                                 (Class<A>) annotationType,
326                                                                 factoryAnnotationProvider,
327                                                                 (FactoryAnnotationProvisionListener<I, A>) provisionListener,
328                                                                 cacheable );
329 
330                 providerFactoryCache.put( cacheKey, provider );
331 
332                 final ScopedBindingBuilder scopeBuilder =
333                     binder().bind( keyType ).annotatedWith( annotationType ).toProvider( provider );
334 
335                 if ( scope != null )
336                 {
337                     scopeBuilder.in( scope );
338 
339                 }
340                 else if ( scopeAnnotation != null )
341                 {
342                     scopeBuilder.in( scopeAnnotation );
343                 }
344             }
345         }
346 
347         public <I, A extends Annotation> void toIdentityProvider( final Class<FactoryAnnotationProvider<I, A>> factoryAnnotationProviderType )
348         {
349 
350             Preconditions.checkNotNull( factoryAnnotationProviderType, "factoryAnnotationProviderType" );
351 
352             final FactoryAnnotationProvider<I, A> identityProvider =
353                 PrivilegedHelper.executePrivileged( factoryAnnotationProviderType,
354                                                     new PrivilegedHelperCallback<FactoryAnnotationProvider<I, A>>()
355                                                     {
356 
357                                                         public FactoryAnnotationProvider<I, A> execute( final Class<FactoryAnnotationProvider<I, A>> clazz )
358                                                         {
359 
360                                                             try
361                                                             {
362                                                                 return clazz.newInstance();
363 
364                                                             }
365                                                             catch ( final InstantiationException e )
366                                                             {
367                                                                 throw new RuntimeException( e );
368 
369                                                             }
370                                                             catch ( final IllegalAccessException e )
371                                                             {
372                                                                 throw new RuntimeException( e );
373                                                             }
374                                                         }
375                                                     } );
376 
377             toAnnotationFactory( identityProvider );
378         }
379 
380         @SuppressWarnings( "unchecked" )
381         public <I, A extends Annotation> FactoryAnnotationBaseProviderBindingBuilder applyListener( final FactoryAnnotationProvisionListener<I, A> provisionListener )
382         {
383 
384             Preconditions.checkNotNull( provisionListener, "provisionListener" );
385 
386             return new IdentityProviderBindingBuilderImpl<I>( (TypeLiteral<I>) injectionType, scopeAnnotation, scope,
387                                                               provisionListener, annotationType, cacheable );
388         }
389 
390         @SuppressWarnings( "unchecked" )
391         public <I, A extends Annotation> CombinedListenerCacheableBindingBuilder cacheable()
392         {
393             return new IdentityProviderBindingBuilderImpl<I>( (TypeLiteral<I>) injectionType, scopeAnnotation, scope,
394                                                               provisionListener, annotationType, true );
395         }
396 
397         @SuppressWarnings( "unchecked" )
398         public <I, A extends Annotation> CombinedListenerCacheableBindingBuilder nonCacheable()
399         {
400             return new IdentityProviderBindingBuilderImpl<I>( (TypeLiteral<I>) injectionType, scopeAnnotation, scope,
401                                                               provisionListener, annotationType, false );
402         }
403 
404         @SuppressWarnings( "unchecked" )
405         private ScopedKey<T> buildScopedKey( final Class<?> type, final Class<? extends Annotation> annotationType,
406                                              final Class<? extends Annotation> scopeAnnotation, final Scope scope )
407         {
408 
409             final Scoping scoping;
410             if ( scopeAnnotation != null )
411             {
412                 scoping = Scoping.forAnnotation( scopeAnnotation );
413 
414             }
415             else if ( scope != null )
416             {
417                 scoping = Scoping.forInstance( scope );
418 
419             }
420             else
421             {
422                 scoping = null;
423             }
424 
425             return new ScopedKey<T>( (Key<T>) Key.get( type, annotationType ), scoping );
426         }
427     }
428 
429     private class ScopedKey<T>
430     {
431         private final Key<T> key;
432 
433         private final Scoping scoping;
434 
435         private ScopedKey( final Key<T> key, final Scoping scoping )
436         {
437             this.key = key;
438             this.scoping = scoping;
439         }
440 
441         @Override
442         public int hashCode()
443         {
444             final int prime = 31;
445             int result = 1;
446             result = prime * result + getOuterType().hashCode();
447             result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
448             result = prime * result + ( ( scoping == null ) ? 0 : scoping.hashCode() );
449             return result;
450         }
451 
452         @Override
453         public boolean equals( final Object obj )
454         {
455             if ( this == obj )
456             {
457                 return true;
458             }
459             if ( obj == null )
460             {
461                 return false;
462             }
463             if ( getClass() != obj.getClass() )
464             {
465                 return false;
466             }
467             final ScopedKey<?> other = (ScopedKey<?>) obj;
468             if ( !getOuterType().equals( other.getOuterType() ) )
469             {
470                 return false;
471             }
472             if ( key == null )
473             {
474                 if ( other.key != null )
475                 {
476                     return false;
477                 }
478             }
479             else if ( !key.equals( other.key ) )
480             {
481                 return false;
482             }
483             if ( scoping == null )
484             {
485                 if ( other.scoping != null )
486                 {
487                     return false;
488                 }
489             }
490             else if ( !scoping.equals( other.scoping ) )
491             {
492                 return false;
493             }
494             return true;
495         }
496 
497         private FactoryAnnotationModule getOuterType()
498         {
499             return FactoryAnnotationModule.this;
500         }
501 
502     }
503 
504 }