Merge branch '5.2.x'
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,17 +25,23 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.reader.UnicodeReader;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -45,6 +51,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class YamlProcessor {
|
||||
@@ -59,6 +66,8 @@ public abstract class YamlProcessor {
|
||||
|
||||
private boolean matchDefault = true;
|
||||
|
||||
private Set<String> supportedTypes = Collections.emptySet();
|
||||
|
||||
|
||||
/**
|
||||
* A map of document matchers allowing callers to selectively use only
|
||||
@@ -117,6 +126,27 @@ public abstract class YamlProcessor {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the supported types that can be loaded from YAML documents.
|
||||
* <p>If no supported types are configured, all types encountered in YAML
|
||||
* documents will be supported. If an unsupported type is encountered, an
|
||||
* {@link IllegalStateException} will be thrown when the corresponding YAML
|
||||
* node is processed.
|
||||
* @param supportedTypes the supported types, or an empty array to clear the
|
||||
* supported types
|
||||
* @since 5.1.16
|
||||
* @see #createYaml()
|
||||
*/
|
||||
public void setSupportedTypes(Class<?>... supportedTypes) {
|
||||
if (ObjectUtils.isEmpty(supportedTypes)) {
|
||||
this.supportedTypes = Collections.emptySet();
|
||||
}
|
||||
else {
|
||||
Assert.noNullElements(supportedTypes, "'supportedTypes' must not contain null elements");
|
||||
this.supportedTypes = Arrays.stream(supportedTypes).map(Class::getName)
|
||||
.collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an opportunity for subclasses to process the Yaml parsed from the supplied
|
||||
@@ -142,12 +172,22 @@ public abstract class YamlProcessor {
|
||||
* Create the {@link Yaml} instance to use.
|
||||
* <p>The default implementation sets the "allowDuplicateKeys" flag to {@code false},
|
||||
* enabling built-in duplicate key handling in SnakeYAML 1.18+.
|
||||
* <p>As of Spring Framework 5.1.16, if custom {@linkplain #setSupportedTypes
|
||||
* supported types} have been configured, the default implementation creates
|
||||
* a {@code Yaml} instance that filters out unsupported types encountered in
|
||||
* YAML documents. If an unsupported type is encountered, an
|
||||
* {@link IllegalStateException} will be thrown when the node is processed.
|
||||
* @see LoaderOptions#setAllowDuplicateKeys(boolean)
|
||||
*/
|
||||
protected Yaml createYaml() {
|
||||
LoaderOptions options = new LoaderOptions();
|
||||
options.setAllowDuplicateKeys(false);
|
||||
return new Yaml(options);
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
loaderOptions.setAllowDuplicateKeys(false);
|
||||
|
||||
if (!this.supportedTypes.isEmpty()) {
|
||||
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
|
||||
new DumperOptions(), loaderOptions);
|
||||
}
|
||||
return new Yaml(loaderOptions);
|
||||
}
|
||||
|
||||
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
|
||||
@@ -388,4 +428,26 @@ public abstract class YamlProcessor {
|
||||
FIRST_FOUND
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link Constructor} that supports filtering of unsupported types.
|
||||
* <p>If an unsupported type is encountered in a YAML document, an
|
||||
* {@link IllegalStateException} will be thrown from {@link #getClassForName(String)}.
|
||||
* @since 5.1.16
|
||||
*/
|
||||
private class FilteringConstructor extends Constructor {
|
||||
|
||||
FilteringConstructor(LoaderOptions loaderOptions) {
|
||||
super(loaderOptions);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Class<?> getClassForName(String name) throws ClassNotFoundException {
|
||||
Assert.state(YamlProcessor.this.supportedTypes.contains(name),
|
||||
() -> "Unsupported type encountered in YAML document: " + name);
|
||||
return super.getClassForName(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
package org.springframework.beans.factory.config;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.yaml.snakeyaml.constructor.ConstructorException;
|
||||
import org.yaml.snakeyaml.parser.ParserException;
|
||||
import org.yaml.snakeyaml.scanner.ScannerException;
|
||||
|
||||
@@ -29,6 +31,7 @@ import org.springframework.core.io.ByteArrayResource;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Tests for {@link YamlProcessor}.
|
||||
@@ -37,14 +40,14 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class YamlProcessorTests {
|
||||
class YamlProcessorTests {
|
||||
|
||||
private final YamlProcessor processor = new YamlProcessor() {};
|
||||
|
||||
|
||||
@Test
|
||||
public void arrayConvertedToIndexedBeanReference() {
|
||||
this.processor.setResources(new ByteArrayResource("foo: bar\nbar: [1,2,3]".getBytes()));
|
||||
void arrayConvertedToIndexedBeanReference() {
|
||||
setYaml("foo: bar\nbar: [1,2,3]");
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties.size()).isEqualTo(4);
|
||||
assertThat(properties.get("foo")).isEqualTo("bar");
|
||||
@@ -59,30 +62,30 @@ public class YamlProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringResource() {
|
||||
this.processor.setResources(new ByteArrayResource("foo # a document that is a literal".getBytes()));
|
||||
void stringResource() {
|
||||
setYaml("foo # a document that is a literal");
|
||||
this.processor.process((properties, map) -> assertThat(map.get("document")).isEqualTo("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badDocumentStart() {
|
||||
this.processor.setResources(new ByteArrayResource("foo # a document\nbar: baz".getBytes()));
|
||||
void badDocumentStart() {
|
||||
setYaml("foo # a document\nbar: baz");
|
||||
assertThatExceptionOfType(ParserException.class)
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("line 2, column 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badResource() {
|
||||
this.processor.setResources(new ByteArrayResource("foo: bar\ncd\nspam:\n foo: baz".getBytes()));
|
||||
void badResource() {
|
||||
setYaml("foo: bar\ncd\nspam:\n foo: baz");
|
||||
assertThatExceptionOfType(ScannerException.class)
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("line 3, column 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapConvertedToIndexedBeanReference() {
|
||||
this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes()));
|
||||
void mapConvertedToIndexedBeanReference() {
|
||||
setYaml("foo: bar\nbar:\n spam: bucket");
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties.get("bar.spam")).isEqualTo("bucket");
|
||||
assertThat(properties).hasSize(2);
|
||||
@@ -90,8 +93,8 @@ public class YamlProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void integerKeyBehaves() {
|
||||
this.processor.setResources(new ByteArrayResource("foo: bar\n1: bar".getBytes()));
|
||||
void integerKeyBehaves() {
|
||||
setYaml("foo: bar\n1: bar");
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties.get("[1]")).isEqualTo("bar");
|
||||
assertThat(properties).hasSize(2);
|
||||
@@ -99,8 +102,8 @@ public class YamlProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void integerDeepKeyBehaves() {
|
||||
this.processor.setResources(new ByteArrayResource("foo:\n 1: bar".getBytes()));
|
||||
void integerDeepKeyBehaves() {
|
||||
setYaml("foo:\n 1: bar");
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties.get("foo[1]")).isEqualTo("bar");
|
||||
assertThat(properties).hasSize(1);
|
||||
@@ -109,8 +112,8 @@ public class YamlProcessorTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void flattenedMapIsSameAsPropertiesButOrdered() {
|
||||
this.processor.setResources(new ByteArrayResource("cat: dog\nfoo: bar\nbar:\n spam: bucket".getBytes()));
|
||||
void flattenedMapIsSameAsPropertiesButOrdered() {
|
||||
setYaml("cat: dog\nfoo: bar\nbar:\n spam: bucket");
|
||||
this.processor.process((properties, map) -> {
|
||||
Map<String, Object> flattenedMap = processor.getFlattenedMap(map);
|
||||
assertThat(flattenedMap).isInstanceOf(LinkedHashMap.class);
|
||||
@@ -134,4 +137,43 @@ public class YamlProcessorTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTypeSupportedByDefault() throws Exception {
|
||||
URL url = new URL("https://localhost:9000/");
|
||||
setYaml("value: !!java.net.URL [\"" + url + "\"]");
|
||||
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties).containsExactly(entry("value", url));
|
||||
assertThat(map).containsExactly(entry("value", url));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTypesSupportedDueToExplicitConfiguration() throws Exception {
|
||||
this.processor.setSupportedTypes(URL.class, String.class);
|
||||
|
||||
URL url = new URL("https://localhost:9000/");
|
||||
setYaml("value: !!java.net.URL [!!java.lang.String [\"" + url + "\"]]");
|
||||
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties).containsExactly(entry("value", url));
|
||||
assertThat(map).containsExactly(entry("value", url));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTypeNotSupportedDueToExplicitConfiguration() {
|
||||
this.processor.setSupportedTypes(List.class);
|
||||
|
||||
setYaml("value: !!java.net.URL [\"https://localhost:9000/\"]");
|
||||
|
||||
assertThatExceptionOfType(ConstructorException.class)
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
|
||||
}
|
||||
|
||||
private void setYaml(String yaml) {
|
||||
this.processor.setResources(new ByteArrayResource(yaml.getBytes()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user