Skip to content

Commit f3388ca

Browse files
snicollwilkinsona
authored andcommitted
Add support for making endpoints accessible via JMX
1 parent fd13d7e commit f3388ca

16 files changed

+2169
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.endpoint.jmx;
18+
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.function.Function;
23+
24+
import javax.management.Attribute;
25+
import javax.management.AttributeList;
26+
import javax.management.AttributeNotFoundException;
27+
import javax.management.DynamicMBean;
28+
import javax.management.InvalidAttributeValueException;
29+
import javax.management.MBeanException;
30+
import javax.management.MBeanInfo;
31+
import javax.management.ReflectionException;
32+
33+
import reactor.core.publisher.Mono;
34+
35+
import org.springframework.boot.endpoint.EndpointInfo;
36+
import org.springframework.util.ClassUtils;
37+
38+
/**
39+
* A {@link DynamicMBean} that invokes operations on an {@link EndpointInfo endpoint}.
40+
*
41+
* @author Stephane Nicoll
42+
* @author Andy Wilkinson
43+
* @since 2.0.0
44+
* @see EndpointMBeanInfoAssembler
45+
*/
46+
public class EndpointMBean implements DynamicMBean {
47+
48+
private static final boolean REACTOR_PRESENT = ClassUtils.isPresent(
49+
"reactor.core.publisher.Mono", EndpointMBean.class.getClassLoader());
50+
51+
private final Function<Object, Object> operationResponseConverter;
52+
53+
private final EndpointMBeanInfo endpointInfo;
54+
55+
EndpointMBean(Function<Object, Object> operationResponseConverter,
56+
EndpointMBeanInfo endpointInfo) {
57+
this.operationResponseConverter = operationResponseConverter;
58+
this.endpointInfo = endpointInfo;
59+
}
60+
61+
/**
62+
* Return the id of the related endpoint.
63+
* @return the endpoint id
64+
*/
65+
public String getEndpointId() {
66+
return this.endpointInfo.getEndpointId();
67+
}
68+
69+
@Override
70+
public MBeanInfo getMBeanInfo() {
71+
return this.endpointInfo.getMbeanInfo();
72+
}
73+
74+
@Override
75+
public Object invoke(String actionName, Object[] params, String[] signature)
76+
throws MBeanException, ReflectionException {
77+
JmxEndpointOperation operationInfo = this.endpointInfo.getOperations()
78+
.get(actionName);
79+
if (operationInfo != null) {
80+
Map<String, Object> arguments = new HashMap<>();
81+
List<JmxEndpointOperationParameterInfo> parameters = operationInfo
82+
.getParameters();
83+
for (int i = 0; i < params.length; i++) {
84+
arguments.put(parameters.get(i).getName(), params[i]);
85+
}
86+
Object result = operationInfo.getOperationInvoker().invoke(arguments);
87+
if (REACTOR_PRESENT) {
88+
result = ReactiveHandler.handle(result);
89+
}
90+
return this.operationResponseConverter.apply(result);
91+
}
92+
throw new ReflectionException(new IllegalArgumentException(
93+
String.format("Endpoint with id '%s' has no operation named %s",
94+
this.endpointInfo.getEndpointId(), actionName)));
95+
}
96+
97+
@Override
98+
public Object getAttribute(String attribute)
99+
throws AttributeNotFoundException, MBeanException, ReflectionException {
100+
throw new AttributeNotFoundException();
101+
}
102+
103+
@Override
104+
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
105+
InvalidAttributeValueException, MBeanException, ReflectionException {
106+
throw new AttributeNotFoundException();
107+
}
108+
109+
@Override
110+
public AttributeList getAttributes(String[] attributes) {
111+
return new AttributeList();
112+
}
113+
114+
@Override
115+
public AttributeList setAttributes(AttributeList attributes) {
116+
return new AttributeList();
117+
}
118+
119+
private static class ReactiveHandler {
120+
121+
public static Object handle(Object result) {
122+
if (result instanceof Mono) {
123+
return ((Mono<?>) result).block();
124+
}
125+
return result;
126+
}
127+
128+
}
129+
130+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.endpoint.jmx;
18+
19+
import java.util.Map;
20+
21+
import javax.management.MBeanInfo;
22+
23+
import org.springframework.boot.endpoint.EndpointInfo;
24+
import org.springframework.boot.endpoint.EndpointOperation;
25+
26+
/**
27+
* The {@link MBeanInfo} for a particular {@link EndpointInfo endpoint}. Maps operation
28+
* names to an {@link EndpointOperation}.
29+
*
30+
* @author Stephane Nicoll
31+
* @since 2.0.0
32+
*/
33+
public final class EndpointMBeanInfo {
34+
35+
private final String endpointId;
36+
37+
private final MBeanInfo mBeanInfo;
38+
39+
private final Map<String, JmxEndpointOperation> operations;
40+
41+
public EndpointMBeanInfo(String endpointId, MBeanInfo mBeanInfo,
42+
Map<String, JmxEndpointOperation> operations) {
43+
this.endpointId = endpointId;
44+
this.mBeanInfo = mBeanInfo;
45+
this.operations = operations;
46+
}
47+
48+
public String getEndpointId() {
49+
return this.endpointId;
50+
}
51+
52+
public MBeanInfo getMbeanInfo() {
53+
return this.mBeanInfo;
54+
}
55+
56+
public Map<String, JmxEndpointOperation> getOperations() {
57+
return this.operations;
58+
}
59+
60+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.endpoint.jmx;
18+
19+
import java.util.HashMap;
20+
import java.util.LinkedHashMap;
21+
import java.util.Map;
22+
import java.util.stream.Collectors;
23+
24+
import javax.management.MBeanInfo;
25+
import javax.management.MBeanOperationInfo;
26+
import javax.management.MBeanParameterInfo;
27+
import javax.management.modelmbean.ModelMBeanAttributeInfo;
28+
import javax.management.modelmbean.ModelMBeanConstructorInfo;
29+
import javax.management.modelmbean.ModelMBeanInfoSupport;
30+
import javax.management.modelmbean.ModelMBeanNotificationInfo;
31+
import javax.management.modelmbean.ModelMBeanOperationInfo;
32+
33+
import org.springframework.boot.endpoint.EndpointInfo;
34+
import org.springframework.boot.endpoint.EndpointOperationType;
35+
36+
/**
37+
* Gather the management operations of a particular {@link EndpointInfo endpoint}.
38+
*
39+
* @author Stephane Nicoll
40+
* @author Andy Wilkinson
41+
*/
42+
class EndpointMBeanInfoAssembler {
43+
44+
private final JmxOperationResponseMapper responseMapper;
45+
46+
EndpointMBeanInfoAssembler(JmxOperationResponseMapper responseMapper) {
47+
this.responseMapper = responseMapper;
48+
}
49+
50+
/**
51+
* Creates the {@link EndpointMBeanInfo} for the specified {@link EndpointInfo
52+
* endpoint}.
53+
* @param endpointInfo the endpoint to handle
54+
* @return the mbean info for the endpoint
55+
*/
56+
EndpointMBeanInfo createEndpointMBeanInfo(
57+
EndpointInfo<JmxEndpointOperation> endpointInfo) {
58+
Map<String, OperationInfos> operationsMapping = getOperationInfo(endpointInfo);
59+
ModelMBeanOperationInfo[] operationsMBeanInfo = operationsMapping.values()
60+
.stream().map(t -> t.mBeanOperationInfo).collect(Collectors.toList())
61+
.toArray(new ModelMBeanOperationInfo[] {});
62+
Map<String, JmxEndpointOperation> operationsInfo = new LinkedHashMap<>();
63+
operationsMapping.forEach((name, t) -> operationsInfo.put(name, t.operation));
64+
65+
MBeanInfo info = new ModelMBeanInfoSupport(EndpointMBean.class.getName(),
66+
getDescription(endpointInfo), new ModelMBeanAttributeInfo[0],
67+
new ModelMBeanConstructorInfo[0], operationsMBeanInfo,
68+
new ModelMBeanNotificationInfo[0]);
69+
return new EndpointMBeanInfo(endpointInfo.getId(), info, operationsInfo);
70+
}
71+
72+
private String getDescription(EndpointInfo<?> endpointInfo) {
73+
return "MBean operations for endpoint " + endpointInfo.getId();
74+
}
75+
76+
private Map<String, OperationInfos> getOperationInfo(
77+
EndpointInfo<JmxEndpointOperation> endpointInfo) {
78+
Map<String, OperationInfos> operationInfos = new HashMap<>();
79+
endpointInfo.getOperations().forEach((operationInfo) -> {
80+
String name = operationInfo.getOperationName();
81+
ModelMBeanOperationInfo mBeanOperationInfo = new ModelMBeanOperationInfo(
82+
operationInfo.getOperationName(), operationInfo.getDescription(),
83+
getMBeanParameterInfos(operationInfo), this.responseMapper
84+
.mapResponseType(operationInfo.getOutputType()).getName(),
85+
mapOperationType(operationInfo.getType()));
86+
operationInfos.put(name,
87+
new OperationInfos(mBeanOperationInfo, operationInfo));
88+
});
89+
return operationInfos;
90+
}
91+
92+
private MBeanParameterInfo[] getMBeanParameterInfos(JmxEndpointOperation operation) {
93+
return operation.getParameters().stream()
94+
.map((operationParameter) -> new MBeanParameterInfo(
95+
operationParameter.getName(),
96+
operationParameter.getType().getName(),
97+
operationParameter.getDescription()))
98+
.collect(Collectors.collectingAndThen(Collectors.toList(),
99+
(parameterInfos) -> parameterInfos
100+
.toArray(new MBeanParameterInfo[parameterInfos.size()])));
101+
}
102+
103+
private int mapOperationType(EndpointOperationType type) {
104+
if (type == EndpointOperationType.READ) {
105+
return MBeanOperationInfo.INFO;
106+
}
107+
if (type == EndpointOperationType.WRITE) {
108+
return MBeanOperationInfo.ACTION;
109+
}
110+
return MBeanOperationInfo.UNKNOWN;
111+
}
112+
113+
private static class OperationInfos {
114+
115+
private final ModelMBeanOperationInfo mBeanOperationInfo;
116+
117+
private final JmxEndpointOperation operation;
118+
119+
OperationInfos(ModelMBeanOperationInfo mBeanOperationInfo,
120+
JmxEndpointOperation operation) {
121+
this.mBeanOperationInfo = mBeanOperationInfo;
122+
this.operation = operation;
123+
}
124+
125+
}
126+
127+
}

0 commit comments

Comments
 (0)