diff --git a/accurest-messaging/accurest-messaging-stream/build.gradle b/accurest-messaging/accurest-messaging-stream/build.gradle index 4b045bb943..7cf3543d4f 100644 --- a/accurest-messaging/accurest-messaging-stream/build.gradle +++ b/accurest-messaging/accurest-messaging-stream/build.gradle @@ -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,)' } \ No newline at end of file diff --git a/accurest-messaging/accurest-messaging-stream/src/main/java/io/codearte/accurest/messaging/stream/StreamMessage.java b/accurest-messaging/accurest-messaging-stream/src/main/java/io/codearte/accurest/messaging/stream/StreamMessage.java index 7b7faeb68a..8bc2a9ecb0 100644 --- a/accurest-messaging/accurest-messaging-stream/src/main/java/io/codearte/accurest/messaging/stream/StreamMessage.java +++ b/accurest-messaging/accurest-messaging-stream/src/main/java/io/codearte/accurest/messaging/stream/StreamMessage.java @@ -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 implements AccurestMessage> { private final Message delegate; public StreamMessage(Message delegate) { + if (delegate == null) { + throw new IllegalArgumentException("Message can't be null"); + } this.delegate = delegate; } diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/build.gradle b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/build.gradle new file mode 100644 index 0000000000..20d6bac9f4 --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/build.gradle @@ -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') + } +} \ No newline at end of file diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamConfiguration.groovy b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamConfiguration.groovy new file mode 100644 index 0000000000..7520883395 --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamConfiguration.groovy @@ -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> accurestContracts = batchStubRunner.accurestContracts + accurestContracts.each { StubConfiguration key, Collection 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 {} +} diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.groovy b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.groovy new file mode 100644 index 0000000000..37e60c85b7 --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.groovy @@ -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 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 + } + } +} diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamTransformer.groovy b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamTransformer.groovy new file mode 100644 index 0000000000..3129f70ace --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/groovy/io/codearte/accurest/stubrunner/messaging/stream/StubRunnerStreamTransformer.groovy @@ -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> { + + private final GroovyDsl groovyDsl + + StubRunnerStreamTransformer(GroovyDsl groovyDsl) { + this.groovyDsl = groovyDsl + } + + @Override + Message transform(Message source) { + String payload = BodyAsString.extractClientValueFrom(groovyDsl.outputMessage.body) + Map headers = groovyDsl.outputMessage.headers.asStubSideMap() + return MessageBuilder.createMessage(payload, new MessageHeaders(headers)) + } +} diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/resources/META-INF/spring.factories b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..7b69d1b85a --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +io.codearte.accurest.stubrunner.messaging.stream.StubRunnerStreamConfiguration diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/groovy/io/codearte/accurest/stubrunner/messaging/stream/BookReturned.groovy b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/groovy/io/codearte/accurest/stubrunner/messaging/stream/BookReturned.groovy new file mode 100644 index 0000000000..0dd3ca4216 --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/groovy/io/codearte/accurest/stubrunner/messaging/stream/BookReturned.groovy @@ -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 + } +} diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/groovy/io/codearte/accurest/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/groovy/io/codearte/accurest/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy new file mode 100644 index 0000000000..ffe676551c --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/groovy/io/codearte/accurest/stubrunner/messaging/stream/StreamStubRunnerSpec.groovy @@ -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' + } + +} diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/application.yml b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/application.yml new file mode 100644 index 0000000000..154bfe1b6a --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/application.yml @@ -0,0 +1,2 @@ +stubrunner.stubs.repository.root: classpath:m2repo/repository/ +stubrunner.stubs.ids: io.codearte.accurest.stubs:streamService \ No newline at end of file diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/maven-metadata-local.xml b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/maven-metadata-local.xml new file mode 100644 index 0000000000..bad844ba2a --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.codearte.accurest.stubs + streamService + 0.0.1-SNAPSHOT + + + true + + 20160409062112 + + diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/streamService-0.0.1-SNAPSHOT-stubs.jar b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/streamService-0.0.1-SNAPSHOT-stubs.jar new file mode 100644 index 0000000000..86c61d52ec Binary files /dev/null and b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/streamService-0.0.1-SNAPSHOT-stubs.jar differ diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/streamService-0.0.1-SNAPSHOT.pom b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/streamService-0.0.1-SNAPSHOT.pom new file mode 100644 index 0000000000..dcb2e4ffd9 --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/0.0.1-SNAPSHOT/streamService-0.0.1-SNAPSHOT.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + io.codearte.accurest.stubs + streamService + 0.0.1-SNAPSHOT + pom + diff --git a/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/maven-metadata-local.xml b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/maven-metadata-local.xml new file mode 100644 index 0000000000..dc32790f53 --- /dev/null +++ b/stub-runner/stub-runner-messaging/stub-runner-messaging-stream/src/test/resources/m2repo/repository/io/codearte/accurest/stubs/streamService/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + io.codearte.accurest.stubs + streamService + 0.0.1-SNAPSHOT + + + 0.0.1-SNAPSHOT + + 20160409062112 + +