Skip to content

Commit

Permalink
Improved unit tests for gh25-140
Browse files Browse the repository at this point in the history
  • Loading branch information
midumitrescu committed May 29, 2020
2 parents ca191c8 + 6d6269f commit 18b6d96
Show file tree
Hide file tree
Showing 33 changed files with 541 additions and 465 deletions.
6 changes: 4 additions & 2 deletions build.gradle
Expand Up @@ -25,8 +25,8 @@ configure(allprojects) { project ->
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.11.0"
mavenBom "io.netty:netty-bom:4.1.50.Final"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR7"
mavenBom "io.rsocket:rsocket-bom:1.0.0"
mavenBom "io.projectreactor:reactor-bom:2020.0.0-SNAPSHOT"
mavenBom "io.rsocket:rsocket-bom:1.0.1-SNAPSHOT"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.29.v20200521"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.3.5"
Expand Down Expand Up @@ -281,6 +281,8 @@ configure(allprojects) { project ->
repositories {
mavenCentral()
maven { url "https://repo.spring.io/libs-spring-framework-build" }
maven { url "https://repo.spring.io/snapshot" } // Reactor
maven { url "https://oss.jfrog.org/artifactory/oss-snapshot-local" } // RSocket
}
}
configurations.all {
Expand Down
@@ -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.
Expand All @@ -25,17 +25,23 @@
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;

/**
Expand All @@ -45,6 +51,7 @@
*
* @author Dave Syer
* @author Juergen Hoeller
* @author Sam Brannen
* @since 4.1
*/
public abstract class YamlProcessor {
Expand All @@ -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
Expand Down Expand Up @@ -117,6 +126,27 @@ public void setResources(Resource... resources) {
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
Expand All @@ -142,12 +172,22 @@ protected void process(MatchCallback callback) {
* 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) {
Expand Down Expand Up @@ -388,4 +428,24 @@ public enum ResolutionMethod {
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}.
*/
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.
Expand All @@ -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;

Expand All @@ -29,6 +31,7 @@
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}.
Expand All @@ -37,14 +40,14 @@
* @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");
Expand All @@ -59,48 +62,48 @@ public void arrayConvertedToIndexedBeanReference() {
}

@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);
});
}

@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);
});
}

@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);
Expand All @@ -109,8 +112,8 @@ public void integerDeepKeyBehaves() {

@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);
Expand All @@ -134,4 +137,43 @@ public void flattenedMapIsSameAsPropertiesButOrdered() {
});
}

@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()));
}

}
@@ -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.
Expand Down Expand Up @@ -79,6 +79,7 @@
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

Expand Down Expand Up @@ -836,7 +837,7 @@ protected void registerListeners() {
// Publish early application events now that we finally have a multicaster...
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
Expand Down Expand Up @@ -223,6 +223,9 @@ public String resolveRequiredPlaceholders(String text) throws IllegalArgumentExc
* @see #setIgnoreUnresolvableNestedPlaceholders
*/
protected String resolveNestedPlaceholders(String value) {
if (value.isEmpty()) {
return value;
}
return (this.ignoreUnresolvableNestedPlaceholders ?
resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
}
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
Expand Down Expand Up @@ -44,10 +44,20 @@
*/
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {

/**
* Create a new {@code EnumerablePropertySource} with the given name and source object.
* @param name the associated name
* @param source the source object
*/
public EnumerablePropertySource(String name, T source) {
super(name, source);
}

/**
* Create a new {@code EnumerablePropertySource} with the given name and with a new
* {@code Object} instance as the underlying source.
* @param name the associated name
*/
protected EnumerablePropertySource(String name) {
super(name);
}
Expand Down

0 comments on commit 18b6d96

Please sign in to comment.