package org.springframework.cloud.localconfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.springframework.cloud.AbstractCloudConnector;
import org.springframework.cloud.FallbackServiceInfoCreator;
import org.springframework.cloud.app.ApplicationInstanceInfo;
import org.springframework.cloud.app.BasicApplicationInstanceInfo;
import org.springframework.cloud.service.BaseServiceInfo;
import org.springframework.cloud.service.FallbackBaseServiceInfoCreator;
import org.springframework.cloud.service.UriBasedServiceData;
import org.springframework.cloud.util.EnvironmentAccessor;
/**
* @author Christopher Smith
*/
public class LocalConfigConnector extends AbstractCloudConnector<UriBasedServiceData> {
private static final Logger logger = Logger.getLogger(LocalConfigConnector.class.getName());
/*--------------- String constants for property keys ---------------*/
public static final String PROPERTY_PREFIX = "spring.cloud.";
public static final Pattern SERVICE_PROPERTY_PATTERN = Pattern.compile("\\A" + Pattern.quote(PROPERTY_PREFIX) + "(.+)" + "\\Z");
public static final String APP_ID_PROPERTY = PROPERTY_PREFIX + "appId";
public static final String PROPERTIES_FILE_PROPERTY = PROPERTY_PREFIX + "propertiesFile";
/**
* These properties configure the connector itself and aren't service definitions.
*/
public static final List<String> META_PROPERTIES = Collections.unmodifiableList(
Arrays.asList(APP_ID_PROPERTY, PROPERTIES_FILE_PROPERTY));
/*--------------- inject system property access for testing ---------------*/
private EnvironmentAccessor env = new EnvironmentAccessor();
void setEnvironmentAccessor(EnvironmentAccessor env) {
this.env = env;
}
/*--------------- API implementation ---------------*/
@SuppressWarnings({"unchecked", "rawtypes"})
public LocalConfigConnector() {
super((Class) LocalConfigServiceInfoCreator.class);
}
/*--------------- properties read out of the file at spring.cloud.propertiesFile ---------------*/
private Properties fileProperties = null;
/**
* Returns {@code true} if a property named {@code spring.cloud.appId} is present in any of the property sources.
* On the first call, attempts to load properties from a file specified in {@code spring.cloud.propertiesFile}.
*/
@Override
public boolean isInMatchingCloud() {
if (fileProperties == null)
readFileProperties();
String appId = findProperty(APP_ID_PROPERTY);
if (appId == null)
logger.info("the property " + APP_ID_PROPERTY + " was not found in the system properties or configuration file");
return appId != null;
}
@Override
public ApplicationInstanceInfo getApplicationInstanceInfo() {
return new BasicApplicationInstanceInfo(UUID.randomUUID().toString(), findProperty(APP_ID_PROPERTY),
Collections.<String, Object>emptyMap());
}
@Override
protected List<UriBasedServiceData> getServicesData() {
if (fileProperties == null)
throw new IllegalStateException("isInMatchingCloud() must be called first to initialize connector");
LinkedHashMap<String, Properties> propertySources = new LinkedHashMap<String, Properties>();
propertySources.put("properties from file", fileProperties);
try {
propertySources.put("system properties", env.getSystemProperties());
} catch (SecurityException e) {
logger.log(Level.WARNING,
"couldn't read system properties; no service definitions from system properties will be applied", e);
}
return LocalConfigUtil.readServicesData(propertySources);
}
@Override
protected FallbackServiceInfoCreator<BaseServiceInfo, UriBasedServiceData> getFallbackServiceInfoCreator() {
return new FallbackBaseServiceInfoCreator();
}
/*--------------- methods for manipulating properties and sources ---------------*/
/**
* Checks for the presence of a supplied or system property named {@code spring.cloud.propertiesFile}. If the property
* is present, load its contents into {@link #fileProperties}. If there's a problem, log but continue.
*/
private void readFileProperties() {
fileProperties = new Properties();
logger.fine("looking for a properties file");
// will search system properties and the classpath
File propertiesFile = new PropertiesFileResolver(env).findCloudPropertiesFile();
if (propertiesFile == null) {
logger.info("not loading service definitions from a properties file");
return;
}
if (!fileExists(propertiesFile)) {
logger.info("properties file " + propertiesFile + " does not exist; probably running in a real cloud");
return;
}
logger.info("loading service definitions from properties file " + propertiesFile);
try {
InputStream fis = openFile(propertiesFile);
fileProperties.load(fis);
fis.close();
} catch (IOException e) {
logger.log(Level.SEVERE, "exception while loading properties from file " + propertiesFile, e);
return;
}
logger.info("properties loaded successfully");
}
/**
* Broken out into a separate method for mocking the filesystem.
*
* @param file the file to check
* @return whether the file exists
*/
boolean fileExists(File file) {
return file.exists();
}
/**
* Broken out into a separate method for mocking the filesystem.
*
* @param file the file to open
* @return a {@code FileInputStream} to the file
* @throws IOException if opening the file throws
*/
InputStream openFile(File file) throws IOException {
return new FileInputStream(file);
}
/**
* Look for a specific property in the config file or the system properties.
*
* @param key the property to look for
* @return the preferred value for the key, or {@code null} if the key is not found
*/
private String findProperty(String key) {
String value = fileProperties.getProperty(key);
try {
value = env.getSystemProperty(key, value);
} catch (SecurityException e) {
logSystemReadException(key, e);
}
return value;
}
private static void logSystemReadException(String key, SecurityException e) {
logger.log(Level.WARNING, "couldn't read system property " + key, e);
}
}