Skip to content

Commit 8002275

Browse files
artembilangaryrussell
authored andcommitted
INT-2549: Ignore MBean call reply in op-invoc-c-a
JIRA: https://jira.spring.io/browse/INT-2549 * Add `expectReply` property into the `OperationInvokingMessageHandler` to align the one-way and request-reply behavior with all other similar components in Spring Integration * Ignore an operation invocation result in case of `expectReply == false` and log warning * Provide some refactoring into the `OperationInvokingMessageHandler` to fix Sonar complains about complexity **Cherry-pick to 5.0.x, 4.3.x**
1 parent f43fa97 commit 8002275

File tree

6 files changed

+190
-112
lines changed

6 files changed

+190
-112
lines changed

spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java

Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -59,30 +59,56 @@
5959
* @author Mark Fisher
6060
* @author Oleg Zhurakousky
6161
* @author Gary Russell
62+
* @author Artem Bilan
63+
*
6264
* @since 2.0
6365
*/
6466
public class OperationInvokingMessageHandler extends AbstractReplyProducingMessageHandler implements InitializingBean {
6567

66-
private volatile MBeanServerConnection server;
68+
private MBeanServerConnection server;
69+
70+
private ObjectName defaultObjectName;
71+
72+
private String operationName;
6773

68-
private volatile ObjectName defaultObjectName;
74+
private boolean expectReply = true;
75+
76+
/**
77+
* Construct an instance with no arguments; for backward compatibility.
78+
* The {@link #setServer(MBeanServerConnection)} must be used as well.
79+
* The {@link #OperationInvokingMessageHandler(MBeanServerConnection)}
80+
* is a preferred way for instantiation.
81+
* @since 4.3.20
82+
* @deprecated since 4.3.20
83+
*/
84+
@Deprecated
85+
public OperationInvokingMessageHandler() {
86+
}
6987

70-
private volatile String operationName;
88+
/**
89+
* Construct an instance based on the provided {@link MBeanServerConnection}.
90+
* @param server the {@link MBeanServerConnection} to use.
91+
* @since 4.3.20
92+
*/
93+
public OperationInvokingMessageHandler(MBeanServerConnection server) {
94+
Assert.notNull(server, "MBeanServer is required.");
95+
this.server = server;
96+
}
7197

7298
/**
7399
* Provide a reference to the MBeanServer within which the MBean
74100
* target for operation invocation has been registered.
75-
*
76101
* @param server The MBean server connection.
102+
* @deprecated since 4.3.20 in favor of {@link #OperationInvokingMessageHandler(MBeanServerConnection)}
77103
*/
104+
@Deprecated
78105
public void setServer(MBeanServerConnection server) {
79106
this.server = server;
80107
}
81108

82109
/**
83110
* Specify a default ObjectName to use when no such header is
84111
* available on the Message being handled.
85-
*
86112
* @param objectName The object name.
87113
*/
88114
public void setObjectName(String objectName) {
@@ -99,16 +125,25 @@ public void setObjectName(String objectName) {
99125
/**
100126
* Specify an operation name to be invoked when no such
101127
* header is available on the Message being handled.
102-
*
103128
* @param operationName The operation name.
104129
*/
105130
public void setOperationName(String operationName) {
106131
this.operationName = operationName;
107132
}
108133

134+
/**
135+
* Specify whether a reply Message is expected. If not, this handler will simply return null for a
136+
* successful response or throw an Exception for a non-successful response. The default is true.
137+
* @param expectReply true if a reply is expected.
138+
* @since 4.3.20
139+
*/
140+
public void setExpectReply(boolean expectReply) {
141+
this.expectReply = expectReply;
142+
}
143+
109144
@Override
110145
public String getComponentType() {
111-
return "jmx:operation-invoking-channel-adapter";
146+
return this.expectReply ? "jmx:operation-invoking-outbound-gateway" : "jmx:operation-invoking-channel-adapter";
112147
}
113148

114149
@Override
@@ -120,49 +155,18 @@ protected void doInit() {
120155
protected Object handleRequestMessage(Message<?> requestMessage) {
121156
ObjectName objectName = resolveObjectName(requestMessage);
122157
String operation = resolveOperationName(requestMessage);
123-
Map<String, Object> paramsFromMessage = this.resolveParameters(requestMessage);
158+
Map<String, Object> paramsFromMessage = resolveParameters(requestMessage);
124159
try {
125-
MBeanInfo mbeanInfo = this.server.getMBeanInfo(objectName);
126-
MBeanOperationInfo[] opInfoArray = mbeanInfo.getOperations();
127-
boolean hasNoArgOption = false;
128-
for (MBeanOperationInfo opInfo : opInfoArray) {
129-
if (operation.equals(opInfo.getName())) {
130-
MBeanParameterInfo[] paramInfoArray = opInfo.getSignature();
131-
if (paramInfoArray.length == 0) {
132-
hasNoArgOption = true;
133-
}
134-
if (paramInfoArray.length == paramsFromMessage.size()) {
135-
int index = 0;
136-
Object[] values = new Object[paramInfoArray.length];
137-
String[] signature = new String[paramInfoArray.length];
138-
for (MBeanParameterInfo paramInfo : paramInfoArray) {
139-
Object value = paramsFromMessage.get(paramInfo.getName());
140-
if (value == null) {
141-
/*
142-
* With Spring 3.2.3 and greater, the parameter names are
143-
* registered instead of the JVM's default p1, p2 etc.
144-
* Fall back to that naming style if not found.
145-
*/
146-
value = paramsFromMessage.get("p" + (index + 1));
147-
}
148-
if (value != null && valueTypeMatchesParameterType(value, paramInfo)) {
149-
values[index] = value;
150-
signature[index] = paramInfo.getType();
151-
index++;
152-
}
153-
}
154-
if (index == paramInfoArray.length) {
155-
return this.server.invoke(objectName, operation, values, signature);
156-
}
157-
}
160+
Object result = invokeOperation(requestMessage, objectName, operation, paramsFromMessage);
161+
if (!this.expectReply && result != null) {
162+
if (logger.isWarnEnabled()) {
163+
logger.warn("This component doesn't expect a reply. " +
164+
"The MBean operation '" + operation + "' result '" + result +
165+
"' for '" + objectName + "' is ignored.");
158166
}
167+
return null;
159168
}
160-
if (hasNoArgOption) {
161-
return this.server.invoke(objectName, operation, null, null);
162-
}
163-
throw new MessagingException(requestMessage, "failed to find JMX operation '"
164-
+ operation + "' on MBean [" + objectName + "] of type [" + mbeanInfo.getClassName()
165-
+ "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage);
169+
return result;
166170
}
167171
catch (JMException e) {
168172
throw new MessageHandlingException(requestMessage, "failed to invoke JMX operation '" +
@@ -174,8 +178,56 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
174178
}
175179
}
176180

181+
private Object invokeOperation(Message<?> requestMessage, ObjectName objectName, String operation,
182+
Map<String, Object> paramsFromMessage) throws JMException, IOException {
183+
184+
MBeanInfo mbeanInfo = this.server.getMBeanInfo(objectName);
185+
MBeanOperationInfo[] opInfoArray = mbeanInfo.getOperations();
186+
boolean hasNoArgOption = false;
187+
for (MBeanOperationInfo opInfo : opInfoArray) {
188+
if (operation.equals(opInfo.getName())) {
189+
MBeanParameterInfo[] paramInfoArray = opInfo.getSignature();
190+
if (paramInfoArray.length == 0) {
191+
hasNoArgOption = true;
192+
}
193+
if (paramInfoArray.length == paramsFromMessage.size()) {
194+
int index = 0;
195+
Object[] values = new Object[paramInfoArray.length];
196+
String[] signature = new String[paramInfoArray.length];
197+
for (MBeanParameterInfo paramInfo : paramInfoArray) {
198+
Object value = paramsFromMessage.get(paramInfo.getName());
199+
if (value == null) {
200+
/*
201+
* With Spring 3.2.3 and greater, the parameter names are
202+
* registered instead of the JVM's default p1, p2 etc.
203+
* Fall back to that naming style if not found.
204+
*/
205+
value = paramsFromMessage.get("p" + (index + 1));
206+
}
207+
if (value != null && valueTypeMatchesParameterType(value, paramInfo)) {
208+
values[index] = value;
209+
signature[index] = paramInfo.getType();
210+
index++;
211+
}
212+
}
213+
if (index == paramInfoArray.length) {
214+
return this.server.invoke(objectName, operation, values, signature);
215+
}
216+
}
217+
}
218+
}
219+
if (hasNoArgOption) {
220+
return this.server.invoke(objectName, operation, null, null);
221+
}
222+
else {
223+
throw new MessagingException(requestMessage, "failed to find JMX operation '"
224+
+ operation + "' on MBean [" + objectName + "] of type [" + mbeanInfo.getClassName()
225+
+ "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage);
226+
}
227+
}
228+
177229
private boolean valueTypeMatchesParameterType(Object value, MBeanParameterInfo paramInfo) {
178-
Class<? extends Object> valueClass = value.getClass();
230+
Class<?> valueClass = value.getClass();
179231
if (valueClass.getName().equals(paramInfo.getType())) {
180232
return true;
181233
}
@@ -209,7 +261,7 @@ else if (objectNameHeader instanceof String) {
209261
}
210262

211263
/**
212-
* First checks if defaultOperationName is set, otherwise falls back on {@link JmxHeaders#OPERATION_NAME} header.
264+
* First checks if defaultOperationName is set, otherwise falls back on {@link JmxHeaders#OPERATION_NAME} header.
213265
*/
214266
private String resolveOperationName(Message<?> message) {
215267
String operation = this.operationName;
@@ -220,28 +272,27 @@ private String resolveOperationName(Message<?> message) {
220272
return operation;
221273
}
222274

223-
@SuppressWarnings({ "unchecked", "rawtypes" })
275+
@SuppressWarnings("unchecked")
224276
private Map<String, Object> resolveParameters(Message<?> message) {
225277
Map<String, Object> map;
226-
if (message.getPayload() instanceof Map) {
227-
map = (Map<String, Object>) message.getPayload();
278+
Object payload = message.getPayload();
279+
if (payload instanceof Map) {
280+
map = (Map<String, Object>) payload;
228281
}
229-
else if (message.getPayload() instanceof List) {
230-
map = this.createParameterMapFromList((List) message.getPayload());
282+
else if (payload instanceof List) {
283+
map = createParameterMapFromList((List<?>) payload);
231284
}
232-
else if (message.getPayload() != null && message.getPayload().getClass().isArray()) {
233-
map = this.createParameterMapFromList(
234-
Arrays.asList(ObjectUtils.toObjectArray(message.getPayload())));
285+
else if (payload.getClass().isArray()) {
286+
map = createParameterMapFromList(Arrays.asList(ObjectUtils.toObjectArray(payload)));
235287
}
236288
else {
237-
map = this.createParameterMapFromList(Collections.singletonList(message.getPayload()));
289+
map = createParameterMapFromList(Collections.singletonList(payload));
238290
}
239291
return map;
240292
}
241293

242-
@SuppressWarnings("rawtypes")
243-
private Map<String, Object> createParameterMapFromList(List parameters) {
244-
Map<String, Object> map = new HashMap<String, Object>();
294+
private Map<String, Object> createParameterMapFromList(List<?> parameters) {
295+
Map<String, Object> map = new HashMap<>();
245296
for (int i = 0; i < parameters.size(); i++) {
246297
map.put("p" + (i + 1), parameters.get(i));
247298
}

spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
2828
/**
2929
* @author Mark Fisher
3030
* @author Gary Russell
31+
* @author Artem Bilan
32+
*
3133
* @since 2.0
3234
*/
3335
public class OperationInvokingChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@@ -40,7 +42,8 @@ protected boolean shouldGenerateIdAsFallback() {
4042
@Override
4143
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
4244
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(OperationInvokingMessageHandler.class);
43-
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server");
45+
builder.addConstructorArgReference(element.getAttribute("server"));
46+
builder.addPropertyValue("expectReply", false);
4447
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name");
4548
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "operation-name");
4649
return builder.getBeanDefinition();

spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayParser.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
/**
2828
* @author Oleg Zhurakousky
2929
* @author Artem Bilan
30+
*
3031
* @since 2.0
3132
*/
3233
public class OperationInvokingOutboundGatewayParser extends AbstractConsumerEndpointParser {
@@ -39,7 +40,7 @@ protected String getInputChannelAttributeName() {
3940
@Override
4041
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
4142
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(OperationInvokingMessageHandler.class);
42-
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server");
43+
builder.addConstructorArgReference(element.getAttribute("server"));
4344
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name");
4445
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "operation-name");
4546
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");

spring-integration-jmx/src/test/java/org/springframework/integration/jmx/OperationInvokingMessageHandlerTests.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -88,8 +88,7 @@ public void cleanup() throws Exception {
8888
@Test
8989
public void invocationWithMapPayload() {
9090
QueueChannel outputChannel = new QueueChannel();
91-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
92-
handler.setServer(server);
91+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
9392
handler.setObjectName(this.objectName);
9493
handler.setOutputChannel(outputChannel);
9594
handler.setOperationName("x");
@@ -108,8 +107,7 @@ public void invocationWithMapPayload() {
108107
@Test
109108
public void invocationWithPayloadNoReturnValue() {
110109
QueueChannel outputChannel = new QueueChannel();
111-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
112-
handler.setServer(server);
110+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
113111
handler.setObjectName(this.objectName);
114112
handler.setOutputChannel(outputChannel);
115113
handler.setOperationName("y");
@@ -122,8 +120,7 @@ public void invocationWithPayloadNoReturnValue() {
122120
@Test(expected = MessagingException.class)
123121
public void invocationWithMapPayloadNotEnoughParameters() {
124122
QueueChannel outputChannel = new QueueChannel();
125-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
126-
handler.setServer(server);
123+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
127124
handler.setObjectName(this.objectName);
128125
handler.setOutputChannel(outputChannel);
129126
handler.setOperationName("x");
@@ -141,8 +138,7 @@ public void invocationWithMapPayloadNotEnoughParameters() {
141138
@Test
142139
public void invocationWithListPayload() {
143140
QueueChannel outputChannel = new QueueChannel();
144-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
145-
handler.setServer(server);
141+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
146142
handler.setObjectName(this.objectName);
147143
handler.setOutputChannel(outputChannel);
148144
handler.setOperationName("x");

spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParserTests-context.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
2222
operation-name="test">
2323
<jmx:request-handler-advice-chain>
24-
<bean class="org.springframework.integration.jmx.config.OperationInvokingChannelAdapterParserTests$FooADvice" />
24+
<bean class="org.springframework.integration.jmx.config.OperationInvokingChannelAdapterParserTests.FooAdvice" />
2525
</jmx:request-handler-advice-chain>
2626
</jmx:operation-invoking-channel-adapter>
2727

@@ -37,8 +37,8 @@
3737
operation-name="test"/>
3838
</si:chain>
3939

40-
<si:chain input-channel="operationWithinChainWithNonNullReturn">
41-
<jmx:operation-invoking-channel-adapter
40+
<si:chain id="chainWithOperation" input-channel="operationWithinChainWithNonNullReturn">
41+
<jmx:operation-invoking-channel-adapter id="operationWithinChainWithNonNullReturnHandler"
4242
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
4343
operation-name="testWithReturn"/>
4444
</si:chain>

0 commit comments

Comments
 (0)