Coverage Report - org.apache.onami.factoryannotation.FactoryAnnotationModule
 
Classes in this File Line Coverage Branch Coverage Complexity
FactoryAnnotationModule
75%
12/16
50%
1/2
2.292
FactoryAnnotationModule$1
N/A
N/A
2.292
FactoryAnnotationModule$BindIdentityBindingBuilderImpl
0%
0/7
N/A
2.292
FactoryAnnotationModule$IdentityAnnotationBindingBuilderImpl
100%
12/12
N/A
2.292
FactoryAnnotationModule$IdentityProviderBindingBuilderImpl
73%
28/38
50%
6/12
2.292
FactoryAnnotationModule$IdentityProviderBindingBuilderImpl$1
0%
0/6
N/A
2.292
FactoryAnnotationModule$ScopedKey
37%
12/32
8%
2/24
2.292
 
 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  404
 public abstract class FactoryAnnotationModule
 116  
     implements Module
 117  
 {
 118  
 
 119  124
     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  136
         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  140
         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  0
         Preconditions.checkNotNull( scopeAnnotation, "scopeAnnotation" );
 157  
 
 158  0
         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  0
         Preconditions.checkNotNull( scope, "scope" );
 169  
 
 170  0
         return new BindIdentityBindingBuilderImpl( null, scope );
 171  
     }
 172  
 
 173  
     public void configure( final Binder binder )
 174  
     {
 175  124
         checkState( this.binder == null, "Re-entry is not allowed." );
 176  
 
 177  124
         this.binder = checkNotNull( binder, "binder" );
 178  
         try
 179  
         {
 180  124
             configure();
 181  
         }
 182  
         finally
 183  
         {
 184  124
             this.binder = null;
 185  124
         }
 186  124
     }
 187  
 
 188  
     protected Binder binder()
 189  
     {
 190  280
         return binder;
 191  
     }
 192  
 
 193  
     protected abstract void configure();
 194  
 
 195  0
     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  0
         {
 205  
 
 206  0
             this.scopeAnnotation = scopeAnnotation;
 207  0
             this.scope = scope;
 208  0
         }
 209  
 
 210  
         public <T> FactoryAnnotationAnnotationBindingBuilder bindIdentity( final Class<T> injectionType )
 211  
         {
 212  
 
 213  0
             return new IdentityAnnotationBindingBuilderImpl<T>( TypeLiteral.get( injectionType ), scopeAnnotation,
 214  
                                                                 scope );
 215  
         }
 216  
 
 217  
         public <T> FactoryAnnotationAnnotationBindingBuilder bindIdentity( final TypeLiteral<T> injectionType )
 218  
         {
 219  
 
 220  0
             return new IdentityAnnotationBindingBuilderImpl<T>( injectionType, scopeAnnotation, scope );
 221  
         }
 222  
     }
 223  
 
 224  140
     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  140
         {
 238  
 
 239  140
             Preconditions.checkNotNull( injectionType );
 240  
 
 241  140
             this.injectionType = injectionType;
 242  140
             this.scopeAnnotation = scopeAnnotation;
 243  140
             this.scope = scope;
 244  140
         }
 245  
 
 246  
         public <I extends Annotation> CombinedListenerCacheableBindingBuilder annotatedWith( final Class<I> annotationType )
 247  
         {
 248  
 
 249  140
             Preconditions.checkNotNull( annotationType, "annotationType" );
 250  
 
 251  140
             Preconditions.checkArgument( Annotations.isRetainedAtRuntime( annotationType ),
 252  
                                          "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).",
 253  
                                          annotationType.getName() );
 254  
 
 255  140
             Preconditions.checkArgument( Annotations.isBindingAnnotation( annotationType ),
 256  
                                          "%s is not a binding annotation. Please annotate it with @BindingAnnotation.",
 257  
                                          annotationType.getName() );
 258  
 
 259  140
             final boolean cacheable = annotationType.isAnnotationPresent( FactoryAnnotationCacheable.class );
 260  
 
 261  140
             return new IdentityProviderBindingBuilderImpl<T>( injectionType, scopeAnnotation, scope, null,
 262  
                                                               annotationType, cacheable );
 263  
         }
 264  
     }
 265  
 
 266  140
     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  180
         {
 289  
 
 290  180
             this.injectionType = injectionType;
 291  180
             this.scopeAnnotation = scopeAnnotation;
 292  180
             this.scope = scope;
 293  180
             this.provisionListener = provisionListener;
 294  180
             this.annotationType = annotationType;
 295  180
             this.cacheable = cacheable;
 296  180
         }
 297  
 
 298  
         @SuppressWarnings( "unchecked" )
 299  
         public <I, A extends Annotation> void toAnnotationFactory( final FactoryAnnotationProvider<I, A> factoryAnnotationProvider )
 300  
         {
 301  
 
 302  140
             Preconditions.checkNotNull( factoryAnnotationProvider, "factoryAnnotationProvider" );
 303  
 
 304  140
             final Class<I> keyType = factoryAnnotationProvider.getInjectionType();
 305  
 
 306  140
             if ( !keyType.equals( injectionType.getRawType() ) )
 307  
             {
 308  0
                 new Errors().cannotInjectTypeLiteralOf( keyType ).throwConfigurationExceptionIfErrorsExist();
 309  
             }
 310  
 
 311  140
             final ScopedKey<T> cacheKey =
 312  
                 buildScopedKey( factoryAnnotationProvider.getClass(), annotationType, scopeAnnotation, scope );
 313  
 
 314  
             final FactoryAnnotationProviderFactory<I, A> provider;
 315  140
             if ( providerFactoryCache.containsKey( cacheKey ) )
 316  
             {
 317  0
                 provider = (FactoryAnnotationProviderFactory<I, A>) providerFactoryCache.get( cacheKey );
 318  
 
 319  
             }
 320  
             else
 321  
             {
 322  140
                 provider =
 323  
                     new FactoryAnnotationProviderFactory<I, A>(
 324  
                                                                 binder(),
 325  
                                                                 (Class<A>) annotationType,
 326  
                                                                 factoryAnnotationProvider,
 327  
                                                                 (FactoryAnnotationProvisionListener<I, A>) provisionListener,
 328  
                                                                 cacheable );
 329  
 
 330  140
                 providerFactoryCache.put( cacheKey, provider );
 331  
 
 332  140
                 final ScopedBindingBuilder scopeBuilder =
 333  
                     binder().bind( keyType ).annotatedWith( annotationType ).toProvider( provider );
 334  
 
 335  140
                 if ( scope != null )
 336  
                 {
 337  0
                     scopeBuilder.in( scope );
 338  
 
 339  
                 }
 340  140
                 else if ( scopeAnnotation != null )
 341  
                 {
 342  0
                     scopeBuilder.in( scopeAnnotation );
 343  
                 }
 344  
             }
 345  140
         }
 346  
 
 347  
         public <I, A extends Annotation> void toIdentityProvider( final Class<FactoryAnnotationProvider<I, A>> factoryAnnotationProviderType )
 348  
         {
 349  
 
 350  0
             Preconditions.checkNotNull( factoryAnnotationProviderType, "factoryAnnotationProviderType" );
 351  
 
 352  0
             final FactoryAnnotationProvider<I, A> identityProvider =
 353  
                 PrivilegedHelper.executePrivileged( factoryAnnotationProviderType,
 354  
                                                     new PrivilegedHelperCallback<FactoryAnnotationProvider<I, A>>()
 355  0
                                                     {
 356  
 
 357  
                                                         public FactoryAnnotationProvider<I, A> execute( final Class<FactoryAnnotationProvider<I, A>> clazz )
 358  
                                                         {
 359  
 
 360  
                                                             try
 361  
                                                             {
 362  0
                                                                 return clazz.newInstance();
 363  
 
 364  
                                                             }
 365  0
                                                             catch ( final InstantiationException e )
 366  
                                                             {
 367  0
                                                                 throw new RuntimeException( e );
 368  
 
 369  
                                                             }
 370  0
                                                             catch ( final IllegalAccessException e )
 371  
                                                             {
 372  0
                                                                 throw new RuntimeException( e );
 373  
                                                             }
 374  
                                                         }
 375  
                                                     } );
 376  
 
 377  0
             toAnnotationFactory( identityProvider );
 378  0
         }
 379  
 
 380  
         @SuppressWarnings( "unchecked" )
 381  
         public <I, A extends Annotation> FactoryAnnotationBaseProviderBindingBuilder applyListener( final FactoryAnnotationProvisionListener<I, A> provisionListener )
 382  
         {
 383  
 
 384  32
             Preconditions.checkNotNull( provisionListener, "provisionListener" );
 385  
 
 386  32
             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  4
             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  4
             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  140
             if ( scopeAnnotation != null )
 411  
             {
 412  0
                 scoping = Scoping.forAnnotation( scopeAnnotation );
 413  
 
 414  
             }
 415  140
             else if ( scope != null )
 416  
             {
 417  0
                 scoping = Scoping.forInstance( scope );
 418  
 
 419  
             }
 420  
             else
 421  
             {
 422  140
                 scoping = null;
 423  
             }
 424  
 
 425  140
             return new ScopedKey<T>( (Key<T>) Key.get( type, annotationType ), scoping );
 426  
         }
 427  
     }
 428  
 
 429  140
     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  140
         {
 437  140
             this.key = key;
 438  140
             this.scoping = scoping;
 439  140
         }
 440  
 
 441  
         @Override
 442  
         public int hashCode()
 443  
         {
 444  280
             final int prime = 31;
 445  280
             int result = 1;
 446  280
             result = prime * result + getOuterType().hashCode();
 447  280
             result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
 448  280
             result = prime * result + ( ( scoping == null ) ? 0 : scoping.hashCode() );
 449  280
             return result;
 450  
         }
 451  
 
 452  
         @Override
 453  
         public boolean equals( final Object obj )
 454  
         {
 455  0
             if ( this == obj )
 456  
             {
 457  0
                 return true;
 458  
             }
 459  0
             if ( obj == null )
 460  
             {
 461  0
                 return false;
 462  
             }
 463  0
             if ( getClass() != obj.getClass() )
 464  
             {
 465  0
                 return false;
 466  
             }
 467  0
             final ScopedKey<?> other = (ScopedKey<?>) obj;
 468  0
             if ( !getOuterType().equals( other.getOuterType() ) )
 469  
             {
 470  0
                 return false;
 471  
             }
 472  0
             if ( key == null )
 473  
             {
 474  0
                 if ( other.key != null )
 475  
                 {
 476  0
                     return false;
 477  
                 }
 478  
             }
 479  0
             else if ( !key.equals( other.key ) )
 480  
             {
 481  0
                 return false;
 482  
             }
 483  0
             if ( scoping == null )
 484  
             {
 485  0
                 if ( other.scoping != null )
 486  
                 {
 487  0
                     return false;
 488  
                 }
 489  
             }
 490  0
             else if ( !scoping.equals( other.scoping ) )
 491  
             {
 492  0
                 return false;
 493  
             }
 494  0
             return true;
 495  
         }
 496  
 
 497  
         private FactoryAnnotationModule getOuterType()
 498  
         {
 499  280
             return FactoryAnnotationModule.this;
 500  
         }
 501  
 
 502  
     }
 503  
 
 504  
 }