001 /*
002 * Copyright 2008-2016 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.TestModuleOverrides;
022 import griffon.core.test.TestModules;
023 import griffon.core.test.TestCaseAware;
024 import griffon.core.test.TestModuleAware;
025 import griffon.inject.BindTo;
026 import griffon.util.AnnotationUtils;
027 import org.codehaus.griffon.runtime.core.injection.AbstractTestingModule;
028 import org.codehaus.griffon.runtime.core.injection.AnnotatedBindingBuilder;
029 import org.codehaus.griffon.runtime.core.injection.LinkedBindingBuilder;
030 import org.codehaus.griffon.runtime.core.injection.SingletonBindingBuilder;
031 import org.slf4j.Logger;
032 import org.slf4j.LoggerFactory;
033
034 import javax.annotation.Nonnull;
035 import javax.inject.Named;
036 import javax.inject.Provider;
037 import javax.inject.Qualifier;
038 import javax.inject.Singleton;
039 import java.lang.annotation.Annotation;
040 import java.lang.reflect.Field;
041 import java.lang.reflect.Method;
042 import java.util.ArrayList;
043 import java.util.Collection;
044 import java.util.Collections;
045 import java.util.LinkedHashMap;
046 import java.util.List;
047 import java.util.Map;
048
049 import static griffon.util.AnnotationUtils.named;
050 import static griffon.util.GriffonNameUtils.getPropertyName;
051 import static griffon.util.GriffonNameUtils.isBlank;
052
053 /**
054 * @author Andres Almiray
055 * @since 2.0.0
056 */
057 public class TestApplicationBootstrapper extends DefaultApplicationBootstrapper implements TestCaseAware {
058 private static final Logger LOG = LoggerFactory.getLogger(TestApplicationBootstrapper.class);
059
060 private static final String METHOD_MODULES = "modules";
061 private static final String METHOD_MODULE_OVERRIDES = "moduleOverrides";
062 private Object testCase;
063
064 public TestApplicationBootstrapper(@Nonnull GriffonApplication application) {
065 super(application);
066 }
067
068 public void setTestCase(@Nonnull Object testCase) {
069 this.testCase = testCase;
070 }
071
072 @Nonnull
073 @Override
074 protected List<Module> loadModules() {
075 List<Module> modules = doCollectModulesFromMethod();
076 if (!modules.isEmpty()) {
077 return modules;
078 }
079 modules = super.loadModules();
080 doCollectOverridingModules(modules);
081 doCollectModulesFromInnerClasses(modules);
082 doCollectModulesFromFields(modules);
083 return modules;
084 }
085
086 @Nonnull
087 @Override
088 protected Map<String, Module> sortModules(@Nonnull List<Module> moduleInstances) {
089 Map<String, Module> sortedModules = super.sortModules(moduleInstances);
090 // move all `TestingModules` at the end
091 // turns out the map is of type LinkedHashMap so insertion order is retained
092 Map<String, Module> testingModules = new LinkedHashMap<>();
093 for (Map.Entry<String, Module> e : sortedModules.entrySet()) {
094 if (e.getValue() instanceof TestingModule) {
095 testingModules.put(e.getKey(), e.getValue());
096 }
097 }
098 for (String key : testingModules.keySet()) {
099 sortedModules.remove(key);
100 }
101 sortedModules.putAll(testingModules);
102
103 LOG.debug("computed {} order is {}", "Module", sortedModules.keySet());
104
105 return sortedModules;
106 }
107
108 @SuppressWarnings("unchecked")
109 private List<Module> doCollectModulesFromMethod() {
110 if (testCase == null) {
111 return Collections.emptyList();
112 }
113
114 if (testCase instanceof TestModuleAware) {
115 return ((TestModuleAware) testCase).modules();
116 } else {
117 Class<?> clazz = testCase.getClass();
118 List<Class<?>> classes = new ArrayList<>();
119 while (clazz != null) {
120 classes.add(clazz);
121 clazz = clazz.getSuperclass();
122 }
123
124 Collections.reverse(classes);
125 for (Class<?> c : classes) {
126 List<Module> ms = harvestModulesFromMethod(c, METHOD_MODULES, TestModules.class);
127 if (!ms.isEmpty()) { return ms; }
128 }
129 }
130
131 return Collections.emptyList();
132 }
133
134 @SuppressWarnings("unchecked")
135 private void doCollectOverridingModules(final @Nonnull Collection<Module> modules) {
136 if (testCase == null) {
137 return;
138 }
139
140 if (testCase instanceof TestModuleAware) {
141 List<Module> overrides = ((TestModuleAware) testCase).moduleOverrides();
142 modules.addAll(overrides);
143 } else {
144 Class<?> clazz = testCase.getClass();
145 List<Class<?>> classes = new ArrayList<>();
146 while (clazz != null) {
147 classes.add(clazz);
148 clazz = clazz.getSuperclass();
149 }
150
151 Collections.reverse(classes);
152 for (Class<?> c : classes) {
153 List<Module> overrides = harvestModulesFromMethod(c, METHOD_MODULE_OVERRIDES, TestModuleOverrides.class);
154 if (!overrides.isEmpty()) {
155 modules.addAll(overrides);
156 return;
157 }
158 }
159 }
160 }
161
162 @Nonnull
163 private List<Module> harvestModulesFromMethod(@Nonnull Class<?> clazz, @Nonnull String methodName, @Nonnull Class<? extends Annotation> annotationClass) {
164 Method annotatedMethod = null;
165
166 // check annotation first
167 for (Method m : clazz.getDeclaredMethods()) {
168 if (m.getAnnotation(annotationClass) != null) {
169 annotatedMethod = m;
170 break;
171 }
172 }
173
174 // check using naming convention
175 Method namedMethod = null;
176 try {
177 namedMethod = clazz.getDeclaredMethod(methodName);
178 namedMethod.setAccessible(true);
179 } catch (NoSuchMethodException e) {
180 if (annotatedMethod == null) {
181 return Collections.emptyList();
182 }
183 }
184
185 if (namedMethod != null && annotatedMethod != namedMethod) {
186 System.err.println("Usage of method named '" + clazz.getName() + "." + methodName + "()' is discouraged. Rename the method and annotate it with @" + annotationClass.getSimpleName());
187 }
188
189 Method method = annotatedMethod != null ? annotatedMethod : namedMethod;
190 try {
191 return (List<Module>) method.invoke(testCase);
192 } catch (Exception e) {
193 throw new IllegalArgumentException("An error occurred while initializing modules from " + clazz.getName() + "." + method.getName(), e);
194 }
195 }
196
197 private void doCollectModulesFromInnerClasses(final @Nonnull Collection<Module> modules) {
198 if (testCase != null) {
199 modules.add(new InnerClassesModule());
200 }
201 }
202
203 private void doCollectModulesFromFields(final @Nonnull Collection<Module> modules) {
204 if (testCase != null) {
205 modules.add(new FieldsModule());
206 }
207 }
208
209 @Nonnull
210 protected List<Annotation> harvestQualifiers(@Nonnull Class<?> clazz) {
211 List<Annotation> list = new ArrayList<>();
212 Annotation[] annotations = clazz.getAnnotations();
213 for (Annotation annotation : annotations) {
214 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
215 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
216 continue;
217 }
218
219 // special case for @Named
220 if (Named.class.isAssignableFrom(annotation.getClass())) {
221 Named named = (Named) annotation;
222 if (isBlank(named.value())) {
223 list.add(named(getPropertyName(clazz)));
224 continue;
225 }
226 }
227 list.add(annotation);
228 }
229 }
230 return list;
231 }
232
233 @Nonnull
234 protected List<Annotation> harvestQualifiers(@Nonnull Field field) {
235 List<Annotation> list = new ArrayList<>();
236 Annotation[] annotations = field.getAnnotations();
237 for (Annotation annotation : annotations) {
238 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
239 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
240 continue;
241 }
242
243 // special case for @Named
244 if (Named.class.isAssignableFrom(annotation.getClass())) {
245 Named named = (Named) annotation;
246 if (isBlank(named.value())) {
247 list.add(named(getPropertyName(field.getName())));
248 continue;
249 }
250 }
251 list.add(annotation);
252 }
253 }
254 return list;
255 }
256
257 private class InnerClassesModule extends AbstractTestingModule {
258 @Override
259 @SuppressWarnings("unchecked")
260 protected void doConfigure() {
261 Class<?> clazz = testCase.getClass();
262 List<Class<?>> classes = new ArrayList<>();
263 while (clazz != null) {
264 classes.add(clazz);
265 clazz = clazz.getSuperclass();
266 }
267
268 Collections.reverse(classes);
269 for (Class<?> c : classes) {
270 harvestBindings(c);
271 }
272 }
273
274 protected void harvestBindings(@Nonnull Class<?> rootClass) {
275 for (Class<?> clazz : rootClass.getDeclaredClasses()) {
276 BindTo bindTo = clazz.getAnnotation(BindTo.class);
277 if (bindTo == null) { continue; }
278 List<Annotation> qualifiers = harvestQualifiers(clazz);
279 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
280 boolean isSingleton = clazz.getAnnotation(Singleton.class) != null;
281
282 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
283 if (classifier != null) {
284 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
285 if (Provider.class.isAssignableFrom(clazz)) {
286 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) clazz);
287 if (isSingleton) { sbuilder.asSingleton(); }
288 } else {
289 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) clazz);
290 if (isSingleton) { sbuilder.asSingleton(); }
291 }
292 } else {
293 if (Provider.class.isAssignableFrom(clazz)) {
294 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) clazz);
295 if (isSingleton) { sbuilder.asSingleton(); }
296 } else {
297 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) clazz);
298 if (isSingleton) { sbuilder.asSingleton(); }
299 }
300 }
301 }
302 }
303 }
304
305 private class FieldsModule extends AbstractTestingModule {
306 @Override
307 @SuppressWarnings("unchecked")
308 protected void doConfigure() {
309 Class<?> clazz = testCase.getClass();
310 List<Class<?>> classes = new ArrayList<>();
311 while (clazz != null) {
312 classes.add(clazz);
313 clazz = clazz.getSuperclass();
314 }
315
316 Collections.reverse(classes);
317 for (Class<?> c : classes) {
318 harvestBindings(c);
319 }
320 }
321
322 protected void harvestBindings(@Nonnull Class<?> rootClass) {
323 for (Field field : rootClass.getDeclaredFields()) {
324 BindTo bindTo = field.getAnnotation(BindTo.class);
325 if (bindTo == null) { continue; }
326 List<Annotation> qualifiers = harvestQualifiers(field);
327 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
328 boolean isSingleton = field.getAnnotation(Singleton.class) != null;
329
330 field.setAccessible(true);
331 Object instance = null;
332 try {
333 instance = field.get(testCase);
334 } catch (IllegalAccessException e) {
335 throw new IllegalArgumentException(e);
336 }
337
338 if (instance != null) {
339 AnnotatedBindingBuilder<Object> abuilder = (AnnotatedBindingBuilder<Object>) bind(bindTo.value());
340 if (classifier != null) {
341 if (Provider.class.isAssignableFrom(instance.getClass())) {
342 SingletonBindingBuilder<?> sbuilder = abuilder
343 .withClassifier(classifier)
344 .toProvider((Provider<Object>) instance);
345 if (isSingleton) { sbuilder.asSingleton(); }
346 } else {
347 abuilder.withClassifier(classifier).toInstance(instance);
348 }
349 } else if (Provider.class.isAssignableFrom(instance.getClass())) {
350 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Provider<Object>) instance);
351 if (isSingleton) { sbuilder.asSingleton(); }
352 } else {
353 abuilder.toInstance(instance);
354 }
355 } else {
356 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
357 if (classifier != null) {
358 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
359 if (Provider.class.isAssignableFrom(field.getType())) {
360 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) field.getType());
361 if (isSingleton) { sbuilder.asSingleton(); }
362 } else {
363 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) field.getType());
364 if (isSingleton) { sbuilder.asSingleton(); }
365 }
366 } else {
367 if (Provider.class.isAssignableFrom(field.getType())) {
368 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) field.getType());
369 if (isSingleton) { sbuilder.asSingleton(); }
370 } else {
371 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) field.getType());
372 if (isSingleton) { sbuilder.asSingleton(); }
373 }
374 }
375 }
376 }
377 }
378 }
379 }
|