diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java index 1abca09c18b..ea55b62fd92 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-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. @@ -28,6 +28,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.log.LogMessage; /** * Imports {@link ConfigData} by {@link ConfigDataLocationResolver resolving} and @@ -117,16 +118,20 @@ class ConfigDataImporter { ConfigDataResolutionResult candidate = candidates.get(i); ConfigDataLocation location = candidate.getLocation(); ConfigDataResource resource = candidate.getResource(); + this.logger.trace(LogMessage.format("Considering resource %s from location %s", resource, location)); if (resource.isOptional()) { this.optionalLocations.add(location); } if (this.loaded.contains(resource)) { + this.logger + .trace(LogMessage.format("Already loaded resource %s ignoring location %s", resource, location)); this.loadedLocations.add(location); } else { try { ConfigData loaded = this.loaders.load(loaderContext, resource); if (loaded != null) { + this.logger.trace(LogMessage.format("Loaded resource %s from location %s", resource, location)); this.loaded.add(resource); this.loadedLocations.add(location); result.put(candidate, loaded); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java index c43d404baf7..ccf175f555a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-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. @@ -16,8 +16,10 @@ package org.springframework.boot.context.config; +import java.io.File; import java.io.IOException; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileUrlResource; import org.springframework.core.io.Resource; @@ -96,12 +98,21 @@ public class StandardConfigDataResource extends ConfigDataResource { return false; } StandardConfigDataResource other = (StandardConfigDataResource) obj; - return this.resource.equals(other.resource) && this.emptyDirectory == other.emptyDirectory; + return (this.emptyDirectory == other.emptyDirectory) && isSameUnderlyingResource(this.resource, other.resource); + } + + private boolean isSameUnderlyingResource(Resource ours, Resource other) { + return ours.equals(other) || isSameFile(getUnderlyingFile(ours), getUnderlyingFile(other)); + } + + private boolean isSameFile(File ours, File other) { + return (ours != null) && (other != null) && ours.equals(other); } @Override public int hashCode() { - return this.resource.hashCode(); + File underlyingFile = getUnderlyingFile(this.resource); + return (underlyingFile != null) ? underlyingFile.hashCode() : this.resource.hashCode(); } @Override @@ -116,4 +127,17 @@ public class StandardConfigDataResource extends ConfigDataResource { return this.resource.toString(); } + private File getUnderlyingFile(Resource resource) { + try { + if (resource instanceof ClassPathResource || resource instanceof FileSystemResource + || resource instanceof FileUrlResource) { + File file = resource.getFile(); + return (file != null) ? file.getAbsoluteFile() : null; + } + } + catch (IOException ex) { + } + return null; + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java index 56bc44058c8..3a27a1117a4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java @@ -16,9 +16,12 @@ package org.springframework.boot.context.config; +import java.io.IOException; + import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileUrlResource; import org.springframework.core.io.Resource; import static org.assertj.core.api.Assertions.assertThat; @@ -66,4 +69,15 @@ class StandardConfigDataResourceTests { assertThat(location).isNotEqualTo(other); } + @Test // gh-34212 + void equalsAndHashCodeWhenSameUnderlyingResource() throws IOException { + ClassPathResource classPathResource = new ClassPathResource("log4j2.springboot"); + FileUrlResource fileUrlResource = new FileUrlResource(classPathResource.getURL()); + ConfigDataResource classPathConfigDataResource = new StandardConfigDataResource(this.reference, + classPathResource); + ConfigDataResource fileUrlConfigDataResource = new StandardConfigDataResource(this.reference, fileUrlResource); + assertThat(classPathConfigDataResource.hashCode()).isEqualTo(fileUrlConfigDataResource.hashCode()); + assertThat(classPathConfigDataResource).isEqualTo(fileUrlConfigDataResource); + } + }