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); }