TestApplicationBootstrapper.java
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 ((TestModuleAwaretestCase).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 = ((TestModuleAwaretestCase).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 = (Namedannotation;
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 = (Namedannotation;
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((Classclazz);
287                         if (isSingleton) { sbuilder.asSingleton()}
288                     else {
289                         SingletonBindingBuilder<?> sbuilder = lbuilder.to((Classclazz);
290                         if (isSingleton) { sbuilder.asSingleton()}
291                     }
292                 else {
293                     if (Provider.class.isAssignableFrom(clazz)) {
294                         SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Classclazz);
295                         if (isSingleton) { sbuilder.asSingleton()}
296                     else {
297                         SingletonBindingBuilder<?> sbuilder = abuilder.to((Classclazz);
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((Classfield.getType());
361                             if (isSingleton) { sbuilder.asSingleton()}
362                         else {
363                             SingletonBindingBuilder<?> sbuilder = lbuilder.to((Classfield.getType());
364                             if (isSingleton) { sbuilder.asSingleton()}
365                         }
366                     else {
367                         if (Provider.class.isAssignableFrom(field.getType())) {
368                             SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Classfield.getType());
369                             if (isSingleton) { sbuilder.asSingleton()}
370                         else {
371                             SingletonBindingBuilder<?> sbuilder = abuilder.to((Classfield.getType());
372                             if (isSingleton) { sbuilder.asSingleton()}
373                         }
374                     }
375                 }
376             }
377         }
378     }
379 }