1 package org.springframework.cloud.client.serviceregistry.endpoint;
2
3 import static com.google.common.collect.Lists.newLinkedList;
4 import static java.lang.Character.isUpperCase;
5
6 import com.google.common.collect.ImmutableMap;
7
8 import lombok.extern.slf4j.Slf4j;
9
10 import org.apache.commons.lang3.tuple.Pair;
11 import org.springframework.boot.actuate.endpoint.Endpoint;
12 import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
13 import org.springframework.cloud.client.serviceregistry.Registration;
14 import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
15 import org.springframework.http.HttpStatus;
16 import org.springframework.http.ResponseEntity;
17 import org.springframework.jmx.export.annotation.ManagedAttribute;
18 import org.springframework.jmx.export.annotation.ManagedOperation;
19 import org.springframework.jmx.export.annotation.ManagedResource;
20 import org.springframework.util.Assert;
21 import org.springframework.web.bind.annotation.RequestBody;
22 import org.springframework.web.bind.annotation.RequestMapping;
23 import org.springframework.web.bind.annotation.RequestMethod;
24 import org.springframework.web.bind.annotation.ResponseBody;
25
26 import java.util.Comparator;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.OptionalInt;
31 import java.util.stream.Collectors;
32 import java.util.stream.IntStream;
33
34
35
36
37
38
39
40 @ManagedResource(description = "Can be used to display and set the service instance status using the service registry")
41 @SuppressWarnings("unchecked")
42 @Slf4j
43 public class MultiServiceRegistryEndpoint implements MvcEndpoint {
44
45 private static final Comparator<ServiceRegistry> SERVICE_REGISTRY_COMPARATOR = (o1, o2) -> {
46 if (o1 != null && o2 != null) {
47 return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName());
48 } else if (o1 != null) {
49 return -1;
50 } else {
51 return 1;
52 }
53 };
54 private final Map<String, ServiceRegistry> serviceRegistries;
55 private List<Registration> registrations;
56 private List<Pair<String, Pair<Registration, ServiceRegistry>>> pairs = newLinkedList();
57
58 public MultiServiceRegistryEndpoint(List<ServiceRegistry> serviceRegistries) {
59 if (serviceRegistries != null) {
60 this.serviceRegistries = serviceRegistries.stream()
61 .sorted(SERVICE_REGISTRY_COMPARATOR)
62 .collect(Collectors.toMap(
63 key -> discoveryServiceName(key.getClass()), value -> value,
64 (oldValue, newValue) -> oldValue,
65 LinkedHashMap::new
66 ));
67 } else {
68 this.serviceRegistries = ImmutableMap.of();
69 }
70 }
71
72 static String discoveryServiceName(final Class<?> registryOrRegistration) {
73 if (registryOrRegistration != null) {
74 final String simpleName = registryOrRegistration.getSimpleName();
75 final OptionalInt secondUpperCase = IntStream.range(1, simpleName.length())
76 .filter(i -> isUpperCase(simpleName.charAt(i)))
77 .findFirst();
78 return simpleName.substring(0, secondUpperCase.orElse(simpleName.length())).toLowerCase();
79 } else {
80 return null;
81 }
82 }
83
84 public void setRegistrations(List<Registration> registrations) {
85 this.registrations = registrations;
86 if (registrations != null) {
87 registrations.forEach(registration -> {
88 final String name = discoveryServiceName(registration.getClass());
89 final ServiceRegistry<?> registry = this.serviceRegistries.get(name);
90 if (registry != null) {
91 this.pairs.add(Pair.of(name, Pair.of(registration, registry)));
92 } else {
93 log.warn("corresponding service registry for registration {} not found.", registration);
94 }
95 });
96 }
97 }
98
99 @RequestMapping(path = "instance-status", method = RequestMethod.POST)
100 @ResponseBody
101 @ManagedOperation
102 public ResponseEntity<?> setStatus(@RequestBody String status) {
103 Assert.notNull(status, "status may not by null");
104
105 if (this.pairs.isEmpty()) {
106 return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
107 }
108
109 this.pairs.forEach(pair -> {
110 final Registration registration = pair.getRight().getLeft();
111 final ServiceRegistry serviceRegistry = pair.getRight().getRight();
112 serviceRegistry.setStatus(registration, status);
113 });
114 return ResponseEntity.ok().build();
115 }
116
117 @RequestMapping(path = "instance-status", method = RequestMethod.GET)
118 @ResponseBody
119 @ManagedAttribute
120 public ResponseEntity getStatus() {
121 if (this.pairs.isEmpty()) {
122 return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
123 }
124
125 return ResponseEntity.ok().body(
126 pairs.stream()
127 .map(pair -> pair.getLeft() + ": " + pair.getRight().getRight().getStatus(pair.getRight().getLeft()))
128 .collect(Collectors.joining(","))
129 );
130 }
131
132 @Override
133 public String getPath() {
134 return "/service-registry";
135 }
136
137 @Override
138 public boolean isSensitive() {
139 return true;
140 }
141
142 @Override
143 public Class<? extends Endpoint<?>> getEndpointType() {
144 return null;
145 }
146 }