From 61be452402938090b104ba35501021012610d95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 28 Nov 2023 17:02:09 +0100 Subject: [PATCH] Quote name attribute if necessary This commit updates MetadataNamingStrategy to quote an ObjectName attribute value if necessary. For now, only the name attribute is handled as it is usually a bean name, and we have no control over its structure. Closes gh-23608 --- .../export/naming/MetadataNamingStrategy.java | 20 +++- .../naming/MetadataNamingStrategyTests.java | 96 +++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/jmx/export/naming/MetadataNamingStrategyTests.java diff --git a/spring-context/src/main/java/org/springframework/jmx/export/naming/MetadataNamingStrategy.java b/spring-context/src/main/java/org/springframework/jmx/export/naming/MetadataNamingStrategy.java index c0a2c4d875e6..ea4792a14b51 100644 --- a/spring-context/src/main/java/org/springframework/jmx/export/naming/MetadataNamingStrategy.java +++ b/spring-context/src/main/java/org/springframework/jmx/export/naming/MetadataNamingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -50,6 +50,9 @@ */ public class MetadataNamingStrategy implements ObjectNamingStrategy, InitializingBean { + private static final char[] QUOTABLE_CHARS = new char[] {',', '=', ':', '"'}; + + /** * The {@code JmxAttributeSource} implementation to use for reading metadata. */ @@ -132,10 +135,23 @@ public ObjectName getObjectName(Object managedBean, @Nullable String beanKey) th } Hashtable properties = new Hashtable<>(); properties.put("type", ClassUtils.getShortName(managedClass)); - properties.put("name", beanKey); + properties.put("name", quoteIfNecessary(beanKey)); return ObjectNameManager.getInstance(domain, properties); } } } + private static String quoteIfNecessary(String value) { + return shouldQuote(value) ? ObjectName.quote(value) : value; + } + + private static boolean shouldQuote(String value) { + for (char quotableChar : QUOTABLE_CHARS) { + if (value.indexOf(quotableChar) != -1) { + return true; + } + } + return false; + } + } diff --git a/spring-context/src/test/java/org/springframework/jmx/export/naming/MetadataNamingStrategyTests.java b/spring-context/src/test/java/org/springframework/jmx/export/naming/MetadataNamingStrategyTests.java new file mode 100644 index 000000000000..d0c13c5c8b73 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/jmx/export/naming/MetadataNamingStrategyTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2023 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 + * + * https://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.jmx.export.naming; + +import java.util.function.Consumer; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource; +import org.springframework.util.ClassUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + + +/** + * Tests for {@link MetadataNamingStrategy}. + * + * @author Stephane Nicoll + */ +class MetadataNamingStrategyTests { + + private static final TestBean TEST_BEAN = new TestBean(); + + private final MetadataNamingStrategy strategy; + + MetadataNamingStrategyTests() { + this.strategy = new MetadataNamingStrategy(); + this.strategy.setDefaultDomain("com.example"); + this.strategy.setAttributeSource(new AnnotationJmxAttributeSource()); + } + + @Test + void getObjectNameWhenBeanNameIsSimple() throws MalformedObjectNameException { + ObjectName name = this.strategy.getObjectName(TEST_BEAN, "myBean"); + assertThat(name.getDomain()).isEqualTo("com.example"); + assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "myBean")); + } + + @Test + void getObjectNameWhenBeanNameIsValidObjectName() throws MalformedObjectNameException { + ObjectName name = this.strategy.getObjectName(TEST_BEAN, "com.another:name=myBean"); + assertThat(name.getDomain()).isEqualTo("com.another"); + assertThat(name.getKeyPropertyList()).containsOnly(entry("name", "myBean")); + } + + @Test + void getObjectNameWhenBeanNamContainsComma() throws MalformedObjectNameException { + ObjectName name = this.strategy.getObjectName(TEST_BEAN, "myBean,"); + assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"myBean,\"")); + } + + @Test + void getObjectNameWhenBeanNamContainsEquals() throws MalformedObjectNameException { + ObjectName name = this.strategy.getObjectName(TEST_BEAN, "my=Bean"); + assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"my=Bean\"")); + } + + @Test + void getObjectNameWhenBeanNamContainsColon() throws MalformedObjectNameException { + ObjectName name = this.strategy.getObjectName(TEST_BEAN, "my:Bean"); + assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"my:Bean\"")); + } + + @Test + void getObjectNameWhenBeanNamContainsQuote() throws MalformedObjectNameException { + ObjectName name = this.strategy.getObjectName(TEST_BEAN, "\"myBean\""); + assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"\\\"myBean\\\"\"")); + } + + private Consumer hasDefaultProperties(Object instance, String expectedName) { + return objectName -> assertThat(objectName.getKeyPropertyList()).containsOnly( + entry("type", ClassUtils.getShortName(instance.getClass())), + entry("name", expectedName)); + } + + static class TestBean {} + +}