Skip to content

Commit 0f516d9

Browse files
Remove commons-beanutils usage
1 parent 095557b commit 0f516d9

5 files changed

Lines changed: 190 additions & 12 deletions

File tree

pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@
6868
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
6969
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
7070

71-
<commons-beanutils.version>1.9.4</commons-beanutils.version>
7271
<sisu-inject-plexus.version>2.6.0</sisu-inject-plexus.version>
7372

7473
<javadoc.failOnError>false</javadoc.failOnError>

spring-cloud-contract-verifier/pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,6 @@
154154
<groupId>com.github.jknack</groupId>
155155
<artifactId>handlebars</artifactId>
156156
</dependency>
157-
<dependency>
158-
<groupId>commons-beanutils</groupId>
159-
<artifactId>commons-beanutils</artifactId>
160-
<version>${commons-beanutils.version}</version>
161-
</dependency>
162157
<dependency>
163158
<groupId>org.yaml</groupId>
164159
<artifactId>snakeyaml</artifactId>

spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/JsonBodyVerificationBuilder.java

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.cloud.contract.verifier.builder;
1818

1919
import java.math.BigDecimal;
20+
import java.lang.reflect.Array;
21+
import java.util.ArrayList;
2022
import java.util.List;
2123
import java.util.Map;
2224
import java.util.Optional;
@@ -26,8 +28,7 @@
2628
import com.jayway.jsonpath.JsonPath;
2729
import com.jayway.jsonpath.PathNotFoundException;
2830
import groovy.json.JsonOutput;
29-
import org.apache.commons.beanutils.PropertyUtilsBean;
30-
31+
import org.springframework.beans.BeanWrapperImpl;
3132
import org.springframework.cloud.contract.spec.Contract;
3233
import org.springframework.cloud.contract.spec.ContractTemplate;
3334
import org.springframework.cloud.contract.spec.internal.BodyMatcher;
@@ -298,7 +299,7 @@ private static Object retrieveObjectByPath(Object body, String path) {
298299
+ contractTemplate.escapedClosingTemplate();
299300
}
300301
try {
301-
Object result = new PropertyUtilsBean().getProperty(templateModel, justEntry);
302+
Object result = resolveTemplateModelEntry(templateModel, justEntry);
302303
// Path from the Test model is an object and we'd like to return its
303304
// String representation
304305
if (FROM_REQUEST_PATH.equals(justEntry)) {
@@ -314,6 +315,105 @@ private static Object retrieveObjectByPath(Object body, String path) {
314315
};
315316
}
316317

318+
private Object resolveTemplateModelEntry(TestSideRequestTemplateModel templateModel, String propertyPath) {
319+
Object current = templateModel;
320+
for (String token : tokenizePropertyPath(propertyPath)) {
321+
current = resolveNextToken(current, token);
322+
}
323+
return current;
324+
}
325+
326+
private Object resolveNextToken(Object current, String token) {
327+
if (current == null) {
328+
throw new IllegalStateException("Unable to resolve property for null value");
329+
}
330+
if (current instanceof Map) {
331+
Map<?, ?> map = (Map<?, ?>) current;
332+
String key = unquote(token);
333+
if (!map.containsKey(key)) {
334+
throw new IllegalStateException("Missing map key [" + key + "]");
335+
}
336+
return map.get(key);
337+
}
338+
if (current instanceof List) {
339+
int index = parseIndex(token);
340+
List<?> list = (List<?>) current;
341+
if (index < 0 || index >= list.size()) {
342+
throw new IllegalStateException("Index [" + index + "] out of bounds");
343+
}
344+
return list.get(index);
345+
}
346+
if (current.getClass().isArray()) {
347+
int index = parseIndex(token);
348+
int length = Array.getLength(current);
349+
if (index < 0 || index >= length) {
350+
throw new IllegalStateException("Index [" + index + "] out of bounds");
351+
}
352+
return Array.get(current, index);
353+
}
354+
BeanWrapperImpl wrapper = new BeanWrapperImpl(current);
355+
if (!wrapper.isReadableProperty(token)) {
356+
throw new IllegalStateException("No readable property [" + token + "]");
357+
}
358+
return wrapper.getPropertyValue(token);
359+
}
360+
361+
private int parseIndex(String token) {
362+
try {
363+
return Integer.parseInt(token);
364+
}
365+
catch (NumberFormatException ex) {
366+
throw new IllegalStateException("Invalid index token [" + token + "]", ex);
367+
}
368+
}
369+
370+
private List<String> tokenizePropertyPath(String propertyPath) {
371+
List<String> tokens = new ArrayList<>();
372+
String[] segments = propertyPath.split("\\.");
373+
for (String segment : segments) {
374+
addTokens(segment, tokens);
375+
}
376+
return tokens;
377+
}
378+
379+
private void addTokens(String segment, List<String> tokens) {
380+
int index = 0;
381+
while (index < segment.length()) {
382+
int bracketStart = segment.indexOf('[', index);
383+
if (bracketStart == -1) {
384+
String token = segment.substring(index);
385+
if (!token.isEmpty()) {
386+
tokens.add(token);
387+
}
388+
return;
389+
}
390+
String before = segment.substring(index, bracketStart);
391+
if (!before.isEmpty()) {
392+
tokens.add(before);
393+
}
394+
int bracketEnd = segment.indexOf(']', bracketStart);
395+
if (bracketEnd == -1) {
396+
String remainder = segment.substring(bracketStart + 1);
397+
if (!remainder.isEmpty()) {
398+
tokens.add(remainder);
399+
}
400+
return;
401+
}
402+
String inside = segment.substring(bracketStart + 1, bracketEnd);
403+
if (!inside.isEmpty()) {
404+
tokens.add(inside);
405+
}
406+
index = bracketEnd + 1;
407+
}
408+
}
409+
410+
private String unquote(String value) {
411+
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) {
412+
return value.substring(1, value.length() - 1);
413+
}
414+
return value;
415+
}
416+
317417
private static String minus(CharSequence self, Object target) {
318418
String s = self.toString();
319419
String text = target.toString();

spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/converter/YamlToContracts.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040

4141
import com.fasterxml.jackson.databind.ObjectMapper;
4242
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
43-
import org.apache.commons.collections.MapUtils;
4443
import org.yaml.snakeyaml.Yaml;
4544

4645
import org.springframework.cloud.contract.spec.Contract;
@@ -228,7 +227,7 @@ private void handleQueryParameters(YamlContract yamlContract, Url url) {
228227

229228
private void mapRequestHeaders(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
230229
Map<String, Object> yamlContractRequestHeaders = yamlContractRequest.headers;
231-
if (MapUtils.isNotEmpty(yamlContractRequestHeaders)) {
230+
if (yamlContractRequestHeaders != null && !yamlContractRequestHeaders.isEmpty()) {
232231
dslContractRequest.headers((headers) -> yamlContractRequestHeaders.forEach((key, value) -> {
233232
List<YamlContract.KeyValueMatcher> matchers = yamlContractRequest.matchers.headers.stream()
234233
.filter((header) -> header.key.equals(key))
@@ -252,7 +251,7 @@ private void mapRequestHeaders(YamlContract.Request yamlContractRequest, Request
252251

253252
private void mapRequestCookies(YamlContract.Request yamlContractRequest, Request dslContractRequest) {
254253
Map<String, Object> yamlContractRequestCookies = yamlContractRequest.cookies;
255-
if (MapUtils.isNotEmpty(yamlContractRequestCookies)) {
254+
if (yamlContractRequestCookies != null && !yamlContractRequestCookies.isEmpty()) {
256255
dslContractRequest.cookies((cookies) -> yamlContractRequestCookies.forEach((key, value) -> {
257256
YamlContract.KeyValueMatcher matcher = yamlContractRequest.matchers.cookies.stream()
258257
.filter(cookie -> cookie.key.equals(key))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.springframework.cloud.contract.verifier.builder;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import java.util.function.Function;
7+
8+
import org.junit.Test;
9+
10+
import org.springframework.cloud.contract.spec.Contract;
11+
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
12+
import org.springframework.cloud.contract.verifier.template.HandlebarsTemplateProcessor;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
16+
class JsonBodyVerificationBuilderTest {
17+
18+
@Test
19+
public void should_resolve_request_template_values_when_body_present() {
20+
// given
21+
Contract contract = contractWithRequest();
22+
JsonBodyVerificationBuilder builder = jsonBuilder(contract);
23+
Map<String, Object> responseBody = new HashMap<>();
24+
responseBody.put("auth0", "{{{ request.headers.Authorization.0 }}}");
25+
responseBody.put("auth1", "{{{ request.headers.Authorization.[1] }}}");
26+
responseBody.put("param", "{{{ request.query.foo.1 }}}");
27+
responseBody.put("path", "{{{ request.path.1 }}}");
28+
29+
// when
30+
Object converted = builder.addJsonResponseBodyCheck(new BlockBuilder(" "), responseBody, new BodyMatchers(),
31+
"\"{}\"", true);
32+
33+
// then
34+
assertThat(converted).isInstanceOf(Map.class);
35+
Map<?, ?> convertedMap = (Map<?, ?>) converted;
36+
assertThat(convertedMap.get("auth0")).isEqualTo("alpha");
37+
assertThat(convertedMap.get("auth1")).isEqualTo("beta");
38+
assertThat(convertedMap.get("param")).isEqualTo("baz");
39+
assertThat(convertedMap.get("path")).isEqualTo("12");
40+
}
41+
42+
@Test
43+
public void should_keep_template_entry_when_property_missing() {
44+
// given
45+
Contract contract = contractWithRequest();
46+
JsonBodyVerificationBuilder builder = jsonBuilder(contract);
47+
Map<String, Object> responseBody = new HashMap<>();
48+
String templateEntry = "{{{ request.headers.Missing.0 }}}";
49+
responseBody.put("missing", templateEntry);
50+
51+
// when
52+
Object converted = builder.addJsonResponseBodyCheck(new BlockBuilder(" "), responseBody, new BodyMatchers(),
53+
"\"{}\"", true);
54+
55+
// then
56+
Map<?, ?> convertedMap = (Map<?, ?>) converted;
57+
assertThat(convertedMap.get("missing")).isEqualTo(templateEntry);
58+
}
59+
60+
private JsonBodyVerificationBuilder jsonBuilder(Contract contract) {
61+
HandlebarsTemplateProcessor templateProcessor = new HandlebarsTemplateProcessor();
62+
return new JsonBodyVerificationBuilder(false, templateProcessor, templateProcessor, contract, Optional.empty(),
63+
Function.identity());
64+
}
65+
66+
private Contract contractWithRequest() {
67+
Contract contract = new Contract();
68+
contract.request(request -> {
69+
request.method("GET");
70+
request.url("/users/12", url -> url.queryParameters(query -> {
71+
query.parameter("foo", "bar");
72+
query.parameter("foo", "baz");
73+
}));
74+
request.headers(headers -> {
75+
headers.header("Authorization", "alpha");
76+
headers.header("Authorization", "beta");
77+
});
78+
Map<String, Object> requestBody = new HashMap<>();
79+
requestBody.put("key", "value");
80+
request.body(requestBody);
81+
});
82+
return contract;
83+
}
84+
85+
}

0 commit comments

Comments
 (0)