View Javadoc

1   package org.apache.onami.logging.core;
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  
24  import java.lang.reflect.Array;
25  import java.lang.reflect.Constructor;
26  import java.lang.reflect.Field;
27  import java.lang.reflect.GenericArrayType;
28  import java.lang.reflect.ParameterizedType;
29  import java.lang.reflect.Type;
30  import java.lang.reflect.TypeVariable;
31  
32  import com.google.inject.Binder;
33  import com.google.inject.MembersInjector;
34  import com.google.inject.Module;
35  import com.google.inject.ProvisionException;
36  import com.google.inject.TypeLiteral;
37  import com.google.inject.matcher.Matcher;
38  import com.google.inject.spi.TypeEncounter;
39  import com.google.inject.spi.TypeListener;
40  
41  /**
42   * Abstract module implementation of Logging module that simplifies Logger
43   * building and injection.
44   *
45   * Subclasses have to specify the Logger and the relative
46   * {@link AbstractLoggerInjector} types.
47   *
48   * @param <L> the Logger type has to be injected.
49   */
50  public class AbstractLoggingModule<L>
51      extends TypeLiteral<L>
52      implements Module, TypeListener
53  {
54  
55      /**
56       * The types matcher for whom the Logger injection has to be performed.
57       */
58      private final Matcher<? super TypeLiteral<?>> matcher;
59  
60      /**
61       * The concrete Logger type.
62       */
63      private final Class<?> loggerClass;
64  
65      /**
66       * The {@link AbstractLoggerInjector} constructor, instances will be created
67       * at runtime.
68       */
69      private final Constructor<? extends MembersInjector<L>> logInjectorConstructor;
70  
71      /**
72       * Creates a new Logger injection module.
73       *
74       * @param <LI> the concrete {@link AbstractLoggerInjector}
75       * @param matcher types matcher for whom the Logger injection has to be
76       *        performed.
77       * @param loggerInjectorClass the {@link AbstractLoggerInjector} constructor.
78       */
79      public <LI extends AbstractLoggerInjector<L>> AbstractLoggingModule( Matcher<? super TypeLiteral<?>> matcher,
80                                                                           Class<LI> loggerInjectorClass )
81      {
82          if ( matcher == null )
83          {
84              throw new IllegalArgumentException( "Parameter 'matcher' must not be null" );
85          }
86          if ( loggerInjectorClass == null )
87          {
88              throw new IllegalArgumentException( "Parameter 'loggerInjectorClass' must not be null" );
89          }
90  
91          this.matcher = matcher;
92          loggerClass = getRawType( getType() );
93          try
94          {
95              logInjectorConstructor = loggerInjectorClass.getConstructor( Field.class );
96          }
97          catch ( SecurityException e )
98          {
99              throw new ProvisionException( format( "Impossible to access to '%s(%s)' public constructor due to security violation: %s",
100                                                   loggerInjectorClass.getName(), Field.class.getName(), e.getMessage() ) );
101         }
102         catch ( NoSuchMethodException e )
103         {
104             throw new ProvisionException( format( "Class '%s' doesn't have a public construcor with <%s> parameter type: %s",
105                                                   loggerInjectorClass.getName(), Field.class.getName(), e.getMessage() ) );
106         }
107     }
108 
109     /**
110      * {@inheritDoc}
111      */
112     public final void configure( Binder binder )
113     {
114         binder.bindListener( matcher, this );
115     }
116 
117     /**
118      * {@inheritDoc}
119      */
120     public final <I> void hear( TypeLiteral<I> type, TypeEncounter<I> encounter )
121     {
122         hear( type.getRawType(), encounter );
123     }
124 
125     @SuppressWarnings("unchecked")
126     private <I> void hear( Class<?> klass, TypeEncounter<I> encounter )
127     {
128         if ( Object.class == klass )
129         {
130             return;
131         }
132 
133         for ( Field field : klass.getDeclaredFields() )
134         {
135             if ( loggerClass == field.getType() && field.isAnnotationPresent( InjectLogger.class ) )
136             {
137                 try
138                 {
139                     encounter.register( (MembersInjector<? super I>) logInjectorConstructor.newInstance( field ) );
140                 }
141                 catch ( Exception e )
142                 {
143                     throw new RuntimeException( format( "Impossible to register '%s' for field '%s', see nested exception",
144                                                         logInjectorConstructor.getName(),
145                                                         field ), e );
146                 }
147             }
148         }
149 
150         hear( klass.getSuperclass(), encounter );
151     }
152 
153     private static Class<?> getRawType( Type type )
154     {
155         if ( type instanceof Class<?> )
156         {
157             // type is a normal class.
158             return (Class<?>) type;
159         }
160         else if ( type instanceof ParameterizedType )
161         {
162             ParameterizedType parameterizedType = (ParameterizedType) type;
163 
164             // I'm not exactly sure why getRawType() returns Type instead of Class.
165             // Neal isn't either but suspects some pathological case related
166             // to nested classes exists.
167             Type rawType = parameterizedType.getRawType();
168             if ( !(rawType instanceof Class) )
169             {
170                 throw new IllegalArgumentException( format( "Expected a Class, but <%s> is of type %s",
171                                                             type,
172                                                             type.getClass().getName() ) );
173             }
174             return (Class<?>) rawType;
175         }
176         else if ( type instanceof GenericArrayType )
177         {
178             Type componentType = ( (GenericArrayType) type ).getGenericComponentType();
179             return Array.newInstance( getRawType( componentType ), 0 ).getClass();
180         }
181         else if ( type instanceof TypeVariable )
182         {
183             // we could use the variable's bounds, but that'll won't work if there are multiple.
184             // having a raw type that's more general than necessary is okay
185             return Object.class;
186         }
187         else
188         {
189             throw new IllegalArgumentException( format( "Expected a Class, ParameterizedType, or GenericArrayType, but <%s> is of type %s",
190                                                         type,
191                                                         type.getClass().getName() ) );
192         }
193     }
194 
195 }