Skip to content

Commit 138b0d0

Browse files
committed
YamlProcessor embraces SnakeYAML 1.18+ duplicate key handling
Includes removal of StrictMapAppenderConstructor for compatibility with SnakeYAML 1.21. Issue: SPR-16791
1 parent 7b894fe commit 138b0d0

File tree

7 files changed

+97
-200
lines changed

7 files changed

+97
-200
lines changed

spring-beans/spring-beans.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ apply plugin: "groovy"
55
dependencies {
66
compile(project(':spring-core'))
77
optional("javax.inject:javax.inject:1")
8-
optional("org.yaml:snakeyaml:1.20")
8+
optional("org.yaml:snakeyaml:1.21")
99
optional("org.codehaus.groovy:groovy-all:${groovyVersion}")
1010
optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
1111
optional("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")

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: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,18 @@
1818

1919
import java.io.IOException;
2020
import java.io.Reader;
21-
import java.util.AbstractMap;
2221
import java.util.Arrays;
2322
import java.util.Collection;
2423
import java.util.Collections;
2524
import java.util.LinkedHashMap;
2625
import java.util.List;
2726
import java.util.Map;
2827
import java.util.Properties;
29-
import java.util.Set;
3028

3129
import org.apache.commons.logging.Log;
3230
import org.apache.commons.logging.LogFactory;
31+
import org.yaml.snakeyaml.LoaderOptions;
3332
import org.yaml.snakeyaml.Yaml;
34-
import org.yaml.snakeyaml.constructor.Constructor;
35-
import org.yaml.snakeyaml.nodes.MappingNode;
36-
import org.yaml.snakeyaml.parser.ParserException;
3733
import org.yaml.snakeyaml.reader.UnicodeReader;
3834

3935
import org.springframework.core.CollectionFactory;
@@ -45,6 +41,8 @@
4541
/**
4642
* Base class for YAML factories.
4743
*
44+
* <p>Requires SnakeYAML 1.18 or higher, as of Spring Framework 5.0.6.
45+
*
4846
* @author Dave Syer
4947
* @author Juergen Hoeller
5048
* @since 4.1
@@ -144,9 +142,14 @@ protected void process(MatchCallback callback) {
144142

145143
/**
146144
* Create the {@link Yaml} instance to use.
145+
* <p>The default implementation sets the "allowDuplicateKeys" flag to {@code false},
146+
* enabling built-in duplicate key handling in SnakeYAML 1.18+.
147+
* @see LoaderOptions#setAllowDuplicateKeys(boolean)
147148
*/
148149
protected Yaml createYaml() {
149-
return new Yaml(new StrictMapAppenderConstructor());
150+
LoaderOptions options = new LoaderOptions();
151+
options.setAllowDuplicateKeys(false);
152+
return new Yaml(options);
150153
}
151154

152155
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -390,45 +393,4 @@ public enum ResolutionMethod {
390393
FIRST_FOUND
391394
}
392395

393-
394-
/**
395-
* A specialized {@link Constructor} that checks for duplicate keys.
396-
*/
397-
protected static class StrictMapAppenderConstructor extends Constructor {
398-
399-
// Declared as public for use in subclasses
400-
public StrictMapAppenderConstructor() {
401-
super();
402-
}
403-
404-
@Override
405-
protected Map<Object, Object> constructMapping(MappingNode node) {
406-
try {
407-
return super.constructMapping(node);
408-
}
409-
catch (IllegalStateException ex) {
410-
throw new ParserException("while parsing MappingNode",
411-
node.getStartMark(), ex.getMessage(), node.getEndMark());
412-
}
413-
}
414-
415-
@Override
416-
protected Map<Object, Object> createDefaultMap() {
417-
final Map<Object, Object> delegate = super.createDefaultMap();
418-
return new AbstractMap<Object, Object>() {
419-
@Override
420-
public Object put(Object key, Object value) {
421-
if (delegate.containsKey(key)) {
422-
throw new IllegalStateException("Duplicate key: " + key);
423-
}
424-
return delegate.put(key, value);
425-
}
426-
@Override
427-
public Set<Entry<Object, Object>> entrySet() {
428-
return delegate.entrySet();
429-
}
430-
};
431-
}
432-
}
433-
434396
}

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
}

0 commit comments

Comments
 (0)