diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java index df19941a0b..407c5019e7 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java @@ -34,6 +34,7 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -44,27 +45,30 @@ import org.springframework.util.ReflectionUtils; * single {@link PropertySource} rather than creating dedicated ones. * * @author Stephane Nicoll + * @author Sam Brannen * @since 6.0 * @see PropertySourceDescriptor */ public class PropertySourceProcessor { - private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory(); + private static final PropertySourceFactory defaultPropertySourceFactory = new DefaultPropertySourceFactory(); private static final Log logger = LogFactory.getLog(PropertySourceProcessor.class); + private final ConfigurableEnvironment environment; private final ResourceLoader resourceLoader; - private final List propertySourceNames; + private final List propertySourceNames = new ArrayList<>(); + public PropertySourceProcessor(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.resourceLoader = resourceLoader; - this.propertySourceNames = new ArrayList<>(); } + /** * Process the specified {@link PropertySourceDescriptor} against the * environment managed by this instance. @@ -78,7 +82,7 @@ public class PropertySourceProcessor { Assert.isTrue(locations.size() > 0, "At least one @PropertySource(value) location is required"); boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound(); PropertySourceFactory factory = (descriptor.propertySourceFactory() != null ? - instantiateClass(descriptor.propertySourceFactory()) : DEFAULT_PROPERTY_SOURCE_FACTORY); + instantiateClass(descriptor.propertySourceFactory()) : defaultPropertySourceFactory); for (String location : locations) { try { @@ -86,9 +90,10 @@ public class PropertySourceProcessor { Resource resource = this.resourceLoader.getResource(resolvedLocation); addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } - catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) { - // Placeholders not resolvable or resource not found when trying to open it - if (ignoreResourceNotFound) { + catch (RuntimeException | IOException ex) { + // Placeholders not resolvable (IllegalArgumentException) or resource not found when trying to open it + if (ignoreResourceNotFound && (ex instanceof IllegalArgumentException || isIgnorableException(ex) || + isIgnorableException(ex.getCause()))) { if (logger.isInfoEnabled()) { logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); } @@ -100,13 +105,13 @@ public class PropertySourceProcessor { } } - private void addPropertySource(org.springframework.core.env.PropertySource propertySource) { + private void addPropertySource(PropertySource propertySource) { String name = propertySource.getName(); MutablePropertySources propertySources = this.environment.getPropertySources(); if (this.propertySourceNames.contains(name)) { // We've already added a version, we need to extend it - org.springframework.core.env.PropertySource existing = propertySources.get(name); + PropertySource existing = propertySources.get(name); if (existing != null) { PropertySource newSource = (propertySource instanceof ResourcePropertySource rps ? rps.withResourceName() : propertySource); @@ -136,7 +141,8 @@ public class PropertySourceProcessor { this.propertySourceNames.add(name); } - private PropertySourceFactory instantiateClass(Class type) { + + private static PropertySourceFactory instantiateClass(Class type) { try { Constructor constructor = type.getDeclaredConstructor(); ReflectionUtils.makeAccessible(constructor); @@ -147,4 +153,14 @@ public class PropertySourceProcessor { } } + /** + * Determine if the supplied exception can be ignored according to + * {@code ignoreResourceNotFound} semantics. + */ + private static boolean isIgnorableException(@Nullable Throwable ex) { + return (ex instanceof FileNotFoundException || + ex instanceof UnknownHostException || + ex instanceof SocketException); + } + } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/PropertySourceProcessorTests.java b/spring-core/src/test/java/org/springframework/core/io/support/PropertySourceProcessorTests.java new file mode 100644 index 0000000000..ee663ec934 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/io/support/PropertySourceProcessorTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2002-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.io.support; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; + +/** + * Unit tests for {@link PropertySourceProcessor}. + * + * @author Sam Brannen + * @since 6.0.12 + */ +class PropertySourceProcessorTests { + + private static final String PROPS_FILE = ClassUtils.classPackageAsResourcePath(PropertySourceProcessorTests.class) + "/test.properties"; + + private final StandardEnvironment environment = new StandardEnvironment(); + private final ResourceLoader resourceLoader = new DefaultResourceLoader(); + private final PropertySourceProcessor processor = new PropertySourceProcessor(environment, resourceLoader); + + + @BeforeEach + void checkInitialPropertySources() { + assertThat(environment.getPropertySources()).hasSize(2); + } + + @Test + void processorRegistersPropertySource() throws Exception { + PropertySourceDescriptor descriptor = new PropertySourceDescriptor(List.of(PROPS_FILE), false, null, DefaultPropertySourceFactory.class, null); + processor.processPropertySource(descriptor); + assertThat(environment.getPropertySources()).hasSize(3); + assertThat(environment.getProperty("enigma")).isEqualTo("42"); + } + + @Nested + class FailOnErrorTests { + + @Test + void processorFailsOnIllegalArgumentException() { + assertProcessorFailsOnError(IllegalArgumentExceptionPropertySourceFactory.class, IllegalArgumentException.class); + } + + @Test + void processorFailsOnFileNotFoundException() { + assertProcessorFailsOnError(FileNotFoundExceptionPropertySourceFactory.class, FileNotFoundException.class); + } + + private void assertProcessorFailsOnError( + Class factoryClass, Class exceptionType) { + + PropertySourceDescriptor descriptor = + new PropertySourceDescriptor(List.of(PROPS_FILE), false, null, factoryClass, null); + assertThatExceptionOfType(exceptionType).isThrownBy(() -> processor.processPropertySource(descriptor)); + assertThat(environment.getPropertySources()).hasSize(2); + } + + } + + @Nested + class IgnoreResourceNotFoundTests { + + @Test + void processorIgnoresIllegalArgumentException() { + assertProcessorIgnoresFailure(IllegalArgumentExceptionPropertySourceFactory.class); + } + + @Test + void processorIgnoresFileNotFoundException() { + assertProcessorIgnoresFailure(FileNotFoundExceptionPropertySourceFactory.class); + } + + @Test + void processorIgnoresUnknownHostException() { + assertProcessorIgnoresFailure(UnknownHostExceptionPropertySourceFactory.class); + } + + @Test + void processorIgnoresSocketException() { + assertProcessorIgnoresFailure(SocketExceptionPropertySourceFactory.class); + } + + @Test + void processorIgnoresSupportedExceptionWrappedInIllegalStateException() { + assertProcessorIgnoresFailure(WrappedIOExceptionPropertySourceFactory.class); + } + + @Test + void processorIgnoresSupportedExceptionWrappedInUncheckedIOException() { + assertProcessorIgnoresFailure(UncheckedIOExceptionPropertySourceFactory.class); + } + + private void assertProcessorIgnoresFailure(Class factoryClass) { + PropertySourceDescriptor descriptor = new PropertySourceDescriptor(List.of(PROPS_FILE), true, null, factoryClass, null); + assertThatNoException().isThrownBy(() -> processor.processPropertySource(descriptor)); + assertThat(environment.getPropertySources()).hasSize(2); + } + + } + + + private static class IllegalArgumentExceptionPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + throw new IllegalArgumentException("bogus"); + } + } + + private static class FileNotFoundExceptionPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + throw new FileNotFoundException("bogus"); + } + } + + private static class UnknownHostExceptionPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + throw new UnknownHostException("bogus"); + } + } + + private static class SocketExceptionPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { + throw new SocketException("bogus"); + } + } + + private static class WrappedIOExceptionPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) { + throw new IllegalStateException("Wrapped", new FileNotFoundException("bogus")); + } + } + + private static class UncheckedIOExceptionPropertySourceFactory implements PropertySourceFactory { + + @Override + public PropertySource createPropertySource(String name, EncodedResource resource) { + throw new UncheckedIOException("Wrapped", new FileNotFoundException("bogus")); + } + } + +} diff --git a/spring-core/src/test/resources/org/springframework/core/io/support/test.properties b/spring-core/src/test/resources/org/springframework/core/io/support/test.properties new file mode 100644 index 0000000000..75456eb381 --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/core/io/support/test.properties @@ -0,0 +1 @@ +enigma = 42