diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc
index 1571e09c..fd45c41d 100644
--- a/docs/src/main/asciidoc/spring-cloud-commons.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc
@@ -311,6 +311,20 @@ For a Spring Boot Actuator application there are some additional management endp
Patterns such as service discovery, load balancing and circuit breakers lend themselves to a common abstraction layer that can be consumed by all Spring Cloud clients, independent of the implementation (e.g. discovery via Eureka or Consul).
+=== @EnableDiscoveryClient
+
+Commons provides the `@EnableDiscoveryClient` annotation. This looks for implementations of the `DiscoveryClient` interface via `META-INF/spring.factories`. Implementations of Discovery Client will add a configuration class to `spring.factories` under the `org.springframework.cloud.client.discovery.EnableDiscoveryClient` key. Examples of `DiscoveryClient` implementations: are http://cloud.spring.io/spring-cloud-netflix/[Spring Cloud Netflix Eureka], http://cloud.spring.io/spring-cloud-consul/[Spring Cloud Consul Discovery] and http://cloud.spring.io/spring-cloud-zookeeper/[Spring Cloud Zookeeper Discovery].
+
+By default, implementations of `DiscoveryClient` will auto-register the local Spring Boot server with the remote discovery server. This can be disabled by setting `autoRegister=false` in `@EnableDiscoveryClient`.
+
+=== ServiceRegistry
+
+Commons now provides a `ServiceRegistry` interface which provides methods like `register(Registration)` and `deregister(Registration)` which allow you to provide custom registered services. `Registration` is a marker interface.
+
+==== Service Registry Actuator Endpoint
+
+A `/service-registry` actuator endpoint is provided by Commons. This endpoint relys on a `Registration` bean in the Spring Application Context. Calling `/service-registry/instance-status` via a GET will return the status of the `Registration`. A POST to the same endpoint with a `String` body will change the status of the current `Registration` to the new value. Please see the documentation of the `ServiceRegistry` implementation you are using for the allowed values for updating the status and the values retured for the status.
+
=== Spring RestTemplate as a Load Balancer Client
`RestTemplate` can be automatically configured to use ribbon. To create a load balanced `RestTemplate` create a `RestTemplate` `@Bean` and use the `@LoadBalanced` qualifier.
diff --git a/spring-cloud-commons-dependencies/pom.xml b/spring-cloud-commons-dependencies/pom.xml
index 01c18553..4f8a142f 100644
--- a/spring-cloud-commons-dependencies/pom.xml
+++ b/spring-cloud-commons-dependencies/pom.xml
@@ -5,7 +5,7 @@
spring-cloud-dependencies-parent
org.springframework.cloud
- 1.1.2.RELEASE
+ 1.2.2.BUILD-SNAPSHOT
spring-cloud-commons-dependencies
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java
index 07f99a5a..0ebc456f 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java
@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
@@ -33,8 +34,12 @@ import org.springframework.core.env.Environment;
/**
* Lifecycle methods that may be useful and common to various DiscoveryClient implementations.
+ *
+ * @deprecated use {@link org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration} instead. This class will be removed in the next release train.
+ *
* @author Spencer Gibb
*/
+@Deprecated
public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
ApplicationContextAware, ApplicationListener {
@@ -89,6 +94,9 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
@Override
public void start() {
if (!isEnabled()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Discovery Lifecycle disabled. Not starting");
+ }
return;
}
@@ -103,7 +111,7 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
if (shouldRegisterManagement()) {
registerManagement();
}
- this.context .publishEvent(new InstanceRegisteredEvent<>(this,
+ this.context.publishEvent(new InstanceRegisteredEvent<>(this,
getConfiguration()));
this.running.compareAndSet(false, true);
}
@@ -113,17 +121,18 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
protected abstract void setConfiguredPort(int port);
/**
- * @return if the management service should be registered with the DiscoveryService
+ * @return if the management service should be registered with the {@link ServiceRegistry}
*/
protected boolean shouldRegisterManagement() {
return getManagementPort() != null && ManagementServerPortUtils.isDifferent(this.context);
}
/**
- * @return the object used to configure the DiscoveryClient
+ * @return the object used to configure the registration
*/
protected abstract Object getConfiguration();
+
/**
* Register the local service with the DiscoveryClient
*/
@@ -147,7 +156,7 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
}
/**
- * @return if the DiscoveryClient is enabled
+ * @return true, if the {@link DiscoveryLifecycle} is enabled
*/
protected abstract boolean isEnabled();
@@ -201,6 +210,10 @@ public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
return this.running.get();
}
+ protected AtomicBoolean getRunning() {
+ return running;
+ }
+
@Override
public int getOrder() {
return this.order;
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java
index 60b76d45..efa96caa 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClient.java
@@ -36,4 +36,8 @@ import org.springframework.context.annotation.Import;
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
+ /**
+ * If true, the ServiceRegistry will automatically register the local server.
+ */
+ boolean autoRegister() default true;
}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java
index 84700a77..2d4bf09b 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelector.java
@@ -19,7 +19,13 @@ package org.springframework.cloud.client.discovery;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.cloud.commons.util.SpringFactoryImportSelector;
import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.Order;
+import org.springframework.core.type.AnnotationMetadata;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* @author Spencer Gibb
@@ -28,6 +34,24 @@ import org.springframework.core.annotation.Order;
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
+ @Override
+ public String[] selectImports(AnnotationMetadata metadata) {
+ String[] imports = super.selectImports(metadata);
+
+ AnnotationAttributes attributes = AnnotationAttributes.fromMap(
+ metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
+
+ boolean autoRegister = attributes.getBoolean("autoRegister");
+
+ if (autoRegister) {
+ List importsList = new ArrayList<>(Arrays.asList(imports));
+ importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
+ imports = importsList.toArray(new String[0]);
+ }
+
+ return imports;
+ }
+
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java
new file mode 100644
index 00000000..960ad191
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistration.java
@@ -0,0 +1,74 @@
+package org.springframework.cloud.client.serviceregistry;
+
+import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle;
+
+/**
+ * Lifecycle methods that may be useful and common to {@link ServiceRegistry} implementations.
+ *
+ * TODO: document the lifecycle
+ *
+ * @param registration type passed to the {@link ServiceRegistry}.
+ *
+ * @author Spencer Gibb
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractAutoServiceRegistration extends AbstractDiscoveryLifecycle implements AutoServiceRegistration {
+
+ private ServiceRegistry serviceRegistry;
+
+ protected AbstractAutoServiceRegistration(ServiceRegistry serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ protected ServiceRegistry getServiceRegistry() {
+ return this.serviceRegistry;
+ }
+
+ protected abstract R getRegistration();
+
+ protected abstract R getManagementRegistration();
+
+ /**
+ * Register the local service with the {@link ServiceRegistry}
+ */
+ @Override
+ protected void register() {
+ this.serviceRegistry.register(getRegistration());
+ }
+
+ /**
+ * Register the local management service with the {@link ServiceRegistry}
+ */
+ @Override
+ protected void registerManagement() {
+ this.serviceRegistry.register(getManagementRegistration());
+ }
+
+ /**
+ * De-register the local service with the {@link ServiceRegistry}
+ */
+ @Override
+ protected void deregister() {
+ this.serviceRegistry.deregister(getRegistration());
+ }
+
+ /**
+ * De-register the local management service with the {@link ServiceRegistry}
+ */
+ @Override
+ protected void deregisterManagement() {
+ this.serviceRegistry.deregister(getManagementRegistration());
+ }
+
+ @Override
+ public void stop() {
+ if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
+ deregister();
+ if (shouldRegisterManagement()) {
+ deregisterManagement();
+ }
+ this.serviceRegistry.close();
+ }
+ }
+
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java
new file mode 100644
index 00000000..3450804f
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistration.java
@@ -0,0 +1,7 @@
+package org.springframework.cloud.client.serviceregistry;
+
+/**
+ * @author Spencer Gibb
+ */
+public interface AutoServiceRegistration {
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java
new file mode 100644
index 00000000..45a95d54
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfiguration.java
@@ -0,0 +1,28 @@
+package org.springframework.cloud.client.serviceregistry;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author Spencer Gibb
+ */
+@Configuration
+@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
+public class AutoServiceRegistrationConfiguration {
+
+ @Autowired(required = false)
+ private AutoServiceRegistration autoServiceRegistration;
+
+ @Autowired
+ private AutoServiceRegistrationProperties properties;
+
+ @PostConstruct
+ protected void init() {
+ if (autoServiceRegistration == null && this.properties.isFailFast()) {
+ throw new IllegalStateException("Auto Service Registration has been requested, but there is no AutoServiceRegistration bean");
+ }
+ }
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java
new file mode 100644
index 00000000..5a8b22d5
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationProperties.java
@@ -0,0 +1,21 @@
+package org.springframework.cloud.client.serviceregistry;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Spencer Gibb
+ */
+@ConfigurationProperties("spring.cloud.service-registry.auto-registration")
+public class AutoServiceRegistrationProperties {
+
+ /** Should startup fail if there is no AutoServiceRegistration, default to false. */
+ private boolean failFast = false;
+
+ public boolean isFailFast() {
+ return failFast;
+ }
+
+ public void setFailFast(boolean failFast) {
+ this.failFast = failFast;
+ }
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java
new file mode 100644
index 00000000..625e275b
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/Registration.java
@@ -0,0 +1,7 @@
+package org.springframework.cloud.client.serviceregistry;
+
+/**
+ * @author Spencer Gibb
+ */
+public interface Registration {
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java
new file mode 100644
index 00000000..eb3d73ee
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistry.java
@@ -0,0 +1,19 @@
+package org.springframework.cloud.client.serviceregistry;
+
+/**
+ * TODO: write javadoc
+ * @author Spencer Gibb
+ */
+public interface ServiceRegistry {
+ void register(R registration);
+
+ void deregister(R registration);
+
+ void close();
+
+ // TODO: return value for success?
+ void setStatus(R registration, String status);
+
+ // TODO: concrete return value? Interface?
+ Object getStatus(R registration);
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java
new file mode 100644
index 00000000..7fbf7d72
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/ServiceRegistryAutoConfiguration.java
@@ -0,0 +1,28 @@
+package org.springframework.cloud.client.serviceregistry;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.endpoint.Endpoint;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Spencer Gibb
+ */
+@ConditionalOnBean(ServiceRegistry.class)
+@Configuration
+public class ServiceRegistryAutoConfiguration {
+
+ @Autowired(required = false)
+ private Registration registration;
+
+ @ConditionalOnClass(Endpoint.class)
+ @Bean
+ public ServiceRegistryEndpoint serviceRegistryEndpoint(ServiceRegistry serviceRegistry) {
+ ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(serviceRegistry);
+ endpoint.setRegistration(registration);
+ return endpoint;
+ }
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java
new file mode 100644
index 00000000..166621ff
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpoint.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013-2016 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.client.serviceregistry.endpoint;
+
+import org.springframework.boot.actuate.endpoint.Endpoint;
+import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * Endpoint to display and set the service instance status using the service registry.
+ *
+ * @author Spencer Gibb
+ */
+@ManagedResource(description = "Can be used to display and set the service instance status using the service registry")
+@SuppressWarnings("unchecked")
+public class ServiceRegistryEndpoint implements MvcEndpoint {
+
+ private final ServiceRegistry serviceRegistry;
+
+ private Registration registration;
+
+ public ServiceRegistryEndpoint(ServiceRegistry> serviceRegistry) {
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ public void setRegistration(Registration registration) {
+ this.registration = registration;
+ }
+
+ @RequestMapping(path = "instance-status", method = RequestMethod.POST)
+ @ResponseBody
+ @ManagedOperation
+ public ResponseEntity> setStatus(@RequestBody String status) {
+ Assert.notNull(status, "status may not by null");
+
+ if (this.registration == null) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
+ }
+
+ this.serviceRegistry.setStatus(this.registration, status);
+ return ResponseEntity.ok().build();
+ }
+
+ @RequestMapping(path = "instance-status", method = RequestMethod.GET)
+ @ResponseBody
+ @ManagedAttribute
+ public ResponseEntity getStatus() {
+ if (this.registration == null) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
+ }
+
+ return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration));
+ }
+
+ @Override
+ public String getPath() {
+ return "/service-registry";
+ }
+
+ @Override
+ public boolean isSensitive() {
+ return true;
+ }
+
+ @Override
+ public Class extends Endpoint>> getEndpointType() {
+ return null;
+ }
+}
diff --git a/spring-cloud-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-commons/src/main/resources/META-INF/spring.factories
index 2f8e25f5..4f996299 100644
--- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories
@@ -4,6 +4,7 @@ org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
+org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java
new file mode 100644
index 00000000..de7d7288
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/EnableDiscoveryClientImportSelectorTests.java
@@ -0,0 +1,58 @@
+package org.springframework.cloud.client.discovery;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.mock.env.MockEnvironment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+
+/**
+ * @author Spencer Gibb
+ */
+public class EnableDiscoveryClientImportSelectorTests {
+
+ private final EnableDiscoveryClientImportSelector importSelector = new EnableDiscoveryClientImportSelector();
+
+ private final MockEnvironment environment = new MockEnvironment();
+
+ @Mock
+ private AnnotationMetadata annotationMetadata;
+
+ @Mock
+ private AnnotationAttributes annotationAttributes;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ this.importSelector.setBeanClassLoader(getClass().getClassLoader());
+ this.importSelector.setEnvironment(this.environment);
+ }
+
+ @Test
+ public void autoRegistrationIsEnabled() {
+ configureAnnotation(true);
+ String[] imports = this.importSelector.selectImports(this.annotationMetadata);
+ assertThat(imports).hasSize(1);
+ }
+
+ @Test
+ public void autoRegistrationIsDisabled() {
+ configureAnnotation(false);
+ String[] imports = this.importSelector.selectImports(this.annotationMetadata);
+ assertThat(imports).isEmpty();
+ }
+
+ private void configureAnnotation(boolean autoRegistration) {
+ String annotationName = EnableDiscoveryClient.class.getName();
+ given(this.annotationMetadata.isAnnotated(annotationName)).willReturn(true);
+ given(this.annotationMetadata.getAnnotationAttributes(annotationName, true))
+ .willReturn(this.annotationAttributes);
+ given(this.annotationAttributes.getBoolean("autoRegister"))
+ .willReturn(autoRegistration);
+ }
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java
new file mode 100644
index 00000000..ae0886c9
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AbstractAutoServiceRegistrationTests.java
@@ -0,0 +1,150 @@
+package org.springframework.cloud.client.serviceregistry;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.boot.test.WebIntegrationTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Spencer Gibb
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = AbstractAutoServiceRegistrationTests.Config.class)
+@WebIntegrationTest(randomPort = true, value = "management.port=0")
+public class AbstractAutoServiceRegistrationTests {
+
+ @Autowired
+ private TestAutoServiceRegistration autoRegistration;
+
+ @Value("${local.server.port}")
+ private int port;
+
+ @Value("${local.management.port}")
+ private int managementPort;
+
+ @Test
+ public void portsWork() {
+ assertNotEquals("Lifecycle port is zero", 0, autoRegistration.getPort().get());
+ assertNotEquals("Lifecycle port is management port", managementPort, autoRegistration.getPort().get());
+ assertEquals("Lifecycle port is wrong", port, autoRegistration.getPort().get());
+ assertTrue("Lifecycle not running", autoRegistration.isRunning());
+ assertThat("ServiceRegistry is wrong type", autoRegistration.getServiceRegistry(), is(instanceOf(TestServiceRegistry.class)));
+ TestServiceRegistry serviceRegistry = (TestServiceRegistry) autoRegistration.getServiceRegistry();
+ assertTrue("Lifecycle not registered", serviceRegistry.isRegistered());
+ assertEquals("Lifecycle appName is wrong", "application", autoRegistration.getAppName());
+ }
+
+ @EnableAutoConfiguration
+ @Configuration
+ public static class Config {
+ @Bean
+ public TestAutoServiceRegistration testAutoServiceRegistration() {
+ return new TestAutoServiceRegistration();
+ }
+ }
+
+ public static class TestRegistration implements Registration {
+ }
+
+ public static class TestServiceRegistry implements ServiceRegistry {
+ private boolean registered = false;
+ private boolean deregistered = false;
+
+ @Override
+ public void register(TestRegistration registration) {
+ this.registered = true;
+ }
+
+ @Override
+ public void deregister(TestRegistration registration) {
+ this.deregistered = true;
+ }
+
+ @Override
+ public void close() { }
+
+ @Override
+ public void setStatus(TestRegistration registration, String status) {
+ //TODO: test setStatus
+ }
+
+ @Override
+ public Object getStatus(TestRegistration registration) {
+ //TODO: test getStatus
+ return null;
+ }
+
+ boolean isRegistered() {
+ return registered;
+ }
+
+ boolean isDeregistered() {
+ return deregistered;
+ }
+ }
+
+ public static class TestAutoServiceRegistration extends AbstractAutoServiceRegistration {
+ private int port = 0;
+
+ @Override
+ protected AtomicInteger getPort() {
+ return super.getPort();
+ }
+
+ @Override
+ protected String getAppName() {
+ return super.getAppName();
+ }
+
+ protected TestAutoServiceRegistration() {
+ super(new TestServiceRegistry());
+ }
+
+ @Override
+ protected int getConfiguredPort() {
+ return port;
+ }
+
+ @Override
+ protected void setConfiguredPort(int port) {
+ this.port = port;
+ }
+
+ @Override
+ protected TestRegistration getRegistration() {
+ return null;
+ }
+
+ @Override
+ protected TestRegistration getManagementRegistration() {
+ return null;
+ }
+
+ @Override
+ protected Object getConfiguration() {
+ return null;
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return true;
+ }
+
+
+ }
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java
new file mode 100644
index 00000000..31476e79
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/AutoServiceRegistrationConfigurationTests.java
@@ -0,0 +1,81 @@
+package org.springframework.cloud.client.serviceregistry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.hamcrest.Matchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.boot.test.util.EnvironmentTestUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Spencer Gibb
+ */
+public class AutoServiceRegistrationConfigurationTests {
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void hasAutoServiceRegistration() {
+ try(AnnotationConfigApplicationContext context = setup(HasAutoServiceRegistrationConfiguration.class)) {
+ AutoServiceRegistration autoServiceRegistration = context.getBean(AutoServiceRegistration.class);
+ assertThat(autoServiceRegistration).isNotNull();
+ }
+ }
+
+ @Test
+ public void noAutoServiceRegistrationAndFailFast() {
+ this.exception.expect(BeanCreationException.class);
+ this.exception.expectMessage(Matchers.containsString("no AutoServiceRegistration"));
+ try(AnnotationConfigApplicationContext context = setup("spring.cloud.service-registry.auto-registration.failFast=true")) {
+ assertNoBean(context);
+ }
+ }
+
+ @Test
+ public void noAutoServiceRegistrationAndFailFastFalse() {
+ try(AnnotationConfigApplicationContext context = setup()) {
+ assertNoBean(context);
+ }
+ }
+
+ private void assertNoBean(AnnotationConfigApplicationContext context) {
+ Map beans = context.getBeansOfType(AutoServiceRegistration.class);
+ assertThat(beans).isEmpty();
+ }
+
+ @Configuration
+ static class HasAutoServiceRegistrationConfiguration {
+ @Bean
+ public AutoServiceRegistration autoServiceRegistration() {
+ return new AutoServiceRegistration() {};
+ }
+ }
+
+ private AnnotationConfigApplicationContext setup(Class... classes) {
+ return setup(null, classes);
+ }
+
+ private AnnotationConfigApplicationContext setup(String property, Class... classes) {
+ ArrayList list = new ArrayList<>();
+ list.add(AutoServiceRegistrationConfiguration.class);
+ list.addAll(Arrays.asList(classes));
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(list.toArray(new Class[0]));
+ if (StringUtils.hasText(property)) {
+ EnvironmentTestUtils.addEnvironment(context, property);
+ }
+ context.refresh();
+ return context;
+ }
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java
new file mode 100644
index 00000000..ede5645b
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointNoRegistrationTests.java
@@ -0,0 +1,70 @@
+package org.springframework.cloud.client.serviceregistry.endpoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpointTests.TestServiceRegistry;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * @author Spencer Gibb
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = ServiceRegistryEndpointNoRegistrationTests.TestConfiguration.class)
+public class ServiceRegistryEndpointNoRegistrationTests {
+
+ @Autowired
+ private WebApplicationContext context;
+
+ private MockMvc mvc;
+
+ @Before
+ public void setUp() {
+ this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ this.mvc.perform(get("/service-registry/instance-status")).andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void testPost() throws Exception {
+ this.mvc.perform(post("/service-registry/instance-status").content("newstatus")).andExpect(status().isNotFound());
+ }
+
+ @Import({JacksonAutoConfiguration.class,
+ HttpMessageConvertersAutoConfiguration.class,
+ EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,
+ ManagementServerPropertiesAutoConfiguration.class})
+ @Configuration
+ public static class TestConfiguration {
+ @Bean
+ ServiceRegistryEndpoint serviceRegistryEndpoint() {
+ return new ServiceRegistryEndpoint(serviceRegistry());
+ }
+
+ @Bean
+ ServiceRegistry serviceRegistry() {
+ return new TestServiceRegistry() ;
+ }
+ }
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java
new file mode 100644
index 00000000..45e88575
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/serviceregistry/endpoint/ServiceRegistryEndpointTests.java
@@ -0,0 +1,124 @@
+package org.springframework.cloud.client.serviceregistry.endpoint;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Spencer Gibb
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = ServiceRegistryEndpointTests.TestConfiguration.class)
+public class ServiceRegistryEndpointTests {
+ private static final String UPDATED_STATUS = "updatedstatus";
+ private static final String MYSTATUS = "mystatus";
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ private TestServiceRegistry serviceRegistry;
+
+ private MockMvc mvc;
+
+ @Before
+ public void setUp() {
+ this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ this.mvc.perform(get("/service-registry/instance-status")).andExpect(status().isOk())
+ .andExpect(content().string(containsString(MYSTATUS)));
+ }
+
+ @Test
+ public void testPost() throws Exception {
+ this.mvc.perform(post("/service-registry/instance-status").content(UPDATED_STATUS)).andExpect(status().isOk());
+ assertThat(this.serviceRegistry.getUpdatedStatus().get()).isEqualTo(UPDATED_STATUS);
+ }
+
+ @Import({JacksonAutoConfiguration.class,
+ HttpMessageConvertersAutoConfiguration.class,
+ EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,
+ ManagementServerPropertiesAutoConfiguration.class})
+ @Configuration
+ public static class TestConfiguration {
+ @Bean
+ Registration registration() {
+ return new Registration() {
+ };
+ }
+
+ @Bean
+ ServiceRegistryEndpoint serviceRegistryEndpoint(Registration reg) {
+ ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(serviceRegistry());
+ endpoint.setRegistration(reg);
+ return endpoint;
+ }
+
+ @Bean
+ ServiceRegistry serviceRegistry() {
+ return new TestServiceRegistry() ;
+ }
+ }
+
+ static class TestServiceRegistry implements ServiceRegistry {
+
+ AtomicReference updatedStatus = new AtomicReference<>();
+
+ @Override
+ public void register(Registration registration) {
+
+ }
+
+ @Override
+ public void deregister(Registration registration) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public void setStatus(Registration registration, String status) {
+ updatedStatus.compareAndSet(null, status);
+ }
+
+ @Override
+ public Object getStatus(Registration registration) {
+ return MYSTATUS;
+ }
+
+ public AtomicReference getUpdatedStatus() {
+ return updatedStatus;
+ }
+ }
+}