Skip to content

add 'spring.config.intergration-location' with 'classpath*:' support #25082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;

Expand Down Expand Up @@ -56,6 +57,7 @@
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Zhengsheng Xia
*/
class ConfigDataEnvironment {

Expand All @@ -69,6 +71,11 @@ class ConfigDataEnvironment {
*/
static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";

/**
* The "config intergration location" property name.
*/
public static final String INTERGRATION_LOCATION_PROPERTY = "spring.config.intergration-location";

/**
* Property used to provide additional locations to import.
*/
Expand Down Expand Up @@ -200,11 +207,22 @@ private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Bind
bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
addInitialImportContributors(initialContributors,
bindLocations(binder, INTERGRATION_LOCATION_PROPERTY, EMPTY_LOCATIONS, true));
return initialContributors;
}

private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other,
boolean allowClasspathAll) {
ConfigDataLocation[] result = binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
if (allowClasspathAll && other != result) {
Stream.of(result).forEach(configDataLocation -> configDataLocation.setAllowClasspathAll(allowClasspathAll));
}
return result;
}

private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {
return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
return bindLocations(binder, propertyName, other, false);
}

private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
* prefixed with {@code optional:}.
*
* @author Phillip Webb
* @author Zhengsheng Xia
* @since 2.4.0
*/
public final class ConfigDataLocation implements OriginProvider {
Expand All @@ -46,6 +47,8 @@ public final class ConfigDataLocation implements OriginProvider {

private final Origin origin;

private boolean allowClasspathAll;

private ConfigDataLocation(boolean optional, String value, Origin origin) {
this.value = value;
this.optional = optional;
Expand Down Expand Up @@ -79,6 +82,22 @@ public boolean hasPrefix(String prefix) {
return this.value.startsWith(prefix);
}

/**
* Getter method for property <tt>allowClasspathAll</tt>.
* @return property value of allowClasspathAll
*/
public boolean isAllowClasspathAll() {
return allowClasspathAll;
}

/**
* Setter method for property <tt>allowClasspathAll</tt>.
* @param allowClasspathAll value to be assigned to property allowClasspathAll
*/
public void setAllowClasspathAll(boolean allowClasspathAll) {
this.allowClasspathAll = allowClasspathAll;
}

/**
* Return {@link #getValue()} with the specified prefix removed. If the location does
* not have the given prefix then the {@link #getValue()} is returned unchanged.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -111,6 +112,7 @@
* @author Eddú Meléndez
* @author Madhura Bhave
* @author Scott Frederick
* @author Zhengsheng Xia
* @since 1.0.0
* @deprecated since 2.4.0 in favor of {@link ConfigDataEnvironmentPostProcessor}
*/
Expand Down Expand Up @@ -162,6 +164,11 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
*/
public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";

/**
* The "config intergration location" property name.
*/
public static final String CONFIG_INTERGRATION_LOCATION_PROPERTY = "spring.config.intergration-location";

/**
* The default order for the processor.
*/
Expand Down Expand Up @@ -588,7 +595,12 @@ private boolean isPatternLocation(String location) {
}

private Resource[] getResourcesFromPatternLocationReference(String locationReference) throws IOException {
String directoryPath = locationReference.substring(0, locationReference.indexOf("*/"));
int wildcardlastIdx = locationReference.indexOf("*/");
String directoryPath = locationReference.substring(0, wildcardlastIdx);
if (locationReference.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) {
return getResourcesFromClasspathAllPatternLocation(locationReference,
locationReference.length() - wildcardlastIdx);
}
Resource resource = this.resourceLoader.getResource(directoryPath);
File[] files = resource.getFile().listFiles(File::isDirectory);
if (files != null) {
Expand All @@ -601,6 +613,29 @@ private Resource[] getResourcesFromPatternLocationReference(String locationRefer
return EMPTY_RESOURCES;
}

private Resource[] getResourcesFromClasspathAllPatternLocation(String location, int propNameLen)
throws IOException {
PathMatchingResourcePatternResolver pathMatchingResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = pathMatchingResolver.getResources(location);
if (resources != null) {
Arrays.sort(resources, Comparator.comparing(resource -> {
String filename;
try {
filename = resource.getFile().getAbsolutePath();
}
catch (IOException e) {
filename = resource.getFilename();
}
int idx = filename.lastIndexOf(File.separator, filename.length() - propNameLen);
if (idx > -1) {
filename = filename.substring(idx);
}
return filename;
}));
}
return resources;
}

private void addIncludedProfiles(Set<Profile> includeProfiles) {
LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
this.profiles.clear();
Expand Down Expand Up @@ -688,19 +723,21 @@ private Set<String> getSearchLocations() {
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
}
locations.addAll(getSearchLocations(CONFIG_INTERGRATION_LOCATION_PROPERTY, true));
return locations;
}

private Set<String> getSearchLocations(String propertyName) {
private Set<String> getSearchLocations(String propertyName, boolean allowClasspathAll) {
Set<String> locations = new LinkedHashSet<>();
if (this.environment.containsProperty(propertyName)) {
for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
Assert.state(!path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX),
boolean hasClasspathAll = path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX);
Assert.state(allowClasspathAll || !hasClasspathAll,
"Classpath wildcard patterns cannot be used as a search location");
validateWildcardLocation(path);
if (!ResourceUtils.isUrl(path)) {
validateWildcardLocation(path, allowClasspathAll);
if (!hasClasspathAll && !ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
Expand All @@ -710,10 +747,17 @@ private Set<String> getSearchLocations(String propertyName) {
return locations;
}

private void validateWildcardLocation(String path) {
private Set<String> getSearchLocations(String propertyName) {
return getSearchLocations(propertyName, false);
}
// xia

// xia
private void validateWildcardLocation(String path, boolean allowClasspathAll) {
if (path.contains("*")) {
Assert.state(StringUtils.countOccurrencesOf(path, "*") == 1,
() -> "Search location '" + path + "' cannot contain multiple wildcards");
int wildCardCount = StringUtils.countOccurrencesOf(path, "*");
boolean rightState = allowClasspathAll ? wildCardCount == 2 : wildCardCount == 1;
Assert.state(rightState, () -> "Search location '" + path + "' cannot contain multiple wildcards");
String directoryPath = path.substring(0, path.lastIndexOf("/") + 1);
Assert.state(directoryPath.endsWith("*/"), () -> "Search location '" + path + "' must end with '*/'");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
Expand All @@ -26,6 +27,7 @@
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
Expand All @@ -37,6 +39,7 @@
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Zhengsheng Xia
*/
class LocationResourceLoader {

Expand Down Expand Up @@ -88,12 +91,19 @@ private void validateNonPattern(String location) {
* Get a multiple resources from a location pattern.
* @param location the location pattern
* @param type the type of resource to return
* @param allowClasspathAll the boolean value of allow param location contains
* 'classpath*:'
* @return the resources
* @see #isPattern(String)
*/
Resource[] getResources(String location, ResourceType type) {
validatePattern(location, type);
String directoryPath = location.substring(0, location.indexOf("*/"));
Resource[] getResources(String location, ResourceType type, boolean allowClasspathAll) {
boolean startsWithClasspathAll = validatePatternAndCheckClasspathAll(location, type, allowClasspathAll);
int wildcardlastIdx = location.indexOf("*/");
String directoryPath = location.substring(0, wildcardlastIdx);
if (startsWithClasspathAll) {
return getResourcesFromClasspathAllPatternLocation(location, location.length() - wildcardlastIdx);
}

String fileName = location.substring(location.lastIndexOf("/") + 1);
Resource directoryResource = getResource(directoryPath);
if (!directoryResource.exists()) {
Expand All @@ -120,15 +130,66 @@ Resource[] getResources(String location, ResourceType type) {
return resources.toArray(EMPTY_RESOURCES);
}

private void validatePattern(String location, ResourceType type) {
/**
* Get a multiple resources from a location pattern.
* @param location the location pattern
* @param type the type of resource to return
* @return the resources
* @see #isPattern(String)
*/
Resource[] getResources(String location, ResourceType type) {
return getResources(location, type, false);
}

/**
* Get a multiple resources from a location pattern.
* @param location the location pattern
* @param allowClasspathAll the boolean value of allow param location contains
* 'classpath*:'
* @return the resources
* @see #isPattern(String)
*/
Resource[] getResources(String location, boolean allowClasspathAll) {
ResourceType resourceType = location.endsWith("/") ? ResourceType.DIRECTORY : ResourceType.FILE;
return getResources(location, resourceType, allowClasspathAll);
}

private Resource[] getResourcesFromClasspathAllPatternLocation(String location, int propNameLen) {
PathMatchingResourcePatternResolver pathMatchingResolver = new PathMatchingResourcePatternResolver();
Resource[] resources;
try {
resources = pathMatchingResolver.getResources(location);
}
catch (IOException e) {
throw new IllegalStateException(String.format("get location: '%s' error", location), e);
}
if (resources != null) {
Arrays.sort(resources, Comparator.comparing(resource -> {
String filename;
FileSystemResource fileResource = (FileSystemResource) resource;
filename = fileResource.getFile().getAbsolutePath();
int idx = filename.lastIndexOf(File.separator, filename.length() - propNameLen);
if (idx > -1) {
filename = filename.substring(idx);
}
return filename;
}));
}
return resources;
}

private boolean validatePatternAndCheckClasspathAll(String location, ResourceType type, boolean allowClasspathAll) {
Assert.state(isPattern(location), () -> String.format("Location '%s' must be a pattern", location));
Assert.state(!location.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX),
boolean startsWithClasspathAll = location.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX);
Assert.state((allowClasspathAll || !startsWithClasspathAll),
() -> String.format("Location '%s' cannot use classpath wildcards", location));
Assert.state(StringUtils.countOccurrencesOf(location, "*") == 1,
int countOfWilldcard = StringUtils.countOccurrencesOf(location, "*");
Assert.state((startsWithClasspathAll ? (countOfWilldcard == 2) : countOfWilldcard == 1),
() -> String.format("Location '%s' cannot contain multiple wildcards", location));
String directoryPath = (type != ResourceType.DIRECTORY) ? location.substring(0, location.lastIndexOf("/") + 1)
: location;
Assert.state(directoryPath.endsWith("*/"), () -> String.format("Location '%s' must end with '*/'", location));
if (type == ResourceType.DIRECTORY) {
Assert.state(location.endsWith("*/"), () -> String.format("Location '%s' must end with '*/'", location));
}
return startsWithClasspathAll;
}

private File getDirectory(String patternLocation, Resource resource) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@

import org.apache.commons.logging.Log;

import org.springframework.boot.context.config.LocationResourceLoader.ResourceType;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
Expand All @@ -48,6 +48,7 @@
* @author Madhura Bhave
* @author Phillip Webb
* @author Scott Frederick
* @author Zhengsheng Xia
* @since 2.4.0
*/
public class StandardConfigDataLocationResolver
Expand Down Expand Up @@ -244,6 +245,9 @@ private Collection<StandardConfigDataResource> resolveEmptyDirectories(
Set<StandardConfigDataResource> empty = new LinkedHashSet<>();
for (StandardConfigDataReference reference : references) {
if (reference.isMandatoryDirectory()) {
if (reference.getDirectory().startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) {
continue;
}
Resource resource = this.resourceLoader.getResource(reference.getDirectory());
if (resource instanceof ClassPathResource) {
continue;
Expand Down Expand Up @@ -274,7 +278,8 @@ private List<StandardConfigDataResource> resolveNonPattern(StandardConfigDataRef

private List<StandardConfigDataResource> resolvePattern(StandardConfigDataReference reference) {
List<StandardConfigDataResource> resolved = new ArrayList<>();
for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(), ResourceType.FILE)) {
for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(),
reference.getConfigDataLocation().isAllowClasspathAll())) {
if (!resource.exists() && reference.isSkippable()) {
logSkippingResource(reference);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener",
"description": "Config file locations used in addition to the defaults."
},
{
"name": "spring.config.intergration-location",
"type": "java.lang.String",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener",
"description": "lowest config file locations ,when intergrate dynamic jars, the value can be such as 'classpath*:/sink/*/'"
},
{
"name": "spring.config.import",
"type": "java.util.List<java.lang.String>",
Expand Down
Loading