Skip to content

Commit 76786de

Browse files
Upgrade to Jackson 3 continued (#2286)
* Adjust to Jackson3-backed implementation. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Adjust to Jackson3-backed implementation. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Revert json schema generation to Jackson 2, as no integration available yet. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Switch MetadataUtil to Jackson 3. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Switch WireMockToDslConverter to Jackson 3. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Fix YamlToContracts implementation. Fix exception handling. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Fix YamlToContracts implementation. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Adjust WireMockToDslConverter implementation to Jackson 3 changes. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> * Reformat. Signed-off-by: Olga Maciaszek-Sharma <[email protected]> --------- Signed-off-by: Olga Maciaszek-Sharma <[email protected]>
1 parent 62013d6 commit 76786de

File tree

27 files changed

+235
-236
lines changed

27 files changed

+235
-236
lines changed

docs/pom.xml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<name>Spring Cloud Contract Docs</name>
1616
<description>Spring Cloud Contract Docs</description>
1717
<properties>
18-
<jackson-module-jsonSchema.version>2.14.3</jackson-module-jsonSchema.version>
18+
<jackson-module-jsonSchema.version>2.20.0</jackson-module-jsonSchema.version>
1919
<docs.main>spring-cloud-contract</docs.main>
2020
<main.basedir>${basedir}/..</main.basedir>
2121
<configprops.inclusionPattern>spring.cloud.*</configprops.inclusionPattern>
@@ -113,10 +113,14 @@
113113
<goal>exec</goal>
114114
</goals>
115115
<configuration>
116-
<workingDirectory>${maven.multiModuleProjectDirectory}/docker/spring-cloud-contract-docker</workingDirectory>
116+
<workingDirectory>
117+
${maven.multiModuleProjectDirectory}/docker/spring-cloud-contract-docker
118+
</workingDirectory>
117119
<executable>./build_adocs.sh</executable>
118120
<arguments>
119-
<argument>${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/</argument>
121+
<argument>
122+
${maven.multiModuleProjectDirectory}/docs/modules/ROOT/partials/
123+
</argument>
120124
</arguments>
121125
</configuration>
122126
</execution>
@@ -127,9 +131,13 @@
127131
<goal>java</goal>
128132
</goals>
129133
<configuration>
130-
<mainClass>org.springframework.cloud.contract.docs.Main</mainClass>
134+
<mainClass>
135+
org.springframework.cloud.contract.docs.Main
136+
</mainClass>
131137
<arguments>
132-
<argument>${maven.multiModuleProjectDirectory}/docs</argument>
138+
<argument>
139+
${maven.multiModuleProjectDirectory}/docs
140+
</argument>
133141
</arguments>
134142
</configuration>
135143
</execution>
@@ -185,11 +193,18 @@
185193
<groupId>org.springframework.amqp</groupId>
186194
<artifactId>spring-amqp</artifactId>
187195
</dependency>
196+
<!-- TODO: remove when replacements with Jackson 3 integration become available -->
188197
<dependency>
189198
<groupId>com.fasterxml.jackson.module</groupId>
190199
<artifactId>jackson-module-jsonSchema</artifactId>
191200
<version>${jackson-module-jsonSchema.version}</version>
192201
</dependency>
202+
<!-- TODO: switch to Jackson 3 when jsonSchema replacements with Jackson 3 integration become available -->
203+
<dependency>
204+
<groupId>com.fasterxml.jackson.dataformat</groupId>
205+
<artifactId>jackson-dataformat-yaml</artifactId>
206+
<version>${jackson-module-jsonSchema.version}</version>
207+
</dependency>
193208
</dependencies>
194209
</profile>
195210
</profiles>

spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/java/org/springframework/cloud/contract/verifier/wiremock/DefaultWireMockStubPostProcessor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23-
import com.fasterxml.jackson.core.JsonProcessingException;
24-
import com.fasterxml.jackson.databind.ObjectMapper;
2523
import com.github.tomakehurst.wiremock.common.Metadata;
2624
import com.github.tomakehurst.wiremock.extension.Parameters;
2725
import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition;
@@ -30,12 +28,14 @@
3028
import com.github.tomakehurst.wiremock.http.Fault;
3129
import com.github.tomakehurst.wiremock.http.ResponseDefinition;
3230
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
31+
import tools.jackson.core.JacksonException;
32+
import tools.jackson.databind.json.JsonMapper;
3333

3434
import org.springframework.cloud.contract.spec.Contract;
3535

3636
class DefaultWireMockStubPostProcessor implements WireMockStubPostProcessor {
3737

38-
private final ObjectMapper objectMapper = new ObjectMapper();
38+
private final JsonMapper objectMapper = new JsonMapper();
3939

4040
@Override
4141
public StubMapping postProcess(StubMapping stubMapping, Contract contract) {
@@ -143,7 +143,7 @@ else if (wiremock instanceof Map) {
143143
try {
144144
return StubMapping.buildFrom(this.objectMapper.writeValueAsString(wiremock));
145145
}
146-
catch (JsonProcessingException e) {
146+
catch (JacksonException e) {
147147
throw new IllegalStateException("Failed to build StubMapping for map [" + wiremock + "]", e);
148148
}
149149
}

spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/java/org/springframework/cloud/contract/verifier/wiremock/WireMockToDslConverter.java

Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,29 @@
1616

1717
package org.springframework.cloud.contract.verifier.wiremock;
1818

19-
import java.io.IOException;
2019
import java.util.ArrayList;
20+
import java.util.Collection;
2121
import java.util.Iterator;
2222
import java.util.List;
2323
import java.util.Map;
24-
import java.util.stream.StreamSupport;
25-
26-
import com.fasterxml.jackson.core.JsonParser;
27-
import com.fasterxml.jackson.core.JsonPointer;
28-
import com.fasterxml.jackson.core.util.DefaultIndenter;
29-
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
30-
import com.fasterxml.jackson.core.util.Separators;
31-
import com.fasterxml.jackson.databind.JsonNode;
32-
import com.fasterxml.jackson.databind.ObjectMapper;
33-
import com.fasterxml.jackson.databind.node.ArrayNode;
34-
import com.fasterxml.jackson.databind.node.IntNode;
35-
import com.fasterxml.jackson.databind.node.ObjectNode;
36-
import com.fasterxml.jackson.databind.node.TextNode;
24+
import java.util.Set;
25+
3726
import repackaged.nl.flotsam.xeger.Xeger;
27+
import tools.jackson.core.JsonPointer;
28+
import tools.jackson.core.util.DefaultIndenter;
29+
import tools.jackson.core.util.DefaultPrettyPrinter;
30+
import tools.jackson.databind.JsonNode;
31+
import tools.jackson.databind.json.JsonMapper;
32+
import tools.jackson.databind.node.ArrayNode;
33+
import tools.jackson.databind.node.IntNode;
34+
import tools.jackson.databind.node.ObjectNode;
35+
import tools.jackson.databind.node.StringNode;
3836

3937
import static org.apache.commons.text.StringEscapeUtils.escapeJava;
38+
import static tools.jackson.core.json.JsonReadFeature.ALLOW_JAVA_COMMENTS;
39+
import static tools.jackson.core.json.JsonReadFeature.ALLOW_SINGLE_QUOTES;
40+
import static tools.jackson.core.json.JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES;
41+
import static tools.jackson.core.json.JsonReadFeature.ALLOW_YAML_COMMENTS;
4042

4143
/**
4244
* Converts WireMock stubs into the DSL format.
@@ -69,14 +71,12 @@ public class WireMockToDslConverter {
6971

7072
private static final JsonPointer RESPONSE_HEADERS_POINTER = JsonPointer.compile("/response/headers");
7173

72-
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
73-
74-
static {
75-
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
76-
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
77-
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
78-
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
79-
}
74+
private static final JsonMapper OBJECT_MAPPER = JsonMapper.builder()
75+
.configure(ALLOW_JAVA_COMMENTS, true)
76+
.configure(ALLOW_YAML_COMMENTS, true)
77+
.configure(ALLOW_UNQUOTED_PROPERTY_NAMES, true)
78+
.configure(ALLOW_SINGLE_QUOTES, true)
79+
.build();
8080

8181
/**
8282
* Returns the string content of the contract.
@@ -100,15 +100,15 @@ private JsonNode parseStubDefinition(String wireMockStringStub) {
100100
try {
101101
return OBJECT_MAPPER.reader().readTree(wireMockStringStub);
102102
}
103-
catch (IOException e) {
103+
catch (Exception e) {
104104
throw new RuntimeException("WireMock string stub could not be read", e);
105105
}
106106
}
107107

108108
private String buildPriority(JsonNode wireMockStub) {
109109
String priority = "";
110110
JsonNode priorityNode = wireMockStub.at(PRIORITY_POINTER);
111-
if (priorityNode != null && priorityNode.asInt() > 0) {
111+
if (!priorityNode.isMissingNode() && priorityNode.asInt() > 0) {
112112
priority = "priority " + priorityNode.asInt() + "\n";
113113
}
114114
return priority;
@@ -118,7 +118,7 @@ private String buildRequestMethod(JsonNode wireMockStub) {
118118
String requestMethod = "";
119119
JsonNode requestMethodNode = wireMockStub.at(REQUEST_METHOD_POINTER);
120120
if (requestMethodNode != null) {
121-
requestMethod = "method '" + requestMethodNode.asText() + "'\n";
121+
requestMethod = "method '" + requestMethodNode.asString() + "'\n";
122122
}
123123
return requestMethod;
124124
}
@@ -127,7 +127,7 @@ private String buildRequestUrl(JsonNode wireMockStub) {
127127
String requestUrl = "";
128128
JsonNode requestUrlNode = wireMockStub.at(REQUEST_URL_POINTER);
129129
if (!requestUrlNode.isMissingNode()) {
130-
requestUrl = "url '" + requestUrlNode.asText() + "'\n";
130+
requestUrl = "url '" + requestUrlNode.asString() + "'\n";
131131
}
132132
return requestUrl;
133133
}
@@ -136,7 +136,7 @@ private String buildRequestUrlPath(JsonNode wireMockStub) {
136136
String requestUrlPath = "";
137137
JsonNode requestUrlPathNode = wireMockStub.at(REQUEST_URL_PATH_POINTER);
138138
if (!requestUrlPathNode.isMissingNode()) {
139-
requestUrlPath = "url '" + requestUrlPathNode.asText() + "'\n";
139+
requestUrlPath = "url '" + requestUrlPathNode.asString() + "'\n";
140140
}
141141
return requestUrlPath;
142142
}
@@ -145,7 +145,7 @@ private String buildRequestUrlPattern(JsonNode wireMockStub) {
145145
String requestUrlPattern = "";
146146
JsonNode requestUrlPatternNode = wireMockStub.at(REQUEST_URL_PATTERN_POINTER);
147147
if (!requestUrlPatternNode.isMissingNode()) {
148-
String escapedRequestUrlPatternValue = escapeJava(requestUrlPatternNode.asText());
148+
String escapedRequestUrlPatternValue = escapeJava(requestUrlPatternNode.asString());
149149
requestUrlPattern = "url $(consumer(regex('" + escapedRequestUrlPatternValue + "')), producer('"
150150
+ new Xeger(escapedRequestUrlPatternValue).generate() + "'))\n";
151151
}
@@ -156,7 +156,7 @@ private String buildRequestUrlPathPattern(JsonNode wireMockStub) {
156156
String requestUrlPathPattern = "";
157157
JsonNode requestUrlPathPatternNode = wireMockStub.at(REQUEST_URL_PATH_PATTERN_POINTER);
158158
if (!requestUrlPathPatternNode.isMissingNode()) {
159-
String escapedRequestUrlPathPatternValue = escapeJava(requestUrlPathPatternNode.asText());
159+
String escapedRequestUrlPathPatternValue = escapeJava(requestUrlPathPatternNode.asString());
160160
requestUrlPathPattern = "urlPath $(consumer(regex('" + escapedRequestUrlPathPatternValue + "')), producer('"
161161
+ new Xeger(escapedRequestUrlPathPatternValue).generate() + "'))'\n";
162162
}
@@ -169,13 +169,17 @@ private String buildRequestHeaders(JsonNode wireMockStub) {
169169

170170
if (requestHeadersNode.isObject()) {
171171
requestHeadersBuilder.append("headers {\n");
172-
ObjectNode requestHeadersObjectNode = requestHeadersNode.deepCopy();
173-
Iterator<Map.Entry<String, JsonNode>> fields = requestHeadersObjectNode.fields();
174-
fields.forEachRemaining(c -> {
172+
JsonNode requestHeadersObjectNode = requestHeadersNode.deepCopy();
173+
Set<Map.Entry<String, JsonNode>> fields = requestHeadersObjectNode.properties();
174+
fields.forEach(c -> {
175175
requestHeadersBuilder.append("header('").append(c.getKey()).append("',");
176-
Map.Entry<String, JsonNode> headerValue = c.getValue().deepCopy().fields().next();
177-
String header = buildHeader(headerValue.getKey(), headerValue.getValue().asText());
178-
requestHeadersBuilder.append(header).append(")").append("\n");
176+
ObjectNode headersNode = (ObjectNode) c.getValue().deepCopy();
177+
Iterator<Map.Entry<String, JsonNode>> headersNodeIterator = headersNode.properties().iterator();
178+
if (headersNodeIterator.hasNext()) {
179+
Map.Entry<String, JsonNode> headerValue = headersNodeIterator.next();
180+
String header = buildHeader(headerValue.getKey(), headerValue.getValue().asString());
181+
requestHeadersBuilder.append(header).append(")").append("\n");
182+
}
179183
});
180184
requestHeadersBuilder.append("}");
181185
}
@@ -198,30 +202,30 @@ private String buildRequestBody(JsonNode wireMockStub) {
198202
final StringBuilder requestBody = new StringBuilder();
199203
JsonNode requestBodyNode = wireMockStub.at(REQUEST_BODY_POINTER);
200204
if (requestBodyNode.isArray()) {
201-
ArrayNode requestBodyArrayNode = requestBodyNode.deepCopy();
202-
Iterator<JsonNode> elements = requestBodyArrayNode.elements();
203-
Iterable<JsonNode> iterableFields = () -> elements;
205+
ArrayNode requestBodyArrayNode = (ArrayNode) requestBodyNode.deepCopy();
206+
Collection<JsonNode> elements = requestBodyArrayNode.elements();
204207
List<Map.Entry<String, JsonNode>> requestBodyObjectNodes = new ArrayList<>();
205-
StreamSupport.stream(iterableFields.spliterator(), false)
208+
209+
elements.stream()
206210
.filter(f -> f instanceof ObjectNode)
207211
.map(f -> (ObjectNode) f)
208-
.map(ObjectNode::fields)
209-
.forEachOrdered(i -> i.forEachRemaining(requestBodyObjectNodes::add));
212+
.map(ObjectNode::properties)
213+
.forEachOrdered(requestBodyObjectNodes::addAll);
210214
requestBodyObjectNodes.stream()
211215
.filter(b -> b.getKey().equals("equalTo"))
212216
.findFirst()
213-
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asText()).append("')"));
217+
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asString()).append("')"));
214218
requestBodyObjectNodes.stream()
215219
.filter(b -> b.getKey().equals("equalToJson"))
216220
.findFirst()
217-
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asText()).append("')"));
221+
.ifPresent(b -> requestBody.append("body ('").append(b.getValue().asString()).append("')"));
218222
requestBodyObjectNodes.stream()
219223
.filter(b -> b.getKey().equals("matches"))
220224
.findFirst()
221225
.ifPresent(b -> requestBody.append("body $(consumer(regex('")
222-
.append(escapeJava(b.getValue().asText()))
226+
.append(escapeJava(b.getValue().asString()))
223227
.append("')), producer('")
224-
.append(new Xeger(escapeJava(b.getValue().asText())).generate())
228+
.append(new Xeger(escapeJava(b.getValue().asString())).generate())
225229
.append("'))"));
226230
}
227231
return requestBody.toString();
@@ -243,28 +247,29 @@ private String buildResponseBody(JsonNode wireMockStub) {
243247
if (responseBodyNode.isInt()) {
244248
responseBody += "body( " + escapeJava(buildPrettyPrintResponseBody((IntNode) responseBodyNode)) + ")\n";
245249
}
246-
if (responseBodyNode.isTextual()) {
247-
responseBody += "body( \"" + escapeJava(buildPrettyPrintResponseBody((TextNode) responseBodyNode))
250+
if (responseBodyNode.isString()) {
251+
responseBody += "body( \"" + escapeJava(buildPrettyPrintResponseBody((StringNode) responseBodyNode))
248252
+ "\")\n";
249253
}
250254
return responseBody;
251255
}
252256

253257
private String buildPrettyPrintResponseBody(IntNode node) {
254-
return node.asText();
258+
return node.asString();
255259
}
256260

257-
private String buildPrettyPrintResponseBody(TextNode node) {
261+
private String buildPrettyPrintResponseBody(StringNode node) {
258262
try {
259-
String textNode = node.asText();
260-
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.reader().readValue(textNode, Object.class);
263+
String stringNode = node.asString();
264+
Object intermediateObjectForPrettyPrinting = OBJECT_MAPPER.readerFor(Object.class).readValue(stringNode);
265+
261266
DefaultIndenter customIndenter = new DefaultIndenter(" ", "\n");
262-
return OBJECT_MAPPER
263-
.writer(new PrivatePrettyPrinter().withArrayIndenter(customIndenter).withObjectIndenter(customIndenter))
267+
return OBJECT_MAPPER.writer()
268+
.with(new DefaultPrettyPrinter().withArrayIndenter(customIndenter).withObjectIndenter(customIndenter))
264269
.writeValueAsString(intermediateObjectForPrettyPrinting);
265270
}
266-
catch (IOException e) {
267-
throw new RuntimeException("WireMock response body could not be pretty printed");
271+
catch (Exception e) {
272+
throw new RuntimeException("WireMock response body could not be pretty printed", e);
268273
}
269274
}
270275

@@ -274,33 +279,17 @@ private String buildResponseHeaders(JsonNode wireMockStub) {
274279

275280
if (requestHeadersNode.isObject()) {
276281
responseHeadersBuilder.append("headers {\n");
277-
ObjectNode responseHeadersObjectNode = requestHeadersNode.deepCopy();
278-
Iterator<Map.Entry<String, JsonNode>> fields = responseHeadersObjectNode.fields();
279-
fields.forEachRemaining(c -> responseHeadersBuilder.append("header('")
282+
JsonNode responseHeadersObjectNode = requestHeadersNode.deepCopy();
283+
Set<Map.Entry<String, JsonNode>> fields = responseHeadersObjectNode.properties();
284+
fields.forEach(c -> responseHeadersBuilder.append("header('")
280285
.append(c.getKey())
281286
.append("',")
282287
.append("'")
283-
.append(c.getValue().asText())
288+
.append(c.getValue().asString())
284289
.append("')\n"));
285290
responseHeadersBuilder.append("}");
286291
}
287292
return responseHeadersBuilder.toString();
288293
}
289294

290-
private static class PrivatePrettyPrinter extends DefaultPrettyPrinter {
291-
292-
@Override
293-
public DefaultPrettyPrinter createInstance() {
294-
return new PrivatePrettyPrinter();
295-
}
296-
297-
@Override
298-
public DefaultPrettyPrinter withSeparators(Separators separators) {
299-
_separators = separators;
300-
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
301-
return this;
302-
}
303-
304-
}
305-
306295
}

spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/java/org/springframework/cloud/contract/verifier/wiremock/DefaultWireMockStubPostProcessorTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@
2020
import java.util.Map;
2121
import java.util.stream.Collectors;
2222

23-
import com.fasterxml.jackson.core.JsonProcessingException;
24-
import com.fasterxml.jackson.databind.ObjectMapper;
2523
import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition;
2624
import com.github.tomakehurst.wiremock.stubbing.StubMapping;
2725
import org.junit.jupiter.api.Test;
26+
import tools.jackson.databind.json.JsonMapper;
2827

2928
import org.springframework.cloud.contract.spec.Contract;
3029

@@ -119,10 +118,10 @@ void should_merge_stub_mappings_when_stub_mapping_is_stub_mapping() {
119118
}
120119

121120
@Test
122-
void should_merge_stub_mappings_when_stub_mapping_is_map() throws JsonProcessingException {
121+
void should_merge_stub_mappings_when_stub_mapping_is_map() {
123122
Contract contract = new Contract();
124123
Map<String, Object> map = new HashMap<>();
125-
map.put("stubMapping", new ObjectMapper().readValue(POST_SERVE_ACTION, HashMap.class));
124+
map.put("stubMapping", new JsonMapper().readValue(POST_SERVE_ACTION, HashMap.class));
126125
contract.getMetadata().put("wiremock", map);
127126
StubMapping stubMapping = StubMapping.buildFrom(STUB_MAPPING);
128127

0 commit comments

Comments
 (0)