Skip to content

Commit c723b69

Browse files
committed
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** # Conflicts: # spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java # spring-integration-jmx/src/test/java/org/springframework/integration/jmx/OperationInvokingMessageHandlerTests.java
1 parent b4a2de1 commit c723b69

File tree

6 files changed

+194
-119
lines changed

6 files changed

+194
-119
lines changed

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

+116-68
Original file line numberDiff line numberDiff line change
@@ -59,36 +59,62 @@
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 objectName;
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) {
89115
try {
90116
if (objectName != null) {
91-
this.objectName = ObjectNameManager.getInstance(objectName);
117+
this.defaultObjectName = ObjectNameManager.getInstance(objectName);
92118
}
93119
}
94120
catch (MalformedObjectNameException e) {
@@ -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
@@ -118,51 +153,20 @@ protected void doInit() {
118153

119154
@Override
120155
protected Object handleRequestMessage(Message<?> requestMessage) {
121-
ObjectName objectName = this.resolveObjectName(requestMessage);
122-
String operationName = this.resolveOperationName(requestMessage);
123-
Map<String, Object> paramsFromMessage = this.resolveParameters(requestMessage);
156+
ObjectName objectName = resolveObjectName(requestMessage);
157+
String operationName = resolveOperationName(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 (operationName.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, operationName, values, signature);
156-
}
157-
}
160+
Object result = invokeOperation(requestMessage, objectName, operationName, 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 '" + operationName + "' result '" + result +
165+
"' for '" + objectName + "' is ignored.");
158166
}
167+
return null;
159168
}
160-
if (hasNoArgOption) {
161-
return this.server.invoke(objectName, operationName, null, null);
162-
}
163-
throw new MessagingException(requestMessage, "failed to find JMX operation '"
164-
+ operationName + "' 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
}
@@ -189,7 +241,7 @@ private boolean valueTypeMatchesParameterType(Object value, MBeanParameterInfo p
189241
* First checks if defaultObjectName is set, otherwise falls back on {@link JmxHeaders#OBJECT_NAME} header.
190242
*/
191243
private ObjectName resolveObjectName(Message<?> message) {
192-
ObjectName objectName = this.objectName;
244+
ObjectName objectName = this.defaultObjectName;
193245
if (objectName == null) {
194246
Object objectNameHeader = message.getHeaders().get(JmxHeaders.OBJECT_NAME);
195247
if (objectNameHeader instanceof ObjectName) {
@@ -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 operationName = this.operationName;
@@ -220,31 +272,27 @@ private String resolveOperationName(Message<?> message) {
220272
return operationName;
221273
}
222274

223-
@SuppressWarnings({ "unchecked", "rawtypes" })
275+
@SuppressWarnings("unchecked")
224276
private Map<String, Object> resolveParameters(Message<?> message) {
225277
Map<String, Object> map = null;
226-
if (message.getPayload() instanceof Map) {
227-
map = (Map<String, Object>) message.getPayload();
228-
}
229-
else if (message.getPayload() instanceof List) {
230-
map = this.createParameterMapFromList((List) message.getPayload());
278+
Object payload = message.getPayload();
279+
if (payload instanceof Map) {
280+
map = (Map<String, Object>) payload;
231281
}
232-
else if (message.getPayload() != null && message.getPayload().getClass().isArray()) {
233-
map = this.createParameterMapFromList(
234-
Arrays.asList(ObjectUtils.toObjectArray(message.getPayload())));
282+
else if (payload instanceof List) {
283+
map = createParameterMapFromList((List<?>) payload);
235284
}
236-
else if (message.getPayload() != null) {
237-
map = this.createParameterMapFromList(Collections.singletonList(message.getPayload()));
285+
else if (payload.getClass().isArray()) {
286+
map = createParameterMapFromList(Arrays.asList(ObjectUtils.toObjectArray(payload)));
238287
}
239288
else {
240-
map = Collections.EMPTY_MAP;
289+
map = createParameterMapFromList(Collections.singletonList(payload));
241290
}
242291
return map;
243292
}
244293

245-
@SuppressWarnings("rawtypes")
246-
private Map<String, Object> createParameterMapFromList(List parameters) {
247-
Map<String, Object> map = new HashMap<String, Object>();
294+
private Map<String, Object> createParameterMapFromList(List<?> parameters) {
295+
Map<String, Object> map = new HashMap<>();
248296
for (int i = 0; i < parameters.size(); i++) {
249297
map.put("p" + (i + 1), parameters.get(i));
250298
}

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

+5-2
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

+3-2
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

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 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.
@@ -73,8 +73,7 @@ public void cleanup() throws Exception {
7373
@Test
7474
public void invocationWithMapPayload() throws Exception {
7575
QueueChannel outputChannel = new QueueChannel();
76-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
77-
handler.setServer(this.server);
76+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
7877
handler.setObjectName(this.objectName);
7978
handler.setOutputChannel(outputChannel);
8079
handler.setOperationName("x");
@@ -93,8 +92,7 @@ public void invocationWithMapPayload() throws Exception {
9392
@Test
9493
public void invocationWithPayloadNoReturnValue() throws Exception {
9594
QueueChannel outputChannel = new QueueChannel();
96-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
97-
handler.setServer(this.server);
95+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
9896
handler.setObjectName(this.objectName);
9997
handler.setOutputChannel(outputChannel);
10098
handler.setOperationName("y");
@@ -107,8 +105,7 @@ public void invocationWithPayloadNoReturnValue() throws Exception {
107105
@Test(expected = MessagingException.class)
108106
public void invocationWithMapPayloadNotEnoughParameters() throws Exception {
109107
QueueChannel outputChannel = new QueueChannel();
110-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
111-
handler.setServer(this.server);
108+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
112109
handler.setObjectName(this.objectName);
113110
handler.setOutputChannel(outputChannel);
114111
handler.setOperationName("x");
@@ -126,8 +123,7 @@ public void invocationWithMapPayloadNotEnoughParameters() throws Exception {
126123
@Test
127124
public void invocationWithListPayload() throws Exception {
128125
QueueChannel outputChannel = new QueueChannel();
129-
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler();
130-
handler.setServer(this.server);
126+
OperationInvokingMessageHandler handler = new OperationInvokingMessageHandler(server);
131127
handler.setObjectName(this.objectName);
132128
handler.setOutputChannel(outputChannel);
133129
handler.setOperationName("x");

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
2121
operation-name="test">
2222
<jmx:request-handler-advice-chain>
23-
<bean class="org.springframework.integration.jmx.config.OperationInvokingChannelAdapterParserTests$FooADvice" />
23+
<bean class="org.springframework.integration.jmx.config.OperationInvokingChannelAdapterParserTests.FooAdvice" />
2424
</jmx:request-handler-advice-chain>
2525
</jmx:operation-invoking-channel-adapter>
2626

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

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

0 commit comments

Comments
 (0)