View Javadoc

1   
2   /*
3    * Grapht, an open source dependency injector.
4    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
5    * Copyright 2010-2014 Regents of the University of Minnesota
6    *
7    * This program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as
9    * published by the Free Software Foundation; either version 2.1 of the
10   * License, or (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful, but WITHOUT
13   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15   * details.
16   *
17   * You should have received a copy of the GNU General Public License along with
18   * this program; if not, write to the Free Software Foundation, Inc., 51
19   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20   */
21  package org.grouplens.grapht.reflect.internal;
22  
23  import org.apache.commons.lang3.reflect.MethodUtils;
24  import org.grouplens.grapht.ConstructionException;
25  import org.grouplens.grapht.Instantiator;
26  import org.grouplens.grapht.LifecycleManager;
27  import org.grouplens.grapht.NullDependencyException;
28  import org.grouplens.grapht.reflect.Desire;
29  import org.grouplens.grapht.reflect.InjectionPoint;
30  import org.grouplens.grapht.util.LogContext;
31  import org.grouplens.grapht.util.Preconditions;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.annotation.PostConstruct;
36  import java.lang.reflect.Constructor;
37  import java.lang.reflect.InvocationTargetException;
38  import java.lang.reflect.Method;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  
43  /**
44   * Instantiates class instances.
45   *
46   * @author <a href="http://grouplens.org">GroupLens Research</a>
47   */
48  public class ClassInstantiator implements Instantiator {
49      private static final Logger logger = LoggerFactory.getLogger(ClassInstantiator.class);
50  
51      private final Class<?> type;
52      private final List<Desire> desires;
53      private final Map<Desire, Instantiator> providers;
54      private final LifecycleManager manager;
55  
56      /**
57       * Create an ClassInstantiator that will provide instances of the given
58       * type, with given the list of desires and a function mapping that
59       * satisfies those providers.
60       *
61       * @param type The type of instance created
62       * @param desires The dependency desires for the instance
63       * @param providers The providers that satisfy the desires of the type
64       */
65      public ClassInstantiator(Class<?> type, List<Desire> desires,
66                               Map<Desire,Instantiator> providers,
67                               LifecycleManager manager) {
68          Preconditions.notNull("type", type);
69          Preconditions.notNull("desires", desires);
70          Preconditions.notNull("providers", providers);
71  
72          this.type = type;
73          this.desires = desires;
74          this.providers = providers;
75          this.manager = manager;
76      }
77  
78      @Override
79      public Class getType() {
80          return type;
81      }
82  
83      @Override
84      public Object instantiate() throws ConstructionException {
85          // find constructor and build up necessary constructor arguments
86  
87          Constructor<?> ctor = getConstructor();
88          LogContext globalLogContext = LogContext.create();
89          Object instance = null;
90          Method[] methods;
91  
92          try {
93              // create the instance that we are injecting
94              try {
95                  globalLogContext.put("org.grouplens.grapht.class", ctor.getClass().toString());
96                  Object[] ctorArgs = new Object[ctor.getParameterTypes().length];
97                  for (Desire d : desires) {
98                      LogContext ipContext = LogContext.create();
99                      if (d.getInjectionPoint() instanceof ConstructorParameterInjectionPoint) {
100                         // this desire is a constructor argument so create it now
101                         Instantiator provider = providers.get(d);
102                         ConstructorParameterInjectionPoint cd = (ConstructorParameterInjectionPoint) d.getInjectionPoint();
103                         logger.trace("Injection point satisfactions in progress {}", cd);
104                         try {
105                             ipContext.put("org.grouplens.grapht.injectionPoint", cd.toString());
106                         } finally {
107                             ipContext.finish();
108                         }
109                         ctorArgs[cd.getParameterIndex()] = checkNull(cd, provider.instantiate());
110                     }
111                 }
112                 logger.trace("Invoking constructor {} with arguments {}", ctor, ctorArgs);
113                 ctor.setAccessible(true);
114                 instance = ctor.newInstance(ctorArgs);
115             } catch (InvocationTargetException e) {
116                 throw new ConstructionException(ctor, "Constructor " + ctor + " failed", e);
117             } catch (InstantiationException e) {
118                 throw new ConstructionException(ctor, "Could not instantiate " + type, e);
119             } catch (IllegalAccessException e) {
120                 throw new ConstructionException(ctor, "Access violation on " + ctor, e);
121             }
122 
123             // satisfy dependencies in the order of the list, which was
124             // prepared to comply with JSR 330
125             Map<Method, InjectionArgs> settersAndArguments = new HashMap<Method, InjectionArgs>();
126             for (Desire d : desires) {
127                 LogContext ipContext = LogContext.create();
128                 try {
129                     final InjectionStrategy injectionStrategy = InjectionStrategy.forInjectionPoint(d.getInjectionPoint());
130                     ipContext.put("org.grouplens.grapht.injectionPoint", d.getInjectionPoint().toString());
131                     injectionStrategy.inject(d.getInjectionPoint(), instance, providers.get(d), settersAndArguments);
132                 } finally {
133                     ipContext.finish();
134                 }
135             }
136         } finally {
137             globalLogContext.finish();
138         }
139         if (manager != null) {
140             manager.registerComponent(instance);
141         }
142 
143         methods = MethodUtils.getMethodsWithAnnotation(type, PostConstruct.class);
144         for(Method method:methods){
145             method.setAccessible(true);
146             try {
147                 method.invoke(instance);
148             } catch (InvocationTargetException e) {
149                 throw new ConstructionException("Exception throw by " + method, e);
150             } catch (IllegalAccessException e) {
151                 throw new ConstructionException("Access violation invoking " + method, e);
152             }
153         }
154 
155         // the instance has been fully configured
156         return instance;
157     }
158 
159     @SuppressWarnings("unchecked")
160     private Constructor<?> getConstructor() {
161         for (Desire d: desires) {
162             if (d.getInjectionPoint() instanceof ConstructorParameterInjectionPoint) {
163                 // since we only allow one injectable constructor, any ConstructorParameterInjectionPoint
164                 // will have the same constructor as all other constructor parameter injection points
165                 Constructor<?> ctor = ((ConstructorParameterInjectionPoint) d.getInjectionPoint()).getMember();
166                 logger.debug("Using constructor annotated with @Inject: {}", ctor);
167                 return ctor;
168             }
169         }
170 
171         try {
172             logger.debug("Using default constructor for {}", type);
173             return type.getDeclaredConstructor();
174         } catch (NoSuchMethodException e) {
175             // this constructor is being invoked for a ClassSatisfaction or a 
176             // ProviderClassSatisfaction, both of which assert that the type is
177             // instantiable, so this should never happen
178             throw new RuntimeException("Unexpected exception", e);
179         }
180     }
181 
182     static Object checkNull(InjectionPoint injectPoint, Object value) throws NullDependencyException {
183         if (value == null && !injectPoint.isNullable()) {
184             throw new NullDependencyException(injectPoint);
185         } else {
186             return value;
187         }
188     }
189 
190     static class InjectionArgs {
191         public final Object[] arguments;
192         private final boolean[] injected;
193 
194         public InjectionArgs(int num) {
195             arguments = new Object[num];
196             injected = new boolean[num];
197         }
198 
199         public void set(int i, Object o) {
200             arguments[i] =o;
201             injected[i] = true;
202         }
203 
204         public boolean isCompleted() {
205             for (int i = 0; i < injected.length; i++) {
206                 if (!injected[i]) {
207                     return false;
208                 }
209             }
210             return true;
211         }
212     }
213 }