Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,26 @@

package org.springframework.cloud.client;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.actuator.FeaturesEndpoint;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator;
import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator;
import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicator;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand Down Expand Up @@ -56,4 +63,22 @@ public DiscoveryCompositeHealthIndicator discoveryCompositeHealthIndicator(
return new DiscoveryCompositeHealthIndicator(aggregator, indicators);
}

@Bean
public HasFeatures commonsFeatures() {
return HasFeatures.abstractFeatures(DiscoveryClient.class,
LoadBalancerClient.class);
}

@Configuration
@ConditionalOnClass(Endpoint.class)
protected static class ActuatorConfiguration {
@Autowired(required = false)
private List<HasFeatures> hasFeatures = new ArrayList<>();

@Bean
public FeaturesEndpoint featuresEndpoint() {
return new FeaturesEndpoint(hasFeatures);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.springframework.cloud.client.actuator;

import java.util.ArrayList;
import java.util.List;

import lombok.Value;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
* @author Spencer Gibb
*/
@ConfigurationProperties(prefix = "endpoints.features", ignoreUnknownFields = false)
public class FeaturesEndpoint extends AbstractEndpoint<FeaturesEndpoint.Features> implements ApplicationContextAware {

private final List<HasFeatures> hasFeaturesList;
private ApplicationContext context;

public FeaturesEndpoint(List<HasFeatures> hasFeaturesList) {
super("features", false);
this.hasFeaturesList = hasFeaturesList;
}

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}

@Override
public Features invoke() {
Features features = new Features();

for (HasFeatures hasFeatures : hasFeaturesList) {
List<Class> abstractFeatures = hasFeatures.getAbstractFeatures();
if (abstractFeatures != null) {
for (Class clazz : abstractFeatures) {
addAbstractFeature(features, clazz);
}
}

List<NamedFeature> namedFeatures = hasFeatures.getNamedFeatures();
if (namedFeatures != null) {
for (NamedFeature namedFeature : namedFeatures) {
addFeature(features, namedFeature);
}
}
}

return features;
}

private void addAbstractFeature(Features features, Class type) {
addFeature(features, new NamedFeature(type.getSimpleName(), type));
}

private void addFeature(Features features, NamedFeature feature) {
try {
Object bean = context.getBean(feature.getType());
Class<?> beanClass = bean.getClass();
features.getEnabled().add(new Feature(feature.getName(),
beanClass.getCanonicalName(),
beanClass.getPackage().getImplementationVersion(),
beanClass.getPackage().getImplementationVendor()));
} catch (NoSuchBeanDefinitionException e) {
features.getDisabled().add(feature.getName());
}
}

@Value
class Features {
List<Feature> enabled = new ArrayList<>();
List<String> disabled = new ArrayList<>();
}

@Value
class Feature {
final String type;
final String name;
final String version;
final String vendor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.springframework.cloud.client.actuator;

import lombok.Builder;
import lombok.Singular;
import lombok.Value;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* @author Spencer Gibb
*/
@Value
@Builder
public class HasFeatures {
@Singular
private final List<Class> abstractFeatures;
@Singular
private final List<NamedFeature> namedFeatures;

public static HasFeatures abstractFeatures(Class... abstractFeatures) {
return new HasFeatures(Arrays.asList(abstractFeatures), Collections.<NamedFeature>emptyList());
}

public static HasFeatures namedFeatures(NamedFeature... namedFeatures) {
return new HasFeatures(Collections.<Class>emptyList(), Arrays.asList(namedFeatures));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.springframework.cloud.client.actuator;

import lombok.Value;

/**
* @author Spencer Gibb
*/
@Value
public class NamedFeature {
private final String name;
private final Class<?> type;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.springframework.cloud.client.actuator;


import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
* @author Spencer Gibb
*/
public class FeaturesEndpointTests {

private AnnotationConfigApplicationContext context;

@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(JacksonAutoConfiguration.class, FeaturesConfig.class, Config.class);
this.context.refresh();
}

@After
public void close() {
if (this.context != null) {
this.context.close();
}
}

@Test
public void invokeWorks() {
FeaturesEndpoint.Features features = this.context.getBean(FeaturesEndpoint.class).invoke();
assertThat(features, is(notNullValue()));
assertThat(features.getEnabled().size(), is(equalTo(2)));
assertThat(features.getDisabled().size(), is(equalTo(1)));
}

@Configuration
public static class FeaturesConfig {
@Bean
Foo foo() {
return new Foo();
}

@Bean
Bar bar() {
return new Bar();
}

@Bean
HasFeatures localFeatures() {
return HasFeatures.builder()
.abstractFeature(Foo.class)
.namedFeature(new NamedFeature("Bar Feature", Bar.class))
.abstractFeature(Baz.class)
.build();
}

}

@Configuration
@EnableConfigurationProperties
public static class Config {
@Autowired(required = false)
private List<HasFeatures> hasFeatures = new ArrayList<>();

@Bean
public FeaturesEndpoint cloudEndpoint() {
return new FeaturesEndpoint(hasFeatures);
}
}

public static class Foo {}
public static class Bar {}
public static class Baz {}
}