diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9411448..13372ae 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6c44661..c8fd080 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnector.java b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnector.java index caf0d14..4b64522 100644 --- a/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnector.java +++ b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnector.java @@ -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> { @@ -25,10 +25,13 @@ public class CloudFoundryConnector extends AbstractCloudConnector serviceDataPostProcessors; + @SuppressWarnings({ "unchecked", "rawtypes" }) public CloudFoundryConnector() { super((Class) CloudFoundryServiceInfoCreator.class); + scanServiceDataPostProcessors(); } @Override @@ -64,20 +67,25 @@ public class CloudFoundryConnector extends AbstractCloudConnector> getServicesData() { String servicesString = environment.getEnvValue("VCAP_SERVICES"); - Map>> rawServices = new HashMap>>(); + 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> flatServices = new ArrayList>(); + for (ServiceDataPostProcessor postProcessor : serviceDataPostProcessors) { + rawServices = postProcessor.process(rawServices); + } + + List> flatServices = new ArrayList>(); for (Map.Entry>> entry : rawServices.entrySet()) { flatServices.addAll(entry.getValue()); } + return flatServices; } @@ -86,6 +94,10 @@ public class CloudFoundryConnector extends AbstractCloudConnector> getFallbackServiceInfoCreator() { return new CloudFoundryFallbackServiceInfoCreator(); } + + private void scanServiceDataPostProcessors() { + serviceDataPostProcessors = ServiceLoader.load(ServiceDataPostProcessor.class); + } } class CloudFoundryFallbackServiceInfoCreator extends FallbackServiceInfoCreator> { diff --git a/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryRawServiceData.java b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryRawServiceData.java new file mode 100644 index 0000000..1546d17 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryRawServiceData.java @@ -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>> { + public CloudFoundryRawServiceData() { + super(); + } + + public CloudFoundryRawServiceData(Map>> map) { + super(map); + } +} diff --git a/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/ServiceDataPostProcessor.java b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/ServiceDataPostProcessor.java new file mode 100644 index 0000000..8520289 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/main/java/org/springframework/cloud/cloudfoundry/ServiceDataPostProcessor.java @@ -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: + * + *
+	 * {@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",
+	 *       }
+	 *     }
+	 *   ]
+	 * }
+	 * }
+	 * 
+ * + * Then the {@link CloudFoundryRawServiceData} data structure would contain the equivalent of this: + * + *
+	 * {@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",
+	 *       }
+	 *     }
+	 *   ]
+	 * }
+	 * }
+	 * 
+ * + * @param serviceData the service data parsed from {@literal VCAP_SERVICES} + * @return the provided {@literal serviceData} with modifications + */ + CloudFoundryRawServiceData process(CloudFoundryRawServiceData serviceData); +} diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorApplicationTest.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorApplicationTest.java index 54ed630..880af4a 100644 --- a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorApplicationTest.java +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryConnectorApplicationTest.java @@ -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 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"); + } + } diff --git a/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/StubServiceDataPostProcessor.java b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/StubServiceDataPostProcessor.java new file mode 100644 index 0000000..dd13cd3 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/test/java/org/springframework/cloud/cloudfoundry/StubServiceDataPostProcessor.java @@ -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> service : serviceData.values()) { + Map serviceMap = service.get(0); + String name = (String) serviceMap.get("name"); + if (name.equals("uppercase")) { + Map credentials = (Map) serviceMap.get("credentials"); + for (Map.Entry entry : credentials.entrySet()) { + String upperCaseValue = ((String) entry.getValue()).toUpperCase(); + credentials.put(entry.getKey(), upperCaseValue); + } + } + } + + return serviceData; + } +} diff --git a/spring-cloud-cloudfoundry-connector/src/test/resources/META-INF/services/org.springframework.cloud.cloudfoundry.ServiceDataPostProcessor b/spring-cloud-cloudfoundry-connector/src/test/resources/META-INF/services/org.springframework.cloud.cloudfoundry.ServiceDataPostProcessor new file mode 100644 index 0000000..a571162 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/test/resources/META-INF/services/org.springframework.cloud.cloudfoundry.ServiceDataPostProcessor @@ -0,0 +1 @@ +org.springframework.cloud.cloudfoundry.StubServiceDataPostProcessor \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-credentials-post-processed.json b/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-credentials-post-processed.json new file mode 100644 index 0000000..f2f6483 --- /dev/null +++ b/spring-cloud-cloudfoundry-connector/src/test/resources/org/springframework/cloud/cloudfoundry/test-credentials-post-processed.json @@ -0,0 +1,8 @@ +{ + "name": "uppercase", + "label": "mysql", + "tags": ["mysql"], + "credentials": { + "uri": "mysql://username:password@db.example.com/db" + } +} \ No newline at end of file diff --git a/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTestUtil.java b/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTestUtil.java index bded696..6a7eb14 100644 --- a/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTestUtil.java +++ b/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTestUtil.java @@ -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 constituents;