Added Stream integration

This commit is contained in:
Marcin Grzejszczak
2016-04-23 12:00:10 +02:00
parent bc53b23f33
commit 4c16055ea5
14 changed files with 338 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ repositories {
dependencies {
compile project(':accurest-messaging-root:accurest-messaging-core')
compile 'org.springframework:spring-messaging:[4.0.0.RELEASE,)'
compile 'org.springframework.cloud:spring-cloud-stream:[1.0.0.RC2,)'
// for MessageCollector
compile 'org.springframework.cloud:spring-cloud-stream-test-support:[1.0.0.RC2,)'
}

View File

@@ -1,9 +1,10 @@
package io.codearte.accurest.messaging.stream;
import io.codearte.accurest.messaging.AccurestMessage;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import io.codearte.accurest.messaging.AccurestMessage;
/**
* @author Marcin Grzejszczak
*/
@@ -12,6 +13,9 @@ public class StreamMessage<T> implements AccurestMessage<T, Message<T>> {
private final Message<T> delegate;
public StreamMessage(Message<T> delegate) {
if (delegate == null) {
throw new IllegalArgumentException("Message can't be null");
}
this.delegate = delegate;
}

View File

@@ -0,0 +1,22 @@
repositories {
mavenLocal()
jcenter()
maven {
url "http://repo.spring.io/snapshot"
}
maven {
url "http://repo.spring.io/milestone"
}
}
dependencies {
compile project(':stub-runner-root:stub-runner-spring')
compile project(':accurest-messaging-root:accurest-messaging-stream')
compile 'org.springframework.integration:spring-integration-java-dsl:[1.1.2.RELEASE,)'
testCompile 'org.springframework.cloud:spring-cloud-stream-test-support:1.0.0.RC2'
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'
testCompile('org.spockframework:spock-spring:1.0-groovy-2.4') {
exclude(group: 'org.codehaus.groovy')
}
}

View File

@@ -0,0 +1,52 @@
package io.codearte.accurest.stubrunner.messaging.stream
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.stubrunner.BatchStubRunner
import io.codearte.accurest.stubrunner.StubConfiguration
import org.springframework.beans.factory.config.AutowireCapableBeanFactory
import org.springframework.cloud.stream.annotation.EnableBinding
import org.springframework.cloud.stream.binding.ChannelBindingService
import org.springframework.context.Lifecycle
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.dsl.FilterEndpointSpec
import org.springframework.integration.dsl.GenericEndpointSpec
import org.springframework.integration.dsl.IntegrationFlow
import org.springframework.integration.dsl.IntegrationFlows
import org.springframework.messaging.MessageChannel
import org.springframework.stereotype.Service
/**
* Spring Cloud Stream configuration that iterates over the downloaded Groovy DSLs
* and registers a flow for each DSL.
*
* @author Marcin Grzejszczak
*/
@Configuration
@EnableBinding
class StubRunnerStreamConfiguration {
@Bean
FlowRegistrar service(AutowireCapableBeanFactory beanFactory, BatchStubRunner batchStubRunner, ChannelBindingService channelBindingService) {
Map<StubConfiguration, Collection<GroovyDsl>> accurestContracts = batchStubRunner.accurestContracts
accurestContracts.each { StubConfiguration key, Collection<GroovyDsl> value ->
String name = "${key.groupId}_${key.artifactId}"
value.findAll { it?.input?.messageFrom && it?.outputMessage?.sentTo }.each { GroovyDsl dsl ->
String flowName = "${name}_${dsl.label}_${dsl.hashCode()}"
IntegrationFlow integrationFlow = IntegrationFlows.from(dsl.input.messageFrom)
.filter(new StubRunnerStreamMessageSelector(dsl), { FilterEndpointSpec e -> e.id("${flowName}.filter") } )
.transform(new StubRunnerStreamTransformer(dsl), { GenericEndpointSpec e -> e.id("${flowName}.transformer") })
.channel(dsl.outputMessage.sentTo)
.get()
beanFactory.initializeBean(integrationFlow, flowName)
beanFactory.getBean("${flowName}.filter", Lifecycle.class).start();
beanFactory.getBean("${flowName}.transformer", Lifecycle.class).start();
channelBindingService.bindConsumer(beanFactory.getBean(dsl.input.messageFrom, MessageChannel.class), dsl.input.messageFrom)
channelBindingService.bindProducer(beanFactory.getBean(dsl.outputMessage.sentTo, MessageChannel.class), dsl.outputMessage.sentTo)
}
}
return new FlowRegistrar()
}
@Service
static class FlowRegistrar {}
}

View File

@@ -0,0 +1,63 @@
package io.codearte.accurest.stubrunner.messaging.stream
import com.fasterxml.jackson.databind.ObjectMapper
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.toomuchcoding.jsonassert.JsonAssertion
import com.toomuchcoding.jsonassert.JsonVerifiable
import groovy.transform.CompileStatic
import io.codearte.accurest.dsl.GroovyDsl
import io.codearte.accurest.util.JsonPaths
import io.codearte.accurest.util.JsonToJsonPathsConverter
import org.springframework.integration.core.MessageSelector
import org.springframework.messaging.Message
import java.util.regex.Pattern
/**
* Passes through a message that matches the one defined in the DSL
*
* @author Marcin Grzejszczak
*/
@CompileStatic
class StubRunnerStreamMessageSelector implements MessageSelector {
private final GroovyDsl groovyDsl
private final ObjectMapper objectMapper = new ObjectMapper()
StubRunnerStreamMessageSelector(GroovyDsl groovyDsl) {
this.groovyDsl = groovyDsl
}
@Override
boolean accept(Message<?> message) {
if(!headersMatch(message)){
return false
}
Object inputMessage = message.getPayload()
JsonPaths jsonPaths = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValues(groovyDsl.input.messageBody)
DocumentContext parsedJson = JsonPath.parse(objectMapper.writeValueAsString(inputMessage))
return jsonPaths.every { matchesJsonPath(parsedJson, it) }
}
private boolean matchesJsonPath(DocumentContext parsedJson, JsonVerifiable jsonVerifiable) {
try {
JsonAssertion.assertThat(parsedJson).matchesJsonPath(jsonVerifiable.jsonPath())
return true
} catch (Exception e) {
return false
}
}
private boolean headersMatch(Message message) {
Map<String, Object> headers = message.getHeaders()
return groovyDsl.input.messageHeaders.entries.every {
String name = it.name
Object value = it.clientValue
Object valueInHeader = headers.get(name)
return value instanceof Pattern ?
value.matcher(valueInHeader.toString()).matches() :
valueInHeader == value
}
}
}

View File

@@ -0,0 +1,29 @@
package io.codearte.accurest.stubrunner.messaging.stream
import io.codearte.accurest.builder.BodyAsString
import io.codearte.accurest.dsl.GroovyDsl
import org.springframework.integration.transformer.GenericTransformer
import org.springframework.messaging.Message
import org.springframework.messaging.MessageHeaders
import org.springframework.messaging.support.MessageBuilder
/**
* Sends forward a message defined in the DSL.
*
* @author Marcin Grzejszczak
*/
class StubRunnerStreamTransformer implements GenericTransformer<Message<?>, Message<?>> {
private final GroovyDsl groovyDsl
StubRunnerStreamTransformer(GroovyDsl groovyDsl) {
this.groovyDsl = groovyDsl
}
@Override
Message<?> transform(Message<?> source) {
String payload = BodyAsString.extractClientValueFrom(groovyDsl.outputMessage.body)
Map<String, Object> headers = groovyDsl.outputMessage.headers.asStubSideMap()
return MessageBuilder.createMessage(payload, new MessageHeaders(headers))
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.codearte.accurest.stubrunner.messaging.stream.StubRunnerStreamConfiguration

View File

@@ -0,0 +1,16 @@
package io.codearte.accurest.stubrunner.messaging.stream
import com.fasterxml.jackson.annotation.JsonCreator
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
@CompileStatic
@EqualsAndHashCode
class BookReturned implements Serializable {
final String bookName
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
BookReturned(String bookName) {
this.bookName = bookName
}
}

View File

@@ -0,0 +1,111 @@
package io.codearte.accurest.stubrunner.messaging.stream
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import io.codearte.accurest.messaging.AccurestMessage
import io.codearte.accurest.messaging.AccurestMessaging
import io.codearte.accurest.stubrunner.StubFinder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import java.util.concurrent.TimeUnit
/**
* @author Marcin Grzejszczak
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration
@ContextConfiguration(classes = StreamStubRunnerSpec, loader = SpringApplicationContextLoader)
class StreamStubRunnerSpec extends Specification {
@Autowired StubFinder stubFinder
@Autowired AccurestMessaging messaging
def 'should download the stub and register a route for it'() {
when:
messaging.send(new BookReturned('foo'), [sample: 'header'], 'input')
then:
AccurestMessage receivedMessage = messaging.receiveMessage('output')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a message by label'() {
when:
stubFinder.trigger('return_book_1')
then:
AccurestMessage receivedMessage = messaging.receiveMessage('output')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label for the existing groupId:artifactId'() {
when:
stubFinder.trigger('io.codearte.accurest.stubs:streamService', 'return_book_1')
then:
AccurestMessage receivedMessage = messaging.receiveMessage('output')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label for the existing artifactId'() {
when:
stubFinder.trigger('streamService', 'return_book_1')
then:
AccurestMessage receivedMessage = messaging.receiveMessage('output')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should not run any wrong trigger when missing label is passed'() {
given:
stubFinder.trigger('missing label')
when:
messaging.receiveMessage('output', 100, TimeUnit.MILLISECONDS)
then:
RuntimeException e = thrown(RuntimeException)
e.cause.message.contains("Message can't be null")
}
def 'should not run any wrong trigger when missing label and artifactid is passed'() {
given:
stubFinder.trigger('some:service', 'return_book_1')
when:
messaging.receiveMessage('output', 100, TimeUnit.MILLISECONDS)
then:
RuntimeException e = thrown(RuntimeException)
e.cause.message.contains("Message can't be null")
}
def 'should trigger messages by running all triggers'() {
when:
stubFinder.trigger()
then:
AccurestMessage receivedMessage = messaging.receiveMessage('output')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
private boolean assertJsons(Object payload) {
String objectAsString = payload instanceof String ? payload :
JsonOutput.toJson(payload)
def json = new JsonSlurper().parseText(objectAsString)
return json.bookName == 'foo'
}
}

View File

@@ -0,0 +1,2 @@
stubrunner.stubs.repository.root: classpath:m2repo/repository/
stubrunner.stubs.ids: io.codearte.accurest.stubs:streamService

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>io.codearte.accurest.stubs</groupId>
<artifactId>streamService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>io.codearte.accurest.stubs</groupId>
<artifactId>streamService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
</project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>io.codearte.accurest.stubs</groupId>
<artifactId>streamService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<versions>
<version>0.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>