Skip to content

Commit

Permalink
Enforce standard Java types in YamlProcessor
Browse files Browse the repository at this point in the history
`spring-beans` ships a `YamlProcessor` that's used as a base class by
`YamlMapFactoryBean` and `YamlPropertiesFactoryBean`. These
implementations have a clear use case: mapping application-internal Yaml
documents for configuration or infrastructure purposes.

Since this use case rarely requires extended types support from the
underlying library, and since we're offering ways to list custom types
(since #25152), we'll restrict to java standard types only by default.
This simplifies the setup and focuses the abstract class on the core
use cases.

Closes gh-26530
  • Loading branch information
bclozel committed Feb 9, 2021
1 parent 9b0c2cc commit 58e9b18
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 26 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
Expand Down Expand Up @@ -52,6 +52,7 @@
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
* @author Brian Clozel
* @since 4.1
*/
public abstract class YamlProcessor {
Expand Down Expand Up @@ -128,10 +129,11 @@ public void setResources(Resource... 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.
* <p>If no supported types are configured, only Java standard classes
* (as defined in {@link org.yaml.snakeyaml.constructor.SafeConstructor})
* 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
Expand Down Expand Up @@ -182,12 +184,8 @@ protected void process(MatchCallback callback) {
protected Yaml createYaml() {
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);
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
new DumperOptions(), loaderOptions);
}

private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
Expand All @@ -17,9 +17,11 @@
package org.springframework.beans.factory.config;

import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.yaml.snakeyaml.constructor.ConstructorException;
Expand All @@ -28,7 +30,6 @@

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;
Expand All @@ -39,10 +40,12 @@
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
* @author Brian Clozel
*/
class YamlProcessorTests {

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


@Test
Expand Down Expand Up @@ -79,8 +82,8 @@ void badDocumentStart() {
void badResource() {
setYaml("foo: bar\ncd\nspam:\n foo: baz");
assertThatExceptionOfType(ScannerException.class)
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("line 3, column 1");
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("line 3, column 1");
}

@Test
Expand Down Expand Up @@ -127,8 +130,8 @@ void flattenedMapIsSameAsPropertiesButOrdered() {
Map<String, Object> bar = (Map<String, Object>) map.get("bar");
assertThat(bar.get("spam")).isEqualTo("bucket");

List<Object> keysFromProperties = properties.keySet().stream().collect(toList());
List<String> keysFromFlattenedMap = flattenedMap.keySet().stream().collect(toList());
List<Object> keysFromProperties = new ArrayList<>(properties.keySet());
List<String> keysFromFlattenedMap = new ArrayList<>(flattenedMap.keySet());
assertThat(keysFromProperties).containsExactlyInAnyOrderElementsOf(keysFromFlattenedMap);
// Keys in the Properties object are sorted.
assertThat(keysFromProperties).containsExactly("bar.spam", "cat", "foo");
Expand All @@ -138,16 +141,25 @@ void flattenedMapIsSameAsPropertiesButOrdered() {
}

@Test
void customTypeSupportedByDefault() throws Exception {
URL url = new URL("https://localhost:9000/");
setYaml("value: !!java.net.URL [\"" + url + "\"]");

void standardTypesSupportedByDefault() throws Exception {
setYaml("value: !!set\n ? first\n ? second");
this.processor.process((properties, map) -> {
assertThat(properties).containsExactly(entry("value", url));
assertThat(map).containsExactly(entry("value", url));
assertThat(properties).containsExactly(entry("value[0]", "first"), entry("value[1]", "second"));
assertThat(map.get("value")).isInstanceOf(Set.class);
Set<String> set = (Set<String>) map.get("value");
assertThat(set).containsExactly("first", "second");
});
}

@Test
void customTypeNotSupportedByDefault() throws Exception {
URL url = new URL("https://localhost:9000/");
setYaml("value: !!java.net.URL [\"" + url + "\"]");
assertThatExceptionOfType(ConstructorException.class)
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
}

@Test
void customTypesSupportedDueToExplicitConfiguration() throws Exception {
this.processor.setSupportedTypes(URL.class, String.class);
Expand All @@ -168,8 +180,8 @@ void customTypeNotSupportedDueToExplicitConfiguration() {
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");
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
}

private void setYaml(String yaml) {
Expand Down

0 comments on commit 58e9b18

Please sign in to comment.