| 
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 }
 |