Translate user-defined exception when invoking JMX operation

This commit makes sure to respect the MBeanServer#invoke contract by
wrapping any user-defined exception in an MBeanException. Also, any
exception not from the JDK is translated, as it may lead to unexpected
issue on the client if that class isn't present. This is consistent
with our operation result mapping strategy.

Closes gh-10448
This commit is contained in:
Stephane Nicoll 2018-04-13 16:26:04 +02:00
parent 2204d5f750
commit fa542bacc0
2 changed files with 52 additions and 3 deletions

View File

@ -93,13 +93,12 @@ public class EndpointMBean implements DynamicMBean {
return invoke(operation, params);
}
private Object invoke(JmxOperation operation, Object[] params) {
private Object invoke(JmxOperation operation, Object[] params) throws MBeanException {
try {
String[] parameterNames = operation.getParameters().stream()
.map(JmxOperationParameter::getName).toArray(String[]::new);
Map<String, Object> arguments = getArguments(parameterNames, params);
Object result = operation
.invoke(new InvocationContext(SecurityContext.NONE, arguments));
Object result = invokeOperation(operation, arguments);
if (REACTOR_PRESENT) {
result = ReactiveHandler.handle(result);
}
@ -110,6 +109,26 @@ public class EndpointMBean implements DynamicMBean {
}
}
private Object invokeOperation(JmxOperation operation,
Map<String, Object> arguments) throws MBeanException {
try {
return operation.invoke(new InvocationContext(SecurityContext.NONE,
arguments));
}
catch (Exception ex) {
throw new MBeanException(translateIfNecessary(ex), ex.getMessage());
}
}
private Exception translateIfNecessary(Exception exception) {
if (exception.getClass().getName().startsWith("java.")) {
return exception;
}
else {
return new IllegalStateException(exception.getMessage());
}
}
private Map<String, Object> getArguments(String[] parameterNames, Object[] params) {
Map<String, Object> arguments = new HashMap<>();
for (int i = 0; i < params.length; i++) {

View File

@ -29,6 +29,8 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import reactor.core.publisher.Mono;
import org.springframework.beans.FatalBeanException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.mockito.Mockito.mock;
@ -84,6 +86,34 @@ public class EndpointMBeanTests {
assertThat(result).isEqualTo("result");
}
@Test
public void invokeWhenOperationFailedShouldTranslateException()
throws MBeanException, ReflectionException {
TestExposableJmxEndpoint endpoint = new TestExposableJmxEndpoint(
new TestJmxOperation((arguments) -> {
throw new FatalBeanException("test failure");
}));
EndpointMBean bean = new EndpointMBean(this.responseMapper, endpoint);
this.thrown.expect(MBeanException.class);
this.thrown.expectCause(instanceOf(IllegalStateException.class));
this.thrown.expectMessage("test failure");
bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
}
@Test
public void invokeWhenOperationFailedWithJdkExceptionShouldReuseException()
throws MBeanException, ReflectionException {
TestExposableJmxEndpoint endpoint = new TestExposableJmxEndpoint(
new TestJmxOperation((arguments) -> {
throw new UnsupportedOperationException("test failure");
}));
EndpointMBean bean = new EndpointMBean(this.responseMapper, endpoint);
this.thrown.expect(MBeanException.class);
this.thrown.expectCause(instanceOf(UnsupportedOperationException.class));
this.thrown.expectMessage("test failure");
bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
}
@Test
public void invokeWhenActionNameIsNotAnOperationShouldThrowException()
throws MBeanException, ReflectionException {