Skip to content

Commit 5f9e951

Browse files
committed
Merge branch '5.2.x'
2 parents 7e2b817 + 7682575 commit 5f9e951

File tree

2 files changed

+126
-22
lines changed

2 files changed

+126
-22
lines changed

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

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -25,17 +25,23 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Properties;
28+
import java.util.Set;
29+
import java.util.stream.Collectors;
2830

2931
import org.apache.commons.logging.Log;
3032
import org.apache.commons.logging.LogFactory;
33+
import org.yaml.snakeyaml.DumperOptions;
3134
import org.yaml.snakeyaml.LoaderOptions;
3235
import org.yaml.snakeyaml.Yaml;
36+
import org.yaml.snakeyaml.constructor.Constructor;
3337
import org.yaml.snakeyaml.reader.UnicodeReader;
38+
import org.yaml.snakeyaml.representer.Representer;
3439

3540
import org.springframework.core.CollectionFactory;
3641
import org.springframework.core.io.Resource;
3742
import org.springframework.lang.Nullable;
3843
import org.springframework.util.Assert;
44+
import org.springframework.util.ObjectUtils;
3945
import org.springframework.util.StringUtils;
4046

4147
/**
@@ -45,6 +51,7 @@
4551
*
4652
* @author Dave Syer
4753
* @author Juergen Hoeller
54+
* @author Sam Brannen
4855
* @since 4.1
4956
*/
5057
public abstract class YamlProcessor {
@@ -59,6 +66,8 @@ public abstract class YamlProcessor {
5966

6067
private boolean matchDefault = true;
6168

69+
private Set<String> supportedTypes = Collections.emptySet();
70+
6271

6372
/**
6473
* A map of document matchers allowing callers to selectively use only
@@ -117,6 +126,27 @@ public void setResources(Resource... resources) {
117126
this.resources = resources;
118127
}
119128

129+
/**
130+
* Set the supported types that can be loaded from YAML documents.
131+
* <p>If no supported types are configured, all types encountered in YAML
132+
* documents will be supported. If an unsupported type is encountered, an
133+
* {@link IllegalStateException} will be thrown when the corresponding YAML
134+
* node is processed.
135+
* @param supportedTypes the supported types, or an empty array to clear the
136+
* supported types
137+
* @since 5.1.16
138+
* @see #createYaml()
139+
*/
140+
public void setSupportedTypes(Class<?>... supportedTypes) {
141+
if (ObjectUtils.isEmpty(supportedTypes)) {
142+
this.supportedTypes = Collections.emptySet();
143+
}
144+
else {
145+
Assert.noNullElements(supportedTypes, "'supportedTypes' must not contain null elements");
146+
this.supportedTypes = Arrays.stream(supportedTypes).map(Class::getName)
147+
.collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
148+
}
149+
}
120150

121151
/**
122152
* Provide an opportunity for subclasses to process the Yaml parsed from the supplied
@@ -142,12 +172,22 @@ protected void process(MatchCallback callback) {
142172
* Create the {@link Yaml} instance to use.
143173
* <p>The default implementation sets the "allowDuplicateKeys" flag to {@code false},
144174
* enabling built-in duplicate key handling in SnakeYAML 1.18+.
175+
* <p>As of Spring Framework 5.1.16, if custom {@linkplain #setSupportedTypes
176+
* supported types} have been configured, the default implementation creates
177+
* a {@code Yaml} instance that filters out unsupported types encountered in
178+
* YAML documents. If an unsupported type is encountered, an
179+
* {@link IllegalStateException} will be thrown when the node is processed.
145180
* @see LoaderOptions#setAllowDuplicateKeys(boolean)
146181
*/
147182
protected Yaml createYaml() {
148-
LoaderOptions options = new LoaderOptions();
149-
options.setAllowDuplicateKeys(false);
150-
return new Yaml(options);
183+
LoaderOptions loaderOptions = new LoaderOptions();
184+
loaderOptions.setAllowDuplicateKeys(false);
185+
186+
if (!this.supportedTypes.isEmpty()) {
187+
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
188+
new DumperOptions(), loaderOptions);
189+
}
190+
return new Yaml(loaderOptions);
151191
}
152192

153193
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -388,4 +428,26 @@ public enum ResolutionMethod {
388428
FIRST_FOUND
389429
}
390430

431+
432+
/**
433+
* {@link Constructor} that supports filtering of unsupported types.
434+
* <p>If an unsupported type is encountered in a YAML document, an
435+
* {@link IllegalStateException} will be thrown from {@link #getClassForName(String)}.
436+
* @since 5.1.16
437+
*/
438+
private class FilteringConstructor extends Constructor {
439+
440+
FilteringConstructor(LoaderOptions loaderOptions) {
441+
super(loaderOptions);
442+
}
443+
444+
445+
@Override
446+
protected Class<?> getClassForName(String name) throws ClassNotFoundException {
447+
Assert.state(YamlProcessor.this.supportedTypes.contains(name),
448+
() -> "Unsupported type encountered in YAML document: " + name);
449+
return super.getClassForName(name);
450+
}
451+
}
452+
391453
}

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

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -16,11 +16,13 @@
1616

1717
package org.springframework.beans.factory.config;
1818

19+
import java.net.URL;
1920
import java.util.LinkedHashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223

2324
import org.junit.jupiter.api.Test;
25+
import org.yaml.snakeyaml.constructor.ConstructorException;
2426
import org.yaml.snakeyaml.parser.ParserException;
2527
import org.yaml.snakeyaml.scanner.ScannerException;
2628

@@ -29,6 +31,7 @@
2931
import static java.util.stream.Collectors.toList;
3032
import static org.assertj.core.api.Assertions.assertThat;
3133
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
34+
import static org.assertj.core.api.Assertions.entry;
3235

3336
/**
3437
* Tests for {@link YamlProcessor}.
@@ -37,14 +40,14 @@
3740
* @author Juergen Hoeller
3841
* @author Sam Brannen
3942
*/
40-
public class YamlProcessorTests {
43+
class YamlProcessorTests {
4144

4245
private final YamlProcessor processor = new YamlProcessor() {};
4346

4447

4548
@Test
46-
public void arrayConvertedToIndexedBeanReference() {
47-
this.processor.setResources(new ByteArrayResource("foo: bar\nbar: [1,2,3]".getBytes()));
49+
void arrayConvertedToIndexedBeanReference() {
50+
setYaml("foo: bar\nbar: [1,2,3]");
4851
this.processor.process((properties, map) -> {
4952
assertThat(properties.size()).isEqualTo(4);
5053
assertThat(properties.get("foo")).isEqualTo("bar");
@@ -59,48 +62,48 @@ public void arrayConvertedToIndexedBeanReference() {
5962
}
6063

6164
@Test
62-
public void stringResource() {
63-
this.processor.setResources(new ByteArrayResource("foo # a document that is a literal".getBytes()));
65+
void stringResource() {
66+
setYaml("foo # a document that is a literal");
6467
this.processor.process((properties, map) -> assertThat(map.get("document")).isEqualTo("foo"));
6568
}
6669

6770
@Test
68-
public void badDocumentStart() {
69-
this.processor.setResources(new ByteArrayResource("foo # a document\nbar: baz".getBytes()));
71+
void badDocumentStart() {
72+
setYaml("foo # a document\nbar: baz");
7073
assertThatExceptionOfType(ParserException.class)
7174
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
7275
.withMessageContaining("line 2, column 1");
7376
}
7477

7578
@Test
76-
public void badResource() {
77-
this.processor.setResources(new ByteArrayResource("foo: bar\ncd\nspam:\n foo: baz".getBytes()));
79+
void badResource() {
80+
setYaml("foo: bar\ncd\nspam:\n foo: baz");
7881
assertThatExceptionOfType(ScannerException.class)
7982
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
8083
.withMessageContaining("line 3, column 1");
8184
}
8285

8386
@Test
84-
public void mapConvertedToIndexedBeanReference() {
85-
this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes()));
87+
void mapConvertedToIndexedBeanReference() {
88+
setYaml("foo: bar\nbar:\n spam: bucket");
8689
this.processor.process((properties, map) -> {
8790
assertThat(properties.get("bar.spam")).isEqualTo("bucket");
8891
assertThat(properties).hasSize(2);
8992
});
9093
}
9194

9295
@Test
93-
public void integerKeyBehaves() {
94-
this.processor.setResources(new ByteArrayResource("foo: bar\n1: bar".getBytes()));
96+
void integerKeyBehaves() {
97+
setYaml("foo: bar\n1: bar");
9598
this.processor.process((properties, map) -> {
9699
assertThat(properties.get("[1]")).isEqualTo("bar");
97100
assertThat(properties).hasSize(2);
98101
});
99102
}
100103

101104
@Test
102-
public void integerDeepKeyBehaves() {
103-
this.processor.setResources(new ByteArrayResource("foo:\n 1: bar".getBytes()));
105+
void integerDeepKeyBehaves() {
106+
setYaml("foo:\n 1: bar");
104107
this.processor.process((properties, map) -> {
105108
assertThat(properties.get("foo[1]")).isEqualTo("bar");
106109
assertThat(properties).hasSize(1);
@@ -109,8 +112,8 @@ public void integerDeepKeyBehaves() {
109112

110113
@Test
111114
@SuppressWarnings("unchecked")
112-
public void flattenedMapIsSameAsPropertiesButOrdered() {
113-
this.processor.setResources(new ByteArrayResource("cat: dog\nfoo: bar\nbar:\n spam: bucket".getBytes()));
115+
void flattenedMapIsSameAsPropertiesButOrdered() {
116+
setYaml("cat: dog\nfoo: bar\nbar:\n spam: bucket");
114117
this.processor.process((properties, map) -> {
115118
Map<String, Object> flattenedMap = processor.getFlattenedMap(map);
116119
assertThat(flattenedMap).isInstanceOf(LinkedHashMap.class);
@@ -134,4 +137,43 @@ public void flattenedMapIsSameAsPropertiesButOrdered() {
134137
});
135138
}
136139

140+
@Test
141+
void customTypeSupportedByDefault() throws Exception {
142+
URL url = new URL("https://localhost:9000/");
143+
setYaml("value: !!java.net.URL [\"" + url + "\"]");
144+
145+
this.processor.process((properties, map) -> {
146+
assertThat(properties).containsExactly(entry("value", url));
147+
assertThat(map).containsExactly(entry("value", url));
148+
});
149+
}
150+
151+
@Test
152+
void customTypesSupportedDueToExplicitConfiguration() throws Exception {
153+
this.processor.setSupportedTypes(URL.class, String.class);
154+
155+
URL url = new URL("https://localhost:9000/");
156+
setYaml("value: !!java.net.URL [!!java.lang.String [\"" + url + "\"]]");
157+
158+
this.processor.process((properties, map) -> {
159+
assertThat(properties).containsExactly(entry("value", url));
160+
assertThat(map).containsExactly(entry("value", url));
161+
});
162+
}
163+
164+
@Test
165+
void customTypeNotSupportedDueToExplicitConfiguration() {
166+
this.processor.setSupportedTypes(List.class);
167+
168+
setYaml("value: !!java.net.URL [\"https://localhost:9000/\"]");
169+
170+
assertThatExceptionOfType(ConstructorException.class)
171+
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
172+
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
173+
}
174+
175+
private void setYaml(String yaml) {
176+
this.processor.setResources(new ByteArrayResource(yaml.getBytes()));
177+
}
178+
137179
}

0 commit comments

Comments
 (0)