INT-3124 Add JMX MBean Tree Inbound Adapter

Add an MBeanTreePollingMessageSource that produces a graph of simple objects representing the JMX tree (INT-3124).

The DefaultMBeanObjectConverter converts MBean objects into a graph of Lists, Maps and arrays or primitives.

Formatting tidy, per feedback.

Feedback incorporated: split setter/attributes, more tests, inner bean constructor, logging changes.

Overloaded setter methods with different parameter types are now named separately, the endpoint attributes reflect this and tests are added to reflect this.

An inner bean can be supplied to provide an alternative MBeanObjectConverter.

Log a warning instead of a more destructive UnsupportedOperationException where there's incomplete parsing in the DefaultMBeanObjectConverter and add a trace level for exception debugging.

Minor test change and doc/reference update

Added attribute filter interface as suggested. Three implementations are provided 'all', 'named only' and 'not named'

actually add notnamedfield filter (doh).

Polishing
This commit is contained in:
Pid
2013-08-30 12:24:33 +01:00
committed by Gary Russell
parent 35e8929f1c
commit 5212ea13ef
17 changed files with 1347 additions and 2 deletions

View File

@@ -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;
}
}

View File

@@ -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<String, Object> attributeMap = new HashMap<String, Object>();
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<Object> converted = new ArrayList<Object>();
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<String, Object> returnable = new HashMap<String, Object>();
Set<String> 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<Object, Object> returnable = new HashMap<Object, Object>();
@SuppressWarnings("unchecked")
Set<List<?>> keySet = (Set<List<?>>) 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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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<Object> {
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<String, Object> beans = new HashMap<String, Object>();
Set<ObjectInstance> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 <em>jmx</em> 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());

View File

@@ -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();
}
}

View File

@@ -32,6 +32,36 @@
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="tree-polling-channel-adapter">
<xsd:annotation>
<xsd:documentation>
Defines an inbound Channel Adapter that polls for JMX MBeans.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="treeType">
<xsd:sequence minOccurs="0" maxOccurs="1" >
<xsd:choice minOccurs="0" maxOccurs="2" >
<xsd:element ref="integration:poller" />
<xsd:element ref="beans:bean">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.jmx.MBeanObjectConverter" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="operation-invoking-outbound-gateway">
<xsd:annotation>
<xsd:documentation>
@@ -181,6 +211,69 @@
</xsd:complexType>
</xsd:element>
<xsd:complexType name="treeType">
<xsd:annotation>
<xsd:documentation>
Defines inbound operation invoking type
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="mbeanServerIdentifyerType">
<xsd:attribute name="channel" type="xsd:string" />
<xsd:attribute name="query-name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
A string to be parsed into an ObjectName object
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="query-name-ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
A reference to an ObjectName instance
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="javax.management.ObjectName"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="query-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
A string to be parsed into a QueryExp object
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="query-expression-ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
A reference to a QueryExp instance
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="javax.management.QueryExp"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="converter" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
A reference to an MBeanObjectConverter.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="org.springframework.integration.jmx.MBeanObjectConverter" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="adapterType">
<xsd:annotation>
<xsd:documentation>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-jmx="http://www.springframework.org/schema/integration/jmx"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jmx http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<context:mbean-export/>
<context:mbean-server/>
<int-jmx:mbean-export/>
<int-jmx:tree-polling-channel-adapter id="adapter"
channel="in"
query-expression="org.springframework.integration:type=MessageChannel,name=*"
auto-startup="false">
<int:poller max-messages-per-poll="1" fixed-rate="5000"/>
<bean class="org.springframework.integration.jmx.DefaultMBeanObjectConverter">
<constructor-arg>
<bean class="org.springframework.integration.jmx.NamedFieldsMBeanAttributeFilter">
<constructor-arg>
<array>
<value type="java.lang.String">SendCount</value>
<value type="java.lang.String">SendErrorCount</value>
</array>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
</int-jmx:tree-polling-channel-adapter>
<int:channel id="in" />
<int:service-activator id="pass"
input-channel="in"
output-channel="out"
expression="payload">
</int:service-activator>
<int:channel id="out">
<int:queue/>
</int:channel>
<int-jmx:tree-polling-channel-adapter id="adapterNot"
channel="in"
query-expression="org.springframework.integration:type=MessageChannel,name=*"
auto-startup="false">
<int:poller max-messages-per-poll="1" fixed-rate="5000"/>
<bean class="org.springframework.integration.jmx.DefaultMBeanObjectConverter">
<constructor-arg>
<bean class="org.springframework.integration.jmx.NotNamedFieldsMBeanAttributeFilter">
<constructor-arg>
<array>
<value type="java.lang.String">SendCount</value>
<value type="java.lang.String">SendErrorCount</value>
</array>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
</int-jmx:tree-polling-channel-adapter>
</beans>

View File

@@ -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<String, Object> payload = (Map<String, Object>) result.getPayload();
assertEquals(4, payload.size());
@SuppressWarnings("unchecked")
Map<String, Object> bean = (Map<String, Object>) 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<String, Object> payload = (Map<String, Object>) result.getPayload();
assertEquals(4, payload.size());
@SuppressWarnings("unchecked")
Map<String, Object> bean = (Map<String, Object>) payload
.get("org.springframework.integration:name=in,type=MessageChannel");
assertEquals(8, bean.size());
assertFalse(bean.containsKey("SendCount"));
assertFalse(bean.containsKey("SendErrorCount"));
adapterNot.stop();
}
}

View File

@@ -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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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"));
}
}

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:jmx="http://www.springframework.org/schema/integration/jmx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jmx
http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd">
<context:mbean-export/>
<context:mbean-server/>
<jmx:tree-polling-channel-adapter id="adapter-default"
channel="channel1"
auto-startup="false">
<si:poller max-messages-per-poll="1" fixed-rate="5000"/>
</jmx:tree-polling-channel-adapter>
<si:channel id="channel1">
<si:queue/>
</si:channel>
<jmx:tree-polling-channel-adapter id="adapter-inner"
channel="channel2"
auto-startup="false">
<si:poller max-messages-per-poll="1" fixed-rate="5000"/>
<bean class="org.springframework.integration.jmx.DefaultMBeanObjectConverter"></bean>
</jmx:tree-polling-channel-adapter>
<si:channel id="channel2">
<si:queue/>
</si:channel>
<jmx:tree-polling-channel-adapter id="adapter-query-name"
channel="channel3"
auto-startup="false"
query-name="java.lang:type=Runtime"
query-expression="*:type=*">
<si:poller max-messages-per-poll="1" fixed-rate="5000"/>
</jmx:tree-polling-channel-adapter>
<si:channel id="channel3">
<si:queue/>
</si:channel>
<jmx:tree-polling-channel-adapter id="adapter-query-name-bean"
channel="channel4"
auto-startup="false"
query-name-ref="queryName">
<si:poller max-messages-per-poll="1" fixed-rate="5000"/>
</jmx:tree-polling-channel-adapter>
<bean id="queryName" class="javax.management.ObjectName">
<constructor-arg>
<value type="java.lang.String">java.lang:type=OperatingSystem</value>
</constructor-arg>
</bean>
<si:channel id="channel4">
<si:queue/>
</si:channel>
<jmx:tree-polling-channel-adapter id="adapter-query-expr-bean"
channel="channel5"
auto-startup="false"
query-expression-ref="queryExp">
<si:poller max-messages-per-poll="1" fixed-rate="5000"/>
</jmx:tree-polling-channel-adapter>
<bean id="queryExp" class="javax.management.ObjectName">
<constructor-arg>
<value type="java.lang.String">java.lang:type=Runtime</value>
</constructor-arg>
</bean>
<si:channel id="channel5">
<si:queue/>
</si:channel>
<jmx:tree-polling-channel-adapter id="adapter-converter"
channel="channel6" converter="converter"
auto-startup="false">
<si:poller max-messages-per-poll="1" fixed-rate="5000"/>
</jmx:tree-polling-channel-adapter>
<bean id="converter" class="org.springframework.integration.jmx.DefaultMBeanObjectConverter" />
<si:channel id="channel6">
<si:queue/>
</si:channel>
</beans>

View File

@@ -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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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<String, Object> beans = (Map<String, Object>) 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"));
}
}

View File

@@ -137,6 +137,39 @@
</int-jmx:attribute-polling-channel-adapter>]]></programlisting>
</section>
<section id="tree-polling-channel-adapter">
<title>Tree Polling Channel Adapter</title>
<para>
The <emphasis>Tree Polling Channel Adapter</emphasis> 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 <emphasis>mbeanServer</emphasis>
by default, just like the <emphasis>Notification-listening Channel Adapter</emphasis>
described above. A basic configuration would be:
</para>
<programlisting language="xml"><![CDATA[<int-jmx:tree-polling-channel-adapter id="adapter"
channel="channel"
query-name="example.domain:type=*">
<int:poller max-messages-per-poll="1" fixed-rate="5000"/>
</int-jmx:tree-polling-channel-adapter>]]></programlisting>
<para>
This will include all attributes on the MBeans selected. You can filter the attributes by
providing an <interfacename>MBeanObjectConverter</interfacename> that
has an appropriate filter configured. The converter can be provided as a reference to
a bean definition using the <code>converter</code> attribute, or as an
inner &lt;bean/&gt; definition. A <classname>DefaultMBeanObjectConverter</classname> is
provided which can take a <interfacename>MBeanAttributeFilter</interfacename> in
its constructor argument.
</para>
<para>
Two standard filters are provided; the <classname>NamedFieldsMBeanAttributeFilter</classname>
allows you to specify a list of attributes to include and the
<classname>NotNamedFieldsMBeanAttributeFilter</classname> allows you to specify a list
of attributes to exclude. You can also implement your own filter
</para>
</section>
<section id="jmx-operation-invoking-channel-adapter">
<title>Operation Invoking Channel Adapter</title>

View File

@@ -60,6 +60,17 @@
<xref linkend="syslog"/>.
</para>
</section>
<section id="3.0-jmx">
<title>JMX Support</title>
<para>
A new <code>&lt;int-jmx:tree-polling-channel-adapter/&gt;</code> 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
<xref linkend="jmx"/>.
</para>
</section>
<section id="3.0-tail">
<title>'Tail' Support</title>
<para>