diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/Cloud.java b/spring-cloud-core/src/main/java/org/springframework/cloud/Cloud.java index 05412a5..82aafeb 100644 --- a/spring-cloud-core/src/main/java/org/springframework/cloud/Cloud.java +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/Cloud.java @@ -14,6 +14,7 @@ import java.util.logging.Logger; import javax.activation.DataSource; import org.springframework.cloud.app.ApplicationInstanceInfo; +import org.springframework.cloud.service.CompositeServiceInfo; import org.springframework.cloud.service.ServiceConnectorConfig; import org.springframework.cloud.service.ServiceConnectorCreator; import org.springframework.cloud.service.ServiceInfo; @@ -88,7 +89,7 @@ public class Cloud { * @return information about all services bound to the application */ public List getServiceInfos() { - return cloudConnector.getServiceInfos(); + return flatten(cloudConnector.getServiceInfos()); } /** @@ -316,6 +317,24 @@ public class Cloud { return labelAnnotation.value(); } } + + private static List flatten(List serviceInfos) { + List flattened = new ArrayList(); + + for (ServiceInfo serviceInfo : serviceInfos) { + if (serviceInfo instanceof CompositeServiceInfo) { + // recursively flatten any CompositeServiceInfos + CompositeServiceInfo compositeServiceInfo = (CompositeServiceInfo)serviceInfo; + flattened.addAll(flatten(compositeServiceInfo.getServiceInfos())); + } else { + flattened.add(serviceInfo); + } + } + + return flattened; + } + + } class ServiceConnectorCreatorRegistry { diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/BaseCompositeServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/BaseCompositeServiceInfo.java new file mode 100644 index 0000000..41dcea2 --- /dev/null +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/BaseCompositeServiceInfo.java @@ -0,0 +1,25 @@ +package org.springframework.cloud.service; + +import java.util.Arrays; +import java.util.List; + +/** + * A generic implementation of {@link CompositeServiceInfo} that should suffice in many situations. + * + * @author Ramnivas Laddad + * + */ +public class BaseCompositeServiceInfo extends BaseServiceInfo implements CompositeServiceInfo { + + private List constituents; + + public BaseCompositeServiceInfo(String id, ServiceInfo... constituents) { + super(id); + this.constituents = Arrays.asList(constituents); + } + + @Override + public List getServiceInfos() { + return constituents; + } +} diff --git a/spring-cloud-core/src/main/java/org/springframework/cloud/service/CompositeServiceInfo.java b/spring-cloud-core/src/main/java/org/springframework/cloud/service/CompositeServiceInfo.java new file mode 100644 index 0000000..c0d3127 --- /dev/null +++ b/spring-cloud-core/src/main/java/org/springframework/cloud/service/CompositeServiceInfo.java @@ -0,0 +1,24 @@ +package org.springframework.cloud.service; + +import java.util.List; + +import org.springframework.cloud.Cloud; + +/** + * Interface to represent services constituted out of other services. + * + *

+ * With services such as Pivotal HD, even though the app will be bound to one such service, + * from the application developer's point of view, it needs to access the constituent services. + * This interface expresses such a service, which the {@link Cloud} class flattens into + * its constituents. Then from the rest of the Spring Cloud infrastructure as well as application + * code, the effect is as if all the constituents were individually bound. + * + * @author Ramnivas Laddad + * + */ +public interface CompositeServiceInfo extends ServiceInfo { + + List getServiceInfos(); + +} diff --git a/spring-cloud-core/src/test/java/org/springframework/cloud/AbstractCloudConnectorTest.java b/spring-cloud-core/src/test/java/org/springframework/cloud/AbstractCloudConnectorTest.java index 4d8bda7..fbe776a 100644 --- a/spring-cloud-core/src/test/java/org/springframework/cloud/AbstractCloudConnectorTest.java +++ b/spring-cloud-core/src/test/java/org/springframework/cloud/AbstractCloudConnectorTest.java @@ -104,4 +104,19 @@ class TestServiceData { public String getTag() { return tag; } +} + +class TestCompositeServiceData extends TestServiceData { + + private TestServiceData[] constituents; + + public TestCompositeServiceData(String id, String tag, TestServiceData... constituents) { + super(id, tag); + this.constituents = constituents; + } + + public TestServiceData[] getConstituents() { + return constituents; + } + } \ No newline at end of file diff --git a/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTest.java b/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTest.java index 496a12a..a9abcc3 100644 --- a/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTest.java +++ b/spring-cloud-core/src/test/java/org/springframework/cloud/CloudTest.java @@ -14,6 +14,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.cloud.CloudTestUtil.StubApplicationInstanceInfo; import org.springframework.cloud.CloudTestUtil.StubCloudConnector; +import org.springframework.cloud.CloudTestUtil.StubCompositeServiceInfo; import org.springframework.cloud.CloudTestUtil.StubServiceInfo; import org.springframework.cloud.service.BaseServiceInfo; import org.springframework.cloud.service.ServiceConnectorConfig; @@ -182,7 +183,41 @@ public class CloudTest { testCloud.getServiceInfo("foo"); } + + @Test + public void compositeServiceInfo() { + StubServiceInfo testServiceInfo1 = new StubServiceInfo("test-id-1", "test-host", 1000, "test-username", "test-password"); + StubServiceInfo testServiceInfo2 = new StubServiceInfo("test-id-2", "test-host", 1000, "test-username", "test-password"); + ServiceInfo testCompositeServiceInfo = new StubCompositeServiceInfo("test-composite",testServiceInfo1, testServiceInfo2); + StubCloudConnector stubCloudConnector = CloudTestUtil.getTestCloudConnector(testCompositeServiceInfo); + Cloud testCloud = new Cloud(stubCloudConnector, serviceConnectorCreators); + + assertNotNull(testCloud.getServiceInfo("test-id-1")); + assertNotNull(testCloud.getServiceInfo("test-id-2")); + } + + @Test + public void compositeServiceInfoRecursive() { + StubServiceInfo testServiceInfo1a = new StubServiceInfo("test-id-1a", "test-host", 1000, "test-username", "test-password"); + StubServiceInfo testServiceInfo1b = new StubServiceInfo("test-id-1b", "test-host", 1000, "test-username", "test-password"); + ServiceInfo testCompositeServiceInfo1 = new StubCompositeServiceInfo("test-composite-1",testServiceInfo1a, testServiceInfo1b); + + StubServiceInfo testServiceInfo2a = new StubServiceInfo("test-id-2a", "test-host", 1000, "test-username", "test-password"); + StubServiceInfo testServiceInfo2b = new StubServiceInfo("test-id-2b", "test-host", 1000, "test-username", "test-password"); + ServiceInfo testCompositeServiceInfo2 = new StubCompositeServiceInfo("test-composite-2",testServiceInfo2a, testServiceInfo2b); + + ServiceInfo testCompositeServiceInfo = new StubCompositeServiceInfo("test-composite",testCompositeServiceInfo1, testCompositeServiceInfo2); + + StubCloudConnector stubCloudConnector = CloudTestUtil.getTestCloudConnector(testCompositeServiceInfo); + Cloud testCloud = new Cloud(stubCloudConnector, serviceConnectorCreators); + + assertNotNull(testCloud.getServiceInfo("test-id-1a")); + assertNotNull(testCloud.getServiceInfo("test-id-1b")); + assertNotNull(testCloud.getServiceInfo("test-id-2a")); + assertNotNull(testCloud.getServiceInfo("test-id-2b")); + } + private void assertStubServiceProp(String leadKey, StubServiceInfo serviceInfo, Properties cloudProperties) { CloudTestUtil.assertBasicProps(leadKey, serviceInfo, cloudProperties); 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 029be7d..bded696 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 @@ -3,6 +3,7 @@ package org.springframework.cloud; import static org.junit.Assert.assertEquals; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -10,6 +11,7 @@ import java.util.Properties; import org.springframework.cloud.CloudConnector; import org.springframework.cloud.app.ApplicationInstanceInfo; +import org.springframework.cloud.service.CompositeServiceInfo; import org.springframework.cloud.service.UriBasedServiceInfo; import org.springframework.cloud.service.ServiceInfo; import org.springframework.cloud.service.ServiceInfo.ServiceLabel; @@ -96,6 +98,26 @@ public class CloudTestUtil { } } + public static class StubCompositeServiceInfo implements CompositeServiceInfo { + private String id; + private List constituents; + + public StubCompositeServiceInfo(String id, ServiceInfo... constituents) { + this.id = id; + this.constituents = Arrays.asList(constituents); + } + + @Override + public String getId() { + return id; + } + + @Override + public List getServiceInfos() { + return constituents; + } + } + public static StubCloudConnector getTestCloudConnector(ApplicationInstanceInfo applicationInstanceInfo, ServiceInfo... serviceInfos) { final StubCloudConnector stubCloudConnector = getTestCloudConnector(serviceInfos); stubCloudConnector.setApplicationInstance(applicationInstanceInfo);