diff --git a/.classpath b/.classpath
index f9034dd..4da6324 100644
--- a/.classpath
+++ b/.classpath
@@ -1,38 +1,39 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/axonivy/jmx/internal/Instruction.java b/src/main/java/com/axonivy/jmx/internal/Instruction.java
index 845bd6a..e040f16 100644
--- a/src/main/java/com/axonivy/jmx/internal/Instruction.java
+++ b/src/main/java/com/axonivy/jmx/internal/Instruction.java
@@ -38,8 +38,8 @@
* }
* }
* -----
- * Instruction.parseInstruction(manager, MyBean.class "Hello #{name}").execute(new MyBean()) -> "Hello Reto"
- * Instruction.parseInstruction(manager, MyBean.class "Hello #{application.description}").execute(new MyBean()) -> "Hello Weiss"
+ * Instruction.parseInstruction(manager, MyBean.class, "Hello #{name}").execute(new MyBean()) -> "Hello Reto"
+ * Instruction.parseInstruction(manager, MyBean.class, "Hello #{application.description}").execute(new MyBean()) -> "Hello Weiss"
*
*
*
diff --git a/src/main/java/com/axonivy/jmx/internal/MBeanType.java b/src/main/java/com/axonivy/jmx/internal/MBeanType.java
index 73f1208..011e0ad 100644
--- a/src/main/java/com/axonivy/jmx/internal/MBeanType.java
+++ b/src/main/java/com/axonivy/jmx/internal/MBeanType.java
@@ -19,7 +19,7 @@ class MBeanType
private MBeanManager manager;
private Class> mBeanClass;
private Instruction descriptionInstruction;
- private Instruction nameInstruction;
+ private NameInstruction nameInstruction;
private List compositionReferenceInfos;
MBeanType(MBeanManager manager, Class> mBeanClass)
@@ -53,7 +53,7 @@ String evaluateName(Object mBean)
{
if (nameInstruction == null)
{
- nameInstruction = Instruction.parseInstruction(manager, mBeanClass, annotation.value());
+ nameInstruction = NameInstruction.parseInstruction(manager, mBeanClass, annotation.value());
}
return nameInstruction.execute(mBean);
}
diff --git a/src/main/java/com/axonivy/jmx/internal/NameInstruction.java b/src/main/java/com/axonivy/jmx/internal/NameInstruction.java
new file mode 100644
index 0000000..bdfdf13
--- /dev/null
+++ b/src/main/java/com/axonivy/jmx/internal/NameInstruction.java
@@ -0,0 +1,163 @@
+package com.axonivy.jmx.internal;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.commons.lang3.StringUtils;
+
+class NameInstruction
+{
+ private ObjectName template;
+ private boolean isRelative;
+ private List> valueInstructions;
+
+ private NameInstruction(MBeanManager manager, Class> mBeanClass, String instruction) throws MalformedObjectNameException
+ {
+ isRelative = isRelative(instruction);
+ if (isRelative)
+ {
+ instruction = ":"+instruction;
+ }
+ template = new ObjectName(instruction);
+ List> properties = getProperties();
+ valueInstructions = properties
+ .stream()
+ .map(property -> new Property<>(property.getKey(), Instruction.parseInstruction(manager, mBeanClass, property.getValue())))
+ .collect(Collectors.toList());
+ }
+
+ static NameInstruction parseInstruction(MBeanManager manager, Class> mBeanClass, String value)
+ {
+ try
+ {
+ return new NameInstruction(manager, mBeanClass, value);
+ }
+ catch(MalformedObjectNameException ex)
+ {
+ throw new IllegalArgumentException("Object name of MBean '"+value+"'is malformed", ex);
+ }
+ }
+
+ String execute(Object mBean)
+ {
+ List> evaluatedValues = evaluateAndQuoteValues(mBean);
+ String name = buildName(evaluatedValues);
+ return name;
+ }
+
+ private boolean isRelative(String instruction)
+ {
+ int firstColon = StringUtils.indexOf(instruction, ":");
+ if (firstColon < 0)
+ {
+ return true;
+ }
+ int firstEqual = StringUtils.indexOf(instruction, "=");
+ return firstEqual> getProperties()
+ {
+ List> properties = template
+ .getKeyPropertyList()
+ .entrySet()
+ .stream()
+ .map(entry -> new Property<>(entry.getKey(), entry.getValue()))
+ .collect(Collectors.toList());
+ String strName = template.getKeyPropertyListString();
+ Collections.sort(properties, new PropertyComparator(strName));
+ return properties;
+ }
+
+ private List> evaluateAndQuoteValues(Object mBean)
+ {
+ List> evaluatedValues = valueInstructions
+ .stream()
+ .map(property -> new Property<>(property.getKey(), property.getValue().execute(mBean)))
+ .map(this::quoteIfNecessary)
+ .collect(Collectors.toList());
+ return evaluatedValues;
+ }
+
+ private String buildName(List> evaluatedValues)
+ {
+ StringBuilder name = new StringBuilder(256);
+ if (!isRelative)
+ {
+ name.append(template.getDomain());
+ name.append(':');
+ }
+ String properties = evaluatedValues
+ .stream()
+ .map(Property::toString)
+ .collect(Collectors.joining(","));
+ name.append(properties);
+ return name.toString();
+ }
+
+ private Property quoteIfNecessary(Property property)
+ {
+ if (needsQuoting(property.getValue()))
+ {
+ return new Property(property.getKey(), ObjectName.quote(property.getValue()));
+ }
+ return property;
+ }
+
+ private boolean needsQuoting(String value)
+ {
+ boolean isQuoted = StringUtils.startsWith(value, "\"") && StringUtils.endsWith(value, "\"");
+ return !isQuoted && StringUtils.containsAny(value, ',',':','=','\n', '\"');
+ }
+
+ private static final class Property
+ {
+ private String key;
+ private T value;
+
+ private Property(String key, T value)
+ {
+ this.key = key;
+ this.value = value;
+ }
+
+ private T getValue()
+ {
+ return value;
+ }
+
+ private String getKey()
+ {
+ return key;
+ }
+
+ @Override
+ public String toString()
+ {
+ return key+"="+value;
+ }
+ }
+
+ private static final class PropertyComparator implements Comparator>
+ {
+ private String strName;
+
+ private PropertyComparator(String strName)
+ {
+ this.strName = strName;
+ }
+
+ @Override
+ public int compare(Property property1, Property property2)
+ {
+ int positionProperty1 = StringUtils.indexOf(strName, property1.getKey()+"=");
+ int positionProperty2 = StringUtils.indexOf(strName, property2.getKey()+"=");
+ return Integer.compare(positionProperty1, positionProperty2);
+ }
+ }
+}
diff --git a/src/test/java/com/axonivy/jmx/TestMBeanNameQuoting.java b/src/test/java/com/axonivy/jmx/TestMBeanNameQuoting.java
new file mode 100644
index 0000000..0e26491
--- /dev/null
+++ b/src/test/java/com/axonivy/jmx/TestMBeanNameQuoting.java
@@ -0,0 +1,100 @@
+package com.axonivy.jmx;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+
+public class TestMBeanNameQuoting
+{
+ @Test
+ public void quotedBean() throws MalformedObjectNameException
+ {
+ assertThat(isRegistered("Test:type=\",=:\\n \\\" hi\"")).isFalse();
+ MBeans.registerMBeanFor(new QuotedMBean());
+ assertThat(isRegistered("Test:type=\",=:\\n \\\" hi\"")).isTrue();
+ }
+
+ @Test
+ public void notQuotedBeanFromElExpression() throws MalformedObjectNameException
+ {
+ assertNameNotQuoted("hello world");
+ }
+
+ @Test
+ public void quotedBeanFromElExpressionWithComma() throws MalformedObjectNameException
+ {
+ assertNameQuoted("hello,world");
+ }
+
+ @Test
+ public void quotedBeanFromElExpressionWithEqual() throws MalformedObjectNameException
+ {
+ assertNameQuoted("hello=world");
+ }
+
+ @Test
+ public void quotedBeanFromElExpressionWithCollon() throws MalformedObjectNameException
+ {
+ assertNameQuoted("hello:world");
+ }
+
+ @Test
+ public void quotedBeanFromElExpressionWithNewLine() throws MalformedObjectNameException
+ {
+ assertNameQuoted("hello\nworld");
+ }
+
+ @Test
+ public void quotedBeanFromElExpressionWithQuote() throws MalformedObjectNameException
+ {
+ assertNameQuoted("hello\"world");
+ }
+
+ @Test
+ public void notQuotedBeanFromElExpressionThatIsAlreadyQuoted() throws MalformedObjectNameException
+ {
+ assertNameNotQuoted("\"hello,=:\\n\\\"world\"");
+ }
+
+ private static void assertNameNotQuoted(String name) throws MalformedObjectNameException
+ {
+ assertName(name, name);
+ }
+
+ private void assertNameQuoted(String name) throws MalformedObjectNameException
+ {
+ assertName(name, ObjectName.quote(name));
+ }
+
+ private static void assertName(String name, String mBeanName) throws MalformedObjectNameException
+ {
+ assertThat(isRegistered("Test:name="+mBeanName)).isFalse();
+ MBeans.registerMBeanFor(new NameMBean(name));
+ assertThat(isRegistered("Test:name="+mBeanName)).isTrue();
+ }
+
+ @MBean(value="Test:type=\",=:\\n \\\" hi\"")
+ private static final class QuotedMBean
+ {
+ }
+
+ @MBean(value="Test:name=#{name}")
+ private static final class NameMBean
+ {
+ @SuppressWarnings("unused")
+ private String name;
+
+ private NameMBean(String name)
+ {
+ this.name = name;
+ }
+ }
+
+ private static boolean isRegistered(String name) throws MalformedObjectNameException
+ {
+ return MBeans.getMBeanServer().isRegistered(new ObjectName(name));
+ }
+}