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 Class<?> clazz = testCase.getClass();
223 List<Class<?>> classes = new ArrayList<>();
224 while (clazz != null) {
225 classes.add(clazz);
226 clazz = clazz.getSuperclass();
227 }
228
229 Collections.reverse(classes);
230 for (Class<?> c : classes) {
231 harvestBindings(c);
232 }
233 }
234
235 protected void harvestBindings(@Nonnull Class<?> rootClass) {
236 for (Class<?> clazz : rootClass.getDeclaredClasses()) {
237 BindTo bindTo = clazz.getAnnotation(BindTo.class);
238 if (bindTo == null) continue;
239 List<Annotation> qualifiers = harvestQualifiers(clazz);
240 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
241 boolean isSingleton = clazz.getAnnotation(Singleton.class) != null;
242
243 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
244 if (classifier != null) {
245 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
246 if (Provider.class.isAssignableFrom(clazz)) {
247 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) clazz);
248 if (isSingleton) sbuilder.asSingleton();
249 } else {
250 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) clazz);
251 if (isSingleton) sbuilder.asSingleton();
252 }
253 } else {
254 if (Provider.class.isAssignableFrom(clazz)) {
255 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) clazz);
256 if (isSingleton) sbuilder.asSingleton();
257 } else {
258 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) clazz);
259 if (isSingleton) sbuilder.asSingleton();
260 }
261 }
262 }
263 }
264 }
265
266 private class FieldsModule extends AbstractTestingModule {
267 @Override
268 @SuppressWarnings("unchecked")
269 protected void doConfigure() {
270 Class<?> clazz = testCase.getClass();
271 List<Class<?>> classes = new ArrayList<>();
272 while (clazz != null) {
273 classes.add(clazz);
274 clazz = clazz.getSuperclass();
275 }
276
277 Collections.reverse(classes);
278 for (Class<?> c : classes) {
279 harvestBindings(c);
280 }
281 }
282
283 protected void harvestBindings(@Nonnull Class<?> rootClass) {
284 for (Field field : rootClass.getDeclaredFields()) {
285 BindTo bindTo = field.getAnnotation(BindTo.class);
286 if (bindTo == null) continue;
287 List<Annotation> qualifiers = harvestQualifiers(field);
288 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
289 boolean isSingleton = field.getAnnotation(Singleton.class) != null;
290
291 field.setAccessible(true);
292 Object instance = null;
293 try {
294 instance = field.get(testCase);
295 } catch (IllegalAccessException e) {
296 throw new IllegalArgumentException(e);
297 }
298
299 if (instance != null) {
300 AnnotatedBindingBuilder<Object> abuilder = (AnnotatedBindingBuilder<Object>) bind(bindTo.value());
301 if (classifier != null) {
302 if (Provider.class.isAssignableFrom(instance.getClass())) {
303 SingletonBindingBuilder<?> sbuilder = abuilder
304 .withClassifier(classifier)
305 .toProvider((Provider<Object>) instance);
306 if (isSingleton) sbuilder.asSingleton();
307 } else {
308 abuilder.withClassifier(classifier).toInstance(instance);
309 }
310 } else if (Provider.class.isAssignableFrom(instance.getClass())) {
311 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Provider<Object>) instance);
312 if (isSingleton) sbuilder.asSingleton();
313 } else {
314 abuilder.toInstance(instance);
315 }
316 } else {
317 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
318 if (classifier != null) {
319 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
320 if (Provider.class.isAssignableFrom(field.getType())) {
321 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) field.getType());
322 if (isSingleton) sbuilder.asSingleton();
323 } else {
324 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) field.getType());
325 if (isSingleton) sbuilder.asSingleton();
326 }
327 } else {
328 if (Provider.class.isAssignableFrom(field.getType())) {
329 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) field.getType());
330 if (isSingleton) sbuilder.asSingleton();
331 } else {
332 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) field.getType());
333 if (isSingleton) sbuilder.asSingleton();
334 }
335 }
336 }
337 }
338 }
339 }
340 }
|