Merge branch 'cf-post-processor'

This commit is contained in:
Scott Frederick
2017-05-10 17:30:09 -05:00
10 changed files with 175 additions and 11 deletions

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Wed Jun 29 17:49:51 EDT 2016
#Tue May 02 16:25:04 CDT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip

View File

@@ -1,9 +1,9 @@
package org.springframework.cloud.cloudfoundry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import org.springframework.cloud.AbstractCloudConnector;
import org.springframework.cloud.CloudException;
@@ -17,7 +17,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
/**
*
* @author Ramnivas Laddad
*
* @author Scott Frederick
*/
public class CloudFoundryConnector extends AbstractCloudConnector<Map<String,Object>> {
@@ -25,10 +25,13 @@ public class CloudFoundryConnector extends AbstractCloudConnector<Map<String,Obj
private EnvironmentAccessor environment = new EnvironmentAccessor();
private ApplicationInstanceInfoCreator applicationInstanceInfoCreator = new ApplicationInstanceInfoCreator();
private Iterable<ServiceDataPostProcessor> serviceDataPostProcessors;
@SuppressWarnings({ "unchecked", "rawtypes" })
public CloudFoundryConnector() {
super((Class) CloudFoundryServiceInfoCreator.class);
scanServiceDataPostProcessors();
}
@Override
@@ -64,20 +67,25 @@ public class CloudFoundryConnector extends AbstractCloudConnector<Map<String,Obj
@SuppressWarnings("unchecked")
protected List<Map<String,Object>> getServicesData() {
String servicesString = environment.getEnvValue("VCAP_SERVICES");
Map<String, List<Map<String,Object>>> rawServices = new HashMap<String, List<Map<String,Object>>>();
CloudFoundryRawServiceData rawServices = new CloudFoundryRawServiceData();
if (servicesString != null && servicesString.length() > 0) {
try {
rawServices = objectMapper.readValue(servicesString, Map.class);
rawServices = objectMapper.readValue(servicesString, CloudFoundryRawServiceData.class);
} catch (Exception e) {
throw new CloudException(e);
}
}
List<Map<String,Object>> flatServices = new ArrayList<Map<String,Object>>();
for (ServiceDataPostProcessor postProcessor : serviceDataPostProcessors) {
rawServices = postProcessor.process(rawServices);
}
List<Map<String, Object>> flatServices = new ArrayList<Map<String, Object>>();
for (Map.Entry<String, List<Map<String,Object>>> entry : rawServices.entrySet()) {
flatServices.addAll(entry.getValue());
}
return flatServices;
}
@@ -86,6 +94,10 @@ public class CloudFoundryConnector extends AbstractCloudConnector<Map<String,Obj
protected FallbackServiceInfoCreator<BaseServiceInfo,Map<String,Object>> getFallbackServiceInfoCreator() {
return new CloudFoundryFallbackServiceInfoCreator();
}
private void scanServiceDataPostProcessors() {
serviceDataPostProcessors = ServiceLoader.load(ServiceDataPostProcessor.class);
}
}
class CloudFoundryFallbackServiceInfoCreator extends FallbackServiceInfoCreator<BaseServiceInfo,Map<String,Object>> {

View File

@@ -0,0 +1,22 @@
package org.springframework.cloud.cloudfoundry;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A {@link CloudFoundryRawServiceData} object represents the data read from the {@literal VCAP_SERVICES}
* environment variable and transformed from JSON text to a collection of objects.
*
* The root of the data structure is a {@link List}. Each element of the list represents one service from
* the JSON.
*/
public class CloudFoundryRawServiceData extends HashMap<String, List<Map<String,Object>>> {
public CloudFoundryRawServiceData() {
super();
}
public CloudFoundryRawServiceData(Map<? extends String, ? extends List<Map<String, Object>>> map) {
super(map);
}
}

View File

@@ -0,0 +1,83 @@
package org.springframework.cloud.cloudfoundry;
/**
* An extension point that allows service data to be processed after it is read from {@literal VCAP_SERVICES}.
*/
public interface ServiceDataPostProcessor {
/**
* Process raw service data as read from {@literal VCAP_SERVICES}.
*
* This method will be called after the {@literal VCAP_SERVICES} environment variable has been read from
* the environment and transformed from JSON text into a {@link CloudFoundryRawServiceData} data structure.
*
* If the {@literal VCAP_SERVICES} environment variable for an application contains the following:
*
* <pre>
* {@code
* "VCAP_SERVICES": {
* "mysql": [
* {
* "label": "mysql",
* "name": "mysql-db",
* "plan": "100mb",
* "tags": [ "mysql", "relational" ],
* "credentials": {
* "jdbcUrl": "jdbc:mysql://mysql-broker:3306/db?user=username\u0026password=password",
* "uri": "mysql://username:password@mysql-broker:3306/db?reconnect=true",
* }
* }
* ],
* "rabbitmq": [
* {
* "label": "rabbitmq",
* "name": "rabbit-queue",
* "plan": "standard",
* "tags": [ "rabbitmq", "messaging" ],
* "credentials": {
* "http_api_uri": "http://username:password@rabbitmq-broker:12345/api",
* "uri": "amqp://username:password@rabbitmq-broker/vhost",
* }
* }
* ]
* }
* }
* </pre>
*
* Then the {@link CloudFoundryRawServiceData} data structure would contain the equivalent of this:
*
* <pre>
* {@code
* {
* "mysql": [
* {
* "label": "mysql",
* "name": "mysql-db",
* "plan": "100mb",
* "tags": [ "mysql", "relational" ],
* "credentials": {
* "jdbcUrl": "jdbc:mysql://mysql-broker:3306/db?user=username\u0026password=password",
* "uri": "mysql://username:password@mysql-broker:3306/db?reconnect=true",
* }
* }
* ]
* "rabbitmq": [
* {
* "label": "rabbitmq",
* "name": "rabbit-queue",
* "plan": "standard",
* "tags": [ "rabbitmq", "messaging" ],
* "credentials": {
* "http_api_uri": "http://username:password@rabbitmq-broker:12345/api",
* "uri": "amqp://username:password@rabbitmq-broker/vhost",
* }
* }
* ]
* }
* }
* </pre>
*
* @param serviceData the service data parsed from {@literal VCAP_SERVICES}
* @return the provided {@literal serviceData} with modifications
*/
CloudFoundryRawServiceData process(CloudFoundryRawServiceData serviceData);
}

View File

@@ -1,8 +1,10 @@
package org.springframework.cloud.cloudfoundry;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@@ -12,6 +14,7 @@ import java.util.List;
import org.junit.Test;
import org.springframework.cloud.CloudException;
import org.springframework.cloud.service.ServiceInfo;
import org.springframework.cloud.service.common.MysqlServiceInfo;
/**
*
@@ -95,4 +98,15 @@ public class CloudFoundryConnectorApplicationTest extends AbstractCloudFoundryCo
assertEquals(1, serviceInfos.size());
}
@Test
public void serviceInfosWithPostProcessedCredentials() {
when(mockEnvironment.getEnvValue("VCAP_SERVICES")).
thenReturn(getServicesPayload(readTestDataFile("test-credentials-post-processed.json")));
List<ServiceInfo> serviceInfos = testCloudConnector.getServiceInfos();
assertNotNull(serviceInfos);
assertEquals(1, serviceInfos.size());
assertThat(serviceInfos.get(0), instanceOf(MysqlServiceInfo.class));
assertEquals(((MysqlServiceInfo) serviceInfos.get(0)).getUri(), "MYSQL://USERNAME:PASSWORD@DB.EXAMPLE.COM/DB");
}
}

View File

@@ -0,0 +1,24 @@
package org.springframework.cloud.cloudfoundry;
import java.util.List;
import java.util.Map;
public class StubServiceDataPostProcessor implements ServiceDataPostProcessor {
@SuppressWarnings("unchecked")
@Override
public CloudFoundryRawServiceData process(CloudFoundryRawServiceData serviceData) {
for (List<Map<String, Object>> service : serviceData.values()) {
Map<String, Object> serviceMap = service.get(0);
String name = (String) serviceMap.get("name");
if (name.equals("uppercase")) {
Map<String, Object> credentials = (Map<String, Object>) serviceMap.get("credentials");
for (Map.Entry<String, Object> entry : credentials.entrySet()) {
String upperCaseValue = ((String) entry.getValue()).toUpperCase();
credentials.put(entry.getKey(), upperCaseValue);
}
}
}
return serviceData;
}
}

View File

@@ -0,0 +1 @@
org.springframework.cloud.cloudfoundry.StubServiceDataPostProcessor

View File

@@ -0,0 +1,8 @@
{
"name": "uppercase",
"label": "mysql",
"tags": ["mysql"],
"credentials": {
"uri": "mysql://username:password@db.example.com/db"
}
}

View File

@@ -90,14 +90,14 @@ public class CloudTestUtil {
public StubServiceInfo(String id, String host, int port, String username, String password) {
super(id, "stub", host, port, username, password, null);
}
// To test the scenario, where the name attribute of a property is explicitly specified
// To test the scenario, where the name attribute of a property is explicitly specified
@ServiceProperty(name="bar")
public String getFoo() {
return "foo";
}
}
public static class StubCompositeServiceInfo implements CompositeServiceInfo {
private String id;
private List<ServiceInfo> constituents;