PageRenderTime 890ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/subprojects/griffon-core/src/test/groovy/integration/GuiceInjectorFactory.java

http://github.com/griffon/griffon
Java | 299 lines | 243 code | 37 blank | 19 comment | 48 complexity | c2e3347900246d7c0d7150fc7be1fef3 MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * SPDX-License-Identifier: Apache-2.0
  3. *
  4. * Copyright 2008-2021 the original author or authors.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * 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, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package integration;
  19. import com.google.inject.AbstractModule;
  20. import com.google.inject.Guice;
  21. import com.google.inject.Module;
  22. import com.google.inject.TypeLiteral;
  23. import com.google.inject.matcher.AbstractMatcher;
  24. import com.google.inject.matcher.Matchers;
  25. import com.google.inject.spi.InjectionListener;
  26. import com.google.inject.spi.TypeEncounter;
  27. import com.google.inject.spi.TypeListener;
  28. import griffon.annotations.core.Nonnull;
  29. import griffon.annotations.inject.Contextual;
  30. import griffon.core.Context;
  31. import griffon.core.GriffonApplication;
  32. import griffon.core.artifact.GriffonArtifact;
  33. import griffon.core.env.ApplicationPhase;
  34. import griffon.core.events.NewInstanceEvent;
  35. import griffon.core.injection.Binding;
  36. import griffon.core.injection.Injector;
  37. import griffon.core.injection.InjectorFactory;
  38. import griffon.exceptions.FieldException;
  39. import griffon.exceptions.NewInstanceException;
  40. import org.codehaus.griffon.runtime.core.injection.InjectorProvider;
  41. import org.kordamp.jipsy.annotations.ServiceProviderFor;
  42. import javax.annotation.PostConstruct;
  43. import javax.inject.Inject;
  44. import javax.inject.Singleton;
  45. import java.beans.PropertyDescriptor;
  46. import java.lang.reflect.AnnotatedElement;
  47. import java.lang.reflect.Field;
  48. import java.lang.reflect.InvocationTargetException;
  49. import java.lang.reflect.Method;
  50. import java.lang.reflect.Modifier;
  51. import java.util.ArrayList;
  52. import java.util.Arrays;
  53. import java.util.Collection;
  54. import java.util.LinkedHashMap;
  55. import java.util.Map;
  56. import java.util.ServiceLoader;
  57. import static com.google.inject.util.Providers.guicify;
  58. import static griffon.util.AnnotationUtils.annotationsOfMethodParameter;
  59. import static griffon.util.AnnotationUtils.findAnnotation;
  60. import static griffon.util.AnnotationUtils.namesFor;
  61. import static griffon.util.GriffonClassUtils.getAllDeclaredFields;
  62. import static griffon.util.GriffonClassUtils.getPropertyDescriptors;
  63. import static griffon.util.GriffonClassUtils.invokeAnnotatedMethod;
  64. import static griffon.util.GriffonClassUtils.setFieldValue;
  65. import static integration.GuiceInjector.moduleFromBindings;
  66. import static java.util.Objects.requireNonNull;
  67. @ServiceProviderFor(InjectorFactory.class)
  68. public class GuiceInjectorFactory implements InjectorFactory {
  69. @Nonnull
  70. @Override
  71. public GuiceInjector createInjector(@Nonnull GriffonApplication application, @Nonnull Iterable<Binding<?>> bindings) {
  72. requireNonNull(application, "Argument 'application' must not be null");
  73. requireNonNull(bindings, "Argument 'bindings' must not be null");
  74. InjectorProvider injectorProvider = new InjectorProvider();
  75. GuiceInjector injector = createModules(application, injectorProvider, bindings);
  76. injectorProvider.setInjector(injector);
  77. return injector;
  78. }
  79. private GuiceInjector createModules(@Nonnull final GriffonApplication application, @Nonnull final InjectorProvider injectorProvider, @Nonnull Iterable<Binding<?>> bindings) {
  80. final InjectionListener<GriffonArtifact> injectionListener = injectee -> application.getEventRouter()
  81. .publishEvent(NewInstanceEvent.of((Class<GriffonArtifact>) injectee.getClass(), injectee));
  82. final InjectionListener<Object> postConstructorInjectorListener = injectee -> {
  83. resolveContextualInjections(injectee, application);
  84. resolveConfigurationInjections(injectee, application);
  85. invokeAnnotatedMethod(injectee, PostConstruct.class);
  86. };
  87. Module injectorModule = new AbstractModule() {
  88. @Override
  89. protected void configure() {
  90. bind(Injector.class)
  91. .toProvider(guicify(injectorProvider))
  92. .in(Singleton.class);
  93. bindListener(new AbstractMatcher<TypeLiteral<?>>() {
  94. public boolean matches(TypeLiteral<?> typeLiteral) {
  95. return GriffonArtifact.class.isAssignableFrom(typeLiteral.getRawType());
  96. }
  97. }, new TypeListener() {
  98. @SuppressWarnings("unchecked")
  99. @Override
  100. public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
  101. if (GriffonArtifact.class.isAssignableFrom(type.getRawType())) {
  102. TypeEncounter<GriffonArtifact> artifactEncounter = (TypeEncounter<GriffonArtifact>) encounter;
  103. artifactEncounter.register(injectionListener);
  104. }
  105. }
  106. }
  107. );
  108. bindListener(Matchers.any(), new TypeListener() {
  109. @Override
  110. public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
  111. encounter.register(postConstructorInjectorListener);
  112. }
  113. });
  114. }
  115. };
  116. Collection<Module> modules = new ArrayList<>();
  117. modules.add(injectorModule);
  118. modules.add(moduleFromBindings(bindings));
  119. ServiceLoader<Module> moduleLoader = ServiceLoader.load(Module.class, getClass().getClassLoader());
  120. for (Module module : moduleLoader) {
  121. modules.add(module);
  122. }
  123. com.google.inject.Injector injector = Guice.createInjector(modules);
  124. return new GuiceInjector(injector);
  125. }
  126. protected void resolveContextualInjections(@Nonnull Object injectee, @Nonnull GriffonApplication application) {
  127. if (application.getPhase() == ApplicationPhase.INITIALIZE || injectee instanceof GriffonArtifact) {
  128. // skip
  129. return;
  130. }
  131. Map<String, Field> fields = new LinkedHashMap<>();
  132. for (Field field : getAllDeclaredFields(injectee.getClass())) {
  133. fields.put(field.getName(), field);
  134. }
  135. Map<String, InjectionPoint> injectionPoints = new LinkedHashMap<>();
  136. for (PropertyDescriptor descriptor : getPropertyDescriptors(injectee.getClass())) {
  137. Method method = descriptor.getWriteMethod();
  138. if (method == null || isInjectable(method)) { continue; }
  139. boolean nullable = method.getAnnotation(Nonnull.class) == null && findAnnotation(annotationsOfMethodParameter(method, 0), Nonnull.class) == null;
  140. InjectionPoint.Type type = resolveType(method);
  141. Field field = fields.get(descriptor.getName());
  142. if (field != null && type == InjectionPoint.Type.OTHER) {
  143. type = resolveType(field);
  144. nullable = field.getAnnotation(Nonnull.class) == null;
  145. }
  146. injectionPoints.put(descriptor.getName(), new MethodInjectionPoint(descriptor.getName(), nullable, method, type));
  147. }
  148. for (Field field : getAllDeclaredFields(injectee.getClass())) {
  149. if (Modifier.isStatic(field.getModifiers()) || isInjectable(field)) { continue; }
  150. if (!injectionPoints.containsKey(field.getName())) {
  151. boolean nullable = field.getAnnotation(Nonnull.class) == null;
  152. InjectionPoint.Type type = resolveType(field);
  153. injectionPoints.put(field.getName(), new FieldInjectionPoint(field.getName(), nullable, field, type));
  154. }
  155. }
  156. for (InjectionPoint ip : injectionPoints.values()) {
  157. ip.apply(application.getContext(), injectee);
  158. }
  159. }
  160. @Nonnull
  161. protected InjectionPoint.Type resolveType(@Nonnull AnnotatedElement element) {
  162. if (isContextual(element)) {
  163. return InjectionPoint.Type.CONTEXTUAL;
  164. }
  165. return InjectionPoint.Type.OTHER;
  166. }
  167. protected boolean isContextual(AnnotatedElement element) {
  168. return element != null && element.getAnnotation(Contextual.class) != null;
  169. }
  170. protected boolean isInjectable(AnnotatedElement element) {
  171. return element != null && element.getAnnotation(Inject.class) != null;
  172. }
  173. protected void resolveConfigurationInjections(@Nonnull Object injectee, @Nonnull GriffonApplication application) {
  174. if (application.getPhase() == ApplicationPhase.INITIALIZE || injectee instanceof GriffonArtifact) {
  175. // skip
  176. return;
  177. }
  178. application.getConfigurationManager().injectConfiguration(injectee);
  179. }
  180. protected abstract static class InjectionPoint {
  181. protected final String name;
  182. protected final boolean nullable;
  183. protected final Type type;
  184. protected InjectionPoint(String name, boolean nullable, Type type) {
  185. this.name = name;
  186. this.nullable = nullable;
  187. this.type = type;
  188. }
  189. protected enum Type {
  190. CONTEXTUAL,
  191. OTHER
  192. }
  193. protected abstract void apply(@Nonnull Context context, @Nonnull Object instance);
  194. }
  195. protected static class FieldInjectionPoint extends InjectionPoint {
  196. protected final Field field;
  197. protected FieldInjectionPoint(String name, boolean nullable, Field field, Type type) {
  198. super(name, nullable, type);
  199. this.field = field;
  200. }
  201. @Override
  202. protected void apply(@Nonnull Context context, @Nonnull Object instance) {
  203. if (type == Type.CONTEXTUAL) {
  204. String[] keys = namesFor(field);
  205. Object argValue = null;
  206. for (String key : keys) {
  207. if (context.containsKey(key)) {
  208. argValue = context.get(key);
  209. break;
  210. }
  211. }
  212. try {
  213. if (argValue == null) {
  214. if (!nullable) {
  215. throw new IllegalStateException("Could not find an instance of type " +
  216. field.getType().getName() + " under keys '" + Arrays.toString(keys) +
  217. "' in the application context to be injected on field '" + field.getName() +
  218. "' in " + instance.getClass().getName() + ". Field does not accept null values.");
  219. }
  220. return;
  221. }
  222. setFieldValue(instance, name, argValue);
  223. } catch (IllegalStateException | FieldException e) {
  224. throw new NewInstanceException(instance.getClass(), e);
  225. }
  226. }
  227. }
  228. }
  229. protected static class MethodInjectionPoint extends InjectionPoint {
  230. protected final Method method;
  231. protected MethodInjectionPoint(String name, boolean nullable, Method method, Type type) {
  232. super(name, nullable, type);
  233. this.method = method;
  234. }
  235. @Override
  236. protected void apply(@Nonnull Context context, @Nonnull Object instance) {
  237. if (type == Type.CONTEXTUAL) {
  238. String[] keys = namesFor(method);
  239. Object argValue = null;
  240. for (String key : keys) {
  241. if (context.containsKey(key)) {
  242. argValue = context.get(key);
  243. break;
  244. }
  245. }
  246. try {
  247. if (argValue == null) {
  248. if (!nullable) {
  249. throw new IllegalStateException("Could not find an instance of type " +
  250. method.getParameterTypes()[0].getName() + " under keys '" + Arrays.toString(keys) +
  251. "' in the application context to be injected on property '" + name +
  252. "' in " + instance.getClass().getName() + "). Property does not accept null values.");
  253. } return;
  254. }
  255. method.invoke(instance, argValue);
  256. } catch (IllegalStateException | IllegalAccessException | InvocationTargetException e) {
  257. throw new NewInstanceException(instance.getClass(), e);
  258. }
  259. }
  260. }
  261. }
  262. }