Skip to content

Commit f6275e0

Browse files
committed
YamlProcessor embraces SnakeYAML 1.18+ duplicate key handling
Includes deprecation of StrictMapAppenderConstructor. Issue: SPR-16791 (cherry picked from commit 138b0d0)
1 parent ed44262 commit f6275e0

File tree

6 files changed

+99
-153
lines changed

6 files changed

+99
-153
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,6 +64,8 @@
6464
* Note that the value of "foo" in the first document is not simply replaced
6565
* with the value in the second, but its nested values are merged.
6666
*
67+
* <p>Requires SnakeYAML 1.18 or higher, as of Spring Framework 5.0.6.
68+
*
6769
* @author Dave Syer
6870
* @author Juergen Hoeller
6971
* @since 4.1

spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import org.apache.commons.logging.Log;
3232
import org.apache.commons.logging.LogFactory;
33+
import org.yaml.snakeyaml.LoaderOptions;
3334
import org.yaml.snakeyaml.Yaml;
3435
import org.yaml.snakeyaml.constructor.Constructor;
3536
import org.yaml.snakeyaml.nodes.MappingNode;
@@ -45,6 +46,8 @@
4546
/**
4647
* Base class for YAML factories.
4748
*
49+
* <p>Requires SnakeYAML 1.18 or higher, as of Spring Framework 5.0.6.
50+
*
4851
* @author Dave Syer
4952
* @author Juergen Hoeller
5053
* @since 4.1
@@ -144,9 +147,14 @@ protected void process(MatchCallback callback) {
144147

145148
/**
146149
* Create the {@link Yaml} instance to use.
150+
* <p>The default implementation sets the "allowDuplicateKeys" flag to {@code false},
151+
* enabling built-in duplicate key handling in SnakeYAML 1.18+.
152+
* @see LoaderOptions#setAllowDuplicateKeys(boolean)
147153
*/
148154
protected Yaml createYaml() {
149-
return new Yaml(new StrictMapAppenderConstructor());
155+
LoaderOptions options = new LoaderOptions();
156+
options.setAllowDuplicateKeys(false);
157+
return new Yaml(options);
150158
}
151159

152160
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -393,7 +401,10 @@ public enum ResolutionMethod {
393401

394402
/**
395403
* A specialized {@link Constructor} that checks for duplicate keys.
404+
* @deprecated as of Spring Framework 5.0.6 (not used anymore here),
405+
* superseded by SnakeYAML's own duplicate key handling
396406
*/
407+
@Deprecated
397408
protected static class StrictMapAppenderConstructor extends Constructor {
398409

399410
// Declared as public for use in subclasses

spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -74,6 +74,8 @@
7474
* servers[1]=foo.bar.com
7575
* </pre>
7676
*
77+
* <p>Requires SnakeYAML 1.18 or higher, as of Spring Framework 5.0.6.
78+
*
7779
* @author Dave Syer
7880
* @author Stephane Nicoll
7981
* @author Juergen Hoeller

spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121
import java.util.LinkedHashMap;
22-
import java.util.List;
2322
import java.util.Map;
2423

2524
import org.junit.Test;
25+
import org.yaml.snakeyaml.constructor.DuplicateKeyException;
2626

2727
import org.springframework.core.io.AbstractResource;
2828
import org.springframework.core.io.ByteArrayResource;
@@ -42,27 +42,27 @@ public class YamlMapFactoryBeanTests {
4242

4343

4444
@Test
45-
public void testSetIgnoreResourceNotFound() throws Exception {
45+
public void testSetIgnoreResourceNotFound() {
4646
this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
4747
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
4848
assertEquals(0, this.factory.getObject().size());
4949
}
5050

5151
@Test(expected = IllegalStateException.class)
52-
public void testSetBarfOnResourceNotFound() throws Exception {
52+
public void testSetBarfOnResourceNotFound() {
5353
this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
5454
assertEquals(0, this.factory.getObject().size());
5555
}
5656

5757
@Test
58-
public void testGetObject() throws Exception {
58+
public void testGetObject() {
5959
this.factory.setResources(new ByteArrayResource("foo: bar".getBytes()));
6060
assertEquals(1, this.factory.getObject().size());
6161
}
6262

6363
@SuppressWarnings("unchecked")
6464
@Test
65-
public void testOverrideAndRemoveDefaults() throws Exception {
65+
public void testOverrideAndRemoveDefaults() {
6666
this.factory.setResources(new ByteArrayResource("foo:\n bar: spam".getBytes()),
6767
new ByteArrayResource("foo:\n spam: bar".getBytes()));
6868

@@ -71,7 +71,7 @@ public void testOverrideAndRemoveDefaults() throws Exception {
7171
}
7272

7373
@Test
74-
public void testFirstFound() throws Exception {
74+
public void testFirstFound() {
7575
this.factory.setResolutionMethod(YamlProcessor.ResolutionMethod.FIRST_FOUND);
7676
this.factory.setResources(new AbstractResource() {
7777
@Override
@@ -88,7 +88,7 @@ public InputStream getInputStream() throws IOException {
8888
}
8989

9090
@Test
91-
public void testMapWithPeriodsInKey() throws Exception {
91+
public void testMapWithPeriodsInKey() {
9292
this.factory.setResources(new ByteArrayResource("foo:\n ? key1.key2\n : value".getBytes()));
9393
Map<String, Object> map = this.factory.getObject();
9494

@@ -103,7 +103,7 @@ public void testMapWithPeriodsInKey() throws Exception {
103103
}
104104

105105
@Test
106-
public void testMapWithIntegerValue() throws Exception {
106+
public void testMapWithIntegerValue() {
107107
this.factory.setResources(new ByteArrayResource("foo:\n ? key1.key2\n : 3".getBytes()));
108108
Map<String, Object> map = this.factory.getObject();
109109

@@ -117,33 +117,10 @@ public void testMapWithIntegerValue() throws Exception {
117117
assertEquals(Integer.valueOf(3), sub.get("key1.key2"));
118118
}
119119

120-
@Test
121-
public void mapWithEmptyArrayValue() {
122-
this.factory.setResources(new ByteArrayResource("a: alpha\ntest: []".getBytes()));
123-
assertTrue(this.factory.getObject().containsKey("test"));
124-
assertEquals(((List<?>)this.factory.getObject().get("test")).size(), 0);
125-
}
126-
127-
@Test
128-
public void mapWithEmptyValue() {
129-
this.factory.setResources(new ByteArrayResource("a: alpha\ntest:".getBytes()));
130-
assertTrue(this.factory.getObject().containsKey("test"));
131-
assertNull(this.factory.getObject().get("test"));
132-
}
133-
134-
@Test
135-
public void testDuplicateKey() throws Exception {
120+
@Test(expected = DuplicateKeyException.class)
121+
public void testDuplicateKey() {
136122
this.factory.setResources(new ByteArrayResource("mymap:\n foo: bar\nmymap:\n bar: foo".getBytes()));
137-
Map<String, Object> map = this.factory.getObject();
138-
139-
assertEquals(1, map.size());
140-
assertTrue(map.containsKey("mymap"));
141-
Object object = map.get("mymap");
142-
assertTrue(object instanceof LinkedHashMap);
143-
@SuppressWarnings("unchecked")
144-
Map<String, Object> sub = (Map<String, Object>) object;
145-
assertEquals(1, sub.size());
146-
assertEquals("foo", sub.get("bar"));
123+
this.factory.getObject().get("mymap");
147124
}
148125

149126
}

spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java

Lines changed: 35 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
1818

1919
import java.util.LinkedHashMap;
2020
import java.util.Map;
21-
import java.util.Properties;
2221

2322
import org.junit.Rule;
2423
import org.junit.Test;
@@ -29,7 +28,6 @@
2928
import org.springframework.core.io.ByteArrayResource;
3029

3130
import static org.junit.Assert.*;
32-
import static org.springframework.beans.factory.config.YamlProcessor.*;
3331

3432
/**
3533
* Tests for {@link YamlProcessor}.
@@ -48,110 +46,81 @@ public class YamlProcessorTests {
4846
@Test
4947
public void arrayConvertedToIndexedBeanReference() {
5048
this.processor.setResources(new ByteArrayResource("foo: bar\nbar: [1,2,3]".getBytes()));
51-
this.processor.process(new MatchCallback() {
52-
@Override
53-
public void process(Properties properties, Map<String, Object> map) {
54-
assertEquals(4, properties.size());
55-
assertEquals("bar", properties.get("foo"));
56-
assertEquals("bar", properties.getProperty("foo"));
57-
assertEquals(1, properties.get("bar[0]"));
58-
assertEquals("1", properties.getProperty("bar[0]"));
59-
assertEquals(2, properties.get("bar[1]"));
60-
assertEquals("2", properties.getProperty("bar[1]"));
61-
assertEquals(3, properties.get("bar[2]"));
62-
assertEquals("3", properties.getProperty("bar[2]"));
63-
}
49+
this.processor.process((properties, map) -> {
50+
assertEquals(4, properties.size());
51+
assertEquals("bar", properties.get("foo"));
52+
assertEquals("bar", properties.getProperty("foo"));
53+
assertEquals(1, properties.get("bar[0]"));
54+
assertEquals("1", properties.getProperty("bar[0]"));
55+
assertEquals(2, properties.get("bar[1]"));
56+
assertEquals("2", properties.getProperty("bar[1]"));
57+
assertEquals(3, properties.get("bar[2]"));
58+
assertEquals("3", properties.getProperty("bar[2]"));
6459
});
6560
}
6661

6762
@Test
68-
public void testStringResource() throws Exception {
63+
public void testStringResource() {
6964
this.processor.setResources(new ByteArrayResource("foo # a document that is a literal".getBytes()));
70-
this.processor.process(new MatchCallback() {
71-
@Override
72-
public void process(Properties properties, Map<String, Object> map) {
73-
assertEquals("foo", map.get("document"));
74-
}
75-
});
65+
this.processor.process((properties, map) -> assertEquals("foo", map.get("document")));
7666
}
7767

7868
@Test
79-
public void testBadDocumentStart() throws Exception {
69+
public void testBadDocumentStart() {
8070
this.processor.setResources(new ByteArrayResource("foo # a document\nbar: baz".getBytes()));
8171
this.exception.expect(ParserException.class);
8272
this.exception.expectMessage("line 2, column 1");
83-
this.processor.process(new MatchCallback() {
84-
@Override
85-
public void process(Properties properties, Map<String, Object> map) {
86-
}
87-
});
73+
this.processor.process((properties, map) -> {});
8874
}
8975

9076
@Test
91-
public void testBadResource() throws Exception {
77+
public void testBadResource() {
9278
this.processor.setResources(new ByteArrayResource("foo: bar\ncd\nspam:\n foo: baz".getBytes()));
9379
this.exception.expect(ScannerException.class);
9480
this.exception.expectMessage("line 3, column 1");
95-
this.processor.process(new MatchCallback() {
96-
@Override
97-
public void process(Properties properties, Map<String, Object> map) {
98-
}
99-
});
81+
this.processor.process((properties, map) -> {});
10082
}
10183

10284
@Test
10385
public void mapConvertedToIndexedBeanReference() {
10486
this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes()));
105-
this.processor.process(new MatchCallback() {
106-
@Override
107-
public void process(Properties properties, Map<String, Object> map) {
108-
// System.err.println(properties);
109-
assertEquals("bucket", properties.get("bar.spam"));
110-
assertEquals(2, properties.size());
111-
}
87+
this.processor.process((properties, map) -> {
88+
assertEquals("bucket", properties.get("bar.spam"));
89+
assertEquals(2, properties.size());
11290
});
11391
}
11492

11593
@Test
11694
public void integerKeyBehaves() {
11795
this.processor.setResources(new ByteArrayResource("foo: bar\n1: bar".getBytes()));
118-
this.processor.process(new MatchCallback() {
119-
@Override
120-
public void process(Properties properties, Map<String, Object> map) {
121-
assertEquals("bar", properties.get("[1]"));
122-
assertEquals(2, properties.size());
123-
}
96+
this.processor.process((properties, map) -> {
97+
assertEquals("bar", properties.get("[1]"));
98+
assertEquals(2, properties.size());
12499
});
125100
}
126101

127102
@Test
128103
public void integerDeepKeyBehaves() {
129104
this.processor.setResources(new ByteArrayResource("foo:\n 1: bar".getBytes()));
130-
this.processor.process(new MatchCallback() {
131-
@Override
132-
public void process(Properties properties, Map<String, Object> map) {
133-
assertEquals("bar", properties.get("foo[1]"));
134-
assertEquals(1, properties.size());
135-
}
105+
this.processor.process((properties, map) -> {
106+
assertEquals("bar", properties.get("foo[1]"));
107+
assertEquals(1, properties.size());
136108
});
137109
}
138110

139111
@Test
140112
@SuppressWarnings("unchecked")
141113
public void flattenedMapIsSameAsPropertiesButOrdered() {
142114
this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes()));
143-
this.processor.process(new MatchCallback() {
144-
@Override
145-
public void process(Properties properties, Map<String, Object> map) {
146-
assertEquals("bucket", properties.get("bar.spam"));
147-
assertEquals(2, properties.size());
148-
Map<String, Object> flattenedMap = processor.getFlattenedMap(map);
149-
assertEquals("bucket", flattenedMap.get("bar.spam"));
150-
assertEquals(2, flattenedMap.size());
151-
assertTrue(flattenedMap instanceof LinkedHashMap);
152-
Map<String, Object> bar = (Map<String, Object>) map.get("bar");
153-
assertEquals("bucket", bar.get("spam"));
154-
}
115+
this.processor.process((properties, map) -> {
116+
assertEquals("bucket", properties.get("bar.spam"));
117+
assertEquals(2, properties.size());
118+
Map<String, Object> flattenedMap = processor.getFlattenedMap(map);
119+
assertEquals("bucket", flattenedMap.get("bar.spam"));
120+
assertEquals(2, flattenedMap.size());
121+
assertTrue(flattenedMap instanceof LinkedHashMap);
122+
Map<String, Object> bar = (Map<String, Object>) map.get("bar");
123+
assertEquals("bucket", bar.get("spam"));
155124
});
156125
}
157126

0 commit comments

Comments
 (0)