TestApplicationBootstrapper.java
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 ((TestModuleAwaretestCase).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 = ((TestModuleAwaretestCase).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 = (Namedannotation;
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 = (Namedannotation;
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 == nullcontinue;
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((Classclazz);
248                         if (isSingletonsbuilder.asSingleton();
249                     else {
250                         SingletonBindingBuilder<?> sbuilder = lbuilder.to((Classclazz);
251                         if (isSingletonsbuilder.asSingleton();
252                     }
253                 else {
254                     if (Provider.class.isAssignableFrom(clazz)) {
255                         SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Classclazz);
256                         if (isSingletonsbuilder.asSingleton();
257                     else {
258                         SingletonBindingBuilder<?> sbuilder = abuilder.to((Classclazz);
259                         if (isSingletonsbuilder.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 == nullcontinue;
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 (isSingletonsbuilder.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 (isSingletonsbuilder.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((Classfield.getType());
322                             if (isSingletonsbuilder.asSingleton();
323                         else {
324                             SingletonBindingBuilder<?> sbuilder = lbuilder.to((Classfield.getType());
325                             if (isSingletonsbuilder.asSingleton();
326                         }
327                     else {
328                         if (Provider.class.isAssignableFrom(field.getType())) {
329                             SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Classfield.getType());
330                             if (isSingletonsbuilder.asSingleton();
331                         else {
332                             SingletonBindingBuilder<?> sbuilder = abuilder.to((Classfield.getType());
333                             if (isSingletonsbuilder.asSingleton();
334                         }
335                     }
336                 }
337             }
338         }
339     }
340 }