Commit 33ce1602 authored by Phillip Webb's avatar Phillip Webb

Automatically X-Forwarded-For in the cloud

Update `ServerProperties` to automatically enable `use-forward-headers`
when running on a cloud platform.

A new `CloudPlatform` enum has been introduced that detects Heroku and
Cloud Foundry.

See gh-4018
parent 20b29db5
...@@ -38,6 +38,7 @@ import org.apache.coyote.AbstractProtocol; ...@@ -38,6 +38,7 @@ import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler; import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol; import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.boot.autoconfigure.web.ServerProperties.Session.Cookie; import org.springframework.boot.autoconfigure.web.ServerProperties.Session.Cookie;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
...@@ -55,7 +56,9 @@ import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServle ...@@ -55,7 +56,9 @@ import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServle
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -70,7 +73,8 @@ import org.springframework.util.StringUtils; ...@@ -70,7 +73,8 @@ import org.springframework.util.StringUtils;
* @author Marcos Barbero * @author Marcos Barbero
*/ */
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties implements EmbeddedServletContainerCustomizer, Ordered { public class ServerProperties implements EmbeddedServletContainerCustomizer,
EnvironmentAware, Ordered {
/** /**
* Server HTTP port. * Server HTTP port.
...@@ -106,7 +110,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord ...@@ -106,7 +110,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
/** /**
* If X-Forwarded-* headers should be applied to the HttpRequest. * If X-Forwarded-* headers should be applied to the HttpRequest.
*/ */
private boolean useForwardHeaders; private Boolean useForwardHeaders;
private Session session = new Session(); private Session session = new Session();
...@@ -125,11 +129,18 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord ...@@ -125,11 +129,18 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
private final Undertow undertow = new Undertow(); private final Undertow undertow = new Undertow();
private Environment environment;
@Override @Override
public int getOrder() { public int getOrder() {
return 0; return 0;
} }
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override @Override
public void customize(ConfigurableEmbeddedServletContainer container) { public void customize(ConfigurableEmbeddedServletContainer container) {
if (getPort() != null) { if (getPort() != null) {
...@@ -280,14 +291,22 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord ...@@ -280,14 +291,22 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
return this.contextParameters; return this.contextParameters;
} }
public boolean isUseForwardHeaders() { public Boolean isUseForwardHeaders() {
return this.useForwardHeaders; return this.useForwardHeaders;
} }
public void setUseForwardHeaders(boolean useForwardHeaders) { public void setUseForwardHeaders(Boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders; this.useForwardHeaders = useForwardHeaders;
} }
protected final boolean getOrDeduceUseForwardHeaders() {
if (this.useForwardHeaders != null) {
return this.useForwardHeaders;
}
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return (platform == null ? false : platform.isUsingForwardHeaders());
}
/** /**
* Get the session timeout. * Get the session timeout.
* @return the session timeout * @return the session timeout
...@@ -722,7 +741,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord ...@@ -722,7 +741,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
// For back compatibility the valve is also enabled if protocol-header is set // For back compatibility the valve is also enabled if protocol-header is set
if (StringUtils.hasText(protocolHeader) if (StringUtils.hasText(protocolHeader)
|| StringUtils.hasText(remoteIpHeader) || StringUtils.hasText(remoteIpHeader)
|| properties.isUseForwardHeaders()) { || properties.getOrDeduceUseForwardHeaders()) {
RemoteIpValve valve = new RemoteIpValve(); RemoteIpValve valve = new RemoteIpValve();
valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader
: "X-Forwarded-Proto"); : "X-Forwarded-Proto");
...@@ -856,7 +875,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord ...@@ -856,7 +875,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
void customizeJetty(ServerProperties serverProperties, void customizeJetty(ServerProperties serverProperties,
JettyEmbeddedServletContainerFactory factory) { JettyEmbeddedServletContainerFactory factory) {
factory.setUseForwardHeaders(serverProperties.isUseForwardHeaders()); factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders());
} }
} }
...@@ -1007,7 +1026,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord ...@@ -1007,7 +1026,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
factory.setAccessLogDirectory(this.accesslog.dir); factory.setAccessLogDirectory(this.accesslog.dir);
factory.setAccessLogPattern(this.accesslog.pattern); factory.setAccessLogPattern(this.accesslog.pattern);
factory.setAccessLogEnabled(this.accesslog.enabled); factory.setAccessLogEnabled(this.accesslog.enabled);
factory.setUseForwardHeaders(serverProperties.isUseForwardHeaders()); factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders());
} }
public static class Accesslog { public static class Accesslog {
......
...@@ -42,6 +42,7 @@ import org.springframework.boot.context.embedded.ServletContextInitializer; ...@@ -42,6 +42,7 @@ import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.mock.env.MockEnvironment;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
...@@ -61,6 +62,7 @@ import static org.mockito.Mockito.verify; ...@@ -61,6 +62,7 @@ import static org.mockito.Mockito.verify;
* @author Dave Syer * @author Dave Syer
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
*/ */
public class ServerPropertiesTests { public class ServerPropertiesTests {
...@@ -242,10 +244,8 @@ public class ServerPropertiesTests { ...@@ -242,10 +244,8 @@ public class ServerPropertiesTests {
Map<String, String> map = new HashMap<String, String>(); Map<String, String> map = new HashMap<String, String>();
map.put("server.display-name", "MyBootApp"); map.put("server.display-name", "MyBootApp");
bindProperties(map); bindProperties(map);
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container); this.properties.customize(container);
assertEquals("MyBootApp", container.getDisplayName()); assertEquals("MyBootApp", container.getDisplayName());
} }
...@@ -277,6 +277,12 @@ public class ServerPropertiesTests { ...@@ -277,6 +277,12 @@ public class ServerPropertiesTests {
testRemoteIpValveConfigured(); testRemoteIpValveConfigured();
} }
@Test
public void deduceUseForwardHeadersTomcat() throws Exception {
this.properties.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
testRemoteIpValveConfigured();
}
private void testRemoteIpValveConfigured() { private void testRemoteIpValveConfigured() {
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container); this.properties.customize(container);
...@@ -321,6 +327,13 @@ public class ServerPropertiesTests { ...@@ -321,6 +327,13 @@ public class ServerPropertiesTests {
assertEquals("192.168.0.1", remoteIpValve.getInternalProxies()); assertEquals("192.168.0.1", remoteIpValve.getInternalProxies());
} }
@Test
public void defaultUseForwardHeadersUndertow() throws Exception {
UndertowEmbeddedServletContainerFactory container = spy(new UndertowEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(false);
}
@Test @Test
public void setUseForwardHeadersUndertow() throws Exception { public void setUseForwardHeadersUndertow() throws Exception {
this.properties.setUseForwardHeaders(true); this.properties.setUseForwardHeaders(true);
...@@ -329,6 +342,21 @@ public class ServerPropertiesTests { ...@@ -329,6 +342,21 @@ public class ServerPropertiesTests {
verify(container).setUseForwardHeaders(true); verify(container).setUseForwardHeaders(true);
} }
@Test
public void deduceUseForwardHeadersUndertow() throws Exception {
this.properties.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
UndertowEmbeddedServletContainerFactory container = spy(new UndertowEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(true);
}
@Test
public void defaultUseForwardHeadersJetty() throws Exception {
JettyEmbeddedServletContainerFactory container = spy(new JettyEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(false);
}
@Test @Test
public void setUseForwardHeadersJetty() throws Exception { public void setUseForwardHeadersJetty() throws Exception {
this.properties.setUseForwardHeaders(true); this.properties.setUseForwardHeaders(true);
...@@ -337,6 +365,14 @@ public class ServerPropertiesTests { ...@@ -337,6 +365,14 @@ public class ServerPropertiesTests {
verify(container).setUseForwardHeaders(true); verify(container).setUseForwardHeaders(true);
} }
@Test
public void deduceUseForwardHeadersJetty() throws Exception {
this.properties.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
JettyEmbeddedServletContainerFactory container = spy(new JettyEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(true);
}
private void bindProperties(Map<String, String> map) { private void bindProperties(Map<String, String> map) {
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues( new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(
map)); map));
......
/*
* Copyright 2010-2015 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
*
* http://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.boot.cloud;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.util.StringUtils;
/**
* An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud
* Foundry) meta data in the existing environment. It parses out the VCAP_APPLICATION and
* VCAP_SERVICES meta data and dumps it in a form that is easily consumed by
* {@link Environment} users. If the app is running in Cloud Foundry then both meta data
* items are JSON objects encoded in OS environment variables. VCAP_APPLICATION is a
* shallow hash with basic information about the application (name, instance id, instance
* index, etc.), and VCAP_SERVICES is a hash of lists where the keys are service labels
* and the values are lists of hashes of service instance meta data. Examples are:
*
* <pre class="code">
* VCAP_APPLICATION: {"instance_id":"2ce0ac627a6c8e47e936d829a3a47b5b","instance_index":0,
* "version":"0138c4a6-2a73-416b-aca0-572c09f7ca53","name":"foo",
* "uris":["foo.cfapps.io"], ...}
* VCAP_SERVICES: {"rds-mysql-1.0":[{"name":"mysql","label":"rds-mysql-1.0","plan":"10mb",
* "credentials":{"name":"d04fb13d27d964c62b267bbba1cffb9da","hostname":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com",
* "host":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com","port":3306,"user":"urpRuqTf8Cpe6",
* "username":"urpRuqTf8Cpe6","password":"pxLsGVpsC9A5S"}
* }]}
* </pre>
*
* These objects are flattened into properties. The VCAP_APPLICATION object goes straight
* to {@code vcap.application.*} in a fairly obvious way, and the VCAP_SERVICES object is
* unwrapped so that it is a hash of objects with key equal to the service instance name
* (e.g. "mysql" in the example above), and value equal to that instances properties, and
* then flattened in the same way. E.g.
*
* <pre class="code">
* vcap.application.instance_id: 2ce0ac627a6c8e47e936d829a3a47b5b
* vcap.application.version: 0138c4a6-2a73-416b-aca0-572c09f7ca53
* vcap.application.name: foo
* vcap.application.uris[0]: foo.cfapps.io
*
* vcap.services.mysql.name: mysql
* vcap.services.mysql.label: rds-mysql-1.0
* vcap.services.mysql.credentials.name: d04fb13d27d964c62b267bbba1cffb9da
* vcap.services.mysql.credentials.port: 3306
* vcap.services.mysql.credentials.host: mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com
* vcap.services.mysql.credentials.username: urpRuqTf8Cpe6
* vcap.services.mysql.credentials.password: pxLsGVpsC9A5S
* ...
* </pre>
*
* N.B. this initializer is mainly intended for informational use (the application and
* instance ids are particularly useful). For service binding you might find that Spring
* Cloud is more convenient and more robust against potential changes in Cloud Foundry.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final Log logger = LogFactory
.getLog(CloudFoundryVcapEnvironmentPostProcessor.class);
private static final String VCAP_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES";
// Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1;
private final JsonParser parser = JsonParserFactory.getJsonParser();
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
Properties properties = new Properties();
addWithPrefix(properties, getPropertiesFromApplication(environment),
"vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment),
"vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources
.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
}
private void addWithPrefix(Properties properties, Properties other, String prefix) {
for (String key : other.stringPropertyNames()) {
String prefixed = prefix + key;
properties.setProperty(prefixed, other.getProperty(key));
}
}
private Properties getPropertiesFromApplication(Environment environment) {
Properties properties = new Properties();
try {
String property = environment.getProperty(VCAP_APPLICATION, "{}");
Map<String, Object> map = this.parser.parseMap(property);
extractPropertiesFromApplication(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_APPLICATION", ex);
}
return properties;
}
private Properties getPropertiesFromServices(Environment environment) {
Properties properties = new Properties();
try {
String property = environment.getProperty(VCAP_SERVICES, "{}");
Map<String, Object> map = this.parser.parseMap(property);
extractPropertiesFromServices(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_SERVICES", ex);
}
return properties;
}
private void extractPropertiesFromApplication(Properties properties,
Map<String, Object> map) {
if (map != null) {
flatten(properties, map, "");
}
}
private void extractPropertiesFromServices(Properties properties,
Map<String, Object> map) {
if (map != null) {
for (Object services : map.values()) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) services;
for (Object object : list) {
@SuppressWarnings("unchecked")
Map<String, Object> service = (Map<String, Object>) object;
String key = (String) service.get("name");
if (key == null) {
key = (String) service.get("label");
}
flatten(properties, service, key);
}
}
}
}
@SuppressWarnings("unchecked")
private void flatten(Properties properties, Map<String, Object> input, String path) {
for (Entry<String, Object> entry : input.entrySet()) {
String key = getFullKey(path, entry.getKey());
Object value = entry.getValue();
if (value instanceof Map) {
// Need a compound key
flatten(properties, (Map<String, Object>) value, key);
}
else if (value instanceof Collection) {
// Need a compound key
Collection<Object> collection = (Collection<Object>) value;
properties.put(key,
StringUtils.collectionToCommaDelimitedString(collection));
int count = 0;
for (Object item : collection) {
String itemKey = "[" + (count++) + "]";
flatten(properties, Collections.singletonMap(itemKey, item), key);
}
}
else if (value instanceof String) {
properties.put(key, value);
}
else if (value instanceof Number) {
properties.put(key, value.toString());
}
else if (value instanceof Boolean) {
properties.put(key, value.toString());
}
else {
properties.put(key, value == null ? "" : value);
}
}
}
private String getFullKey(String path, String key) {
if (!StringUtils.hasText(path)) {
return key;
}
if (key.startsWith("[")) {
return path + key;
}
return path + "." + key;
}
}
/*
* Copyright 2012-2015 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
*
* http://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.boot.cloud;
import org.springframework.core.env.Environment;
/**
* Simple detection for well known cloud platforms. For more advanced cloud provider
* integration consider the Spring Clould project.
*
* @author Phillip Webb
* @since 1.3.0
* @see "http://cloud.spring.io"
*/
public enum CloudPlatform {
/**
* Cloud Foundry platform.
*/
CLOUD_FOUNDRY {
@Override
public boolean isActive(Environment environment) {
return environment.containsProperty("VCAP_APPLICATION")
|| environment.containsProperty("VCAP_SERVICES");
}
},
/**
* Heroku platform.
*/
HEROKU {
@Override
public boolean isActive(Environment environment) {
return environment.containsProperty("DYNO");
}
};
/**
* Determines if the platform is active (i.e. the application is running in it).
* @param environment the environment
* @return if the platform is active.
*/
public abstract boolean isActive(Environment environment);
/**
* Returns if the platform is behind a load balancer and uses
* {@literal X-Forwarded-For} headers.
* @return if {@literal X-Forwarded-For} headers are used
*/
public boolean isUsingForwardHeaders() {
return true;
}
/**
* Returns the active {@link CloudPlatform} or {@code null} if one cannot be deduced.
* @param environment the environment
* @return the {@link CloudPlatform} or {@code null}
*/
public static CloudPlatform getActive(Environment environment) {
if (environment != null) {
for (CloudPlatform cloudPlatform : values()) {
if (cloudPlatform.isActive(environment)) {
return cloudPlatform;
}
}
}
return null;
}
}
/* /*
* Copyright 2012-2014 the original author or authors. * Copyright 2012-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
/** /**
* Support for Cloud Foundry PAAS based deployment. * Low level support for Cloud deployments.
*/ */
package org.springframework.boot.cloudfoundry; package org.springframework.boot.cloud;
...@@ -16,27 +16,9 @@ ...@@ -16,27 +16,9 @@
package org.springframework.boot.cloudfoundry; package org.springframework.boot.cloudfoundry;
import java.util.Collection; import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.util.StringUtils;
/** /**
* An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud * An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud
...@@ -87,156 +69,10 @@ import org.springframework.util.StringUtils; ...@@ -87,156 +69,10 @@ import org.springframework.util.StringUtils;
* *
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson * @author Andy Wilkinson
* @deprecated since 1.3.0 in favor of CloudFoundryVcapEnvironmentPostProcessor
*/ */
public class VcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { @Deprecated
public class VcapEnvironmentPostProcessor extends
private static final Log logger = LogFactory CloudFoundryVcapEnvironmentPostProcessor {
.getLog(VcapEnvironmentPostProcessor.class);
private static final String VCAP_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES";
// Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1;
private final JsonParser parser = JsonParserFactory.getJsonParser();
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
if (!environment.containsProperty(VCAP_APPLICATION)
&& !environment.containsProperty(VCAP_SERVICES)) {
return;
}
Properties properties = new Properties();
addWithPrefix(properties, getPropertiesFromApplication(environment),
"vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment),
"vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
private void addWithPrefix(Properties properties, Properties other, String prefix) {
for (String key : other.stringPropertyNames()) {
String prefixed = prefix + key;
properties.setProperty(prefixed, other.getProperty(key));
}
}
private Properties getPropertiesFromApplication(Environment environment) {
Properties properties = new Properties();
try {
Map<String, Object> map = this.parser.parseMap(environment.getProperty(
VCAP_APPLICATION, "{}"));
extractPropertiesFromApplication(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_APPLICATION", ex);
}
return properties;
}
private Properties getPropertiesFromServices(Environment environment) {
Properties properties = new Properties();
try {
Map<String, Object> map = this.parser.parseMap(environment.getProperty(
VCAP_SERVICES, "{}"));
extractPropertiesFromServices(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_SERVICES", ex);
}
return properties;
}
private void extractPropertiesFromApplication(Properties properties,
Map<String, Object> map) {
if (map != null) {
flatten(properties, map, "");
}
}
private void extractPropertiesFromServices(Properties properties,
Map<String, Object> map) {
if (map != null) {
for (Object services : map.values()) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) services;
for (Object object : list) {
@SuppressWarnings("unchecked")
Map<String, Object> service = (Map<String, Object>) object;
String key = (String) service.get("name");
if (key == null) {
key = (String) service.get("label");
}
flatten(properties, service, key);
}
}
}
}
@SuppressWarnings("unchecked")
private void flatten(Properties properties, Map<String, Object> input, String path) {
for (Entry<String, Object> entry : input.entrySet()) {
String key = getFullKey(path, entry.getKey());
Object value = entry.getValue();
if (value instanceof Map) {
// Need a compound key
flatten(properties, (Map<String, Object>) value, key);
}
else if (value instanceof Collection) {
// Need a compound key
Collection<Object> collection = (Collection<Object>) value;
properties.put(key,
StringUtils.collectionToCommaDelimitedString(collection));
int count = 0;
for (Object item : collection) {
String itemKey = "[" + (count++) + "]";
flatten(properties, Collections.singletonMap(itemKey, item), key);
}
}
else if (value instanceof String) {
properties.put(key, value);
}
else if (value instanceof Number) {
properties.put(key, value.toString());
}
else if (value instanceof Boolean) {
properties.put(key, value.toString());
}
else {
properties.put(key, value == null ? "" : value);
}
}
}
private String getFullKey(String path, String key) {
if (!StringUtils.hasText(path)) {
return key;
}
if (key.startsWith("[")) {
return path + key;
}
return path + "." + key;
}
} }
...@@ -28,5 +28,5 @@ org.springframework.boot.logging.LoggingApplicationListener ...@@ -28,5 +28,5 @@ org.springframework.boot.logging.LoggingApplicationListener
# Environment Post Processors # Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloudfoundry.VcapEnvironmentPostProcessor,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor
\ No newline at end of file
/*
* Copyright 2012-2015 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
*
* http://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.boot.cloud;
import org.junit.Test;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link CloudPlatform}.
*
* @author Phillip Webb
*/
public class CloudPlatformTests {
@Test
public void getActiveWhenEnvironmentIsNullShouldReturnNull() throws Exception {
CloudPlatform platform = CloudPlatform.getActive(null);
assertThat(platform, nullValue());
}
@Test
public void getActiveWhenNotInCloudShouldReturnNull() throws Exception {
Environment environment = new MockEnvironment();
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, nullValue());
}
@Test
public void getActiveWhenHasVcapApplicationShouldReturnCloudFoundry()
throws Exception {
Environment environment = new MockEnvironment().withProperty("VCAP_APPLICATION",
"---");
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, equalTo(CloudPlatform.CLOUD_FOUNDRY));
assertThat(platform.isActive(environment), equalTo(true));
}
@Test
public void getActiveWhenHasVcapServicesShouldReturnCloudFoundry() throws Exception {
Environment environment = new MockEnvironment().withProperty("VCAP_SERVICES",
"---");
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, equalTo(CloudPlatform.CLOUD_FOUNDRY));
assertThat(platform.isActive(environment), equalTo(true));
}
@Test
public void getActiveWhenHasDynoShouldReturnHeroku() throws Exception {
Environment environment = new MockEnvironment().withProperty("DYNO", "---");
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, equalTo(CloudPlatform.HEROKU));
assertThat(platform.isActive(environment), equalTo(true));
}
}
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.cloudfoundry; package org.springframework.boot.cloud.cloudfoundry;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -25,14 +26,14 @@ import static org.junit.Assert.assertEquals; ...@@ -25,14 +26,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
/** /**
* Tests for {@link VcapEnvironmentPostProcessor}. * Tests for {@link CloudFoundryVcapEnvironmentPostProcessor}.
* *
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class VcapEnvironmentPostProcessorTests { public class CloudFoundryVcapEnvironmentPostProcessorTests {
private final VcapEnvironmentPostProcessor initializer = new VcapEnvironmentPostProcessor(); private final CloudFoundryVcapEnvironmentPostProcessor initializer = new CloudFoundryVcapEnvironmentPostProcessor();
private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment