diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/ClassUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/util/ClassUtils.java index 5f2140e975..9558a76eb7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/ClassUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/ClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.integration.util; +import java.util.HashMap; +import java.util.Map; import java.util.Set; /** @@ -24,6 +26,24 @@ import java.util.Set; */ public abstract class ClassUtils { + /** + * Map with primitive wrapper type as key and corresponding primitive + * type as value, for example: Integer.class -> int.class. + */ + private static final Map, Class> primitiveWrapperTypeMap = new HashMap, Class>(8); + + + static { + primitiveWrapperTypeMap.put(Boolean.class, boolean.class); + primitiveWrapperTypeMap.put(Byte.class, byte.class); + primitiveWrapperTypeMap.put(Character.class, char.class); + primitiveWrapperTypeMap.put(Double.class, double.class); + primitiveWrapperTypeMap.put(Float.class, float.class); + primitiveWrapperTypeMap.put(Integer.class, int.class); + primitiveWrapperTypeMap.put(Long.class, long.class); + primitiveWrapperTypeMap.put(Short.class, short.class); + } + public static Class findClosestMatch(Class type, Set> candidates, boolean failOnTie) { int minTypeDiffWeight = Integer.MAX_VALUE; Class closestMatch = null; @@ -35,7 +55,7 @@ public abstract class ClassUtils { } else if (failOnTie && typeDiffWeight < Integer.MAX_VALUE && (typeDiffWeight == minTypeDiffWeight)) { throw new IllegalStateException("Unresolvable ambiguity while attempting to find closest match for [" + - type.getName() + "]. Candidate types [" + closestMatch.getName() + "] and [" + candidate.getName() + + type.getName() + "]. Candidate types [" + closestMatch.getName() + "] and [" + candidate.getName() + "] have equal weight."); } } @@ -67,4 +87,14 @@ public abstract class ClassUtils { return result; } + /** + * Resolve the given class if it is a primitive wrapper class, + * returning the corresponding primitive type instead. + * @param clazz the wrapper class to check + * @return the corresponding primitive if the clazz is a wrapper, otherwise null + */ + public static Class resolvePrimitiveType(Class clazz) { + return primitiveWrapperTypeMap.get(clazz); + } + } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java index 8f5c8ecc5b..f82f37257d 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java @@ -37,6 +37,7 @@ import org.springframework.integration.MessageHandlingException; import org.springframework.integration.MessagingException; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.util.ClassUtils; import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -69,7 +70,6 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa private volatile String operationName; - /** * Provide a reference to the MBeanServer within which the MBean * target for operation invocation has been registered. @@ -135,7 +135,7 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa */ value = paramsFromMessage.get("p" + (index + 1)); } - if (value != null && value.getClass().getName().equals(paramInfo.getType())) { + if (value != null && valueTypeMatchesParameterType(value, paramInfo)) { values[index] = value; signature[index] = paramInfo.getType(); index++; @@ -152,18 +152,29 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa } throw new MessagingException(requestMessage, "failed to find JMX operation '" + operationName + "' on MBean [" + objectName + "] of type [" + mbeanInfo.getClassName() - + "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet()); + + "] with " + paramsFromMessage.size() + " parameters: " + paramsFromMessage); } catch (JMException e) { throw new MessageHandlingException(requestMessage, "failed to invoke JMX operation '" + operationName + "' on MBean [" + objectName + "]" + " with " + - paramsFromMessage.size() + " parameters: " + paramsFromMessage.keySet(), e); + paramsFromMessage.size() + " parameters: " + paramsFromMessage, e); } catch (IOException e) { throw new MessageHandlingException(requestMessage, "IOException on MBeanServerConnection", e); } } + private boolean valueTypeMatchesParameterType(Object value, MBeanParameterInfo paramInfo) { + Class valueClass = value.getClass(); + if (valueClass.getName().equals(paramInfo.getType())) { + return true; + } + else { + Class primitiveType = ClassUtils.resolvePrimitiveType(valueClass); + return primitiveType != null && primitiveType.getName().equals(paramInfo.getType()); + } + } + /** * First checks if defaultObjectName is set, otherwise falls back on {@link JmxHeaders#OBJECT_NAME} header. */ diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml index f412ab7bd6..e9e3dd8e8e 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml @@ -33,6 +33,14 @@ + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests.java index 618b54175c..113b68f3c3 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests.java @@ -17,17 +17,25 @@ package org.springframework.integration.jmx.config; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessagingException; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; @@ -38,9 +46,11 @@ import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + /** * @author Oleg Zhurakousky * @author Artem Bilan + * @author Gary Russell * */ @ContextConfiguration @@ -48,15 +58,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class OperationInvokingOutboundGatewayTests { @Autowired - @Qualifier("withReplyChannel") private MessageChannel withReplyChannel; @Autowired - @Qualifier("withReplyChannelOutput") + private MessageChannel primitiveChannel; + + @Autowired private PollableChannel withReplyChannelOutput; @Autowired - @Qualifier("withNoReplyChannel") private MessageChannel withNoReplyChannel; @Autowired @@ -89,6 +99,42 @@ public class OperationInvokingOutboundGatewayTests { assertEquals(3, adviceCalled); } + @Test + public void gatewayWithPrimitiveArgs() throws Exception { + primitiveChannel.send(new GenericMessage(new Object[] { true, 0L, 1 })); + assertEquals(1, testBean.messages.size()); + List argList = new ArrayList(); + argList.add(false); + argList.add(123L); + argList.add(42); + primitiveChannel.send(new GenericMessage>(argList)); + assertEquals(2, testBean.messages.size()); + Map argMap = new HashMap(); + argMap.put("p1", true); + argMap.put("p2", 0L); + argMap.put("p3", 42); + primitiveChannel.send(new GenericMessage>(argMap)); + assertEquals(3, testBean.messages.size()); + argMap.put("p2", true); + argMap.put("p1", 0L); + argMap.put("p3", 42); + try { + primitiveChannel.send(new GenericMessage>(argMap)); + fail("Expected Exception"); + } + catch (Exception e) { + assertThat(e, Matchers.instanceOf(MessagingException.class)); + assertThat(e.getMessage(), Matchers.containsString("failed to find JMX operation")); + } + // TODO: Uncomment when Spring Framework minimum is 3.2.3 +// argMap = new HashMap(); +// argMap.put("bool", true); +// argMap.put("time", 0L); +// argMap.put("foo", 42); +// primitiveChannel.send(new GenericMessage>(argMap)); +// assertEquals(4, testBean.messages.size()); + } + @Test public void gatewayWithNoReplyChannel() throws Exception { withNoReplyChannel.send(new GenericMessage("1")); @@ -101,7 +147,7 @@ public class OperationInvokingOutboundGatewayTests { @Test //INT-1029, INT-2822 public void testOutboundGatewayInsideChain() throws Exception { - List handlers = TestUtils.getPropertyValue(this.operationInvokingWithinChain, "handlers", List.class); + List handlers = TestUtils.getPropertyValue(this.operationInvokingWithinChain, "handlers", List.class); assertEquals(1, handlers.size()); Object handler = handlers.get(0); assertTrue(handler instanceof OperationInvokingMessageHandler); diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestBean.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestBean.java index fa34a4a236..e492db08f9 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestBean.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/TestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.jmx.export.annotation.ManagedResource; /** * @author Mark Fisher * @author Oleg Zhurakousky + * @author Gary Russell * @since 2.0 */ @ManagedResource @@ -42,11 +43,16 @@ public class TestBean { public void test(String text) { this.messages.add(text); } - + @ManagedOperation public List testWithReturn(String text) { this.messages.add(text); return messages; } + @ManagedOperation + public void testPrimitiveArgs(boolean bool, long time, int foo) { + this.messages.add(bool + " " + time + " " + foo); + } + }