001 /*
002 * Copyright 2008-2014 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.inject.BindTo;
023 import griffon.util.AnnotationUtils;
024 import org.codehaus.griffon.runtime.core.injection.AbstractTestingModule;
025 import org.codehaus.griffon.runtime.core.injection.AnnotatedBindingBuilder;
026 import org.codehaus.griffon.runtime.core.injection.LinkedBindingBuilder;
027 import org.codehaus.griffon.runtime.core.injection.SingletonBindingBuilder;
028 import org.slf4j.Logger;
029 import org.slf4j.LoggerFactory;
030
031 import javax.annotation.Nonnull;
032 import javax.inject.Named;
033 import javax.inject.Provider;
034 import javax.inject.Qualifier;
035 import javax.inject.Singleton;
036 import java.lang.annotation.Annotation;
037 import java.lang.reflect.Field;
038 import java.lang.reflect.Method;
039 import java.util.ArrayList;
040 import java.util.Collection;
041 import java.util.Collections;
042 import java.util.LinkedHashMap;
043 import java.util.List;
044 import java.util.Map;
045
046 import static griffon.util.AnnotationUtils.named;
047 import static griffon.util.GriffonNameUtils.getPropertyName;
048 import static griffon.util.GriffonNameUtils.isBlank;
049
050 /**
051 * @author Andres Almiray
052 * @since 2.0.0
053 */
054 public class TestApplicationBootstrapper extends DefaultApplicationBootstrapper implements TestCaseAware {
055 private static final Logger LOG = LoggerFactory.getLogger(TestApplicationBootstrapper.class);
056
057 private static final String METHOD_MODULES = "modules";
058 private static final String METHOD_MODULE_OVERRIDES = "moduleOverrides";
059 private Object testCase;
060
061 public TestApplicationBootstrapper(@Nonnull GriffonApplication application) {
062 super(application);
063 }
064
065 public void setTestCase(@Nonnull Object testCase) {
066 this.testCase = testCase;
067 }
068
069 @Nonnull
070 @Override
071 protected List<Module> loadModules() {
072 List<Module> modules = doCollectModulesFromMethod();
073 if (!modules.isEmpty()) {
074 return modules;
075 }
076 modules = super.loadModules();
077 doCollectOverridingModules(modules);
078 doCollectModulesFromInnerClasses(modules);
079 doCollectModulesFromFields(modules);
080 return modules;
081 }
082
083 @Nonnull
084 @Override
085 protected Map<String, Module> sortModules(@Nonnull List<Module> moduleInstances) {
086 Map<String, Module> sortedModules = super.sortModules(moduleInstances);
087 // move all `TestingModules` at the end
088 // turns out the map is of type LinkedHashMap so insertion order is retained
089 Map<String, Module> testingModules = new LinkedHashMap<>();
090 for (Map.Entry<String, Module> e : sortedModules.entrySet()) {
091 if (e.getValue() instanceof TestingModule) {
092 testingModules.put(e.getKey(), e.getValue());
093 }
094 }
095 for (String key : testingModules.keySet()) {
096 sortedModules.remove(key);
097 }
098 sortedModules.putAll(testingModules);
099
100 LOG.debug("computed {} order is {}", "Module", sortedModules.keySet());
101
102 return sortedModules;
103 }
104
105 @SuppressWarnings("unchecked")
106 private List<Module> doCollectModulesFromMethod() {
107 Method method = null;
108 try {
109 method = testCase.getClass().getDeclaredMethod(METHOD_MODULES);
110 method.setAccessible(true);
111 } catch (NoSuchMethodException e) {
112 return Collections.emptyList();
113 }
114
115 try {
116 return (List<Module>) method.invoke(testCase);
117 } catch (Exception e) {
118 throw new IllegalArgumentException("An error occurred while initializing modules from " + testCase.getClass().getName() + "." + METHOD_MODULES, e);
119 }
120 }
121
122 @SuppressWarnings("unchecked")
123 private void doCollectOverridingModules(final @Nonnull Collection<Module> modules) {
124 Method method = null;
125 try {
126 method = testCase.getClass().getDeclaredMethod(METHOD_MODULE_OVERRIDES);
127 method.setAccessible(true);
128 } catch (NoSuchMethodException e) {
129 return;
130 }
131
132 try {
133 List<Module> overrides = (List<Module>) method.invoke(testCase);
134 modules.addAll(overrides);
135 } catch (Exception e) {
136 throw new IllegalArgumentException("An error occurred while initializing modules from " + testCase.getClass().getName() + "." + METHOD_MODULE_OVERRIDES, e);
137 }
138 }
139
140 private void doCollectModulesFromInnerClasses(final @Nonnull Collection<Module> modules) {
141 modules.add(new InnerClassesModule());
142 }
143
144 private void doCollectModulesFromFields(final @Nonnull Collection<Module> modules) {
145 modules.add(new FieldsModule());
146 }
147
148 @Nonnull
149 protected List<Annotation> harvestQualifiers(@Nonnull Class<?> clazz) {
150 List<Annotation> list = new ArrayList<>();
151 Annotation[] annotations = clazz.getAnnotations();
152 for (Annotation annotation : annotations) {
153 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
154 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
155 continue;
156 }
157
158 // special case for @Named
159 if (Named.class.isAssignableFrom(annotation.getClass())) {
160 Named named = (Named) annotation;
161 if (isBlank(named.value())) {
162 list.add(named(getPropertyName(clazz)));
163 continue;
164 }
165 }
166 list.add(annotation);
167 }
168 }
169 return list;
170 }
171
172 @Nonnull
173 protected List<Annotation> harvestQualifiers(@Nonnull Field field) {
174 List<Annotation> list = new ArrayList<>();
175 Annotation[] annotations = field.getAnnotations();
176 for (Annotation annotation : annotations) {
177 if (AnnotationUtils.isAnnotatedWith(annotation, Qualifier.class)) {
178 if (BindTo.class.isAssignableFrom(annotation.getClass())) {
179 continue;
180 }
181
182 // special case for @Named
183 if (Named.class.isAssignableFrom(annotation.getClass())) {
184 Named named = (Named) annotation;
185 if (isBlank(named.value())) {
186 list.add(named(getPropertyName(field.getName())));
187 continue;
188 }
189 }
190 list.add(annotation);
191 }
192 }
193 return list;
194 }
195
196 private class InnerClassesModule extends AbstractTestingModule {
197 @Override
198 @SuppressWarnings("unchecked")
199 protected void doConfigure() {
200 for (Class<?> clazz : testCase.getClass().getDeclaredClasses()) {
201 BindTo bindTo = clazz.getAnnotation(BindTo.class);
202 if (bindTo == null) continue;
203 List<Annotation> qualifiers = harvestQualifiers(clazz);
204 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
205 boolean isSingleton = clazz.getAnnotation(Singleton.class) != null;
206
207 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
208 if (classifier != null) {
209 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
210 if (Provider.class.isAssignableFrom(clazz)) {
211 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) clazz);
212 if (isSingleton) sbuilder.asSingleton();
213 } else {
214 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) clazz);
215 if (isSingleton) sbuilder.asSingleton();
216 }
217 } else {
218 if (Provider.class.isAssignableFrom(clazz)) {
219 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) clazz);
220 if (isSingleton) sbuilder.asSingleton();
221 } else {
222 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) clazz);
223 if (isSingleton) sbuilder.asSingleton();
224 }
225 }
226 }
227 }
228 }
229
230 private class FieldsModule extends AbstractTestingModule {
231 @Override
232 @SuppressWarnings("unchecked")
233 protected void doConfigure() {
234 for (Field field : testCase.getClass().getDeclaredFields()) {
235 BindTo bindTo = field.getAnnotation(BindTo.class);
236 if (bindTo == null) continue;
237 List<Annotation> qualifiers = harvestQualifiers(field);
238 Annotation classifier = qualifiers.isEmpty() ? null : qualifiers.get(0);
239 boolean isSingleton = field.getAnnotation(Singleton.class) != null;
240
241 field.setAccessible(true);
242 Object instance = null;
243 try {
244 instance = field.get(testCase);
245 } catch (IllegalAccessException e) {
246 throw new IllegalArgumentException(e);
247 }
248
249 if (instance != null) {
250 AnnotatedBindingBuilder<Object> abuilder = (AnnotatedBindingBuilder<Object>) bind(bindTo.value());
251 if (classifier != null) {
252 if (Provider.class.isAssignableFrom(instance.getClass())) {
253 SingletonBindingBuilder<?> sbuilder = abuilder
254 .withClassifier(classifier)
255 .toProvider((Provider<Object>) instance);
256 if (isSingleton) sbuilder.asSingleton();
257 } else {
258 abuilder.withClassifier(classifier).toInstance(instance);
259 }
260 } else if (Provider.class.isAssignableFrom(instance.getClass())) {
261 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Provider<Object>) instance);
262 if (isSingleton) sbuilder.asSingleton();
263 } else {
264 abuilder.toInstance(instance);
265 }
266 } else {
267 AnnotatedBindingBuilder<?> abuilder = bind(bindTo.value());
268 if (classifier != null) {
269 LinkedBindingBuilder<?> lbuilder = abuilder.withClassifier(classifier);
270 if (Provider.class.isAssignableFrom(field.getType())) {
271 SingletonBindingBuilder<?> sbuilder = lbuilder.toProvider((Class) field.getType());
272 if (isSingleton) sbuilder.asSingleton();
273 } else {
274 SingletonBindingBuilder<?> sbuilder = lbuilder.to((Class) field.getType());
275 if (isSingleton) sbuilder.asSingleton();
276 }
277 } else {
278 if (Provider.class.isAssignableFrom(field.getType())) {
279 SingletonBindingBuilder<?> sbuilder = abuilder.toProvider((Class) field.getType());
280 if (isSingleton) sbuilder.asSingleton();
281 } else {
282 SingletonBindingBuilder<?> sbuilder = abuilder.to((Class) field.getType());
283 if (isSingleton) sbuilder.asSingleton();
284 }
285 }
286 }
287 }
288 }
289 }
290 }
|