Skip to content

Commit

Permalink
Rewrite of the Spring integration
Browse files Browse the repository at this point in the history
- Instead of using a Spring-based interceptor, the new integration uses
  the standard JGiven interceptor using ByteBuddy.
- The implementation is based on using a BeanFactoryPostProcessor that
  replaces the original Stage bean class with the one created by
  ByteBuddy

Fixes #259
  • Loading branch information
Jan Schäfer committed Dec 11, 2016
1 parent ac7b0b8 commit 80183a0
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 191 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# v0.14.0

## Spring Integration: Backwards Incompatible Changes

In order to fix issue #259 the Spring integration was largely rewritten. If you only had used the `@EnableJGiven` and `@JGivenStage` annotations, nothing should change.
If you had a custom Spring configuration for JGiven you have to change the following:

* The classes `SpringStepMethodInterceptor` and `JGivenStageAutoProxyCreator` do not exist anymore, you have to remove all references
* As a replacement the new class `JGivenBeanFactoryPostProcessor` exists now. You have to register this bean in your Spring configuration

## Fixed Issues

* Spring Integration: Nested Steps are now supported when using Spring [#259](https://github.com/TNG/JGiven/issues/259)

# v0.13.0

## Backwards Incompatible Changes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
package com.tngtech.jgiven.impl;

import static net.bytebuddy.matcher.ElementMatchers.any;

import com.tngtech.jgiven.impl.intercept.ByteBuddyMethodInterceptor;
import com.tngtech.jgiven.impl.intercept.StageInterceptorInternal;
import com.tngtech.jgiven.impl.intercept.StepInterceptor;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.MethodDelegation;

import static net.bytebuddy.matcher.ElementMatchers.any;

public class ByteBuddyStageCreator implements StageCreator {

@SuppressWarnings( "unchecked" )
@Override
public <T> T createStage(Class<T> stageClass, StepInterceptor stepInterceptor) {
public <T> T createStage( Class<T> stageClass, StepInterceptor stepInterceptor ) {
try {
T result = new ByteBuddy()
.subclass(stageClass, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
.method(any())
.intercept(MethodDelegation.to(new ByteBuddyMethodInterceptor(stepInterceptor)))
.make()
.load(getClassLoader(stageClass),
getClassLoadingStrategy(stageClass))
.getLoaded()
.newInstance();
T result = createStageClass( stageClass, stepInterceptor )
.newInstance();
return result;
} catch (Error e) {
} catch( Error e ) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Error while trying to create an instance of class "+stageClass, e);
} catch( Exception e ) {
throw new RuntimeException( "Error while trying to create an instance of class " + stageClass, e );
}
}

protected ClassLoadingStrategy getClassLoadingStrategy(Class<?> stageClass) {
return getClassLoader(stageClass) == null
public <T> Class<? extends T> createStageClass( Class<T> stageClass, StepInterceptor stepInterceptor ) {
return new ByteBuddy()
.subclass( stageClass, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING )
.implement( StageInterceptorInternal.class )
.method( any() )
.intercept( MethodDelegation.to( new ByteBuddyMethodInterceptor( stepInterceptor ) ) )
.make()
.load( getClassLoader( stageClass ),
getClassLoadingStrategy( stageClass ) )
.getLoaded();
}

protected ClassLoadingStrategy getClassLoadingStrategy( Class<?> stageClass ) {
return getClassLoader( stageClass ) == null
? ClassLoadingStrategy.Default.WRAPPER
: ClassLoadingStrategy.Default.INJECTION;
}

protected ClassLoader getClassLoader(Class<?> stageClass) {
protected ClassLoader getClassLoader( Class<?> stageClass ) {
return Thread.currentThread().getContextClassLoader();
}


}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.tngtech.jgiven.impl.intercept;

import com.tngtech.jgiven.impl.intercept.StepInterceptor.Invoker;

import java.lang.reflect.Method;

import java.util.concurrent.Callable;

import com.tngtech.jgiven.impl.intercept.StepInterceptor.Invoker;

import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.BindingPriority;
import net.bytebuddy.implementation.bind.annotation.DefaultCall;
Expand All @@ -19,45 +19,75 @@
*/
public class ByteBuddyMethodInterceptor {

private final StepInterceptor interceptor;
private StepInterceptor interceptor;

public ByteBuddyMethodInterceptor(StepInterceptor interceptor ) {
public ByteBuddyMethodInterceptor() {}

public ByteBuddyMethodInterceptor( StepInterceptor interceptor ) {
this.interceptor = interceptor;
}

public void setInterceptor( StepInterceptor interceptor ) {
this.interceptor = interceptor;
}

@RuntimeType
@BindingPriority(BindingPriority.DEFAULT * 3)
public Object interceptSuper( @SuperCall final Callable<?> zuper,@This final Object receiver,@Origin
Method method, @AllArguments final Object[] parameters)
throws Throwable {
@BindingPriority( BindingPriority.DEFAULT * 3 )
public Object interceptSuper( @SuperCall final Callable<?> zuper, @This final Object receiver, @Origin Method method,
@AllArguments final Object[] parameters )
throws Throwable {

if( handleSetStepInterceptor( method, parameters ) ) {
return null;
}

Invoker invoker = new Invoker() {
@Override
public Object proceed() throws Throwable {
return zuper.call();
}

};
return interceptor.intercept( receiver , method, parameters, invoker );
return interceptor.intercept( receiver, method, parameters, invoker );
}

@RuntimeType
@BindingPriority(BindingPriority.DEFAULT * 2)
public Object interceptDefault(@DefaultCall final Callable<?> zuper,@This final Object receiver,@Origin
Method method, @AllArguments final Object[] parameters)
throws Throwable {
Invoker invoker = new Invoker() {
@BindingPriority( BindingPriority.DEFAULT * 2 )
public Object interceptDefault( @DefaultCall final Callable<?> zuper, @This final Object receiver, @Origin Method method,
@AllArguments final Object[] parameters )
throws Throwable {

if( handleSetStepInterceptor( method, parameters ) ) {
return null;
}

Invoker invoker = new Invoker() {
@Override
public Object proceed() throws Throwable {
return zuper.call();
}

};
return interceptor.intercept( receiver , method, parameters, invoker );
return interceptor.intercept( receiver, method, parameters, invoker );
}

private boolean handleSetStepInterceptor( Method method, final Object[] parameters ) {
if( method.getName().equals( "setStepInterceptor" ) && method.getDeclaringClass().equals( StageInterceptorInternal.class ) ) {
setInterceptor( (StepInterceptor) parameters[0] );
return true;
}
return false;
}

@RuntimeType
public Object intercept( @This final Object receiver,@Origin final Method method, @AllArguments final Object[] parameters) throws Throwable {
public Object intercept( @This final Object receiver, @Origin final Method method, @AllArguments final Object[] parameters )
throws Throwable {
// this intercepted method does not have a non-abstract super method

if( handleSetStepInterceptor( method, parameters ) ) {
return null;
}

Invoker invoker = new Invoker() {

@Override
Expand All @@ -66,8 +96,7 @@ public Object proceed() throws Throwable {
}

};
return interceptor.intercept( receiver , method, parameters, invoker );
return interceptor.intercept( receiver, method, parameters, invoker );
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tngtech.jgiven.impl.intercept;

public interface StageInterceptorInternal {
void setStepInterceptor( StepInterceptor stepInterceptor );
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
Expand All @@ -24,31 +23,19 @@ public BeanDefinition parse( Element element, ParserContext parserContext ) {
if( !parserContext.getRegistry().containsBeanDefinition( BEAN_NAME ) ) {
Object eleSource = parserContext.extractSource( element );

// create Interceptor
RootBeanDefinition interceptorDef = new RootBeanDefinition( SpringStepMethodInterceptor.class );
interceptorDef.setScope( AbstractBeanDefinition.SCOPE_PROTOTYPE );
interceptorDef.setSource( eleSource );
interceptorDef.setRole( BeanDefinition.ROLE_INFRASTRUCTURE );
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName( interceptorDef );
logger.debug( "Registered SpringStepMethodInterceptor with name " + interceptorName );

// create Scenario Executor
RootBeanDefinition executorDef = new RootBeanDefinition( SpringStageCreator.class );
executorDef.setScope( AbstractBeanDefinition.SCOPE_PROTOTYPE );
executorDef.setSource( eleSource );
interceptorDef.setRole( BeanDefinition.ROLE_INFRASTRUCTURE );
String executorName = parserContext.getReaderContext().registerWithGeneratedName( executorDef );
RootBeanDefinition stageCreator = new RootBeanDefinition( SpringStageCreator.class );
stageCreator.setSource( eleSource );
stageCreator.setRole( BeanDefinition.ROLE_INFRASTRUCTURE );
String executorName = parserContext.getReaderContext().registerWithGeneratedName( stageCreator );
logger.debug( "Registered SpringStageCreator with name " + executorName );

// create AutoProxyCreator
RootBeanDefinition autoProxyCreatorDef = new RootBeanDefinition( JGivenStageAutoProxyCreator.class );
autoProxyCreatorDef.setRole( BeanDefinition.ROLE_INFRASTRUCTURE );
parserContext.getRegistry().registerBeanDefinition( BEAN_NAME, autoProxyCreatorDef );
RootBeanDefinition beanFactoryPostProcessor = new RootBeanDefinition( JGivenBeanFactoryPostProcessor.class );
beanFactoryPostProcessor.setRole( BeanDefinition.ROLE_INFRASTRUCTURE );
parserContext.getRegistry().registerBeanDefinition( BEAN_NAME, beanFactoryPostProcessor );

CompositeComponentDefinition componentDefinition = new CompositeComponentDefinition( element.getTagName(), eleSource );
componentDefinition.addNestedComponent( new BeanComponentDefinition( interceptorDef, interceptorName ) );
componentDefinition.addNestedComponent( new BeanComponentDefinition( executorDef, executorName ) );
componentDefinition.addNestedComponent( new BeanComponentDefinition( autoProxyCreatorDef, BEAN_NAME ) );
componentDefinition.addNestedComponent( new BeanComponentDefinition( stageCreator, executorName ) );
componentDefinition.addNestedComponent( new BeanComponentDefinition( beanFactoryPostProcessor, BEAN_NAME ) );
parserContext.registerComponent( componentDefinition );
}
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.tngtech.jgiven.integration.spring;

import com.tngtech.jgiven.impl.ByteBuddyStageCreator;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import java.util.Iterator;


public class JGivenBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

private final ByteBuddyStageCreator buddyStageCreator = new ByteBuddyStageCreator();

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Iterator<String> beanNames = beanFactory.getBeanNamesIterator();
while (beanNames.hasNext()) {
String beanName = beanNames.next();
if (beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
if (annotatedBeanDefinition.getMetadata().hasAnnotation(JGivenStage.class.getName())) {
String className = beanDefinition.getBeanClassName();
Class<?> stageClass = createStageClass(beanName, className);
beanDefinition.setBeanClassName(stageClass.getName());
}
}
}

}
}

private Class<?> createStageClass(String beanName, String className) {
try {
Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass(className);
return buddyStageCreator.createStageClass(aClass, null);
} catch (ClassNotFoundException e) {
throw new FatalBeanException("Error while trying to create JGiven stage for bean "+beanName, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.tngtech.jgiven.integration.spring;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.context.annotation.Scope;

/**
* {@code @Configuration} class that registers all beans an post-processors
Expand All @@ -16,24 +13,13 @@
public class JGivenSpringConfiguration {

@Bean
@Scope( "prototype" )
public SpringStepMethodInterceptor springStepMethodInterceptor() {
return new SpringStepMethodInterceptor();
}

@Bean
@Scope( "prototype" )
public SpringStageCreator springStageCreator() {
return new SpringStageCreator();
}

/*
* configure support for {@link JGivenStage} annotation
*/
@Bean
@Role( BeanDefinition.ROLE_INFRASTRUCTURE )
public JGivenStageAutoProxyCreator jGivenStageAutoProxyCreator() {
return new JGivenStageAutoProxyCreator();
public JGivenBeanFactoryPostProcessor jGivenPostBeanProcessor() {
return new JGivenBeanFactoryPostProcessor();
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
public class SpringScenarioTest<GIVEN, WHEN, THEN> extends
ScenarioTest<GIVEN, WHEN, THEN> implements BeanFactoryAware {

@Override
public void setBeanFactory( BeanFactory beanFactory ) {
getScenario().setStageCreator( beanFactory.getBean( SpringStageCreator.class ) );
}
Expand Down
Loading

0 comments on commit 80183a0

Please sign in to comment.