001 /*
002 * Copyright 2008-2015 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.codehaus.griffon.runtime.core;
017
018 import griffon.core.GriffonApplication;
019 import griffon.core.injection.Module;
020 import griffon.core.injection.TestingModule;
021 import griffon.core.test.TestCaseAware;
022 import griffon.core.test.TestModuleAware;
023 import griffon.inject.BindTo;
024 import griffon.util.AnnotationUtils;
025 import org.codehaus.griffon.runtime.core.injection.AbstractTestingModule;
026 import org.codehaus.griffon.runtime.core.injection.AnnotatedBindingBuilder;
027 import org.codehaus.griffon.runtime.core.injection.LinkedBindingBuilder;
028 import org.codehaus.griffon.runtime.core.injection.SingletonBindingBuilder;
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031
032 import javax.annotation.Nonnull;
033 import javax.inject.Named;
034 import javax.inject.Provider;
035 import javax.inject.Qualifier;
036 import javax.inject.Singleton;
037 import java.lang.annotation.Annotation;
038 import java.lang.reflect.Field;
039 import java.lang.reflect.Method;
040 import java.util.ArrayList;
041 import java.util.Collection;
042 import java.util.Collections;
043 import java.util.LinkedHashMap;
044 import java.util.List;
045 import java.util.Map;
046
047 import static griffon.util.AnnotationUtils.named;
048 import static griffon.util.GriffonNameUtils.getPropertyName;
049 import static griffon.util.GriffonNameUtils.isBlank;
050
051 /**
052 * @author Andres Almiray
053 * @since 2.0.0
054 */
055 public class TestApplicationBootstrapper extends DefaultApplicationBootstrapper implements TestCaseAware {
056 private static final Logger LOG = LoggerFactory.getLogger(TestApplicationBootstrapper.class);
057
058 private static final String METHOD_MODULES = "modules";
059 private static final String METHOD_MODULE_OVERRIDES = "moduleOverrides";
060 private Object testCase;
061
062 public TestApplicationBootstrapper(@Nonnull GriffonApplication application) {
063 super(application);
064 }
065
066 public void setTestCase(@Nonnull Object testCase) {
067 this.testCase = testCase;
068 }
069
070 @Nonnull
071 @Override
072 protected List<Module> loadModules() {
073 List<Module> modules = doCollectModulesFromMethod();
074 if (!modules.isEmpty()) {
075 return modules;
076 }
077 modules = super.loadModules();
078 doCollectOverridingModules(modules);
079 doCollectModulesFromInnerClasses(modules);
080 doCollectModulesFromFields(modules);
081 return modules;
082 }
083
084 @Nonnull
085 @Override
086 protected Map<String, Module> sortModules(@Nonnull List<Module> moduleInstances) {
087 Map<String, Module> sortedModules = super.sortModules(moduleInstances);
088 // move all `TestingModules` at the end
089 // turns out the map is of type LinkedHashMap so insertion order is retained
090 Map<String, Module> testingModules = new LinkedHashMap<>();
091 for (Map.Entry<String, Module> e : sortedModules.entrySet()) {
092 if (e.getValue() instanceof TestingModule) {
093 testingModules.put(e.getKey(), e.getValue());
094 }
095 }
096 for (String key : testingModules.keySet()) {
097 sortedModules.remove(key);
098 }
099 sortedModules.putAll(testingModules);
100
101 LOG.debug("computed {} order is {}", "Module", sortedModules.keySet());
102
103 return sortedModules;
104 }
105
106 @SuppressWarnings("unchecked")
107 private List<Module> doCollectModulesFromMethod() {
108 if (testCase == null) {
109 return Collections.emptyList();
110 }
111
112 if (testCase instanceof TestModuleAware) {
113 return ((TestModuleAware) testCase).modules();
114 } else {
115 Method method = null;
116 try {
117 method = testCase.getClass().getDeclaredMethod(METHOD_MODULES);
118 method.setAccessible(true);
119 } catch (NoSuchMethodException e) {
120 return Collections.emptyList();
121 }
122
123 try {
124 return (List<Module>) method.invoke(testCase);
125 } catch (Exception e) {
126 throw new IllegalArgumentException("An error occurred while initializing modules from " + testCase.getClass().getName() + "." + METHOD_MODULES, e);
127 }
128 }
129 }
130
131 @SuppressWarnings("unchecked")
132 private void doCollectOverridingModules(final @Nonnull Collection<Module> modules) {
133 if (testCase == null) {
134 return;
135 }
136
137 if (testCase instanceof TestModuleAware) {
138 List<Module> overrides = ((TestModuleAware) testCase).moduleOverrides();
139 modules.addAll(overrides);
140 } else {
141 Method method = null;
142 try {
143 method = testCase.getClass().getDeclaredMethod(METHOD_MODULE_OVERRIDES);
144 method.setAccessible(true);
145 } catch (NoSuchMethodException e) {
146 return;
147 }
148
149 try {
150 List<Module> overrides = (List<Module>) method.invoke(testCase);
151 modules.addAll(overrides);
152 } catch (Exception e) {
153 throw new IllegalArgumentException("An error occurred while initializing modules from " + testCase.getClass().getName() + "." + METHOD_MODULE_OVERRIDES, e);
154 }
155 }
156 }
157
158 private void doCollectModulesFromInnerClasses(final @Nonnull Collection<Module> modules) {
159 if (testCase != null) {
160 modules.add(new InnerClassesModule());
161 }
162 }
163
164 private void doCollectModulesFromFields(final @Nonnull Collection<Module> modules) {
165 if (testCase != null) {
166 modules.add(new FieldsModule());
167 }
168 }
169
170 @Nonnull
171 protected List<Annotation> harvestQualifiers(@Nonnull Class<?> clazz) {
172 List<Annotation> list = new ArrayList<>();
173 Annotation[] annotations = clazz.getAnnotations();
174 for (Annotation annotation : annotations) {
175 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
176 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
177 continue;
178 }
179
180 // special case for @Named
181 if (Named.class.isAssignableFrom(annotation.getClass())) {
182 Named named = (Named) annotation;
183 if (isBlank(named.value())) {
184 list.add(named(getPropertyName(clazz)));
185 continue;
186 }
187 }
188 list.add(annotation);
189 }
190 }
191 return list;
192 }
193
194 @Nonnull
195 protected List<Annotation> harvestQualifiers(@Nonnull Field field) {
196 List<Annotation> list = new ArrayList<>();
197 Annotation[] annotations = field.getAnnotations();
198 for (Annotation annotation : annotations) {
199 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
200 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
201 continue;
202 }
203
204 // special case for @Named
205 if (Named.class.isAssignableFrom(annotation.getClass())) {
206 Named named = (Named) annotation;
207 if (isBlank(named.value())) {
208 list.add(named(getPropertyName(field.getName())));
209 continue;
210 }
211 }
212 list.add(annotation);
213 }
214 }
215 return list;
216 }
217
218 private class InnerClassesModule extends AbstractTestingModule {
219 @Override
220 @SuppressWarnings("unchecked")
221 protected void doConfigure() {
222 for (Class<?> clazz : testCase.getClass().getDeclaredClasses()) {
223 BindTo bindTo = clazz.getAnnotation(BindTo.class);
224 if (bindTo == null) continue;
225 List<Annotation> qualifiers = harvestQualifiers(clazz);
226 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
227 boolean isSingleton = clazz.getAnnotation(Singleton.class) != null;
228
229 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
230 if (classifier != null) {
231 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
232 if (Provider.class.isAssignableFrom(clazz)) {
233 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) clazz);
234 if (isSingleton) sbuilder.asSingleton();
235 } else {
236 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) clazz);
237 if (isSingleton) sbuilder.asSingleton();
238 }
239 } else {
240 if (Provider.class.isAssignableFrom(clazz)) {
241 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) clazz);
242 if (isSingleton) sbuilder.asSingleton();
243 } else {
244 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) clazz);
245 if (isSingleton) sbuilder.asSingleton();
246 }
247 }
248 }
249 }
250 }
251
252 private class FieldsModule extends AbstractTestingModule {
253 @Override
254 @SuppressWarnings("unchecked")
255 protected void doConfigure() {
256 for (Field field : testCase.getClass().getDeclaredFields()) {
257 BindTo bindTo = field.getAnnotation(BindTo.class);
258 if (bindTo == null) continue;
259 List<Annotation> qualifiers = harvestQualifiers(field);
260 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
261 boolean isSingleton = field.getAnnotation(Singleton.class) != null;
262
263 field.setAccessible(true);
264 Object instance = null;
265 try {
266 instance = field.get(testCase);
267 } catch (IllegalAccessException e) {
268 throw new IllegalArgumentException(e);
269 }
270
271 if (instance != null) {
272 AnnotatedBindingBuilder<Object> abuilder = (AnnotatedBindingBuilder<Object>) bind(bindTo.value());
273 if (classifier != null) {
274 if (Provider.class.isAssignableFrom(instance.getClass())) {
275 SingletonBindingBuilder<?> sbuilder = abuilder
276 .withClassifier(classifier)
277 .toProvider((Provider<Object>) instance);
278 if (isSingleton) sbuilder.asSingleton();
279 } else {
280 abuilder.withClassifier(classifier).toInstance(instance);
281 }
282 } else if (Provider.class.isAssignableFrom(instance.getClass())) {
283 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Provider<Object>) instance);
284 if (isSingleton) sbuilder.asSingleton();
285 } else {
286 abuilder.toInstance(instance);
287 }
288 } else {
289 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
290 if (classifier != null) {
291 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
292 if (Provider.class.isAssignableFrom(field.getType())) {
293 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) field.getType());
294 if (isSingleton) sbuilder.asSingleton();
295 } else {
296 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) field.getType());
297 if (isSingleton) sbuilder.asSingleton();
298 }
299 } else {
300 if (Provider.class.isAssignableFrom(field.getType())) {
301 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) field.getType());
302 if (isSingleton) sbuilder.asSingleton();
303 } else {
304 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) field.getType());
305 if (isSingleton) sbuilder.asSingleton();
306 }
307 }
308 }
309 }
310 }
311 }
312 }
|