001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2018 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.codehaus.griffon.runtime.injection;
019
020 import com.google.inject.AbstractModule;
021 import com.google.inject.Guice;
022 import com.google.inject.Module;
023 import com.google.inject.TypeLiteral;
024 import com.google.inject.matcher.AbstractMatcher;
025 import com.google.inject.matcher.Matchers;
026 import com.google.inject.spi.InjectionListener;
027 import com.google.inject.spi.ProvisionListener;
028 import com.google.inject.spi.TypeEncounter;
029 import com.google.inject.spi.TypeListener;
030 import griffon.core.ApplicationEvent;
031 import griffon.core.Context;
032 import griffon.core.GriffonApplication;
033 import griffon.core.artifact.GriffonArtifact;
034 import griffon.core.env.ApplicationPhase;
035 import griffon.core.injection.Binding;
036 import griffon.core.injection.Injector;
037 import griffon.core.injection.InjectorFactory;
038 import griffon.exceptions.FieldException;
039 import griffon.exceptions.NewInstanceException;
040 import griffon.inject.Contextual;
041 import org.codehaus.griffon.runtime.core.injection.InjectorProvider;
042 import org.kordamp.jipsy.ServiceProviderFor;
043 import org.slf4j.Logger;
044 import org.slf4j.LoggerFactory;
045
046 import javax.annotation.Nonnull;
047 import javax.annotation.PostConstruct;
048 import javax.inject.Inject;
049 import javax.inject.Singleton;
050 import java.beans.PropertyDescriptor;
051 import java.lang.reflect.AnnotatedElement;
052 import java.lang.reflect.Field;
053 import java.lang.reflect.InvocationTargetException;
054 import java.lang.reflect.Method;
055 import java.lang.reflect.Modifier;
056 import java.util.ArrayList;
057 import java.util.Arrays;
058 import java.util.Collection;
059 import java.util.LinkedHashMap;
060 import java.util.List;
061 import java.util.Map;
062 import java.util.ServiceLoader;
063
064 import static com.google.inject.util.Providers.guicify;
065 import static griffon.util.AnnotationUtils.annotationsOfMethodParameter;
066 import static griffon.util.AnnotationUtils.findAnnotation;
067 import static griffon.util.AnnotationUtils.namesFor;
068 import static griffon.util.AnnotationUtils.sortByDependencies;
069 import static griffon.util.GriffonClassUtils.getAllDeclaredFields;
070 import static griffon.util.GriffonClassUtils.getPropertyDescriptors;
071 import static griffon.util.GriffonClassUtils.invokeAnnotatedMethod;
072 import static griffon.util.GriffonClassUtils.setFieldValue;
073 import static java.util.Arrays.asList;
074 import static java.util.Objects.requireNonNull;
075 import static org.codehaus.griffon.runtime.injection.GuiceInjector.moduleFromBindings;
076
077 /**
078 * @author Andres Almiray
079 * @since 2.0.0
080 */
081 @ServiceProviderFor(InjectorFactory.class)
082 public class GuiceInjectorFactory implements InjectorFactory {
083 private static final Logger LOG = LoggerFactory.getLogger(GuiceInjectorFactory.class);
084
085 @Nonnull
086 @Override
087 public GuiceInjector createInjector(@Nonnull GriffonApplication application, @Nonnull Iterable<Binding<?>> bindings) {
088 requireNonNull(application, "Argument 'application' must not be null");
089 requireNonNull(bindings, "Argument 'bindings' must not be null");
090 InjectorProvider injectorProvider = new InjectorProvider();
091 GuiceInjector injector = createModules(application, injectorProvider, bindings);
092 injectorProvider.setInjector(injector);
093 return injector;
094 }
095
096 private GuiceInjector createModules(@Nonnull final GriffonApplication application, @Nonnull final InjectorProvider injectorProvider, @Nonnull Iterable<Binding<?>> bindings) {
097 final InjectionListener<GriffonArtifact> injectionListener = injectee -> application.getEventRouter().publishEvent(
098 ApplicationEvent.NEW_INSTANCE.getName(),
099 asList(injectee.getClass(), injectee)
100 );
101
102 final InjectionListener<Object> postConstructorInjectorListener = injectee -> {
103 resolveContextualInjections(injectee, application);
104 resolveConfigurationInjections(injectee, application);
105 invokeAnnotatedMethod(injectee, PostConstruct.class);
106 };
107
108 final InstanceTracker instanceTracker = new InstanceTracker();
109 Module injectorModule = new AbstractModule() {
110 @Override
111 protected void configure() {
112 bind(Injector.class)
113 .toProvider(guicify(injectorProvider))
114 .in(Singleton.class);
115
116 bindListener(new AbstractMatcher<TypeLiteral<?>>() {
117 public boolean matches(TypeLiteral<?> typeLiteral) {
118 return GriffonArtifact.class.isAssignableFrom(typeLiteral.getRawType());
119 }
120 }, new TypeListener() {
121 @SuppressWarnings("unchecked")
122 @Override
123 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
124 if (GriffonArtifact.class.isAssignableFrom(type.getRawType())) {
125 TypeEncounter<GriffonArtifact> artifactEncounter = (TypeEncounter<GriffonArtifact>) encounter;
126 artifactEncounter.register(injectionListener);
127 }
128 }
129 }
130 );
131
132 bindListener(Matchers.any(), new TypeListener() {
133 @Override
134 public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
135 encounter.register(postConstructorInjectorListener);
136 }
137 });
138
139 bindListener(Matchers.any(), new ProvisionListener() {
140 @Override
141 public <T> void onProvision(ProvisionInvocation<T> provision) {
142 instanceTracker.track(provision.getBinding(), provision.provision());
143 }
144 });
145 }
146 };
147
148 Collection<Module> modules = new ArrayList<>();
149 modules.add(injectorModule);
150 modules.add(moduleFromBindings(bindings));
151
152 List<Module> loadedModules = new ArrayList<>();
153 ServiceLoader<Module> moduleLoader = ServiceLoader.load(Module.class, getClass().getClassLoader());
154 for (Module module : moduleLoader) {
155 LOG.trace("Adding module {}", module);
156 loadedModules.add(module);
157 }
158 Map<String, Module> sortedModules = sortByDependencies(loadedModules, "Module", "module");
159 modules.addAll(sortedModules.values());
160
161 com.google.inject.Injector injector = Guice.createInjector(modules);
162 instanceTracker.setInjector(injector);
163 return new GuiceInjector(instanceTracker);
164 }
165
166 protected void resolveContextualInjections(@Nonnull Object injectee, @Nonnull GriffonApplication application) {
167 if (application.getPhase() == ApplicationPhase.INITIALIZE || injectee instanceof GriffonArtifact) {
168 // skip
169 return;
170 }
171
172 Map<String, Field> fields = new LinkedHashMap<>();
173 for (Field field : getAllDeclaredFields(injectee.getClass())) {
174 fields.put(field.getName(), field);
175 }
176
177 Map<String, InjectionPoint> injectionPoints = new LinkedHashMap<>();
178 for (PropertyDescriptor descriptor : getPropertyDescriptors(injectee.getClass())) {
179 Method method = descriptor.getWriteMethod();
180 if (method == null || isInjectable(method)) { continue; }
181 boolean nullable = method.getAnnotation(Nonnull.class) == null && findAnnotation(annotationsOfMethodParameter(method, 0), Nonnull.class) == null;
182 InjectionPoint.Type type = resolveType(method);
183 Field field = fields.get(descriptor.getName());
184 if (field != null && type == InjectionPoint.Type.OTHER) {
185 type = resolveType(field);
186 nullable = field.getAnnotation(Nonnull.class) == null;
187 }
188 injectionPoints.put(descriptor.getName(), new MethodInjectionPoint(descriptor.getName(), nullable, method, type));
189 }
190
191 for (Field field : getAllDeclaredFields(injectee.getClass())) {
192 if (Modifier.isStatic(field.getModifiers()) || isInjectable(field)) { continue; }
193 if (!injectionPoints.containsKey(field.getName())) {
194 boolean nullable = field.getAnnotation(Nonnull.class) == null;
195 InjectionPoint.Type type = resolveType(field);
196 injectionPoints.put(field.getName(), new FieldInjectionPoint(field.getName(), nullable, field, type));
197 }
198 }
199
200 for (InjectionPoint ip : injectionPoints.values()) {
201 ip.apply(application.getContext(), injectee);
202 }
203 }
204
205 @Nonnull
206 protected InjectionPoint.Type resolveType(@Nonnull AnnotatedElement element) {
207 if (isContextual(element)) {
208 return InjectionPoint.Type.CONTEXTUAL;
209 }
210 return InjectionPoint.Type.OTHER;
211 }
212
213 protected boolean isContextual(AnnotatedElement element) {
214 return element != null && element.getAnnotation(Contextual.class) != null;
215 }
216
217 protected boolean isInjectable(AnnotatedElement element) {
218 return element != null && element.getAnnotation(Inject.class) != null;
219 }
220
221 protected void resolveConfigurationInjections(@Nonnull Object injectee, @Nonnull GriffonApplication application) {
222 if (application.getPhase() == ApplicationPhase.INITIALIZE || injectee instanceof GriffonArtifact) {
223 // skip
224 return;
225 }
226 application.getConfigurationManager().injectConfiguration(injectee);
227 }
228
229 protected abstract static class InjectionPoint {
230 protected final String name;
231 protected final boolean nullable;
232 protected final Type type;
233
234 protected InjectionPoint(String name, boolean nullable, Type type) {
235 this.name = name;
236 this.nullable = nullable;
237 this.type = type;
238 }
239
240 protected enum Type {
241 CONTEXTUAL,
242 OTHER
243 }
244
245 protected abstract void apply(@Nonnull Context context, @Nonnull Object instance);
246 }
247
248 protected static class FieldInjectionPoint extends InjectionPoint {
249 protected final Field field;
250
251 protected FieldInjectionPoint(String name, boolean nullable, Field field, Type type) {
252 super(name, nullable, type);
253 this.field = field;
254 }
255
256 @Override
257 protected void apply(@Nonnull Context context, @Nonnull Object instance) {
258 if (type == Type.CONTEXTUAL) {
259 String[] keys = namesFor(field);
260 Object argValue = null;
261
262 for (String key : keys) {
263 if (context.containsKey(key)) {
264 argValue = context.get(key);
265 break;
266 }
267 }
268
269 try {
270 if (argValue == null) {
271 if (!nullable) {
272 throw new IllegalStateException("Could not find an instance of type " +
273 field.getType().getName() + " under keys '" + Arrays.toString(keys) +
274 "' in the application context to be injected on field '" + field.getName() +
275 "' in " + instance.getClass().getName() + ". Field does not accept null values.");
276 }
277 return;
278 }
279
280 setFieldValue(instance, name, argValue);
281 } catch (IllegalStateException | FieldException e) {
282 throw new NewInstanceException(instance.getClass(), e);
283 }
284 }
285 }
286 }
287
288 protected static class MethodInjectionPoint extends InjectionPoint {
289 protected final Method method;
290
291 protected MethodInjectionPoint(String name, boolean nullable, Method method, Type type) {
292 super(name, nullable, type);
293 this.method = method;
294 }
295
296 @Override
297 protected void apply(@Nonnull Context context, @Nonnull Object instance) {
298 if (type == Type.CONTEXTUAL) {
299 String[] keys = namesFor(method);
300 Object argValue = null;
301
302 for (String key : keys) {
303 if (context.containsKey(key)) {
304 argValue = context.get(key);
305 break;
306 }
307 }
308
309 try {
310 if (argValue == null) {
311 if (!nullable) {
312 throw new IllegalStateException("Could not find an instance of type " +
313 method.getParameterTypes()[0].getName() + " under keys '" + Arrays.toString(keys) +
314 "' in the application context to be injected on property '" + name +
315 "' in " + instance.getClass().getName() + "). Property does not accept null values.");
316 } return;
317 }
318
319 method.invoke(instance, argValue);
320 } catch (IllegalStateException | IllegalAccessException | InvocationTargetException e) {
321 throw new NewInstanceException(instance.getClass(), e);
322 }
323 }
324 }
325 }
326 }
|