MultipleJGitEnvironmentRepository.java
/*
* Copyright 2013-2019 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.cloud.config.server.environment;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* {@link EnvironmentRepository} that based on one or more git repositories. Can be
* configured just like a single {@link JGitEnvironmentRepository}, for the "default"
* properties, and then additional repositories can be registered by name. The simplest
* form of the registration is just a map from name to uri (plus credentials if needed),
* where each app has its own git repository. As well as a name you can provide a pattern
* that matches on the application name (or even a list of patterns). Each sub-repository
* additionally can have its own search paths (subdirectories inside the top level of the
* repository).
*
* @author Andy Chan (iceycake)
* @author Dave Syer
* @author Gareth Clay
*
*/
public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
/**
* Map of repository identifier to location and other properties.
*/
private Map<String, PatternMatchingJGitEnvironmentRepository> repos = new LinkedHashMap<>();
private Map<String, JGitEnvironmentRepository> placeholders = new LinkedHashMap<>();
private final ObservationRegistry observationRegistry;
public MultipleJGitEnvironmentRepository(ConfigurableEnvironment environment,
MultipleJGitEnvironmentProperties properties, ObservationRegistry observationRegistry) {
super(environment, properties, observationRegistry);
this.observationRegistry = observationRegistry;
properties.getRepos().forEach((name, props) -> this.repos.put(name,
new PatternMatchingJGitEnvironmentRepository(environment, props, this.observationRegistry)));
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
for (String name : this.repos.keySet()) {
PatternMatchingJGitEnvironmentRepository repo = this.repos.get(name);
repo.setEnvironment(getEnvironment());
if (!StringUtils.hasText(repo.getName())) {
repo.setName(name);
}
if (repo.getPattern() == null || repo.getPattern().length == 0) {
repo.setPattern(new String[] { name });
}
if (repo.getTransportConfigCallback() == null) {
repo.setTransportConfigCallback(getTransportConfigCallback());
}
if (getTimeout() != 0 && repo.getTimeout() == 0) {
repo.setTimeout(getTimeout());
}
if (getRefreshRate() != 0 && repo.getRefreshRate() == 0) {
repo.setRefreshRate(getRefreshRate());
}
String user = repo.getUsername();
String passphrase = repo.getPassphrase();
if (user == null) {
repo.setUsername(getUsername());
repo.setPassword(getPassword());
}
if (passphrase == null) {
repo.setPassphrase(getPassphrase());
}
if (isSkipSslValidation()) {
repo.setSkipSslValidation(true);
}
repo.afterPropertiesSet();
}
if (!getBasedir().exists() && !getBasedir().mkdirs()) {
throw new IllegalStateException("Basedir does not exist and can not be created: " + getBasedir());
}
if (!getBasedir().getParentFile().canWrite()) {
throw new IllegalStateException(
"Cannot write parent of basedir (please configure a writable location): " + getBasedir());
}
}
public Map<String, PatternMatchingJGitEnvironmentRepository> getRepos() {
return this.repos;
}
public void setRepos(Map<String, PatternMatchingJGitEnvironmentRepository> repos) {
this.repos.putAll(repos);
}
@Override
public Locations getLocations(String application, String profile, String label) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository, application, profile, label)) {
try {
Environment source = candidate.findOne(application, profile, label, false);
if (source != null) {
return candidate.getLocations(application, profile, label);
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Cannot retrieve resource locations from " + candidate.getUri()
+ ", cause: (" + e.getClass().getSimpleName() + ") " + e.getMessage(), e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile, label);
if (candidate == this) {
return super.getLocations(application, profile, label);
}
return candidate.getLocations(application, profile, label);
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
if (repository.matches(application, profile, label)) {
for (JGitEnvironmentRepository candidate : getRepositories(repository, application, profile, label)) {
try {
if (label == null) {
label = candidate.getDefaultLabel();
}
Environment source = candidate.findOne(application, profile, label, includeOrigin);
if (source != null) {
return source;
}
}
catch (Exception e) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Cannot load configuration from " + candidate.getUri() + ", cause: ("
+ e.getClass().getSimpleName() + ") " + e.getMessage(), e);
}
continue;
}
}
}
}
JGitEnvironmentRepository candidate = getRepository(this, application, profile, label);
if (label == null) {
label = candidate.getDefaultLabel();
}
try {
return findOneFromCandidate(candidate, application, profile, label, includeOrigin);
}
catch (Exception e) {
if (MultipleJGitEnvironmentProperties.MAIN_LABEL.equals(label) && isTryMasterBranch()) {
logger.info("Cannot find Environment with default label " + getDefaultLabel(), e);
logger.info("Will try to find Environment master label instead.");
candidate = getRepository(this, application, profile, MultipleJGitEnvironmentProperties.MASTER_LABEL);
return findOneFromCandidate(candidate, application, profile,
MultipleJGitEnvironmentProperties.MASTER_LABEL, includeOrigin);
}
throw e;
}
}
private Environment findOneFromCandidate(JGitEnvironmentRepository candidate, String application, String profile,
String label, boolean includeOrigin) {
if (candidate == this) {
return super.findOne(application, profile, label, includeOrigin);
}
return candidate.findOne(application, profile, label, includeOrigin);
}
private List<JGitEnvironmentRepository> getRepositories(JGitEnvironmentRepository repository, String application,
String profile, String label) {
List<JGitEnvironmentRepository> list = new ArrayList<>();
String[] profiles = profile == null ? new String[] { null }
: StringUtils.commaDelimitedListToStringArray(profile);
for (int i = profiles.length; i-- > 0;) {
list.add(getRepository(repository, application, profiles[i], label));
}
return list;
}
JGitEnvironmentRepository getRepository(JGitEnvironmentRepository repository, String application, String profile,
String label) {
if (!repository.getUri().contains("{")) {
return repository;
}
String key = repository.getUri();
// cover the case where label is in the uri, but no label was sent with the
// request
if (key.contains("{label}") && label == null) {
label = repository.getDefaultLabel();
}
if (application != null) {
key = key.replace("{application}", application);
}
if (profile != null) {
key = key.replace("{profile}", profile);
}
if (label != null) {
key = key.replace("{label}", label);
}
if (!this.placeholders.containsKey(key)) {
this.placeholders.put(key, getRepository(repository, key));
}
return this.placeholders.get(key);
}
private JGitEnvironmentRepository getRepository(JGitEnvironmentRepository source, String uri) {
JGitEnvironmentRepository repository = new JGitEnvironmentRepository(null, new JGitEnvironmentProperties(),
observationRegistry);
File basedir = repository.getBasedir();
BeanUtils.copyProperties(source, repository);
repository.setUri(uri);
repository.setBasedir(new File(source.getBasedir(), basedir.getName()));
return repository;
}
@Override
public void setOrder(int order) {
super.setOrder(order);
}
/**
* A {@link JGitEnvironmentProperties} that matches patterns.
*/
public static class PatternMatchingJGitEnvironmentRepository extends JGitEnvironmentRepository {
/**
* Pattern to match on application name and profiles.
*/
private String[] pattern = new String[0];
/**
* Name of repository (same as map key by default).
*/
private String name;
private final ObservationRegistry observationRegistry;
public PatternMatchingJGitEnvironmentRepository(ObservationRegistry observationRegistry) {
super(null, new JGitEnvironmentProperties(), observationRegistry);
this.observationRegistry = observationRegistry;
}
public PatternMatchingJGitEnvironmentRepository(ConfigurableEnvironment environment,
MultipleJGitEnvironmentProperties.PatternMatchingJGitEnvironmentProperties properties,
ObservationRegistry observationRegistry) {
super(environment, properties, observationRegistry);
this.observationRegistry = observationRegistry;
this.setPattern(properties.getPattern());
this.name = properties.getName();
}
public boolean matches(String application, String profile, String label) {
if (this.pattern == null || this.pattern.length == 0) {
return false;
}
String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
for (int i = profiles.length; i-- > 0;) {
if (PatternMatchUtils.simpleMatch(this.pattern, application + "/" + profiles[i])) {
return true;
}
}
return false;
}
@Override
public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
if (this.pattern == null || this.pattern.length == 0) {
return null;
}
if (PatternMatchUtils.simpleMatch(this.pattern, application + "/" + profile)) {
return super.findOne(application, profile, label, includeOrigin);
}
return null;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String[] getPattern() {
return this.pattern;
}
public void setPattern(String[] pattern) {
Collection<String> patterns = new ArrayList<>();
List<String> otherProfiles = new ArrayList<>();
for (String p : pattern) {
if (p != null) {
if (!p.contains("/")) {
// Match any profile
patterns.add(p + "/*");
}
if (!p.endsWith("*")) {
// If user supplies only one profile, allow others
otherProfiles.add(p + ",*");
}
}
patterns.add(p);
}
patterns.addAll(otherProfiles);
if (!patterns.contains(null)) {
// Make sure they are unique
patterns = new LinkedHashSet<>(patterns);
}
this.pattern = patterns.toArray(new String[0]);
}
}
}