Browse Source

Merge branch '5.2.x'

pull/25170/head
Sam Brannen 6 years ago
parent
commit
5f9e9513ef
  1. 70
      spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java
  2. 78
      spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java

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

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}
}

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

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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()));
}
}

Loading…
Cancel
Save