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 instanceof TestModuleAware) {
109 return ((TestModuleAware) testCase).modules();
110 } else {
111 Method method = null;
112 try {
113 method = testCase.getClass().getDeclaredMethod(METHOD_MODULES);
114 method.setAccessible(true);
115 } catch (NoSuchMethodException e) {
116 return Collections.emptyList();
117 }
118
119 try {
120 return (List<Module>) method.invoke(testCase);
121 } catch (Exception e) {
122 throw new IllegalArgumentException("An error occurred while initializing modules from " + testCase.getClass().getName() + "." + METHOD_MODULES, e);
123 }
124 }
125 }
126
127 @SuppressWarnings("unchecked")
128 private void doCollectOverridingModules(final @Nonnull Collection<Module> modules) {
129 if (testCase instanceof TestModuleAware) {
130 List<Module> overrides = ((TestModuleAware) testCase).moduleOverrides();
131 modules.addAll(overrides);
132 } else {
133 Method method = null;
134 try {
135 method = testCase.getClass().getDeclaredMethod(METHOD_MODULE_OVERRIDES);
136 method.setAccessible(true);
137 } catch (NoSuchMethodException e) {
138 return;
139 }
140
141 try {
142 List<Module> overrides = (List<Module>) method.invoke(testCase);
143 modules.addAll(overrides);
144 } catch (Exception e) {
145 throw new IllegalArgumentException("An error occurred while initializing modules from " + testCase.getClass().getName() + "." + METHOD_MODULE_OVERRIDES, e);
146 }
147 }
148 }
149
150 private void doCollectModulesFromInnerClasses(final @Nonnull Collection<Module> modules) {
151 modules.add(new InnerClassesModule());
152 }
153
154 private void doCollectModulesFromFields(final @Nonnull Collection<Module> modules) {
155 modules.add(new FieldsModule());
156 }
157
158 @Nonnull
159 protected List<Annotation> harvestQualifiers(@Nonnull Class<?> clazz) {
160 List<Annotation> list = new ArrayList<>();
161 Annotation[] annotations = clazz.getAnnotations();
162 for (Annotation annotation : annotations) {
163 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
164 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
165 continue;
166 }
167
168 // special case for @Named
169 if (Named.class.isAssignableFrom(annotation.getClass())) {
170 Named named = (Named) annotation;
171 if (isBlank(named.value())) {
172 list.add(named(getPropertyName(clazz)));
173 continue;
174 }
175 }
176 list.add(annotation);
177 }
178 }
179 return list;
180 }
181
182 @Nonnull
183 protected List<Annotation> harvestQualifiers(@Nonnull Field field) {
184 List<Annotation> list = new ArrayList<>();
185 Annotation[] annotations = field.getAnnotations();
186 for (Annotation annotation : annotations) {
187 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
188 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
189 continue;
190 }
191
192 // special case for @Named
193 if (Named.class.isAssignableFrom(annotation.getClass())) {
194 Named named = (Named) annotation;
195 if (isBlank(named.value())) {
196 list.add(named(getPropertyName(field.getName())));
197 continue;
198 }
199 }
200 list.add(annotation);
201 }
202 }
203 return list;
204 }
205
206 private class InnerClassesModule extends AbstractTestingModule {
207 @Override
208 @SuppressWarnings("unchecked")
209 protected void doConfigure() {
210 for (Class<?> clazz : testCase.getClass().getDeclaredClasses()) {
211 BindTo bindTo = clazz.getAnnotation(BindTo.class);
212 if (bindTo == null) continue;
213 List<Annotation> qualifiers = harvestQualifiers(clazz);
214 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
215 boolean isSingleton = clazz.getAnnotation(Singleton.class) != null;
216
217 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
218 if (classifier != null) {
219 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
220 if (Provider.class.isAssignableFrom(clazz)) {
221 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) clazz);
222 if (isSingleton) sbuilder.asSingleton();
223 } else {
224 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) clazz);
225 if (isSingleton) sbuilder.asSingleton();
226 }
227 } else {
228 if (Provider.class.isAssignableFrom(clazz)) {
229 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) clazz);
230 if (isSingleton) sbuilder.asSingleton();
231 } else {
232 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) clazz);
233 if (isSingleton) sbuilder.asSingleton();
234 }
235 }
236 }
237 }
238 }
239
240 private class FieldsModule extends AbstractTestingModule {
241 @Override
242 @SuppressWarnings("unchecked")
243 protected void doConfigure() {
244 for (Field field : testCase.getClass().getDeclaredFields()) {
245 BindTo bindTo = field.getAnnotation(BindTo.class);
246 if (bindTo == null) continue;
247 List<Annotation> qualifiers = harvestQualifiers(field);
248 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
249 boolean isSingleton = field.getAnnotation(Singleton.class) != null;
250
251 field.setAccessible(true);
252 Object instance = null;
253 try {
254 instance = field.get(testCase);
255 } catch (IllegalAccessException e) {
256 throw new IllegalArgumentException(e);
257 }
258
259 if (instance != null) {
260 AnnotatedBindingBuilder<Object> abuilder = (AnnotatedBindingBuilder<Object>) bind(bindTo.value());
261 if (classifier != null) {
262 if (Provider.class.isAssignableFrom(instance.getClass())) {
263 SingletonBindingBuilder<?> sbuilder = abuilder
264 .withClassifier(classifier)
265 .toProvider((Provider<Object>) instance);
266 if (isSingleton) sbuilder.asSingleton();
267 } else {
268 abuilder.withClassifier(classifier).toInstance(instance);
269 }
270 } else if (Provider.class.isAssignableFrom(instance.getClass())) {
271 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Provider<Object>) instance);
272 if (isSingleton) sbuilder.asSingleton();
273 } else {
274 abuilder.toInstance(instance);
275 }
276 } else {
277 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
278 if (classifier != null) {
279 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
280 if (Provider.class.isAssignableFrom(field.getType())) {
281 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) field.getType());
282 if (isSingleton) sbuilder.asSingleton();
283 } else {
284 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) field.getType());
285 if (isSingleton) sbuilder.asSingleton();
286 }
287 } else {
288 if (Provider.class.isAssignableFrom(field.getType())) {
289 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) field.getType());
290 if (isSingleton) sbuilder.asSingleton();
291 } else {
292 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) field.getType());
293 if (isSingleton) sbuilder.asSingleton();
294 }
295 }
296 }
297 }
298 }
299 }
300 }
|