From 232e44ad3b6205073e0cac8cd1efbf93f2adfb20 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 18 Feb 2021 11:14:52 +0100 Subject: [PATCH 1/2] Migrating to Github Actions --- .circleci/config.yml | 53 ------------------------------------- .github/workflows/maven.yml | 31 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 53 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/maven.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index bec2ac981c..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: cloudpipelines/pipeline-base - environment: - JAVA_TOOL_OPTIONS: "-Xms64m -Xmx250m" - GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xms64m -Xmx250m -XX:+HeapDumpOnOutOfMemoryError" -Dorg.gradle.daemon=false' - TERM: dumb - branches: - ignore: - - gh-pages # list of branches to ignore - steps: - - setup_remote_docker - - checkout - - restore_cache: - key: sc-contract-{{ .Branch }} - - run: - name: "Download dependencies" - command: ./scripts/downloadDependencies.sh - - save_cache: - key: sc-contract-{{ .Branch }} - paths: - - ~/.m2 - - ~/.gradle - - run: - name: "Adding daemon entries for Gradle" - command: touch ~/.gradle/gradle.properties && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties - - run: - name: "Clearing caches" - command: rm -rf ~/.m2/repository/org/springframework/cloud/contract && rm -rf ~/.m2/repository/com/example - - run: - name: "Running build" - command: ./scripts/ciBuild.sh - - run: - name: "Aggregate test results" - when: always - command: | - mkdir -p $CIRCLE_TEST_REPORTS/junit/ - find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; - find . -type f -regex ".*/build/test-results/.*" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; - bash <(curl -s https://codecov.io/bash) - - store_artifacts: - path: /junit/ - destination: artifacts - - store_test_results: - path: /junit/ - destination: testartifacts -notify: - webhooks: - # A list of hook hashes, containing the url field - # gitter hook - - url: https://webhooks.gitter.im/e/ece0ece9a76a5af2aa91 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000000..e6b32f5e3b --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,31 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Build + +on: + push: + branches: [ 2.2.x ] + pull_request: + branches: [ 2.2.x ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build with Maven + run: ./mvnw clean install -B -U From f9115dc30d432a24d4a4ec9a8baaf677802727e1 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 8 Mar 2021 12:11:03 +0100 Subject: [PATCH 2/2] Includes charset in regular expressions; fixes gh-1603 --- .../contract/spec/internal/RegexProperty.java | 23 +++++++- .../spec/internal/RegexPropertyTests.java | 52 +++++++++++++++++++ .../kafka/StubRunnerKafkaRouter.java | 17 +++--- ...tVerifierKafkaStubMessagesInitializer.java | 16 +++--- .../messaging/kafka/KafkaStubMessages.java | 41 +++++++++------ .../main/java/com/example/RabbitManager.java | 20 ++++--- 6 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 specs/spring-cloud-contract-spec-java/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPropertyTests.java diff --git a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java index 80be800827..5480a246b1 100644 --- a/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java +++ b/specs/spring-cloud-contract-spec-java/src/main/java/org/springframework/cloud/contract/spec/internal/RegexProperty.java @@ -16,6 +16,9 @@ package org.springframework.cloud.contract.spec.internal; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,6 +35,8 @@ public class RegexProperty extends DslProperty implements CanBeDynamic { final Pattern pattern; + String charset = StandardCharsets.UTF_8.name(); + private final Class clazz; public RegexProperty(Object value) { @@ -109,6 +114,18 @@ public class RegexProperty extends DslProperty implements CanBeDynamic { String.class); } + public RegexProperty asString(Charset charset) { + RegexProperty regexProperty = asString(); + regexProperty.charset = charset.name(); + return regexProperty; + } + + public RegexProperty asString(String charset) { + RegexProperty regexProperty = asString(); + regexProperty.charset = charset; + return regexProperty; + } + public RegexProperty asBooleanType() { return new RegexProperty(this.getClientValue(), this.getServerValue(), Boolean.class); @@ -139,7 +156,8 @@ public class RegexProperty extends DslProperty implements CanBeDynamic { else if (Boolean.class.equals(this.clazz)) { return Boolean.parseBoolean(generatedValue); } - return generatedValue; + return new String(generatedValue.getBytes(Charset.forName(this.charset)), + this.charset); } catch (NumberFormatException ex) { if (retries > 0) { @@ -148,6 +166,9 @@ public class RegexProperty extends DslProperty implements CanBeDynamic { } throw ex; } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } } public Object generateAndEscapeJavaStringIfNeeded() { diff --git a/specs/spring-cloud-contract-spec-java/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPropertyTests.java b/specs/spring-cloud-contract-spec-java/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPropertyTests.java new file mode 100644 index 0000000000..0b077147e0 --- /dev/null +++ b/specs/spring-cloud-contract-spec-java/src/test/groovy/org/springframework/cloud/contract/spec/internal/RegexPropertyTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.spec.internal; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +class RegexPropertyTests { + + @Test + void should_generate_utf8_compliant_text_from_regex() { + RegexProperty regexProperty = new RegexProperty( + Pattern.compile("......................")); + + String object = (String) regexProperty.generate(); + + String utf8EncodedString = new String(object.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8); + BDDAssertions.then(object).isEqualTo(utf8EncodedString); + } + + @Test + void should_generate_custom_charset_compliant_text_from_regex() { + RegexProperty regexProperty = new RegexProperty( + Pattern.compile("......................")); + + String object = (String) regexProperty.asString(StandardCharsets.US_ASCII) + .generate(); + + String utf8EncodedString = new String(object.getBytes(StandardCharsets.US_ASCII), + StandardCharsets.US_ASCII); + BDDAssertions.then(object).isEqualTo(utf8EncodedString); + } + +} diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaRouter.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaRouter.java index 907085a5fe..4ba930dfb4 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaRouter.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/kafka/StubRunnerKafkaRouter.java @@ -68,13 +68,16 @@ class StubRunnerKafkaRouter implements MessageListener { } Message message = messageConverter.toMessage(data, null, null, null); Contract dsl = this.selector.matchingContract(message); - if (dsl != null && dsl.getOutputMessage() != null && dsl.getOutputMessage().getSentTo() != null) { + if (dsl != null && dsl.getOutputMessage() != null + && dsl.getOutputMessage().getSentTo() != null) { String destination = dsl.getOutputMessage().getSentTo().getClientValue(); if (log.isDebugEnabled()) { - log.debug("Found a matching contract with an output message. Will send it to the [" + destination - + "] destination"); + log.debug( + "Found a matching contract with an output message. Will send it to the [" + + destination + "] destination"); } - Message transform = new StubRunnerKafkaTransformer(this.contracts).transform(dsl); + Message transform = new StubRunnerKafkaTransformer(this.contracts) + .transform(dsl); String defaultTopic = kafkaTemplate().getDefaultTopic(); try { kafkaTemplate().setDefaultTopic(destination); @@ -87,7 +90,8 @@ class StubRunnerKafkaRouter implements MessageListener { } @Override - public void onMessage(ConsumerRecord data, Acknowledgment acknowledgment) { + public void onMessage(ConsumerRecord data, + Acknowledgment acknowledgment) { onMessage(data); } @@ -97,7 +101,8 @@ class StubRunnerKafkaRouter implements MessageListener { } @Override - public void onMessage(ConsumerRecord data, Acknowledgment acknowledgment, Consumer consumer) { + public void onMessage(ConsumerRecord data, + Acknowledgment acknowledgment, Consumer consumer) { onMessage(data); } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java index 8c9f2988ee..0a2d2e7693 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/ContractVerifierKafkaStubMessagesInitializer.java @@ -29,12 +29,15 @@ import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.test.EmbeddedKafkaBroker; import org.springframework.kafka.test.utils.KafkaTestUtils; -class ContractVerifierKafkaStubMessagesInitializer implements KafkaStubMessagesInitializer { +class ContractVerifierKafkaStubMessagesInitializer + implements KafkaStubMessagesInitializer { - private static final Log log = LogFactory.getLog(ContractVerifierKafkaStubMessagesInitializer.class); + private static final Log log = LogFactory + .getLog(ContractVerifierKafkaStubMessagesInitializer.class); @Override - public Map initialize(EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties) { + public Map initialize(EmbeddedKafkaBroker broker, + KafkaProperties kafkaProperties) { Map map = new HashMap<>(); for (String topic : broker.getTopics()) { map.put(topic, prepareListener(broker, topic, kafkaProperties)); @@ -42,9 +45,10 @@ class ContractVerifierKafkaStubMessagesInitializer implements KafkaStubMessagesI return map; } - private Consumer prepareListener(EmbeddedKafkaBroker broker, String destination, KafkaProperties kafkaProperties) { - Map consumerProperties = KafkaTestUtils - .consumerProps(kafkaProperties.getConsumer().getGroupId(), "false", broker); + private Consumer prepareListener(EmbeddedKafkaBroker broker, String destination, + KafkaProperties kafkaProperties) { + Map consumerProperties = KafkaTestUtils.consumerProps( + kafkaProperties.getConsumer().getGroupId(), "false", broker); // Respect custom key/value deserializers and any additional props under // 'spring.kafka.consumer.properties' diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java index ec69ea4857..94a8ec5e8d 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/kafka/KafkaStubMessages.java @@ -49,10 +49,11 @@ class KafkaStubMessages implements MessageVerifier> { private final Receiver receiver; - KafkaStubMessages(KafkaTemplate kafkaTemplate, EmbeddedKafkaBroker broker, KafkaProperties kafkaProperties, - KafkaStubMessagesInitializer initializer) { + KafkaStubMessages(KafkaTemplate kafkaTemplate, EmbeddedKafkaBroker broker, + KafkaProperties kafkaProperties, KafkaStubMessagesInitializer initializer) { this.kafkaTemplate = kafkaTemplate; - Map topicToConsumer = initializer.initialize(broker, kafkaProperties); + Map topicToConsumer = initializer.initialize(broker, + kafkaProperties); this.receiver = new Receiver(topicToConsumer); } @@ -62,7 +63,8 @@ class KafkaStubMessages implements MessageVerifier> { try { this.kafkaTemplate.setDefaultTopic(destination); if (log.isDebugEnabled()) { - log.debug("Will send a message [" + message + "] to destination [" + destination + "]"); + log.debug("Will send a message [" + message + "] to destination [" + + destination + "]"); } this.kafkaTemplate.send(message).get(5, TimeUnit.SECONDS); this.kafkaTemplate.flush(); @@ -87,7 +89,8 @@ class KafkaStubMessages implements MessageVerifier> { @Override public void send(Object payload, Map headers, String destination) { - Message message = MessageBuilder.createMessage(payload, new MessageHeaders(headers)); + Message message = MessageBuilder.createMessage(payload, + new MessageHeaders(headers)); send(message, destination); } @@ -108,9 +111,11 @@ class Receiver { Message receive(String topic, long timeout, TimeUnit timeUnit) { Consumer consumer = this.consumers.get(topic); if (consumer == null) { - throw new IllegalStateException("No consumer set up for topic [" + topic + "]"); + throw new IllegalStateException( + "No consumer set up for topic [" + topic + "]"); } - ConsumerRecord record = KafkaTestUtils.getSingleRecord(consumer, topic, timeUnit.toMillis(timeout)); + ConsumerRecord record = KafkaTestUtils.getSingleRecord(consumer, topic, + timeUnit.toMillis(timeout)); if (log.isDebugEnabled()) { log.debug("Got a single record for destination [" + topic + "]"); } @@ -121,9 +126,10 @@ class Receiver { Map headersMap = toMap(record.headers()); // Leverage spring-kafka to add the headers - messagingMessageConverter.commonHeaders(null, consumer, headersMap, record.key(), record.topic(), - record.partition(), record.offset(), - record.timestampType() != null ? record.timestampType().name() : null, record.timestamp()); + messagingMessageConverter.commonHeaders(null, consumer, headersMap, record.key(), + record.topic(), record.partition(), record.offset(), + record.timestampType() != null ? record.timestampType().name() : null, + record.timestamp()); // commonHeaders() maps the record key under 'kafka_receivedMessageKey' - put // under 'kafka_messageKey' as well to satisfy both client/server usages as there // is not currently a way to set a header name based on client/server @@ -136,18 +142,21 @@ class Receiver { if (textPayload instanceof String && ((String) textPayload).contains("payload") && ((String) textPayload).contains("headers")) { try { - Object object = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse((String) textPayload); + Object object = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE) + .parse((String) textPayload); JSONObject jo = (JSONObject) object; String payload = (String) jo.get("payload"); JSONObject headersInJson = (JSONObject) jo.get("headers"); headersMap.putAll(headersInJson); - return MessageBuilder.createMessage(unquoted(payload), new MessageHeaders(headersMap)); + return MessageBuilder.createMessage(unquoted(payload), + new MessageHeaders(headersMap)); } catch (ParseException ex) { throw new IllegalStateException(ex); } } - return MessageBuilder.createMessage(unquoted(textPayload), new MessageHeaders(headersMap)); + return MessageBuilder.createMessage(unquoted(textPayload), + new MessageHeaders(headersMap)); } private Map toMap(Headers headers) { @@ -159,9 +168,11 @@ class Receiver { } private Object unquoted(Object value) { - String textPayload = value instanceof byte[] ? new String((byte[]) value) : value.toString(); + String textPayload = value instanceof byte[] ? new String((byte[]) value) + : value.toString(); if (textPayload.startsWith("\"") && textPayload.endsWith("\"")) { - return textPayload.substring(1, textPayload.length() - 1).replace("\\\"", "\""); + return textPayload.substring(1, textPayload.length() - 1).replace("\\\"", + "\""); } return textPayload; } diff --git a/tests/samples-messaging-amqp/src/main/java/com/example/RabbitManager.java b/tests/samples-messaging-amqp/src/main/java/com/example/RabbitManager.java index 7b1012b927..8501334936 100644 --- a/tests/samples-messaging-amqp/src/main/java/com/example/RabbitManager.java +++ b/tests/samples-messaging-amqp/src/main/java/com/example/RabbitManager.java @@ -60,25 +60,29 @@ public class RabbitManager { this.rabbitTemplate = rabbitTemplate; } - @RabbitListener(bindings = @QueueBinding(value = @Queue, - exchange = @Exchange(value = "input", durable = "true", autoDelete = "false", type = "topic"), - key = "event")) + @RabbitListener( + bindings = @QueueBinding( + value = @Queue, exchange = @Exchange(value = "input", + durable = "true", autoDelete = "false", type = "topic"), + key = "event")) // Workaround for https://github.com/spring-projects/spring-amqp/issues/1285 // public void newBook(Book book, @Headers Map headers) { public void newBook(Book book, @Header("amqp_replyTo") String replyTo) { LOG.info("Received new book with bookname = " + book.getName()); -// LOG.info("Headers = " + headers); + // LOG.info("Headers = " + headers); this.service.sendBook(book, replyTo); } - @RabbitListener(bindings = @QueueBinding(value = @Queue, - exchange = @Exchange(value = "input", durable = "true", autoDelete = "false", type = "topic"), - key = "event2")) + @RabbitListener( + bindings = @QueueBinding( + value = @Queue, exchange = @Exchange(value = "input", + durable = "true", autoDelete = "false", type = "topic"), + key = "event2")) // Workaround for https://github.com/spring-projects/spring-amqp/issues/1285 // public void newBook2(Book book, @Headers Map headers) { public void newBook2(Book book, @Header("amqp_replyTo") String replyTo) { LOG.info("newBook2 Received new book with bookname = " + book.getName()); -// LOG.info("newBook2 Headers = " + headers); + // LOG.info("newBook2 Headers = " + headers); this.service.sendBook(book, replyTo); }