commit 0aa9ae5de242ffc0bccf13b00ac34f797ac6bd64 Author: Spencer Gibb Date: Mon Dec 1 14:54:06 2014 -0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8c5133e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*~ +#* +*# +.#* +.classpath +.project +.settings/ +.springBeans +target/ +_site/ +.idea +*.iml +*.swp diff --git a/.settings.xml b/.settings.xml new file mode 100644 index 00000000..006f51ef --- /dev/null +++ b/.settings.xml @@ -0,0 +1,60 @@ + + + + + repo.spring.io + ${env.CI_DEPLOY_USERNAME} + ${env.CI_DEPLOY_PASSWORD} + + + + + spring + true + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + http://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + http://repo.spring.io/release + + false + + + + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + http://repo.spring.io/libs-milestone-local + + false + + + + + + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..c538a813 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: java +before_install: + - git config user.name "$GIT_NAME" + - git config user.email "$GIT_EMAIL" + - git config credential.helper "store --file=.git/credentials" + - echo "https://$GH_TOKEN:@github.com" > .git/credentials + - gem install asciidoctor +install: +- mvn --settings .settings.xml install -P docs -q -U -DskipTests=true -Dmaven.test.redirectTestOutputToFile=true +- ./docs/src/main/asciidoc/ghpages.sh +script: +- '[ "${TRAVIS_PULL_REQUEST}" != "false" ] || mvn --settings .settings.xml deploy -nsu -Dmaven.test.redirectTestOutputToFile=true' +- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] || mvn --settings .settings.xml install -nsu -Dmaven.test.redirectTestOutputToFile=true' +env: + global: + - GIT_NAME="Dave Syer" + - GIT_EMAIL=dsyer@pivotal.io + - CI_DEPLOY_USERNAME=buildmaster + - secure: aeLXRC5oFSddwnZt1/7G2/OHr7jDbxz0ET7sej3I+eSbe3N5vbzQ6FC08es4l89l54ciXd90I1g2BMw7DTYKOO373FP78XPdAEbifJTU4DGd6fCELmoTtUPhjunBIk7E49hisPbv82892IYYA7qi/hzG548cPyZ1IgiJjq0NCsc= diff --git a/README.md b/README.md new file mode 100644 index 00000000..8e6c29d0 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +## Spring Cloud Commons +======== + +Developer preview of common classes used in different Spring Cloud implementations (eg. Spring Cloud Netflix vs. Spring Cloud Consul) \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..85bd28f8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + org.springframework.cloud + spring-cloud-commons + 1.0.0.BUILD-SNAPSHOT + jar + Spring Cloud Commons + Spring Cloud Commons + + + org.springframework.cloud + spring-cloud-build + 1.0.0.BUILD-SNAPSHOT + + + + + + https://github.com/spring-cloud/spring-cloud-commons + scm:git:git://github.com/spring-cloud/spring-cloud-commons.git + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-commons.git + HEAD + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + + + + + + org.projectlombok + lombok + 1.12.6 + provided + + + com.google.guava + guava + 18.0 + + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.projectlombok + lombok + provided + + + com.google.guava + guava + + + + + + + diff --git a/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPortUtils.java b/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPortUtils.java new file mode 100644 index 00000000..2ec8437b --- /dev/null +++ b/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerPortUtils.java @@ -0,0 +1,27 @@ +package org.springframework.boot.actuate.autoconfigure; + +import static org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration.ManagementServerPort; + +import org.springframework.beans.factory.BeanFactory; + +/** + * @author Spencer Gibb + */ +public class ManagementServerPortUtils { + + public static ManagementServerPort get(BeanFactory beanFactory) { + return ManagementServerPort.get(beanFactory); + } + + public static boolean isDifferent(BeanFactory beanFactory) { + return get(beanFactory) == ManagementServerPort.DIFFERENT; + } + + public static boolean isDisabled(BeanFactory beanFactory) { + return get(beanFactory) == ManagementServerPort.DISABLE; + } + + public static boolean isSame(BeanFactory beanFactory) { + return get(beanFactory) == ManagementServerPort.SAME; + } +} diff --git a/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java b/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java new file mode 100644 index 00000000..cc2a3105 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/CommonsClientAutoConfiguration.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.client; + +import org.springframework.cloud.client.discovery.DiscoveryHealthIndicator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +/** + * @author Spencer Gibb + */ +@Configuration +@Order(0) +public class CommonsClientAutoConfiguration { + + @Bean + public DiscoveryHealthIndicator discoveryHealthIndicator() { + return new DiscoveryHealthIndicator(); + } +} diff --git a/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java b/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java new file mode 100644 index 00000000..32cf1a79 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/DefaultServiceInstance.java @@ -0,0 +1,13 @@ +package org.springframework.cloud.client; + +import lombok.Data; + +/** + * @author Spencer Gibb + */ +@Data +public class DefaultServiceInstance implements ServiceInstance { + private final String serviceId; + private final String host; + private final int port; +} diff --git a/src/main/java/org/springframework/cloud/client/ServiceInstance.java b/src/main/java/org/springframework/cloud/client/ServiceInstance.java new file mode 100644 index 00000000..2480f2a3 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/ServiceInstance.java @@ -0,0 +1,12 @@ +package org.springframework.cloud.client; + + +/** + * @author Spencer Gibb + * TODO: name? Server? HostAndPort? Instance? + */ +public interface ServiceInstance { + public String getServiceId(); + public String getHost(); + public int getPort(); +} diff --git a/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java b/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java new file mode 100644 index 00000000..4cd41d30 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/discovery/AbstractDiscoveryLifecycle.java @@ -0,0 +1,107 @@ +package org.springframework.cloud.client.discovery; + +import org.springframework.beans.BeansException; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPortUtils; +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.env.Environment; + +import javax.annotation.PreDestroy; + +/** + * @author Spencer Gibb + */ +public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle, ApplicationContextAware { + + protected boolean autoStartup = true; + protected boolean running; + protected int order = 0; + protected ApplicationContext context; + protected Environment environment; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.context = applicationContext; + environment = context.getEnvironment(); + } + + @Override + public boolean isAutoStartup() { + return autoStartup; + } + + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + + @Override + public void start() { + if (!isEnabled()) return; + + register(); + if (ManagementServerPortUtils.isDifferent(context)) { + registerManagement(); + } + running = true; + } + + protected abstract void register(); + + protected void registerManagement() {} + + protected abstract void deregister(); + + protected void deregisterManagement() {} + + protected abstract boolean isEnabled(); + + protected String getManagementServiceId() { + return context.getId() + ":management"; //TODO: configurable management suffix + } + + protected String getManagementServiceName() { + return getAppName() + ":management"; //TODO: configurable management suffix + } + + protected Integer getManagementPort() { + return context.getBean(ManagementServerProperties.class).getPort(); + } + + protected String getAppName() { + return environment.getProperty("spring.application.name"); + } + + @Override + public void stop() { + if (isEnabled()) { + deregister(); + if (getManagementPort() != null) { + deregisterManagement(); + } + } + running = false; + } + + @PreDestroy + public void destroy() { + stop(); + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public int getOrder() { + return order; + } + + @Override + public int getPhase() { + return 0; + } +} diff --git a/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java b/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java new file mode 100644 index 00000000..fa3822aa --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.client.discovery; + +import org.springframework.cloud.client.ServiceInstance; + +import java.util.List; + +/** + * @author Spencer Gibb + */ +//TODO: merge with LoadBalancerClient? +public interface DiscoveryClient { + /** + * @return ServiceInstance with information used to register the local service + */ + public ServiceInstance getLocalServiceInstance(); + + /** + * Get all ServiceInstance's associated with a particular serviceId + * @param serviceId the serviceId to query + * @return a List of ServiceInstance + */ + public List getInstances(String serviceId); + + public List getAllInstances(); + + /** + * @return all known service id's + */ + public List getServices(); +} diff --git a/src/main/java/org/springframework/cloud/client/discovery/DiscoveryHealthIndicator.java b/src/main/java/org/springframework/cloud/client/discovery/DiscoveryHealthIndicator.java new file mode 100644 index 00000000..50709af6 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/discovery/DiscoveryHealthIndicator.java @@ -0,0 +1,42 @@ +package org.springframework.cloud.client.discovery; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.BeansException; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * @author Spencer Gibb + */ +@Slf4j +public class DiscoveryHealthIndicator extends AbstractHealthIndicator implements ApplicationContextAware { + + private ApplicationContext context; + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + try { + DiscoveryClient client = context.getBean(DiscoveryClient.class); + if (client == null) { + builder.unknown().withDetail("warning", "No DiscoveryClient found"); + return; + } + List instances = client.getAllInstances(); + builder.up().withDetail("instances", instances); + } catch (Exception e) { + log.error("Error", e); + builder.down(e); + } + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } +} diff --git a/src/main/java/org/springframework/cloud/client/discovery/DiscoveryLifecycle.java b/src/main/java/org/springframework/cloud/client/discovery/DiscoveryLifecycle.java new file mode 100644 index 00000000..c6a43026 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/discovery/DiscoveryLifecycle.java @@ -0,0 +1,10 @@ +package org.springframework.cloud.client.discovery; + +import org.springframework.context.SmartLifecycle; +import org.springframework.core.Ordered; + +/** + * @author Spencer Gibb + */ +public interface DiscoveryLifecycle extends SmartLifecycle, Ordered { +} diff --git a/src/main/java/org/springframework/cloud/client/discovery/InstanceRegisteredEvent.java b/src/main/java/org/springframework/cloud/client/discovery/InstanceRegisteredEvent.java new file mode 100644 index 00000000..f7c28823 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/discovery/InstanceRegisteredEvent.java @@ -0,0 +1,25 @@ +package org.springframework.cloud.client.discovery; + +import org.springframework.context.ApplicationEvent; + +/** + * @author Spencer Gibb + */ +public class InstanceRegisteredEvent extends ApplicationEvent { + private T config; + + /** + * Create a new ApplicationEvent. + * + * @param source the component that published the event (never {@code null}) + * @param config the configuration of the instance + */ + public InstanceRegisteredEvent(Object source, T config) { + super(source); + this.config = config; + } + + public T getConfig() { + return config; + } +} diff --git a/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClient.java b/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClient.java new file mode 100644 index 00000000..23485714 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClient.java @@ -0,0 +1,23 @@ +package org.springframework.cloud.client.loadbalancer; + +import org.springframework.cloud.client.ServiceInstance; + +/** + * @author Spencer Gibb + */ +public interface LoadBalancerClient { + /** + * Choose a {@see ServiceInstance} from the LoadBalancer for the specified service + * @param serviceId the service id to look up the LoadBalancer + * @return a ServiceInstance that matches the serviceId + */ + public ServiceInstance choose(String serviceId); + + /** + * Choose a {@see ServiceInstance} from the LoadBalancer for the specified service + * @param serviceId the service id to look up the LoadBalancer + * @param request allows implementations to execute pre and post actions such as incrementing metrics + * @return the result of the LoadBalancerRequest callback on the selected ServiceInstance + */ + public T choose(String serviceId, LoadBalancerRequest request); +} diff --git a/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequest.java b/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequest.java new file mode 100644 index 00000000..396848d2 --- /dev/null +++ b/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRequest.java @@ -0,0 +1,10 @@ +package org.springframework.cloud.client.loadbalancer; + +import org.springframework.cloud.client.ServiceInstance; + +/** + * @author Spencer Gibb + */ +public interface LoadBalancerRequest { + public T apply(ServiceInstance instance); +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..9b8a902a --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Bootstrap Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.client.CommonsClientAutoConfiguration