+ *
+ * You can configure all sorts of other things including which Cloud Foundry cloud controller URI to use,
+ * how and whether to use an HTTP proxy, and more using alternative constructors. As configured above, the client
+ * will talk to all services and applications deployed in all spaces and organizations. Use one of the
+ * {@link CloudFoundryClient#CloudFoundryClient(CloudCredentials, URL, String, String)} variants to specify which space
+ * and organization to use.
+ *
+ *
+ * @author Josh Long
+ * @author Spencer Gibb
+ */
+public class CloudFoundryDiscoveryClient implements DiscoveryClient {
+
+ private static final String DESCRIPTION = "Cloud Foundry " + DiscoveryClient.class.getName() + " implementation";
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
+
+ private final CloudFoundryClient cloudFoundryClient;
+
+ private final String vcapApplicationName;
+
+ public CloudFoundryDiscoveryClient(CloudFoundryClient cloudFoundryClient, Environment environment) {
+
+ this.cloudFoundryClient = cloudFoundryClient;
+
+ String vcapApplication = environment.getProperty("VCAP_APPLICATION");
+
+ try {
+ JsonNode jsonNode = objectMapper.readTree(vcapApplication);
+ JsonNode appNameNode = jsonNode.get("application_name");
+
+ this.vcapApplicationName = appNameNode.toString().replaceAll("\"", "");
+
+ this.log.debug("Current ServiceInstance information...");
+ this.log.debug("\tvcapApplicationName: " + this.vcapApplicationName);
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String description() {
+ return DESCRIPTION;
+ }
+
+ @Override
+ public ServiceInstance getLocalServiceInstance() {
+ CloudApplication application = this.cloudFoundryClient.getApplication(this.vcapApplicationName);
+ List serviceInstances =
+ this.createServiceInstancesFromCloudApplications(Collections.singletonList(application));
+ return serviceInstances.size() > 0 ? serviceInstances.iterator().next() : null;
+ }
+
+ @Override
+ public List getInstances(String s) {
+ CloudApplication applications = this.cloudFoundryClient.getApplication(s);
+ return this.createServiceInstancesFromCloudApplications(
+ Collections.singletonList(applications));
+ }
+
+ private boolean isRunning(CloudApplication ca) {
+ InstancesInfo ii = this.cloudFoundryClient.getApplicationInstances(ca);
+ List instances;
+ if (ii != null && (instances = ii.getInstances()) != null) {
+ for (InstanceInfo resolved : instances) {
+ InstanceState state = resolved.getState();
+ if (state != null && state.equals(InstanceState.RUNNING)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public List getServices() {
+ List services = new ArrayList<>();
+ List applications = this.cloudFoundryClient.getApplications();
+ Set serviceIds = new HashSet<>();
+ for (CloudApplication ca : applications) {
+ if (isRunning(ca)) {
+ serviceIds.add(ca.getName());
+ }
+ }
+ services.addAll(serviceIds);
+ return services;
+ }
+
+ protected List createServiceInstancesFromCloudApplications(
+ Collection cloudApplications) {
+ Set serviceInstances = new HashSet<>();
+ for (CloudApplication ca : cloudApplications) {
+ if (isRunning(ca)) {
+ serviceInstances.add(new CloudFoundryServiceInstance(ca));
+ }
+ }
+ List instances = new ArrayList<>();
+ instances.addAll(serviceInstances);
+ return instances;
+ }
+
+ public static class CloudFoundryServiceInstance extends DefaultServiceInstance {
+
+ private final CloudApplication cloudApplication;
+
+ public CloudApplication getCloudApplication() {
+ return cloudApplication;
+ }
+
+ public CloudFoundryServiceInstance(CloudApplication ca) {
+ super(ca.getName(),
+ ca.getUris().iterator().next(),
+ 80,
+ false);
+
+ this.cloudApplication = ca;
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java
new file mode 100644
index 0000000..8e01ace
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import org.cloudfoundry.client.lib.CloudCredentials;
+import org.cloudfoundry.client.lib.CloudFoundryClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+
+/**
+ * @author Josh Long
+ */
+@Configuration
+@EnableConfigurationProperties
+public class CloudFoundryDiscoveryClientConfiguration {
+
+ @Autowired
+ private CloudFoundryDiscoveryProperties cloudFoundryDiscoveryProperties;
+
+ @Bean
+ @ConditionalOnMissingBean(CloudCredentials.class)
+ public CloudCredentials cloudCredentials() {
+ return new CloudCredentials(this.cloudFoundryDiscoveryProperties.getEmail(),
+ this.cloudFoundryDiscoveryProperties.getPassword());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(CloudFoundryClient.class)
+ public CloudFoundryClient cloudFoundryClient(CloudCredentials cc) throws MalformedURLException {
+ CloudFoundryClient cloudFoundryClient = new CloudFoundryClient(cc,
+ URI.create(this.cloudFoundryDiscoveryProperties.getCloudControllerUrl()).toURL());
+ cloudFoundryClient.login();
+ return cloudFoundryClient;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(CloudFoundryDiscoveryClient.class)
+ public CloudFoundryDiscoveryClient cloudFoundryDiscoveryClient(
+ CloudFoundryClient cloudFoundryClient, Environment environment) {
+ return new CloudFoundryDiscoveryClient(cloudFoundryClient, environment);
+ }
+
+ @Bean
+ public CloudFoundryDiscoveryProperties cloudFoundryDiscoveryProperties() {
+ return new CloudFoundryDiscoveryProperties();
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java
new file mode 100644
index 0000000..1aa0f8a
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryProperties.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Josh Long
+ */
+@ConfigurationProperties(prefix = "spring.cloud.cloudfoundry.discovery")
+public class CloudFoundryDiscoveryProperties {
+
+ private String cloudControllerUrl = "https://api.run.pivotal.io";
+
+ private String email;
+
+ private String password;
+
+ public String getCloudControllerUrl() {
+ return cloudControllerUrl;
+ }
+
+ public void setCloudControllerUrl(String cloudControllerUrl) {
+ this.cloudControllerUrl = cloudControllerUrl;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java
new file mode 100644
index 0000000..2e77484
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryRibbonClientConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import com.netflix.client.config.CommonClientConfigKey;
+import com.netflix.client.config.IClientConfig;
+import com.netflix.config.ConfigurationManager;
+import com.netflix.config.DynamicPropertyFactory;
+import com.netflix.config.DynamicStringProperty;
+import com.netflix.loadbalancer.ServerList;
+import org.cloudfoundry.client.lib.CloudFoundryClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author Josh Long
+ */
+@Configuration
+public class CloudFoundryRibbonClientConfiguration {
+
+ protected static final String DEFAULT_NAMESPACE = "ribbon";
+ protected static final String VALUE_NOT_SET = "__not__set__";
+
+ @Value("${ribbon.client.name}")
+ private String serviceId;
+
+ public CloudFoundryRibbonClientConfiguration (){ }
+
+ public CloudFoundryRibbonClientConfiguration (String svcId) {
+ this.serviceId = svcId;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ServerList> ribbonServerList(CloudFoundryClient cloudFoundryClient, IClientConfig config) {
+ CloudFoundryServerList cloudFoundryServerList = new CloudFoundryServerList(cloudFoundryClient);
+ cloudFoundryServerList.initWithNiwsConfig(config);
+ return cloudFoundryServerList;
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ // FIXME: what should this be?
+ setProp(this.serviceId, CommonClientConfigKey.DeploymentContextBasedVipAddresses.key(), this.serviceId);
+ setProp(this.serviceId, CommonClientConfigKey.EnableZoneAffinity.key(), "true");
+ }
+
+ protected void setProp(String serviceId, String suffix, String value) {
+ // how to set the namespace properly?
+ String key = getKey(serviceId, suffix);
+ DynamicStringProperty property = getProperty(key);
+ if (property.get().equals(VALUE_NOT_SET)) {
+ ConfigurationManager.getConfigInstance().setProperty(key, value);
+ }
+ }
+
+ protected DynamicStringProperty getProperty(String key) {
+ return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
+ }
+
+ protected String getKey(String serviceId, String suffix) {
+ return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java
new file mode 100644
index 0000000..99a440c
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import com.netflix.loadbalancer.Server;
+import org.cloudfoundry.client.lib.domain.CloudApplication;
+
+/**
+ * @author Josh Long
+ */
+public class CloudFoundryServer extends Server {
+
+ private final MetaInfo metaInfo;
+
+ public CloudFoundryServer(final CloudApplication cloudApplication) {
+
+ super(cloudApplication.getUris().iterator().next(), 80);
+
+ this.metaInfo = new MetaInfo() {
+ @Override
+ public String getAppName() {
+ return cloudApplication.getName();
+ }
+
+ @Override
+ public String getServerGroup() {
+ return null;
+ }
+
+ @Override
+ public String getServiceIdForDiscovery() {
+ return cloudApplication.getName();
+ }
+
+ @Override
+ public String getInstanceId() {
+ return cloudApplication.getName();
+ }
+ };
+ }
+
+ @Override
+ public MetaInfo getMetaInfo() {
+ return metaInfo;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java
new file mode 100644
index 0000000..94dd134
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import com.netflix.client.config.IClientConfig;
+import com.netflix.loadbalancer.AbstractServerList;
+import org.cloudfoundry.client.lib.CloudFoundryClient;
+import org.cloudfoundry.client.lib.domain.CloudApplication;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Josh Long
+ */
+public class CloudFoundryServerList extends AbstractServerList {
+
+ protected String serviceId;
+
+ private final CloudFoundryClient cloudFoundryClient;
+
+ public CloudFoundryServerList(CloudFoundryClient cloudFoundryClient) {
+ this.cloudFoundryClient = cloudFoundryClient;
+ }
+
+ @Override
+ public void initWithNiwsConfig(IClientConfig iClientConfig) {
+ this.serviceId = iClientConfig.getClientName();
+ }
+
+ @Override
+ public List getInitialListOfServers() {
+ return this.cloudFoundryServers();
+ }
+
+ @Override
+ public List getUpdatedListOfServers() {
+ return this.cloudFoundryServers();
+ }
+
+ protected List cloudFoundryServers() {
+ CloudApplication cloudApplications = this.cloudFoundryClient.getApplication(this.serviceId);
+ return Collections.singletonList(new CloudFoundryServer(cloudApplications));
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/EnableCloudFoundryClient.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/EnableCloudFoundryClient.java
new file mode 100644
index 0000000..9829d6b
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/EnableCloudFoundryClient.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+import java.lang.annotation.*;
+
+/**
+ * Convenience annotation for clients to enable Cloud Foundry discovery configuration (specifically).
+ * Use this (optionally) in case you want discovery and know for sure that it is Cloud Foundry you want.
+ * All it does is turn on discovery and let the auto-configuration find the Cloud Foundry classes.
+ *
+ * @author Josh Long
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@EnableDiscoveryClient
+public @interface EnableCloudFoundryClient {
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/RibbonCloudFoundryAutoConfiguration.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/RibbonCloudFoundryAutoConfiguration.java
new file mode 100644
index 0000000..779f814
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/RibbonCloudFoundryAutoConfiguration.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
+import org.springframework.cloud.netflix.ribbon.RibbonClients;
+import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties
+@ConditionalOnBean(SpringClientFactory.class)
+@ConditionalOnProperty(value = "ribbon.cloudfoundry.enabled", matchIfMissing = true)
+@AutoConfigureAfter(RibbonAutoConfiguration.class)
+@RibbonClients(defaultConfiguration = CloudFoundryRibbonClientConfiguration.class)
+public class RibbonCloudFoundryAutoConfiguration {
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-cloudfoundry-discovery/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..5b452c3
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,6 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.springframework.cloud.cloudfoundry.discovery.RibbonCloudFoundryAutoConfiguration
+
+# Discovery Client Configuration
+org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
+org.springframework.cloud.cloudfoundry.discovery.CloudFoundryDiscoveryClientConfiguration
diff --git a/src/main/ruby/generate_readme.sh b/spring-cloud-cloudfoundry-discovery/src/main/ruby/generate_readme.sh
similarity index 100%
rename from src/main/ruby/generate_readme.sh
rename to spring-cloud-cloudfoundry-discovery/src/main/ruby/generate_readme.sh
diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java
new file mode 100644
index 0000000..6c79845
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryAutoConfigurationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import org.apache.commons.logging.LogFactory;
+import org.cloudfoundry.client.lib.CloudCredentials;
+import org.cloudfoundry.client.lib.CloudFoundryClient;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+
+/**
+ * @author Josh Long
+ */
+public class CloudFoundryAutoConfigurationTest {
+
+ private ConfigurableApplicationContext context;
+
+ @Before
+ public void setUp() {
+
+ String hiServiceServiceId = "foo-service";
+
+ Object vcapAppl = "{\"limits\":{\"mem\":1024,\"disk\":1024,\"fds\":16384},\"application_version\":" +
+ "\"36eff082-96d6-498f-8214-508fda72ba65\",\"application_name\":\"" + hiServiceServiceId +
+ "\",\"application_uris\"" +
+ ":[\"" + hiServiceServiceId +
+ ".cfapps.io\"],\"version\":\"36eff082-96d6-498f-8214-508fda72ba65\",\"name\":" +
+ "\"hi-service\",\"space_name\":\"joshlong\",\"space_id\":\"e0cd969c-3461-41ae-abde-4e11bb5acbd1\"," +
+ "\"uris\":[\"hi-service.cfapps.io\"],\"users\":null,\"application_id\":\"af350f7c-88c4-4e35-a04e-698a1dbc7354\"," +
+ "\"instance_id\":\"e4843ca23bd947b28e6d4cb3f9b92cbb\",\"instance_index\":0,\"host\":\"0.0.0.0\",\"port\":61590," +
+ "\"started_at\":\"2015-05-07 20:00:10 +0000\",\"started_at_timestamp\":1431028810,\"start\":\"2015-05-07 20:00:10 +0000\"," +
+ "\"state_timestamp\":1431028810}";
+
+ this.context = new SpringApplicationBuilder()
+ .properties(Collections.singletonMap("VCAP_APPLICATION", vcapAppl))
+ .sources(SimpleConfiguration.class)
+ .run();
+ }
+
+ @After
+ public void after() throws Throwable {
+ synchronized (this) {
+ if (null != this.context)
+ this.context.close();
+ }
+ }
+
+
+ @Configuration
+ @EnableDiscoveryClient
+ @EnableFeignClients
+ @EnableAutoConfiguration
+ public static class SimpleConfiguration {
+
+ @Bean
+ CloudCredentials cloudCredentials() {
+ return Mockito.mock(CloudCredentials.class);
+ }
+
+ @Bean
+ CloudFoundryClient cloudFoundryClient() {
+ return Mockito.mock(CloudFoundryClient.class);
+ }
+
+ }
+
+ @Test
+ public void contextLoaded() {
+ LogFactory.getLog(getClass()).debug("contextLoad()");
+ Assert.assertTrue(this.context.getBeansOfType(CloudFoundryDiscoveryClient.class).size() > 0);
+ Assert.assertTrue(this.context.getBeansOfType(CloudFoundryDiscoveryProperties.class).size() > 0);
+ Assert.assertTrue(this.context.getBeansOfType(CloudFoundryClient.class).size() > 0);
+ }
+
+}
diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java
new file mode 100644
index 0000000..5c88da8
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.cloudfoundry.client.lib.CloudFoundryClient;
+import org.cloudfoundry.client.lib.domain.CloudApplication;
+import org.cloudfoundry.client.lib.domain.InstanceInfo;
+import org.cloudfoundry.client.lib.domain.InstanceState;
+import org.cloudfoundry.client.lib.domain.InstancesInfo;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.core.env.Environment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+
+/**
+ * @author Josh Long
+ */
+public class CloudFoundryDiscoveryClientTest {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private CloudFoundryDiscoveryClient cloudFoundryDiscoveryClient;
+
+ private CloudApplication cloudApplication;
+
+ private String hiServiceServiceId = "hi-service";
+
+ private CloudFoundryClient cloudFoundryClient;
+
+ private CloudApplication fakeCloudApplication(String name, String... uri) {
+ CloudApplication cloudApplication = mock(CloudApplication.class);
+ given(cloudApplication.getName()).willReturn(name);
+ given(cloudApplication.getUris()).willReturn(Arrays.asList(uri));
+ return cloudApplication;
+ }
+
+ @Before
+ public void setUp() {
+ this.cloudFoundryClient = mock(CloudFoundryClient.class);
+ Environment environment = mock(Environment.class);
+
+ given(environment.getProperty("VCAP_APPLICATION"))
+ .willReturn("{\"limits\":{\"mem\":1024,\"disk\":1024,\"fds\":16384},\"application_version\":" +
+ "\"36eff082-96d6-498f-8214-508fda72ba65\",\"application_name\":\"" + hiServiceServiceId +
+ "\",\"application_uris\"" +
+ ":[\"" + hiServiceServiceId +
+ ".cfapps.io\"],\"version\":\"36eff082-96d6-498f-8214-508fda72ba65\",\"name\":" +
+ "\"hi-service\",\"space_name\":\"joshlong\",\"space_id\":\"e0cd969c-3461-41ae-abde-4e11bb5acbd1\"," +
+ "\"uris\":[\"hi-service.cfapps.io\"],\"users\":null,\"application_id\":\"af350f7c-88c4-4e35-a04e-698a1dbc7354\"," +
+ "\"instance_id\":\"e4843ca23bd947b28e6d4cb3f9b92cbb\",\"instance_index\":0,\"host\":\"0.0.0.0\",\"port\":61590," +
+ "\"started_at\":\"2015-05-07 20:00:10 +0000\",\"started_at_timestamp\":1431028810,\"start\":\"2015-05-07 20:00:10 +0000\"," +
+ "\"state_timestamp\":1431028810}");
+
+ List cloudApplications = new ArrayList<>();
+ cloudApplications.add(fakeCloudApplication(this.hiServiceServiceId, "hi-service.cfapps.io", "hi-service-1.cfapps.io"));
+ cloudApplications.add(fakeCloudApplication("config-service", "conf-service.cfapps.io", "conf-service-1.cfapps.io"));
+
+ given(this.cloudFoundryClient.getApplications())
+ .willReturn(cloudApplications);
+
+ cloudApplication = cloudApplications.get(0);
+ given(this.cloudFoundryClient.getApplication(this.hiServiceServiceId))
+ .willReturn(cloudApplication);
+
+ given(this.cloudFoundryClient.getApplication(this.hiServiceServiceId))
+ .willReturn(this.cloudApplication);
+
+ InstanceInfo instanceInfo = mock(InstanceInfo.class);
+ InstancesInfo instancesInfo = mock(InstancesInfo.class);
+ given(instancesInfo.getInstances())
+ .willReturn(Collections.singletonList(instanceInfo));
+ given(instanceInfo.getState())
+ .willReturn(InstanceState.RUNNING);
+
+ given(this.cloudFoundryClient.getApplicationInstances(this.cloudApplication))
+ .willReturn(instancesInfo);
+
+ this.cloudFoundryDiscoveryClient = new CloudFoundryDiscoveryClient(cloudFoundryClient, environment);
+ }
+
+ @Test
+ public void testServiceResolution() {
+ List serviceNames = this.cloudFoundryDiscoveryClient.getServices();
+
+ Assert.assertTrue("there should be one registered service.", serviceNames.contains(
+ this.hiServiceServiceId));
+
+ for (String serviceName : serviceNames) {
+ this.log.debug("\t discovered serviceName: " + serviceName);
+ }
+ }
+
+ @Test
+ public void testInstances() {
+ List instances = this.cloudFoundryDiscoveryClient.getInstances(
+ this.hiServiceServiceId);
+ assertEquals(instances.size(), 1);
+ }
+
+ @Test
+ public void testLocalServiceInstanceRunning() {
+
+ InstanceInfo instanceInfo = mock(InstanceInfo.class);
+ InstancesInfo instancesInfo = mock(InstancesInfo.class);
+ given(instancesInfo.getInstances()).willReturn(Collections.singletonList(instanceInfo));
+ given(instanceInfo.getState()).willReturn(InstanceState.RUNNING);
+
+ given(cloudFoundryClient.getApplicationInstances(this.cloudApplication)).willReturn(instancesInfo);
+
+ ServiceInstance localServiceInstance = this.cloudFoundryDiscoveryClient.getLocalServiceInstance();
+ assertTrue(localServiceInstance.getHost().contains("hi-service.cfapps.io"));
+ assertTrue(localServiceInstance.getServiceId().equals(this.hiServiceServiceId));
+ assertEquals(localServiceInstance.getPort(), 80);
+ }
+
+ @Test
+ public void testLocalServiceInstanceNotRunning() {
+
+ InstanceInfo instanceInfo = mock(InstanceInfo.class);
+ InstancesInfo instancesInfo = mock(InstancesInfo.class);
+ given(instancesInfo.getInstances()).willReturn(Collections.singletonList(instanceInfo));
+ given(instanceInfo.getState()).willReturn(InstanceState.CRASHED);
+
+ given(cloudFoundryClient.getApplicationInstances(this.cloudApplication)).willReturn(instancesInfo);
+
+ ServiceInstance localServiceInstance = this.cloudFoundryDiscoveryClient.getLocalServiceInstance();
+ assertNull(localServiceInstance);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java
new file mode 100644
index 0000000..1df7822
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerListTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import com.netflix.client.config.IClientConfig;
+import org.cloudfoundry.client.lib.CloudFoundryClient;
+import org.cloudfoundry.client.lib.domain.CloudApplication;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+
+/**
+ * @author Josh Long
+ */
+public class CloudFoundryServerListTest {
+
+ private CloudFoundryServerList cloudFoundryServerList;
+ private String serviceId = "foo-service";
+
+ @Before
+ public void setUp() {
+
+ CloudApplication cloudApplication = mock(CloudApplication.class);
+ given(cloudApplication.getUris()).will(new Answer>() {
+ @Override
+ public List answer(InvocationOnMock invocationOnMock) throws Throwable {
+ return Arrays.asList("a-url.com", "b-url.com");
+ }
+ });
+
+ CloudFoundryClient cloudFoundryClient = mock(CloudFoundryClient.class);
+ given(cloudFoundryClient.getApplication(this.serviceId)).willReturn(cloudApplication);
+
+ IClientConfig iClientConfig = mock(IClientConfig.class);
+ given(iClientConfig.getClientName()).willReturn(this.serviceId);
+
+ this.cloudFoundryServerList = new CloudFoundryServerList(cloudFoundryClient);
+ this.cloudFoundryServerList.initWithNiwsConfig(iClientConfig);
+ }
+
+ @Test
+ public void testListOfServers() {
+ List initialListOfServers = this.cloudFoundryServerList.getInitialListOfServers();
+ List updatedListOfServers = this.cloudFoundryServerList.getUpdatedListOfServers();
+ Assert.assertEquals(updatedListOfServers, initialListOfServers);
+ Assert.assertTrue(initialListOfServers.size() == 1);
+ }
+
+ @Test
+ public void testInit() {
+ Assert.assertEquals(this.cloudFoundryServerList.serviceId, this.serviceId);
+ }
+}
diff --git a/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerTest.java b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerTest.java
new file mode 100644
index 0000000..a9487bb
--- /dev/null
+++ b/spring-cloud-cloudfoundry-discovery/src/test/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.discovery;
+
+import com.netflix.loadbalancer.Server;
+import org.cloudfoundry.client.lib.domain.CloudApplication;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.mock;
+
+/**
+ * @author Josh Long
+ */
+public class CloudFoundryServerTest {
+
+ private CloudFoundryServer cloudFoundryServer;
+ private List urls = Arrays.asList("a-url.com", "b-url.com");
+ private String serverName = "server-name";
+
+ @Before
+ public void setUp() {
+ CloudApplication cloudApplication = mock(CloudApplication.class);
+ given(cloudApplication.getUris()).willReturn(this.urls);
+ given(cloudApplication.getName()).willReturn(this.serverName);
+ given(cloudApplication.getRunningInstances()).willReturn(1);
+ this.cloudFoundryServer = new CloudFoundryServer(cloudApplication);
+ }
+
+ @Test
+ public void testProperConstruction() {
+ Server.MetaInfo metaInfo = this.cloudFoundryServer.getMetaInfo();
+
+ Assert.assertEquals(metaInfo.getAppName(), this.serverName);
+ Assert.assertEquals(metaInfo.getServiceIdForDiscovery(), this.serverName);
+ Assert.assertEquals(metaInfo.getInstanceId(), this.serverName);
+ Assert.assertEquals(this.cloudFoundryServer.getHost(), this.urls.get(0));
+ }
+}
diff --git a/spring-cloud-cloudfoundry-sample/hi-service.groovy b/spring-cloud-cloudfoundry-sample/hi-service.groovy
new file mode 100644
index 0000000..d70fb6b
--- /dev/null
+++ b/spring-cloud-cloudfoundry-sample/hi-service.groovy
@@ -0,0 +1,20 @@
+// to run on ur local machine this use:
+// spring run hi-service.groovy
+//
+// to deploy this to Cloud Foundry, use
+// spring jar hi.jar hi-service.groovy
+// cf push hi-service -p hi.jar
+
+
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class GreetingRestController {
+
+ @RequestMapping("/hi/{name}")
+ def hi(@PathVariable String name) {
+ [greeting: "Hello, " + name + "!"]
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-sample/pom.xml b/spring-cloud-cloudfoundry-sample/pom.xml
new file mode 100644
index 0000000..d2ecc0c
--- /dev/null
+++ b/spring-cloud-cloudfoundry-sample/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ spring-cloud-cloudfoundry-sample
+ jar
+
+
+ org.springframework.cloud
+ spring-cloud-cloudfoundry
+ 1.0.2.BUILD-SNAPSHOT
+ ..
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+ maven-deploy-plugin
+
+ true
+
+
+
+
+
+
+ com.netflix.feign
+ feign-core
+
+
+ com.netflix.feign
+ feign-ribbon
+
+
+ com.netflix.feign
+ feign-slf4j
+
+
+ org.springframework.cloud
+ spring-cloud-cloudfoundry-discovery
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
diff --git a/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/DemoApplication.java b/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/DemoApplication.java
new file mode 100644
index 0000000..68b34f5
--- /dev/null
+++ b/spring-cloud-cloudfoundry-sample/src/main/java/org/springframework/cloud/cloudfoundry/sample/DemoApplication.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013-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.cloud.cloudfoundry.sample;
+
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This example assumes you've registered an application on Cloud Foundry
+ * named {@code hi-service} that responds with a String at {@code /hi/{name}}. There is a sample file in the project
+ * root called {@code hi-service.groovy} which you can deploy using the {@code spring} CLI and the {@code cf} CLI that
+ * works appropriately for this demonstration.
+ *
+ * @author Josh Long
+ * @author Spencer Gibb
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class DemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoApplication.class, args);
+ }
+
+ private Log log = LogFactory.getLog(getClass());
+
+ @Bean
+ CommandLineRunner consume(final LoadBalancerClient loadBalancerClient,
+ final DiscoveryClient discoveryClient,
+ final HiServiceClient hiServiceClient,
+ final RestTemplate restTemplate) {
+
+ return new CommandLineRunner() {
+ @Override
+ public void run(String... args) throws Exception {
+
+ // this demonstrates using the CF/Ribbon-aware RestTemplate interceptor
+ log.info("=====================================");
+ log.info("Hi: " + restTemplate.getForEntity("http://hi-service/hi/{name}", String.class, "Josh"));
+
+ // this demonstrates using the Spring Cloud Commons DiscoveryClient abstraction
+ log.info("=====================================");
+ for (String svc : discoveryClient.getServices()) {
+ log.info("service = " + svc);
+ List instances = discoveryClient.getInstances(svc);
+ for (ServiceInstance si : instances) {
+ log.info("\t" + ReflectionToStringBuilder.reflectionToString(si, ToStringStyle.MULTI_LINE_STYLE));
+ }
+ }
+
+ log.info("=====================================");
+ log.info("local: ");
+ log.info("\t" + ReflectionToStringBuilder.reflectionToString(
+ discoveryClient.getLocalServiceInstance(), ToStringStyle.MULTI_LINE_STYLE));
+
+ // this demonstrates using a CF/Ribbon-aware Feign client
+ log.info("=====================================");
+ log.info("Hi:" + hiServiceClient.hi("Josh"));
+
+ // this demonstrates using the Spring Cloud Commons LoadBalancerClient
+ log.info("=====================================");
+ ServiceInstance choose = loadBalancerClient.choose("hi-service");
+ log.info("chose: " + '(' + choose.getServiceId() + ") " + choose.getHost() + ':' + choose.getPort());
+ }
+ };
+ }
+}
+
+
+@FeignClient("hi-service")
+interface HiServiceClient {
+
+ @RequestMapping(value = "/hi/{name}", method = RequestMethod.GET)
+ Map hi(@PathVariable("name") String name);
+
+}
+
diff --git a/spring-cloud-cloudfoundry-sample/src/main/resources/application.properties b/spring-cloud-cloudfoundry-sample/src/main/resources/application.properties
new file mode 100644
index 0000000..aaa99e9
--- /dev/null
+++ b/spring-cloud-cloudfoundry-sample/src/main/resources/application.properties
@@ -0,0 +1,4 @@
+spring.application.name=test-app
+spring.cloud.cloudfoundry.discovery.email=starbuxman@gmail.com
+spring.cloud.cloudfoundry.discovery.password=xxxx
+VCAP_APPLICATION={"limits":{"mem":1024,"disk":1024,"fds":16384},"application_version":"36eff082-96d6-498f-8214-508fda72ba65","application_name":"hi-service","application_uris":["hi-service.cfapps.io"],"version":"36eff082-96d6-498f-8214-508fda72ba65","name":"hi-service","space_name":"joshlong","space_id":"e0cd969c-3461-41ae-abde-4e11bb5acbd1","uris":["hi-service.cfapps.io"],"users":null,"application_id":"af350f7c-88c4-4e35-a04e-698a1dbc7354","instance_id":"e4843ca23bd947b28e6d4cb3f9b92cbb","instance_index":0,"host":"0.0.0.0","port":61590,"started_at":"2015-05-07 20:00:10 +0000","started_at_timestamp":1431028810,"start":"2015-05-07 20:00:10 +0000","state_timestamp":1431028810}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-sample/src/main/resources/bootstrap.properties b/spring-cloud-cloudfoundry-sample/src/main/resources/bootstrap.properties
new file mode 100644
index 0000000..723e99d
--- /dev/null
+++ b/spring-cloud-cloudfoundry-sample/src/main/resources/bootstrap.properties
@@ -0,0 +1 @@
+spring.application.name=test-app
diff --git a/spring-cloud-cloudfoundry-web/pom.xml b/spring-cloud-cloudfoundry-web/pom.xml
new file mode 100644
index 0000000..e856923
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/pom.xml
@@ -0,0 +1,71 @@
+
+
+ 4.0.0
+
+ spring-cloud-cloudfoundry-web
+ jar
+ Spring Cloud CloudFoundry Web
+
+
+ org.springframework.cloud
+ spring-cloud-cloudfoundry
+ 1.0.2.BUILD-SNAPSHOT
+ ..
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.cloud
+ spring-cloud-netflix-core
+ true
+
+
+ org.springframework.cloud
+ spring-cloud-netflix-eureka-server
+ true
+
+
+ spring-boot-starter-log4j
+ org.springframework.boot
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ true
+
+
+
+
+ org.projectlombok
+ lombok
+ compile
+ true
+
+
+ com.netflix.eureka
+ eureka-core
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
diff --git a/spring-cloud-cloudfoundry-web/src/main/asciidoc/README.adoc b/spring-cloud-cloudfoundry-web/src/main/asciidoc/README.adoc
new file mode 100644
index 0000000..28c40fb
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/asciidoc/README.adoc
@@ -0,0 +1,2 @@
+include::intro.adoc[]
+
diff --git a/spring-cloud-cloudfoundry-web/src/main/asciidoc/ghpages.sh b/spring-cloud-cloudfoundry-web/src/main/asciidoc/ghpages.sh
new file mode 100755
index 0000000..67da0d2
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/asciidoc/ghpages.sh
@@ -0,0 +1,46 @@
+#!/bin/bash -x
+
+git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'`
+
+if ! (git remote set-branches --add origin gh-pages && git fetch -q); then
+ echo "No gh-pages, so not syncing"
+ exit 0
+fi
+
+if ! [ -d target/generated-docs ]; then
+ echo "No gh-pages sources in target/generated-docs, so not syncing"
+ exit 0
+fi
+
+# Stash any outstanding changes
+###################################################################
+git diff-index --quiet HEAD
+dirty=$?
+if [ "$dirty" != "0" ]; then git stash; fi
+
+# Switch to gh-pages branch to sync it with master
+###################################################################
+git checkout gh-pages
+
+for f in target/generated-docs/*; do
+ file=${f#target/generated-docs/*}
+ if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
+ # Not ignored...
+ cp -rf $f .
+ git add -A $file
+ fi
+done
+
+git commit -a -m "Sync docs from master to gh-pages"
+
+# Uncomment the following push if you want to auto push to
+# the gh-pages branch whenever you commit to master locally.
+# This is a little extreme. Use with care!
+###################################################################
+git push origin gh-pages
+
+# Finally, switch back to the master branch and exit block
+git checkout master
+if [ "$dirty" != "0" ]; then git stash pop; fi
+
+exit 0
diff --git a/spring-cloud-cloudfoundry-web/src/main/asciidoc/intro.adoc b/spring-cloud-cloudfoundry-web/src/main/asciidoc/intro.adoc
new file mode 100644
index 0000000..913ef7b
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/asciidoc/intro.adoc
@@ -0,0 +1,15 @@
+Spring Cloud for Cloudfoundry makes it easy to run
+https://github.com/spring-cloud[Spring Cloud] apps in
+https://github.com/cloudfoundry[Cloud Foundry] (the Platform as a
+Service). Cloud Foundry has the notion of a "service", which is
+middlware that you "bind" to an app, essentially providing it with an
+environment variable containing credentials (e.g. the location and
+username to use for the service).
+
+Add this project as a dependency to any Spring Cloud UI app or REST
+service and deploy to Cloudfoundry. If you use Spring Cloud Security
+OAuth2 features this will make them bindable to Cloud Foundry services
+instead of enironment properties in `spring.oauth2.*`. For a UI app you can
+declare `@EnableOAuth2Sso` and bind to a service called "sso", and for
+a service you can add `@EnableOAuth2Resource` and bind to a service
+called "resource" (see below for how to change the names).
diff --git a/spring-cloud-cloudfoundry-web/src/main/asciidoc/quickstart.adoc b/spring-cloud-cloudfoundry-web/src/main/asciidoc/quickstart.adoc
new file mode 100644
index 0000000..d08fca6
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/asciidoc/quickstart.adoc
@@ -0,0 +1,50 @@
+Here's a Spring Cloud app with OAuth2 SSO:
+
+.app.groovy
+[source,java]
+----
+@Controller
+@EnableOAuth2Sso
+class Application {
+
+ @RequestMapping('/')
+ String home() {
+ 'Hello World'
+ }
+
+}
+----
+
+If you run it without any service bindings:
+
+----
+$ spring jar app.jar app.groovy
+$ cf push -p app.jar
+----
+
+it will be secure with (Spring Boot default) Basic authentication,
+i.e. the password will be in the logs (or set it with
+`security.user.password` as normal). To turn on OAuth2 SSO all you
+need to do is bind the app to a service with the right
+credentials. For example, a
+http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html[user-provided
+service] can be created like this on PWS:
+
+----
+$ cf create-user-provided-service sso -p '{clientId:"",clientSecret:"",userInfoUri:"https://uaa.run.pivotal.io/userinfo",tokenUri: "https://login.run.pivotal.io/oauth/token",authorizationUri:"https://login.run.pivotal.io/oauth/authorize"}
+----
+
+Then bind and restart the app:
+
+----
+$ cf bind app sso
+$ cf restart app
+----
+
+and visit it in a browser. It will redirect to the Cloud Foundry (PWS)
+login server instead of challenging for Basic authentication. The
+`clientId` and `clientSecret` are credentials of a registered client
+in Cloud Foundry. To get a Cloud Foundry client registration for
+testing please ask your local platform administrator if it's a private
+instance).
+
diff --git a/spring-cloud-cloudfoundry-web/src/main/asciidoc/spring-cloud-cloudfoundry.adoc b/spring-cloud-cloudfoundry-web/src/main/asciidoc/spring-cloud-cloudfoundry.adoc
new file mode 100644
index 0000000..8faa3b5
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/asciidoc/spring-cloud-cloudfoundry.adoc
@@ -0,0 +1,75 @@
+= Spring Cloud for Cloud Foundry
+
+include::intro.adoc[]
+
+== Quickstart
+
+include::quickstart.adoc[]
+
+== How Does it Work?
+
+=== OAuth2 Single Sign On
+
+Spring Cloud Security provides the `@EnableOAuth2Sso` annotation and
+binds the app to environment properties in `spring.oauth2.\*`. Spring Cloud
+for Cloud Foundry just sets up default environment properties so that
+it all just works if you bind to a Cloud Foundry service instance
+called "sso". The service credentials are mapped to the SSO
+properties, i.e. (from `spring.oauth2.client.*`) `clientId`, `clientSecret`,
+`tokenUri`, `authorizationUri`, (and from `spring.oauth2.resource.*`)
+`userInfoUri`, `tokenInfoUri`, `keyValue`, `keyUri`. Refer to the
+Spring Cloud Security documentation for details of which combinations
+will work together. The main thing is that in Cloud Foundry you only
+need one service to cover all the necessary credentials.
+
+To use a different service instance name (i.e. not "sso") just set
+`spring.oauth2.sso.serviceId` to your custom name.
+
+=== JWT Tokens
+
+Spring Cloud Security already has support for decoding JWT tokens if
+you just provide the verification key (as an environment property). In
+Cloud Foundry you can pick that property up from a service binding
+(`keyValue` or `keyUri`).
+
+For example the `keyUri` in PWS is
+"https://uaa.run.pivotal.io/token_key":
+
+----
+$ curl https://uaa.run.pivotal.io/token_key
+{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}d
+----
+
+=== OAuth2 Resource Server
+
+Similarly, the `@EnableOAuth2Resource` annotation will protect your
+API endpoints if you bind to a service instance called "resource".
+The "sso" service above will work for a resource server as well (so
+just bind to that if it's there). If the OAuth2 tokens are JWTs (as in
+Cloud Foundry), it is common to use a separate service for resources
+to avoid a network round trip decoding the token on every access. A
+user-provided-service for an OAuth2 resource can be created like this
+on PWS:
+
+----
+$ cf create-user-provided-service resource -p '{keyUri:"https://uaa.run.pivotal.io/token_key"}
+----
+
+To use JWT you need to add the verification key as either
+`keyValue` or `keyUri` (these could be added to the "sso"
+service or the "resource" service if you have one).
+
+To use a different sercice instance name (i.e. not "resource" or
+"sso") just set `spring.oauth2.resource.serviceId` to your custom name.
+
+=== Default Environment Keys
+
+The precise mapppings are as follows:
+
+* `spring.oauth2.sso.\*` to `vcap.services.${spring.oauth2.sso.serviceId:sso}.credentials.*`
+
+* `spring.oauth2.client.\*` to `vcap.services.${spring.oauth2.sso.serviceId:sso}.credentials.tokenUri:${vcap.services.${spring.oauth2.resource.serviceId:resource}.credentials.*`
+
+* `spring.oauth2.resource.(jwt).\*` to `vcap.services.${spring.oauth2.resource.serviceId:resource}.credentials.tokenUri:${vcap.services.${spring.oauth2.sso.serviceId:sso}.credentials.*`
+
+
diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/web/EnableStickyFilter.java b/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/discovery/EnableStickyFilter.java
similarity index 94%
rename from src/main/java/org/springframework/cloud/cloudfoundry/web/EnableStickyFilter.java
rename to spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/discovery/EnableStickyFilter.java
index 8962629..2828a67 100644
--- a/src/main/java/org/springframework/cloud/cloudfoundry/web/EnableStickyFilter.java
+++ b/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/discovery/EnableStickyFilter.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.cloud.cloudfoundry.web;
+package org.springframework.cloud.cloudfoundry.discovery;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -33,4 +33,4 @@ import org.springframework.context.annotation.Import;
@Import(StickyFilterConfiguration.class)
public @interface EnableStickyFilter {
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/web/StickyFilterConfiguration.java b/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/discovery/StickyFilterConfiguration.java
similarity index 57%
rename from src/main/java/org/springframework/cloud/cloudfoundry/web/StickyFilterConfiguration.java
rename to spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/discovery/StickyFilterConfiguration.java
index 10f5d53..cf63dff 100644
--- a/src/main/java/org/springframework/cloud/cloudfoundry/web/StickyFilterConfiguration.java
+++ b/spring-cloud-cloudfoundry-web/src/main/java/org/springframework/cloud/cloudfoundry/discovery/StickyFilterConfiguration.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.cloud.cloudfoundry.web;
+package org.springframework.cloud.cloudfoundry.discovery;
import java.io.IOException;
import java.util.UUID;
@@ -34,34 +34,33 @@ import org.springframework.web.filter.OncePerRequestFilter;
/**
* @author Dave Syer
- *
*/
@Configuration
public class StickyFilterConfiguration {
-
- private String cookie = UUID.randomUUID().toString();
-
- @Autowired
- public void init(EurekaInstanceConfigBean eurekaInstance) {
- eurekaInstance.getMetadataMap().put("cookie", cookie);
- }
- @Bean
- public FilterRegistrationBean stickyCloudFoundryFilter() {
- FilterRegistrationBean filter = new FilterRegistrationBean();
- filter.setOrder(Ordered.LOWEST_PRECEDENCE);
- filter.setFilter(new OncePerRequestFilter() {
- @Override
- protected void doFilterInternal(HttpServletRequest request,
- HttpServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
- if (!response.containsHeader("Set-Cookie")) {
- response.addCookie(new Cookie("JSESSIONID", cookie));
- }
- filterChain.doFilter(request, response);
- }
- });
- return filter;
- }
+ private String cookie = UUID.randomUUID().toString();
-}
+ @Autowired
+ public void init(EurekaInstanceConfigBean eurekaInstance) {
+ eurekaInstance.getMetadataMap().put("cookie", cookie);
+ }
+
+ @Bean
+ public FilterRegistrationBean stickyCloudFoundryFilter() {
+ FilterRegistrationBean filter = new FilterRegistrationBean();
+ filter.setOrder(Ordered.LOWEST_PRECEDENCE);
+ filter.setFilter(new OncePerRequestFilter() {
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ if (!response.containsHeader("Set-Cookie")) {
+ response.addCookie(new Cookie("JSESSIONID", cookie));
+ }
+ filterChain.doFilter(request, response);
+ }
+ });
+ return filter;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-cloud-cloudfoundry-web/src/main/ruby/generate_readme.sh b/spring-cloud-cloudfoundry-web/src/main/ruby/generate_readme.sh
new file mode 100755
index 0000000..fc5b7f1
--- /dev/null
+++ b/spring-cloud-cloudfoundry-web/src/main/ruby/generate_readme.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+
+base_dir = File.join(File.dirname(__FILE__),'../../..')
+src_dir = File.join(base_dir, "/src/main/asciidoc")
+require 'asciidoctor'
+require 'optparse'
+
+options = {}
+file = "#{src_dir}/README.adoc"
+
+OptionParser.new do |o|
+ o.on('-o OUTPUT_FILE', 'Output file (default is stdout)') { |file| options[:to_file] = file unless file=='-' }
+ o.on('-h', '--help') { puts o; exit }
+ o.parse!
+end
+
+file = ARGV[0] if ARGV.length>0
+
+srcDir = File.dirname(file)
+out = "// Do not edit this file (e.g. go instead to src/main/asciidoc)\n\n"
+doc = Asciidoctor.load_file file, safe: :safe, parse: false
+out << doc.reader.read
+
+unless options[:to_file]
+ puts out
+else
+ File.open(options[:to_file],'w+') do |file|
+ file.write(out)
+ end
+end
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
deleted file mode 100644
index e69de29..0000000
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
deleted file mode 100644
index 26fb4b3..0000000
--- a/src/test/resources/application.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-debug: true
-server:
- port: 8080
-spring:
- application:
- name: eureka
-security:
- basic:
- enabled: false
-management:
- context-path: /admin
-eureka:
- server:
- enabled: false
-spring:
- oauth2:
- sso:
- tokenUri: http://localhost:8080/uaa/oauth/token
- authorizationUri: http://localhost:8080/uaa/oauth/authorize
- clientId: app
- clientSecret: appclientsecret
-logging:
- level:
- com.netflix.discovery: 'OFF'
- org.springframework.security: DEBUG
-
----
-spring:
- profiles: github
-spring:
- oauth2:
- sso:
- tokenUri: https://github.com/login/oauth/access_token
- authorizationUri: https://github.com/login/oauth/authorize
- clientId: bd1c0a783ccdd1c9b9e4
- clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
- authenticationScheme: form
- resource:
- clientId: ${spring.oauth2.sso.clientId}
- userInfoUri: https://api.github.com/user
- preferTokenInfo: false
-
\ No newline at end of file