diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
index d12420ad35..10b4e076d3 100644
--- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
+++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import javax.lang.model.SourceVersion;
import javax.management.DynamicMBean;
import javax.management.JMException;
import javax.management.ObjectName;
@@ -745,28 +746,43 @@ public class IntegrationMBeanExporter extends MBeanExporter implements Applicati
}
private String getChannelBeanKey(String channel) {
- String name = "" + channel;
- if (name.startsWith("org.springframework.integration")) {
- name = name + ",source=anonymous";
+ String extra = "";
+ if (channel.startsWith("org.springframework.integration")) {
+ extra = ",source=anonymous";
}
- return String.format(this.domain + ":type=MessageChannel,name=%s" + getStaticNames(), name);
+ return String.format(this.domain + ":type=MessageChannel,name=%s%s" + getStaticNames(),
+ quoteIfNecessary(channel), extra);
}
private String getHandlerBeanKey(MessageHandlerMetrics handler) {
// This ordering of keys seems to work with default settings of JConsole
return String.format(this.domain + ":type=MessageHandler,name=%s,bean=%s" + getStaticNames(),
- handler.getManagedName(), handler.getManagedType());
+ quoteIfNecessary(handler.getManagedName()), quoteIfNecessary(handler.getManagedType()));
}
private String getSourceBeanKey(MessageSourceMetrics source) {
// This ordering of keys seems to work with default settings of JConsole
return String.format(this.domain + ":type=MessageSource,name=%s,bean=%s" + getStaticNames(),
- source.getManagedName(), source.getManagedType());
+ quoteIfNecessary(source.getManagedName()), quoteIfNecessary(source.getManagedType()));
}
private String getEndpointBeanKey(AbstractEndpoint endpoint, String name, String source) {
// This ordering of keys seems to work with default settings of JConsole
- return String.format(this.domain + ":type=ManagedEndpoint,name=%s,bean=%s" + getStaticNames(), name, source);
+ return String.format(this.domain + ":type=ManagedEndpoint,name=%s,bean=%s" + getStaticNames(),
+ quoteIfNecessary(name), source);
+ }
+
+ /*
+ * https://www.oracle.com/technetwork/java/javase/tech/best-practices-jsp-136021.html
+ *
+ * The set of characters in a value is also limited. If special characters may
+ * occur, it is recommended that the value be quoted, using ObjectName.quote. If
+ * the value for a given key is sometimes quoted, then it should always be quoted.
+ * By default, if a value is a string (rather than a number, say), then it should
+ * be quoted unless you are sure that it will never contain special characters.
+ */
+ private String quoteIfNecessary(String name) {
+ return SourceVersion.isName(name) ? name : ObjectName.quote(name);
}
private String getStaticNames() {
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml
index bbf9a99c1e..fd148ede37 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml
@@ -23,6 +23,8 @@
+
+
names = server
- .queryNames(new ObjectName("*:type=MessageHandler," + "name=chainWithJmxNotificationPublishing$child."
- + "jmx-notification-publishing-channel-adapter-within-chain,*"), null);
+ .queryNames(new ObjectName("*:type=MessageHandler," + "name=\"chainWithJmxNotificationPublishing$child."
+ + "jmx-notification-publishing-channel-adapter-within-chain\",*"), null);
+ assertEquals(1, names.size());
+ names = server
+ .queryNames(new ObjectName("*:type=MessageChannel,"
+ + "name=org.springframework.integration.test.anon,source=anonymous,*"), null);
assertEquals(1, names.size());
}
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/MessagingGatewaySupportRegistrationTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/MessagingGatewaySupportRegistrationTests.java
index 870c931af3..83deedfb68 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/MessagingGatewaySupportRegistrationTests.java
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/MessagingGatewaySupportRegistrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2016 the original author or authors.
+ * Copyright 2015-2018 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.
@@ -60,7 +60,7 @@ public class MessagingGatewaySupportRegistrationTests {
names = this.server.queryNames(new ObjectName("org.springframework.integration:*,type=MessageSource,name=foo"),
null);
assertEquals(1, names.size());
- names = this.server.queryNames(new ObjectName("org.springframework.integration:*,name=foo#2"), null);
+ names = this.server.queryNames(new ObjectName("org.springframework.integration:*,name=\"foo#2\""), null);
assertEquals(1, names.size());
}
diff --git a/src/reference/asciidoc/jmx.adoc b/src/reference/asciidoc/jmx.adoc
index 8f6da23496..9c503a690b 100644
--- a/src/reference/asciidoc/jmx.adoc
+++ b/src/reference/asciidoc/jmx.adoc
@@ -332,6 +332,8 @@ The exporter propagates the `default-domain` to that object to let it generate a
If your custom naming strategy is a `MetadataNamingStrategy` (or a subclass of it), the exporter does not propagate the `default-domain`.
You must configure it on your strategy bean.
+Starting with version 5.1; any bean names (represented by the `name` key in the object name) will be quoted if they contain any characters that are not allowed in a Java identifier (or period `.`).
+
[[jmx-42-improvements]]
===== JMX Improvements
diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc
index 07ac4d80e3..f6f5d17f49 100644
--- a/src/reference/asciidoc/whats-new.adoc
+++ b/src/reference/asciidoc/whats-new.adoc
@@ -182,3 +182,11 @@ See <> for more information.
The `statusCodeExpression` (and `Function`) is now supplied with the `RequestEntity>` as a root object for evaluation context, so request headers, method, URI and body are available for target status code calculation.
See <> and <> for more information.
+
+[[x51.-jmx]]
+=== JMX Changes
+
+Object name key values are now quoted if they contain any characters other than those allowed in a Java identifier (or period `.`).
+e.g. `org.springframework.integration:type=MessageChannel,name="input:foo.myGroup.errors"`.
+This has the side effect that previously "allowed" names, with such characters, will now be quoted.
+e.g. `org.springframework.integration:type=MessageChannel,name="input#foo.myGroup.errors"`.