Added Spring Integration client side

This commit is contained in:
Marcin Grzejszczak
2016-04-23 01:16:06 +02:00
parent fc96041227
commit 1a50a4baf1
21 changed files with 411 additions and 29 deletions

View File

@@ -1,8 +1,8 @@
package io.codearte.accurest.messaging.camel;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessageBuilder;
import io.codearte.accurest.messaging.AccurestMessaging;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.camel.CamelContext;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.Exchange;
@@ -14,8 +14,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessageBuilder;
import io.codearte.accurest.messaging.AccurestMessaging;
/**
* @author Marcin Grzejszczak
@@ -49,6 +50,12 @@ public class AccurestCamelMessaging<T> implements AccurestMessaging<T, Message>
}
}
@Override
@SuppressWarnings("unchecked")
public void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
}
@Override
@SuppressWarnings("unchecked")
public AccurestMessage<T, Message> receiveMessage(String destination, long timeout, TimeUnit timeUnit) {

View File

@@ -1,5 +1,6 @@
package io.codearte.accurest.messaging;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -15,6 +16,11 @@ public interface AccurestMessaging<PAYLOAD, TYPE_TO_CONVERT_INTO> extends Accure
*/
void send(AccurestMessage<PAYLOAD, TYPE_TO_CONVERT_INTO> message, String destination);
/**
* Sends the given payload with headers, to the given destination.
*/
void send(PAYLOAD payload, Map<String, Object> headers, String destination);
/**
* Receives the {@link AccurestMessage} from the given destination. You can provide the timeout
* for receiving that message.

View File

@@ -1,11 +1,11 @@
package io.codearte.accurest.messaging.noop;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessaging;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessaging;
/**
* @author Marcin Grzejszczak
*/
@@ -15,6 +15,11 @@ public class NoOpAccurestMessaging implements AccurestMessaging {
}
@Override
public void send(Object payload, Map headers, String destination) {
}
@Override
public AccurestMessage receiveMessage(String destination, long timeout, TimeUnit timeUnit) {
return null;

View File

@@ -1,8 +1,8 @@
package io.codearte.accurest.messaging.integration;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessageBuilder;
import io.codearte.accurest.messaging.AccurestMessaging;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -12,8 +12,9 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessageBuilder;
import io.codearte.accurest.messaging.AccurestMessaging;
/**
* @author Marcin Grzejszczak
@@ -33,6 +34,12 @@ public class AccurestIntegrationMessaging<T> implements AccurestMessaging<T, Mes
this.builder = accurestMessageBuilder;
}
@Override
@SuppressWarnings("unchecked")
public void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
}
@Override
public void send(AccurestMessage<T, Message<T>> message, String destination) {
try {

View File

@@ -1,8 +1,8 @@
package io.codearte.accurest.messaging.stream;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessageBuilder;
import io.codearte.accurest.messaging.AccurestMessaging;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -11,8 +11,9 @@ import org.springframework.context.ApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.codearte.accurest.messaging.AccurestMessage;
import io.codearte.accurest.messaging.AccurestMessageBuilder;
import io.codearte.accurest.messaging.AccurestMessaging;
/**
* @author Marcin Grzejszczak
@@ -33,6 +34,12 @@ public class AccurestStreamMessaging<T> implements AccurestMessaging<T, Message<
this.builder = builder;
}
@Override
@SuppressWarnings("unchecked")
public void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
}
@Override
public void send(AccurestMessage<T, Message<T>> message, String destination) {
try {

View File

@@ -13,7 +13,6 @@ dependencies {
compile project(':accurest-core')
compile 'org.springframework.boot:spring-boot-starter-actuator:1.3.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-integration:1.3.3.RELEASE'
compile 'org.springframework:spring-messaging:[4.0.0.RELEASE,)'
testCompile project(':accurest-messaging-root:accurest-messaging-integration')
testCompile 'org.springframework.boot:spring-boot-starter-test:1.3.3.RELEASE'

View File

@@ -29,6 +29,12 @@ public class AccurestSpringMessaging<T> implements AccurestMessaging<T, Message>
this.jmsTemplate = jmsTemplate
}
@Override
@SuppressWarnings("unchecked")
public void send(T payload, Map<String, Object> headers, String destination) {
send(builder.create(payload, headers), destination);
}
@Override
public void send(AccurestMessage<T, Message> message, String destination) {
try {

View File

@@ -1,6 +1,6 @@
package io.codearte.accurest.stubrunner.messaging.camel
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import io.codearte.accurest.stubrunner.StubFinder
import org.apache.activemq.camel.component.ActiveMQComponent
@@ -34,7 +34,7 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{"bookName":"foo"}'
assertJsons(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@@ -45,7 +45,7 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{ "bookName" : "foo" }'
assertJsons(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@@ -56,7 +56,7 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{ "bookName" : "foo" }'
assertJsons(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@@ -67,7 +67,7 @@ class CamelStubRunnerSpec extends Specification {
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
receivedMessage.in.body == '{ "bookName" : "foo" }'
assertJsons(receivedMessage.in.body)
receivedMessage.in.headers.get('BOOK-NAME') == 'foo'
}
@@ -90,20 +90,23 @@ class CamelStubRunnerSpec extends Specification {
}
def 'should trigger messages by running all triggers'() {
given:
ObjectMapper objectMapper = new ObjectMapper()
when:
stubFinder.trigger()
then:
Exchange receivedMessage = camelContext.createConsumerTemplate().receive('jms:output', 5000)
and:
receivedMessage != null
new JsonSlurper().parseText(
objectMapper.writeValueAsString(
receivedMessage.in.body)) == new JsonSlurper().parseText('{"bookName":"foo"}')
assertJsons(receivedMessage.in.body)
receivedMessage.in.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'
}
@Bean
ActiveMQComponent activeMQComponent(@Value('${activemq.url:vm://localhost?broker.persistent=false}') String url) {
return new ActiveMQComponent(brokerURL: url)

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-integration')
compile 'org.springframework.integration:spring-integration-java-dsl:[1.1.2.RELEASE,)'
testCompile 'org.springframework.boot:spring-boot-starter-integration:1.3.3.RELEASE'
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,46 @@
package io.codearte.accurest.stubrunner.messaging.integration
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.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.stereotype.Service
/**
* Spring Integration configuration that iterates over the downloaded Groovy DSLs
* and registers a flow for each DSL.
*
* @author Marcin Grzejszczak
*/
@Configuration
class StubRunnerIntegrationConfiguration {
@Bean
FlowRegistrar service(AutowireCapableBeanFactory beanFactory, BatchStubRunner batchStubRunner) {
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 StubRunnerIntegrationMessageSelector(dsl), { FilterEndpointSpec e -> e.id("${flowName}.filter") } )
.transform(new StubRunnerIntegrationTransformer(dsl), { GenericEndpointSpec e -> e.id("${flowName}.transformer") })
.channel(dsl.outputMessage.sentTo)
.get()
beanFactory.initializeBean(integrationFlow, "${name}_${dsl.label}_${dsl.hashCode()}")
beanFactory.getBean("${flowName}.filter", Lifecycle.class).start();
beanFactory.getBean("${flowName}.transformer", Lifecycle.class).start();
}
}
return new FlowRegistrar()
}
@Service
static class FlowRegistrar {}
}

View File

@@ -0,0 +1,58 @@
package io.codearte.accurest.stubrunner.messaging.integration
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
/**
* Passes through a message that matches the one defined in the DSL
*
* @author Marcin Grzejszczak
*/
@CompileStatic
class StubRunnerIntegrationMessageSelector implements MessageSelector {
private final GroovyDsl groovyDsl
private final ObjectMapper objectMapper = new ObjectMapper()
StubRunnerIntegrationMessageSelector(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
return headers.get(name) == value
}
}
}

View File

@@ -0,0 +1,29 @@
package io.codearte.accurest.stubrunner.messaging.integration
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 StubRunnerIntegrationTransformer implements GenericTransformer<Message<?>, Message<?>> {
private final GroovyDsl groovyDsl
StubRunnerIntegrationTransformer(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.integration.StubRunnerIntegrationConfiguration

View File

@@ -0,0 +1,16 @@
package io.codearte.accurest.stubrunner.messaging.integration
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,114 @@
package io.codearte.accurest.stubrunner.messaging.integration
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.BeanNotOfRequiredTypeException
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.context.annotation.ImportResource
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import java.util.concurrent.TimeUnit
/**
* @author Marcin Grzejszczak
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration
@ContextConfiguration(classes = IntegrationStubRunnerSpec, loader = SpringApplicationContextLoader)
@ImportResource("classpath*:integration-context.xml")
class IntegrationStubRunnerSpec 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('outputTest')
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('outputTest')
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:integrationService', 'return_book_1')
then:
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
and:
receivedMessage != null
assertJsons(receivedMessage.payload)
receivedMessage.headers.get('BOOK-NAME') == 'foo'
}
def 'should trigger a label for the existing artifactId'() {
when:
stubFinder.trigger('integrationService', 'return_book_1')
then:
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
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:
Exception e = thrown(RuntimeException)
e.cause.class == BeanNotOfRequiredTypeException
}
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:
Exception e = thrown(RuntimeException)
e.cause.class == BeanNotOfRequiredTypeException
}
def 'should trigger messages by running all triggers'() {
when:
stubFinder.trigger()
then:
AccurestMessage receivedMessage = messaging.receiveMessage('outputTest')
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:integrationService

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<!-- REQUIRED FOR TESTING -->
<bridge input-channel="output"
output-channel="outputTest"/>
<channel id="outputTest">
<queue/>
</channel>
</beans:beans>

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>integrationService</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>integrationService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20160409062112</lastUpdated>
</versioning>
</metadata>

View File

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