diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/DefaultMBeanAttributeFilter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/DefaultMBeanAttributeFilter.java new file mode 100644 index 0000000000..d8e0085865 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/DefaultMBeanAttributeFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import javax.management.ObjectName; + +/** + * @author Stuart Williams + * @since 3.0 + * + */ +public class DefaultMBeanAttributeFilter implements MBeanAttributeFilter { + + @Override + public boolean accept(ObjectName objectName, String attributeName) { + return true; + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/DefaultMBeanObjectConverter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/DefaultMBeanObjectConverter.java new file mode 100644 index 0000000000..864b7e5a21 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/DefaultMBeanObjectConverter.java @@ -0,0 +1,192 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanInfo; +import javax.management.MBeanServerConnection; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.RuntimeMBeanException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Stuart Williams + * @since 3.0 + * + */ +public class DefaultMBeanObjectConverter implements MBeanObjectConverter { + + private static final Log log = LogFactory.getLog(DefaultMBeanObjectConverter.class); + + private final MBeanAttributeFilter filter; + + public DefaultMBeanObjectConverter() { + this(new DefaultMBeanAttributeFilter()); + } + + public DefaultMBeanObjectConverter(MBeanAttributeFilter filter) { + this.filter = filter; + } + + @Override + public Object convert(MBeanServerConnection connection, ObjectInstance instance) { + Map attributeMap = new HashMap(); + + try { + ObjectName objName = instance.getObjectName(); + if (!connection.isRegistered(objName)) { + return attributeMap; + } + + MBeanInfo info = connection.getMBeanInfo(objName); + MBeanAttributeInfo[] attributeInfos = info.getAttributes(); + + for (MBeanAttributeInfo attrInfo : attributeInfos) { + // we don't need to repeat name of this as an attribute + if ("ObjectName".equals(attrInfo.getName()) || !filter.accept(objName, attrInfo.getName())) { + continue; + } + + Object value; + try { + value = connection.getAttribute(objName, attrInfo.getName()); + } + catch (RuntimeMBeanException e) { + // N.B. standard MemoryUsage MBeans will throw an exception when some + // measurement is unsupported. Logging at trace rather than debug to + // avoid confusion. + if (log.isTraceEnabled()) { + log.trace("Error getting attribute '" + attrInfo.getName() + "' on '" + objName + "'", e); + } + + // try to unwrap the exception somewhat; not sure this is ideal + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + } + value = String.format("%s[%s]", t.getClass().getName(), t.getMessage()); + } + + attributeMap.put(attrInfo.getName(), checkAndConvert(value)); + } + + } + catch (Exception e) { + throw new IllegalArgumentException(e); + } + + return attributeMap; + } + + /** + * @param input + * @return recursively mapped object + */ + private Object checkAndConvert(Object input) { + if (input == null) { + return input; + } + else if (input.getClass().isArray()) { + + if (CompositeData.class.isAssignableFrom(input.getClass().getComponentType())) { + List converted = new ArrayList(); + int length = Array.getLength(input); + for (int i = 0; i < length; i++) { + Object value = checkAndConvert(Array.get(input, i)); + converted.add(value); + } + return converted; + } + if (TabularData.class.isAssignableFrom(input.getClass().getComponentType())) { + // TODO haven't hit this yet, but expect to + log.warn("TabularData.isAssignableFrom(getComponentType) for " + input.toString()); + } + } + else if (input instanceof CompositeData) { + CompositeData data = (CompositeData) input; + + if (data.getCompositeType().isArray()) { + // TODO? I haven't found an example where this gets thrown - but need to test it on Tomcat/Jetty or + // something + log.warn("(data.getCompositeType().isArray for " + input.toString()); + } + else { + Map returnable = new HashMap(); + Set keys = data.getCompositeType().keySet(); + for (String key : keys) { + // we don't need to repeat name of this as an attribute + if ("ObjectName".equals(key)) { + continue; + } + Object value = checkAndConvert(data.get(key)); + returnable.put(key, value); + } + return returnable; + } + } + else if (input instanceof TabularData) { + TabularData data = (TabularData) input; + + if (data.getTabularType().isArray()) { + // TODO? I haven't found an example where this gets thrown, so might not be required + log.warn("TabularData.isArray for " + input.toString()); + } + else { + + Map returnable = new HashMap(); + @SuppressWarnings("unchecked") + Set> keySet = (Set>) data.keySet(); + for (List keys : keySet) { + CompositeData cd = data.get(keys.toArray()); + Object value = checkAndConvert(cd); + + if (keys.size() == 1 && (value instanceof Map) && ((Map) value).size() == 2) { + + Object actualKey = keys.get(0); + Map valueMap = (Map) value; + + if (valueMap.containsKey("key") && valueMap.containsKey("value") + && actualKey.equals(valueMap.get("key"))) { + returnable.put(valueMap.get("key"), valueMap.get("value")); + } + else { + returnable.put(actualKey, value); + } + } + else { + returnable.put(keys, value); + } + } + + return returnable; + } + } + + return input; + } +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanAttributeFilter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanAttributeFilter.java new file mode 100644 index 0000000000..bb295c9d24 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanAttributeFilter.java @@ -0,0 +1,34 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import javax.management.ObjectName; + +/** + * @author Stuart Williams + * @since 3.0 + * + */ +public interface MBeanAttributeFilter { + + /** + * @param objectName + * @param attributeName + * @return outcome of test + */ + boolean accept(ObjectName objectName, String attributeName); + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanObjectConverter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanObjectConverter.java new file mode 100644 index 0000000000..348ec1c135 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanObjectConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import javax.management.MBeanServerConnection; +import javax.management.ObjectInstance; + + +/** + * @author Stuart Williams + * @since 3.0 + * + */ +public interface MBeanObjectConverter { + + /** + * @param connection + * @param instance + * @return mapped object instance + */ + Object convert(MBeanServerConnection connection, ObjectInstance instance); + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanTreePollingMessageSource.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanTreePollingMessageSource.java new file mode 100644 index 0000000000..837e0df590 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/MBeanTreePollingMessageSource.java @@ -0,0 +1,127 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.management.MBeanServerConnection; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.QueryExp; + +import org.springframework.integration.MessagingException; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.endpoint.AbstractMessageSource; +import org.springframework.util.Assert; + +/** + * A {@link MessageSource} implementation that retrieves a snapshot of a filtered subset of the MBean tree. + * + * @author Stuart Williams + * @since 3.0 + * + */ +public class MBeanTreePollingMessageSource extends AbstractMessageSource { + + private volatile MBeanServerConnection server; + + private volatile ObjectName queryName = null; + + private volatile QueryExp queryExpression = ObjectName.WILDCARD; + + private final MBeanObjectConverter converter; + + /** + * @param converter + */ + public MBeanTreePollingMessageSource(MBeanObjectConverter converter) { + this.converter = converter; + } + + /** + * Provides the mapped tree object + */ + @Override + protected Object doReceive() { + Assert.notNull(this.server, "MBeanServer is required"); + + try { + Map beans = new HashMap(); + Set results = server.queryMBeans(queryName, queryExpression); + + for (ObjectInstance instance : results) { + Object result = converter.convert(server, instance); + beans.put(instance.getObjectName().getCanonicalName(), result); + } + + return beans; + + } + catch (Exception e) { + throw new MessagingException("Failed to retrieve tree snapshot", e); + } + } + + /** + * Provide the MBeanServer where the JMX MBean has been registered. + */ + public void setServer(MBeanServerConnection server) { + this.server = server; + } + + /** + * @param queryName + */ + public void setQueryName(String queryName) { + Assert.notNull(queryName, "'queryName' must not be null"); + try { + setQueryNameReference(ObjectName.getInstance(queryName)); + } + catch (MalformedObjectNameException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * @param queryName + */ + public void setQueryNameReference(ObjectName queryName) { + this.queryName = queryName; + } + + /** + * @param queryExpression + */ + public void setQueryExpression(String queryExpression) { + Assert.notNull(queryExpression, "'queryExpression' must not be null"); + try { + setQueryExpressionReference(ObjectName.getInstance(queryExpression)); + } + catch (MalformedObjectNameException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * @param queryExpression + */ + public void setQueryExpressionReference(QueryExp queryExpression) { + this.queryExpression = queryExpression; + } +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NamedFieldsMBeanAttributeFilter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NamedFieldsMBeanAttributeFilter.java new file mode 100644 index 0000000000..f7e045442f --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NamedFieldsMBeanAttributeFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import java.util.Arrays; + +import javax.management.ObjectName; + +/** + * @author Stuart Williams + * @since 3.0 + * + */ +public class NamedFieldsMBeanAttributeFilter implements MBeanAttributeFilter { + + private final String[] namedFields; + + /** + * @param namedFields + */ + public NamedFieldsMBeanAttributeFilter(String... namedFields) { + this.namedFields = (String[]) Arrays.asList(namedFields).toArray(); + } + + @Override + public boolean accept(ObjectName objectName, String attributeName) { + for (String namedField : namedFields) { + if (namedField.equals(attributeName)) { + return true; + } + } + return false; + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotNamedFieldsMBeanAttributeFilter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotNamedFieldsMBeanAttributeFilter.java new file mode 100644 index 0000000000..de31d41488 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/NotNamedFieldsMBeanAttributeFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import java.util.Arrays; + +import javax.management.ObjectName; + +/** + * @author Stuart Williams + * @since 3.0 + * + */ +public class NotNamedFieldsMBeanAttributeFilter implements MBeanAttributeFilter { + + private final String[] namedFields; + + /** + * @param namedFields + */ + public NotNamedFieldsMBeanAttributeFilter(String... namedFields) { + this.namedFields = (String[]) Arrays.asList(namedFields).toArray(); + } + + @Override + public boolean accept(ObjectName objectName, String attributeName) { + for (String namedField : namedFields) { + if (namedField.equals(attributeName)) { + return false; + } + } + return true; + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java index 3d498e3924..45d05c69f4 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.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. @@ -20,10 +20,11 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa /** * Namespace handler for Spring Integration's jmx namespace. - * + * * @author Mark Fisher * @author Oleg Zhurakousky * @author Dave Syer + * @author Stuart Williams * @since 2.0 */ public class JmxNamespaceHandler extends AbstractIntegrationNamespaceHandler { @@ -32,6 +33,7 @@ public class JmxNamespaceHandler extends AbstractIntegrationNamespaceHandler { this.registerBeanDefinitionParser("operation-invoking-channel-adapter", new OperationInvokingChannelAdapterParser()); this.registerBeanDefinitionParser("operation-invoking-outbound-gateway", new OperationInvokingOutboundGatewayParser()); this.registerBeanDefinitionParser("attribute-polling-channel-adapter", new AttributePollingChannelAdapterParser()); + this.registerBeanDefinitionParser("tree-polling-channel-adapter", new MBeanTreePollingChannelAdapterParser()); this.registerBeanDefinitionParser("notification-listening-channel-adapter", new NotificationListeningChannelAdapterParser()); this.registerBeanDefinitionParser("notification-publishing-channel-adapter", new NotificationPublishingChannelAdapterParser()); this.registerBeanDefinitionParser("mbean-export", new MBeanExporterParser()); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParser.java new file mode 100644 index 0000000000..305da25ed2 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParser.java @@ -0,0 +1,77 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.jmx.DefaultMBeanObjectConverter; +import org.springframework.integration.jmx.MBeanTreePollingMessageSource; +import org.springframework.util.StringUtils; + + +/** + * @author Stuart Williams + * @author Gary Russell + * @since 3.0 + * + */ +public class MBeanTreePollingChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( + MBeanTreePollingMessageSource.class); + + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server", "server"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "query-name-ref", "queryNameReference"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "query-expression-ref", "queryExpressionReference"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "query-name", "queryName"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "query-expression", "queryExpression"); + + BeanComponentDefinition innerBeanDef = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext); + String beanName = element.getAttribute("converter"); + + if (innerBeanDef != null) { + if (StringUtils.hasText(beanName)) { + parserContext.getReaderContext().error("Cannot have both a 'converter' and an inner bean", element); + } + beanName = BeanDefinitionReaderUtils.generateBeanName(innerBeanDef.getBeanDefinition(), parserContext.getRegistry(), true); + parserContext.getRegistry().registerBeanDefinition(beanName, innerBeanDef.getBeanDefinition()); + } + else if (!StringUtils.hasText(beanName)) { + BeanDefinitionBuilder childBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultMBeanObjectConverter.class); + beanName = BeanDefinitionReaderUtils.generateBeanName(childBuilder.getBeanDefinition(), parserContext.getRegistry(), true); + parserContext.getRegistry().registerBeanDefinition(beanName, childBuilder.getBeanDefinition()); + } + + builder.addConstructorArgReference(beanName); + + return builder.getBeanDefinition(); + } + +} diff --git a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-3.0.xsd b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-3.0.xsd index ceaae8ef8b..65b2ff0920 100644 --- a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-3.0.xsd +++ b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-3.0.xsd @@ -32,6 +32,36 @@ + + + + + Defines an inbound Channel Adapter that polls for JMX MBeans. + + + + + + + + + + + + + + + + + + + + + + + + + @@ -181,6 +211,69 @@ + + + + Defines inbound operation invoking type + + + + + + + + + A string to be parsed into an ObjectName object + + + + + + + A reference to an ObjectName instance + + + + + + + + + + + + A string to be parsed into a QueryExp object + + + + + + + A reference to a QueryExp instance + + + + + + + + + + + + A reference to an MBeanObjectConverter. + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanAttributeFilterTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanAttributeFilterTests-context.xml new file mode 100644 index 0000000000..0a3cc9b813 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanAttributeFilterTests-context.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + SendCount + SendErrorCount + + + + + + + + + + + + + + + + + + + + + + + + SendCount + SendErrorCount + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanAttributeFilterTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanAttributeFilterTests.java new file mode 100644 index 0000000000..1fa4f09a3d --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanAttributeFilterTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +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.core.PollableChannel; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Stuart Williams + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class MBeanAttributeFilterTests { + + @Autowired + @Qualifier("out") + private PollableChannel channel; + + @Autowired + private SourcePollingChannelAdapter adapter; + + @Autowired + private SourcePollingChannelAdapter adapterNot; + + private final long testTimeout = 1000L; + + @Test + public void testAttributeFilter() { + while (channel.receive(0) != null) { + ; + } + adapter.start(); + + Message result = channel.receive(testTimeout); + assertNotNull(result); + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map payload = (Map) result.getPayload(); + assertEquals(4, payload.size()); + + @SuppressWarnings("unchecked") + Map bean = (Map) payload + .get("org.springframework.integration:name=in,type=MessageChannel"); + + assertEquals(2, bean.size()); + assertTrue(bean.containsKey("SendCount")); + assertTrue(bean.containsKey("SendErrorCount")); + + adapter.stop(); + } + + @Test + public void testAttributeFilterNot() { + while (channel.receive(0) != null) { + ; + } + adapterNot.start(); + + Message result = channel.receive(testTimeout); + assertNotNull(result); + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map payload = (Map) result.getPayload(); + assertEquals(4, payload.size()); + + @SuppressWarnings("unchecked") + Map bean = (Map) payload + .get("org.springframework.integration:name=in,type=MessageChannel"); + + assertEquals(8, bean.size()); + assertFalse(bean.containsKey("SendCount")); + assertFalse(bean.containsKey("SendErrorCount")); + + adapterNot.stop(); + } + +} diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanTreePollingMessageSourceTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanTreePollingMessageSourceTests.java new file mode 100644 index 0000000000..586b2fa0c8 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/MBeanTreePollingMessageSourceTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import javax.management.MBeanServer; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.jmx.support.MBeanServerFactoryBean; + +/** + * @author Stuart Williams + * + */ +public class MBeanTreePollingMessageSourceTests { + + private MBeanServer server; + + @Before + public void setup() { + MBeanServerFactoryBean factoryBean = new MBeanServerFactoryBean(); + factoryBean.setLocateExistingServerIfPossible(true); + factoryBean.afterPropertiesSet(); + this.server = factoryBean.getObject(); + } + + @Test + public void testDefaultPoll() { + + MBeanObjectConverter converter = new DefaultMBeanObjectConverter(); + MBeanTreePollingMessageSource source = new MBeanTreePollingMessageSource(converter); + source.setServer(server); + + Object received = source.doReceive(); + + assertEquals(HashMap.class, received.getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) received; + + // test for a couple of MBeans + assertTrue(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + } + + @Test + public void testQueryNameFilteredPoll() { + MBeanObjectConverter converter = new DefaultMBeanObjectConverter(); + MBeanTreePollingMessageSource source = new MBeanTreePollingMessageSource(converter); + source.setServer(server); + source.setQueryName("java.lang:*"); + + Object received = source.doReceive(); + + assertEquals(HashMap.class, received.getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) received; + + // test for a few MBeans + assertTrue(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + assertFalse(beans.containsKey("java.util.logging:type=Logging")); + } + + @Test + public void testQueryExpressionFilteredPoll() { + MBeanObjectConverter converter = new DefaultMBeanObjectConverter(); + MBeanTreePollingMessageSource source = new MBeanTreePollingMessageSource(converter); + source.setServer(server); + source.setQueryExpression("*:type=Logging"); + + Object received = source.doReceive(); + + assertEquals(HashMap.class, received.getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) received; + + // test for a few MBeans + assertFalse(beans.containsKey("java.lang:type=OperatingSystem")); + assertFalse(beans.containsKey("java.lang:type=Runtime")); + assertTrue(beans.containsKey("java.util.logging:type=Logging")); + } + +} diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests-context.xml new file mode 100644 index 0000000000..4594f50722 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests-context.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + java.lang:type=OperatingSystem + + + + + + + + + + + + + + java.lang:type=Runtime + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java new file mode 100644 index 0000000000..2cd2531585 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanTreePollingChannelAdapterParserTests.java @@ -0,0 +1,230 @@ +/* + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.jmx.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.QueryExp; + +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.core.PollableChannel; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.jmx.MBeanObjectConverter; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Stuart Williams + * @author Gary Russell + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class MBeanTreePollingChannelAdapterParserTests { + + @Autowired + @Qualifier("channel1") + private PollableChannel channel1; + + @Autowired + @Qualifier("channel2") + private PollableChannel channel2; + + @Autowired + @Qualifier("channel3") + private PollableChannel channel3; + + @Autowired + @Qualifier("channel4") + private PollableChannel channel4; + + @Autowired + @Qualifier("channel5") + private PollableChannel channel5; + + @Autowired + @Qualifier("channel6") + private PollableChannel channel6; + + @Autowired + @Qualifier("adapter-default") + private SourcePollingChannelAdapter adapterDefault; + + @Autowired + @Qualifier("adapter-inner") + private SourcePollingChannelAdapter adapterInner; + + @Autowired + @Qualifier("adapter-query-name") + private SourcePollingChannelAdapter adapterQueryName; + + @Autowired + @Qualifier("adapter-query-name-bean") + private SourcePollingChannelAdapter adapterQueryNameBean; + + @Autowired + @Qualifier("adapter-query-expr-bean") + private SourcePollingChannelAdapter adapterQueryExprBean; + + @Autowired + @Qualifier("adapter-converter") + private SourcePollingChannelAdapter adapterConverter; + + @Autowired + private MBeanObjectConverter converter; + + @Autowired + private MBeanServer mbeanServer; + + private final long testTimeout = 2000L; + + @Test + public void pollDefaultAdapter() throws Exception { + adapterDefault.start(); + + Message result = channel1.receive(testTimeout); + assertNotNull(result); + + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) result.getPayload(); + + // test for a couple of MBeans + assertTrue(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + + adapterDefault.stop(); + } + + @Test + public void pollInnerAdapter() throws Exception { + adapterInner.start(); + + Message result = channel2.receive(testTimeout); + assertNotNull(result); + + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) result.getPayload(); + + // test for a couple of MBeans + assertTrue(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + + adapterDefault.stop(); + } + + @Test + public void pollQueryNameAdapter() throws Exception { + adapterQueryName.start(); + + ObjectName queryName = TestUtils.getPropertyValue(adapterQueryName, "source.queryName", ObjectName.class); + assertEquals("java.lang:type=Runtime", queryName.getCanonicalName()); + + QueryExp queryExp = TestUtils.getPropertyValue(adapterQueryName, "source.queryExpression", QueryExp.class); + assertTrue(queryExp.apply(new ObjectName("java.lang:type=Runtime"))); + + Message result = channel3.receive(testTimeout); + assertNotNull(result); + + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) result.getPayload(); + + // test for a couple of MBeans + assertFalse(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + + adapterDefault.stop(); + } + + @Test + public void pollQueryNameBeanAdapter() throws Exception { + adapterQueryNameBean.start(); + + Message result = channel4.receive(testTimeout); + assertNotNull(result); + + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) result.getPayload(); + + // test for a couple of MBeans + assertTrue(beans.containsKey("java.lang:type=OperatingSystem")); + assertFalse(beans.containsKey("java.lang:type=Runtime")); + + adapterDefault.stop(); + } + + @Test + public void pollQueryExprBeanAdapter() throws Exception { + adapterQueryExprBean.start(); + + Message result = channel5.receive(testTimeout); + assertNotNull(result); + + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) result.getPayload(); + + // test for a couple of MBeans + assertFalse(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + + adapterDefault.stop(); + } + + @Test + public void pollConverterAdapter() throws Exception { + adapterConverter.start(); + + Message result = channel6.receive(testTimeout); + assertNotNull(result); + + assertEquals(HashMap.class, result.getPayload().getClass()); + + @SuppressWarnings("unchecked") + Map beans = (Map) result.getPayload(); + + // test for a couple of MBeans + assertTrue(beans.containsKey("java.lang:type=OperatingSystem")); + assertTrue(beans.containsKey("java.lang:type=Runtime")); + + adapterDefault.stop(); + assertSame(converter, TestUtils.getPropertyValue(adapterConverter, "source.converter")); + } + +} diff --git a/src/reference/docbook/jmx.xml b/src/reference/docbook/jmx.xml index 00230f8b1a..b41b366eda 100644 --- a/src/reference/docbook/jmx.xml +++ b/src/reference/docbook/jmx.xml @@ -137,6 +137,39 @@ ]]> +
+ Tree Polling Channel Adapter + + The Tree Polling Channel Adapter queries the JMX MBean tree and + sends a message with a payload that is the graph of objects that matches the query. By + default the MBeans are mapped to primitives and simple Objects like Map, List and arrays - + permitting simple transformation, for example, to JSON. An MBeanServer reference is also + required, but it will automatically check for a bean named mbeanServer + by default, just like the Notification-listening Channel Adapter + described above. A basic configuration would be: + + + +]]> + + This will include all attributes on the MBeans selected. You can filter the attributes by + providing an MBeanObjectConverter that + has an appropriate filter configured. The converter can be provided as a reference to + a bean definition using the converter attribute, or as an + inner <bean/> definition. A DefaultMBeanObjectConverter is + provided which can take a MBeanAttributeFilter in + its constructor argument. + + + Two standard filters are provided; the NamedFieldsMBeanAttributeFilter + allows you to specify a list of attributes to include and the + NotNamedFieldsMBeanAttributeFilter allows you to specify a list + of attributes to exclude. You can also implement your own filter + +
+
Operation Invoking Channel Adapter diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index b9c512e73b..800456cd6e 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -60,6 +60,17 @@ .
+
+ JMX Support + + A new <int-jmx:tree-polling-channel-adapter/> is provided; this + adapter queries the JMX MBean tree and sends a message with a payload that is the + graph of objects that matches the query. By default the MBeans are mapped to + primitives and simple Objects like Map, List and arrays - permitting simple + transformation, for example, to JSON + . + +
'Tail' Support