NeedleStepsFactory.java

  1. package org.jbehave.core.steps.needle;

  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Type;
  5. import java.util.ArrayList;
  6. import java.util.LinkedHashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Set;

  10. import org.jbehave.core.annotations.AsParameterConverter;
  11. import org.jbehave.core.configuration.Configuration;
  12. import org.jbehave.core.configuration.MostUsefulConfiguration;
  13. import org.jbehave.core.steps.AbstractStepsFactory;
  14. import org.jbehave.core.steps.CandidateSteps;
  15. import org.jbehave.core.steps.InjectableStepsFactory;
  16. import org.jbehave.core.steps.ParameterConverters.MethodReturningConverter;
  17. import org.jbehave.core.steps.ParameterConverters.ParameterConverter;
  18. import org.jbehave.core.steps.Steps;
  19. import org.jbehave.core.steps.needle.configuration.CollectInjectionProvidersFromStepsInstance;
  20. import org.jbehave.core.steps.needle.configuration.CreateInstanceByDefaultConstructor;
  21. import org.jbehave.core.steps.needle.configuration.JBehaveNeedleConfiguration;
  22. import org.needle4j.NeedleTestcase;
  23. import org.needle4j.injection.InjectionProvider;
  24. import org.needle4j.reflection.ReflectionUtil;

  25. /**
  26.  * An {@link InjectableStepsFactory} that uses a Needle {@link InjectionProvider} for the composition and instantiation
  27.  * of all components that contain JBehave annotated methods.
  28.  * @author Simon Zambrovski (simon.zambrovski@holisticon.de)
  29.  * @author Jan Galinski (jan.galinski@holisticon.de)
  30.  */
  31. public class NeedleStepsFactory extends NeedleTestcase implements InjectableStepsFactory {

  32.     private final Map<Class<?>, Object> cachedTypeInstances = new LinkedHashMap<>();

  33.     private Configuration configuration;
  34.     private Class<?>[] steps;

  35.     /**
  36.      * Creates factory with given configuration and step instances.
  37.      * @param configuration
  38.      *        JBehave configuration
  39.      * @param steps
  40.      *        step classes
  41.      */
  42.     public NeedleStepsFactory(final Configuration configuration, final Class<?>... steps) {
  43.         this(configuration, null, steps);
  44.     }

  45.     /**
  46.      * Creates factory with given configuration, injection providers and step instances.
  47.      * @param configuration
  48.      *        JBehave configuration
  49.      * @param injectionProviders
  50.      *        injection providers.
  51.      * @param steps
  52.      *        step classes
  53.      */
  54.     public NeedleStepsFactory(final Configuration configuration, final Set<InjectionProvider<?>> injectionProviders,
  55.             final Class<?>... steps) {
  56.         super(setUpInjectionProviders(JBehaveNeedleConfiguration.RESOURCE_JBEHAVE_NEEDLE));
  57.         if (injectionProviders != null) {
  58.             addInjectionProvider(toArray(injectionProviders));
  59.         }
  60.         if (this.configuration == null) {
  61.             this.configuration = new MostUsefulConfiguration();
  62.         } else {
  63.             this.configuration = configuration;
  64.         }
  65.         this.steps = steps;
  66.     }

  67.     /**
  68.      * {@inheritDoc}
  69.      */
  70.     @Override
  71.     public List<CandidateSteps> createCandidateSteps() {
  72.         final List<CandidateSteps> result = new ArrayList<>();
  73.         for (final Class<?> type : steps) {
  74.             if (hasAnnotatedMethods(type)) {
  75.                 configuration.parameterConverters().addConverters(methodReturningConverters(type));
  76.                 result.add(new Steps(configuration, type, this));
  77.             }
  78.         }
  79.         return result;
  80.     }

  81.     @Override
  82.     public Object createInstanceOfType(final Class<?> type) {
  83.         final Object instance = cachedTypeInstances.get(type);
  84.         if (instance == null) {
  85.             try {
  86.                 final Object stepsInstance = createInstanceUsingNeedleTestCase(type);
  87.                 final InjectionProvider<?>[] foundProviders = CollectInjectionProvidersFromStepsInstance.INSTANCE.apply(
  88.                         stepsInstance);

  89.                 addInjectionProvider(foundProviders);

  90.                 initTestcase(stepsInstance);

  91.                 cachedTypeInstances.put(type, stepsInstance);
  92.                 return stepsInstance;
  93.             } catch (final Exception e) {
  94.                 throw new IllegalStateException(e);
  95.             }
  96.         }
  97.         return instance;
  98.     }

  99.     /**
  100.      * Uses private instantiation methods of NeedleTestCase via
  101.      * {@link ReflectionUtil#invokeMethod(Object, String, Object...)}. First tries to create new instance with
  102.      * constructor injection, then falls back to default constructor. If creation fails, an IllegalStateException is
  103.      * thrown.
  104.      *
  105.      * @param type type of instance to create
  106.      * @return new instance of type. Never <code>null</code>
  107.      * @throws IllegalStateException when creation fails.
  108.      */
  109.     private Object createInstanceUsingNeedleTestCase(final Class<?> type) throws IllegalStateException {
  110.         try {
  111.             Object instance = ReflectionUtil.invokeMethod(this, "getInstanceByConstructorInjection", type);
  112.             if (instance == null) {
  113.                 instance = CreateInstanceByDefaultConstructor.INSTANCE.apply(type);
  114.             }
  115.             if (instance == null) {
  116.                 throw new IllegalStateException("failed to create instance of type " + type.getCanonicalName());
  117.             }
  118.             return instance;
  119.         } catch (final Exception e) {
  120.             throw new IllegalStateException(e);
  121.         }

  122.     }

  123.     /**
  124.      * Create parameter converters from methods annotated with @AsParameterConverter
  125.      * @see AbstractStepsFactory
  126.      */
  127.     private List<ParameterConverter> methodReturningConverters(final Class<?> type) {
  128.         final List<ParameterConverter> converters = new ArrayList<>();
  129.         for (final Method method : type.getMethods()) {
  130.             if (method.isAnnotationPresent(AsParameterConverter.class)) {
  131.                 converters.add(new MethodReturningConverter(method, type, this));
  132.             }
  133.         }
  134.         return converters;
  135.     }

  136.     /**
  137.      * Add injection providers.
  138.      * @param providers
  139.      *        add injection providers after factory construction.
  140.      */
  141.     public void addInjectionProviders(final Set<InjectionProvider<?>> providers) {
  142.         if (providers != null) {
  143.             addInjectionProvider(toArray(providers));
  144.         }
  145.     }

  146.     /**
  147.      * Determines if the given type is a {@link Class} containing at least one method annotated with annotations from
  148.      * package "org.jbehave.core.annotations".
  149.      * @param type
  150.      *        the Type of the steps instance
  151.      * @return A boolean, <code>true</code> if at least one annotated method is found.
  152.      * @see AbstractStepsFactory
  153.      */
  154.     static boolean hasAnnotatedMethods(final Type type) {
  155.         if (type instanceof Class<?>) {
  156.             for (final Method method : ((Class<?>)type).getMethods()) {
  157.                 for (final Annotation annotation : method.getAnnotations()) {
  158.                     if (annotation.annotationType().getName().startsWith("org.jbehave.core.annotations")) {
  159.                         return true;
  160.                     }
  161.                 }
  162.             }
  163.         }
  164.         return false;
  165.     }

  166.     /**
  167.      * Read injection providers configuration from a resource.
  168.      * @param resourceName
  169.      *        resource name
  170.      * @return injection providers.
  171.      */
  172.     static InjectionProvider<?>[] setUpInjectionProviders(final String resourceName) {
  173.         return new JBehaveNeedleConfiguration(resourceName).getInjectionProviders();
  174.     }

  175.     /**
  176.      * Set to array.
  177.      * @param injectionProviders
  178.      *        set of providers
  179.      * @return array of providers
  180.      */
  181.     static InjectionProvider<?>[] toArray(final Set<InjectionProvider<?>> injectionProviders) {
  182.         return injectionProviders.toArray(new InjectionProvider<?>[injectionProviders.size()]);
  183.     }

  184. }