Separate endpoint concerns
Update endpoint code to provide cleaner separation of concerns. Specifically, the top level endpoint package is no longer aware of the fact that JMX and HTTP are ultimately used to expose endpoints. Caching concerns have also been abstracted behind a general purpose `OperationMethodInvokerAdvisor` interface. Configuration properties have been refined to further enforce separation. The `management.endpoint.<name>` prefix provides configuration for a single endpoint (including enable and cache time-to-live). These properties are now technology agnostic (they don't include `web` or `jmx` sub properties). The `management.endpoints.<technology>` prefix provide exposure specific configuration. For example, `management.endpoints.web.path-mapping` allow endpoint URLs to be changed. Endpoint enabled/disabled logic has been simplified so that endpoints can't be disabled per exposure technology. Instead a filter based approach is used to allow refinement of what endpoints are exposed over a given technology. Fixes gh-10176
This commit is contained in:
@@ -23,10 +23,10 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
@@ -42,6 +42,7 @@ import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
@@ -157,9 +158,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
}
|
||||
TypeElement endpointType = elementUtils.getTypeElement(endpointAnnotation());
|
||||
if (endpointType != null) { // Is @Endpoint available
|
||||
for (Element element : roundEnv.getElementsAnnotatedWith(endpointType)) {
|
||||
processEndpoint(element);
|
||||
}
|
||||
getElementsAnnotatedOrMetaAnnotatedWith(roundEnv, endpointType)
|
||||
.forEach(this::processEndpoint);
|
||||
}
|
||||
if (roundEnv.processingOver()) {
|
||||
try {
|
||||
@@ -172,6 +172,41 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
return false;
|
||||
}
|
||||
|
||||
private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(
|
||||
RoundEnvironment roundEnv, TypeElement annotation) {
|
||||
DeclaredType annotationType = (DeclaredType) annotation.asType();
|
||||
Map<Element, List<Element>> result = new LinkedHashMap<>();
|
||||
for (Element element : roundEnv.getRootElements()) {
|
||||
LinkedList<Element> stack = new LinkedList<>();
|
||||
stack.push(element);
|
||||
collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack);
|
||||
stack.removeFirst();
|
||||
if (!stack.isEmpty()) {
|
||||
result.put(element, Collections.unmodifiableList(stack));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean collectElementsAnnotatedOrMetaAnnotatedWith(
|
||||
DeclaredType annotationType, LinkedList<Element> stack) {
|
||||
Element element = stack.peekLast();
|
||||
for (AnnotationMirror annotation : this.processingEnv.getElementUtils()
|
||||
.getAllAnnotationMirrors(element)) {
|
||||
Element annotationElement = annotation.getAnnotationType().asElement();
|
||||
if (!stack.contains(annotationElement)) {
|
||||
stack.addLast(annotationElement);
|
||||
if (annotationElement.equals(annotationType.asElement())) {
|
||||
return true;
|
||||
}
|
||||
if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) {
|
||||
stack.removeLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processElement(Element element) {
|
||||
try {
|
||||
AnnotationMirror annotation = getAnnotation(element,
|
||||
@@ -360,9 +395,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private void processEndpoint(Element element) {
|
||||
private void processEndpoint(Element element, List<Element> annotations) {
|
||||
try {
|
||||
AnnotationMirror annotation = getAnnotation(element, endpointAnnotation());
|
||||
String annotationName = this.typeUtils.getQualifiedName(annotations.get(0));
|
||||
AnnotationMirror annotation = getAnnotation(element, annotationName);
|
||||
if (element instanceof TypeElement) {
|
||||
processEndpoint(annotation, (TypeElement) element);
|
||||
}
|
||||
@@ -379,51 +415,17 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
if (endpointId == null || "".equals(endpointId)) {
|
||||
return; // Can't process that endpoint
|
||||
}
|
||||
Boolean enabledByDefault = determineEnabledByDefault(
|
||||
elementValues.get("defaultEnablement"));
|
||||
Boolean enabledByDefault = (Boolean) elementValues.get("enableByDefault");
|
||||
String type = this.typeUtils.getQualifiedName(element);
|
||||
this.metadataCollector
|
||||
.add(ItemMetadata.newGroup(endpointKey(endpointId), type, type, null));
|
||||
this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId),
|
||||
"enabled", Boolean.class.getName(), type, null,
|
||||
String.format("Enable the %s endpoint.", endpointId), enabledByDefault,
|
||||
null));
|
||||
String.format("Enable the %s endpoint.", endpointId),
|
||||
(enabledByDefault == null ? true : enabledByDefault), null));
|
||||
this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId),
|
||||
"cache.time-to-live", Long.class.getName(), type, null,
|
||||
"Maximum time in milliseconds that a response can be cached.", 0, null));
|
||||
EndpointExposure endpointTypes = EndpointExposure
|
||||
.parse(elementValues.get("exposure"));
|
||||
if (endpointTypes.hasJmx()) {
|
||||
this.metadataCollector.add(ItemMetadata.newProperty(
|
||||
endpointKey(endpointId + ".jmx"), "enabled", Boolean.class.getName(),
|
||||
type, null,
|
||||
String.format("Expose the %s endpoint as a JMX MBean.", endpointId),
|
||||
enabledByDefault, null));
|
||||
}
|
||||
if (endpointTypes.hasWeb()) {
|
||||
this.metadataCollector.add(ItemMetadata.newProperty(
|
||||
endpointKey(endpointId + ".web"), "enabled", Boolean.class.getName(),
|
||||
type, null, String.format("Expose the %s endpoint as a Web endpoint.",
|
||||
endpointId),
|
||||
enabledByDefault, null));
|
||||
this.metadataCollector.add(ItemMetadata.newProperty(endpointKey(endpointId),
|
||||
"web.path", String.class.getName(), type, null,
|
||||
String.format("Path of the %s endpoint.", endpointId), endpointId,
|
||||
null));
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean determineEnabledByDefault(Object defaultEnablement) {
|
||||
if (defaultEnablement != null) {
|
||||
String value = String.valueOf(defaultEnablement);
|
||||
if ("ENABLED".equals(value)) {
|
||||
return true;
|
||||
}
|
||||
if ("DISABLED".equals(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String endpointKey(String suffix) {
|
||||
@@ -550,46 +552,4 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
this.processingEnv.getMessager().printMessage(kind, msg);
|
||||
}
|
||||
|
||||
private static class EndpointExposure {
|
||||
|
||||
private static final List<String> ALL = Arrays.asList("JMX", "WEB");
|
||||
|
||||
private final List<String> types;
|
||||
|
||||
EndpointExposure(List<String> types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
static EndpointExposure parse(Object exposureAttribute) {
|
||||
List<AnnotationValue> values = asAnnotationValues(exposureAttribute);
|
||||
if (values.isEmpty()) {
|
||||
return new EndpointExposure(ALL);
|
||||
}
|
||||
return new EndpointExposure(
|
||||
values.stream().map(EndpointExposure::getValueAttribute)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AnnotationValue> asAnnotationValues(Object typesAttribute) {
|
||||
if (!(typesAttribute instanceof List)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (List<AnnotationValue>) typesAttribute;
|
||||
}
|
||||
|
||||
private static String getValueAttribute(AnnotationValue value) {
|
||||
return ((VariableElement) value.getValue()).getSimpleName().toString();
|
||||
}
|
||||
|
||||
public boolean hasJmx() {
|
||||
return this.types.contains("JMX");
|
||||
}
|
||||
|
||||
public boolean hasWeb() {
|
||||
return this.types.contains("WEB");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user