You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ro...@apache.org on 2019/04/01 17:29:10 UTC
[qpid-jms] branch master updated: QPIDJMS-448: support simple
variable expansion for JNDI config
This is an automated email from the ASF dual-hosted git repository.
robbie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/qpid-jms.git
The following commit(s) were added to refs/heads/master by this push:
new 74e0427 QPIDJMS-448: support simple variable expansion for JNDI config
74e0427 is described below
commit 74e0427f43049b357342d1a9e69b32027c59cf2a
Author: Robbie Gemmell <ro...@apache.org>
AuthorDate: Mon Apr 1 18:23:20 2019 +0100
QPIDJMS-448: support simple variable expansion for JNDI config
---
qpid-jms-client/pom.xml | 10 +
.../qpid/jms/jndi/JmsInitialContextFactory.java | 32 ++-
.../apache/qpid/jms/util/VariableExpansion.java | 137 ++++++++++
.../jms/jndi/JmsInitialContextFactoryTest.java | 133 +++++++++
.../qpid/jms/util/VariableExpansionTest.java | 302 +++++++++++++++++++++
qpid-jms-docs/Configuration.md | 8 +-
6 files changed, 613 insertions(+), 9 deletions(-)
diff --git a/qpid-jms-client/pom.xml b/qpid-jms-client/pom.xml
index a952fc4..6875d71 100644
--- a/qpid-jms-client/pom.xml
+++ b/qpid-jms-client/pom.xml
@@ -128,6 +128,16 @@
</resources>
<plugins>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <environmentVariables combine.children="append">
+ <!-- Env variable for use with VariableExpansionTest and JmsInitialContextFactoryTest -->
+ <VAR_EXPANSION_TEST_ENV_VAR>TestEnvVariableValue123</VAR_EXPANSION_TEST_ENV_VAR>
+ </environmentVariables>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/jndi/JmsInitialContextFactory.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/jndi/JmsInitialContextFactory.java
index 5588095..e55970c 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/jndi/JmsInitialContextFactory.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/jndi/JmsInitialContextFactory.java
@@ -40,6 +40,7 @@ import javax.naming.spi.InitialContextFactory;
import org.apache.qpid.jms.JmsConnectionFactory;
import org.apache.qpid.jms.JmsQueue;
import org.apache.qpid.jms.JmsTopic;
+import org.apache.qpid.jms.util.VariableExpansion;
public class JmsInitialContextFactory implements InitialContextFactory {
@@ -184,7 +185,7 @@ public class JmsInitialContextFactory implements InitialContextFactory {
value = String.valueOf(entry.getValue());
}
- factories.put(factoryName, value);
+ factories.put(factoryName, expand(value, environment));
}
}
@@ -206,7 +207,8 @@ public class JmsInitialContextFactory implements InitialContextFactory {
String key = String.valueOf(entry.getKey());
if (key.toLowerCase().startsWith(CONNECTION_FACTORY_DEFAULT_KEY_PREFIX)) {
String jndiName = key.substring(CONNECTION_FACTORY_DEFAULT_KEY_PREFIX.length());
- map.put(jndiName, String.valueOf(entry.getValue()));
+ String value = String.valueOf(entry.getValue());
+ map.put(jndiName, expand(value, environment));
}
}
@@ -224,7 +226,8 @@ public class JmsInitialContextFactory implements InitialContextFactory {
if (key.toLowerCase().startsWith(CONNECTION_FACTORY_PROPERTY_KEY_PREFIX)) {
if(key.substring(CONNECTION_FACTORY_PROPERTY_KEY_PREFIX.length()).startsWith(factoryNameSuffix)) {
String propertyName = key.substring(CONNECTION_FACTORY_PROPERTY_KEY_PREFIX.length() + factoryNameSuffix.length());
- map.put(propertyName, String.valueOf(entry.getValue()));
+ String value = String.valueOf(entry.getValue());
+ map.put(propertyName, expand(value, environment));
}
}
}
@@ -238,7 +241,8 @@ public class JmsInitialContextFactory implements InitialContextFactory {
String key = entry.getKey().toString();
if (key.startsWith(QUEUE_KEY_PREFIX)) {
String jndiName = key.substring(QUEUE_KEY_PREFIX.length());
- bindings.put(jndiName, createQueue(entry.getValue().toString()));
+ String value = expand(entry.getValue().toString(), environment);
+ bindings.put(jndiName, createQueue(value));
}
}
}
@@ -249,7 +253,8 @@ public class JmsInitialContextFactory implements InitialContextFactory {
String key = entry.getKey().toString();
if (key.startsWith(TOPIC_KEY_PREFIX)) {
String jndiName = key.substring(TOPIC_KEY_PREFIX.length());
- bindings.put(jndiName, createTopic(entry.getValue().toString()));
+ String value = expand(entry.getValue().toString(), environment);
+ bindings.put(jndiName, createTopic(value));
}
}
}
@@ -284,4 +289,21 @@ public class JmsInitialContextFactory implements InitialContextFactory {
return factory;
}
+
+ protected static String expand(String input, Map<Object, Object> environment) {
+ return VariableExpansion.expand(input, variable -> {
+ String resolve = VariableExpansion.SYS_PROP_RESOLVER.resolve(variable);
+ if (resolve == null) {
+ resolve = VariableExpansion.ENV_VAR_RESOLVER.resolve(variable);
+ if (resolve == null) {
+ Object o = environment.get(variable);
+ if (o != null) {
+ resolve = String.valueOf(o);
+ }
+ }
+ }
+
+ return resolve;
+ });
+ }
}
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/VariableExpansion.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/VariableExpansion.java
new file mode 100644
index 0000000..f5093c5
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/util/VariableExpansion.java
@@ -0,0 +1,137 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.jms.util;
+
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class VariableExpansion {
+
+ @FunctionalInterface
+ public interface Resolver {
+ String resolve(String name);
+ }
+
+ private static final Pattern VARIABLE_OR_ESCAPE_PATTERN = Pattern.compile("(?:\\$\\{([^\\}]*)\\})|(?:\\$(\\$))");
+ private static final String ESCAPE = "$";
+
+ private VariableExpansion() {
+ // No instances
+ }
+
+ //================== Helper Resolvers ==================
+
+ public static final Resolver ENV_VAR_RESOLVER = prop -> System.getenv(prop);
+
+ public static final Resolver SYS_PROP_RESOLVER = prop -> System.getProperty(prop);
+
+ public static final class MapResolver implements Resolver {
+ private final Map<String, String> map;
+
+ public MapResolver(Map<String, String> map) {
+ this.map = map;
+ }
+
+ public String resolve(String name) {
+ return map.get(name);
+ }
+ }
+
+ //======================= Methods ======================
+
+ /**
+ * Expands any variables found in the given input string.
+ *
+ * @param input
+ * the string to expand any variables in
+ * @param resolver
+ * the resolver to use
+ * @return the expanded output
+ *
+ * @throws IllegalArgumentException
+ * if an argument can't be expanded, e.g because a variable is not resolvable.
+ * @throws NullPointerException
+ * if a resolver is not supplied
+ */
+ public static final String expand(String input, Resolver resolver) throws IllegalArgumentException, NullPointerException {
+ if(resolver == null) {
+ throw new NullPointerException("Resolver must be supplied");
+ }
+
+ if (input == null) {
+ return null;
+ }
+
+ return expand(input, resolver, new Stack<String>());
+ }
+
+ private static final String expand(String input, Resolver resolver, Stack<String> stack) {
+ Matcher matcher = VARIABLE_OR_ESCAPE_PATTERN.matcher(input);
+
+ StringBuffer result = null;
+ while (matcher.find()) {
+ if(result == null) {
+ result = new StringBuffer();
+ }
+
+ String var = matcher.group(1); // Variable match
+ if (var != null) {
+ matcher.appendReplacement(result, Matcher.quoteReplacement(resolve(var, resolver, stack)));
+ } else {
+ String esc = matcher.group(2); // Escape matcher
+ if (ESCAPE.equals(esc)) {
+ matcher.appendReplacement(result, Matcher.quoteReplacement(ESCAPE));
+ } else {
+ throw new IllegalArgumentException(esc);
+ }
+ }
+ }
+
+ if(result == null) {
+ // No match found, return the original input
+ return input;
+ }
+
+ matcher.appendTail(result);
+
+ return result.toString();
+ }
+
+ private static final String resolve(String var, Resolver resolver, Stack<String> stack) {
+ if (stack.contains(var)) {
+ throw new IllegalArgumentException(String.format("Recursively defined variable '%s', stack=%s", var, stack));
+ }
+
+ String result = resolver.resolve(var);
+ if (result == null) {
+ throw new IllegalArgumentException("Unable to resolve variable: " + var);
+ }
+
+ stack.push(var);
+ try {
+ return expand(result, resolver, stack);
+ } finally {
+ stack.pop();
+ }
+ }
+}
\ No newline at end of file
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/jndi/JmsInitialContextFactoryTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/jndi/JmsInitialContextFactoryTest.java
index 9400ad5..7d4a7ad 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/jndi/JmsInitialContextFactoryTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/jndi/JmsInitialContextFactoryTest.java
@@ -17,9 +17,11 @@
package org.apache.qpid.jms.jndi;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.FileNotFoundException;
@@ -43,6 +45,10 @@ import org.junit.Test;
public class JmsInitialContextFactoryTest extends QpidJmsTestCase {
+ // Environment variable name+value for test, configured in Surefire config
+ private static final String TEST_ENV_VARIABLE_NAME = "VAR_EXPANSION_TEST_ENV_VAR";
+ private static final String TEST_ENV_VARIABLE_VALUE = "TestEnvVariableValue123";
+
private JmsInitialContextFactory factory;
private Context context;
@@ -450,4 +456,131 @@ public class JmsInitialContextFactoryTest extends QpidJmsTestCase {
f.delete();
}
}
+
+ @Test
+ public void testVariableExpansionUnresolvableVariable() throws Exception {
+ //Check exception is thrown for variable that doesn't resolve
+ String factoryName = "myFactory";
+ String unknownVariable = "unknownVariable";
+ String uri = "amqp://${"+ unknownVariable +"}:1234";
+
+ Hashtable<Object, Object> env = new Hashtable<Object, Object>();
+ env.put("connectionfactory." + factoryName, uri);
+
+ try {
+ createInitialContext(env);
+ fail("Expected to fail due to unresolved variable");
+ } catch (IllegalArgumentException iae) {
+ // Expected
+ }
+
+ String nowKnownHostValue = "nowKnownValue";
+
+ //Now make the variable resolve, check the exact same env+URI now works
+ setTestSystemProperty(unknownVariable, nowKnownHostValue);
+
+ Context ctx = createInitialContext(env);
+
+ Object o = ctx.lookup("myFactory");
+
+ assertNotNull("No object returned", o);
+ assertEquals("Unexpected class type for returned object", JmsConnectionFactory.class, o.getClass());
+
+ assertEquals("Unexpected URI for returned factory", "amqp://" + nowKnownHostValue + ":1234", ((JmsConnectionFactory) o).getRemoteURI());
+ }
+
+ @Test
+ public void testVariableExpansionConnectionFactory() throws Exception {
+ doVariableExpansionConnectionFactoryTestImpl(false);
+ }
+
+ @Test
+ public void testVariableExpansionConnectionFactoryWithEnvVar() throws Exception {
+ doVariableExpansionConnectionFactoryTestImpl(true);
+ }
+
+ private void doVariableExpansionConnectionFactoryTestImpl(boolean useEnvVarForHost) throws NamingException {
+ String factoryName = "myFactory";
+
+ String hostVariableName = useEnvVarForHost ? TEST_ENV_VARIABLE_NAME : "myHostVar";
+ String portVariableName = "myPortVar";
+ String clientIdVariableName = "myClientIDVar";
+ String hostVariableValue = useEnvVarForHost ? TEST_ENV_VARIABLE_VALUE : "myHostValue";
+ String portVariableValue= "1234";
+ String clientIdVariableValue= "myClientIDValue" + getTestName();
+ Object environmentProperty = "connectionfactory." + factoryName;
+
+ if(useEnvVarForHost) {
+ // Verify variable is set (by Surefire config),
+ // prevents spurious failure if not manually configured when run in IDE.
+ assertEquals("Expected to use env variable name", TEST_ENV_VARIABLE_NAME, hostVariableName);
+ assumeTrue("Environment variable not set as required", System.getenv().containsKey(TEST_ENV_VARIABLE_NAME));
+ assertEquals("Environment variable value not as expected", TEST_ENV_VARIABLE_VALUE, System.getenv(TEST_ENV_VARIABLE_NAME));
+ } else {
+ assertNotEquals("Expected to use a different name", TEST_ENV_VARIABLE_NAME, hostVariableName);
+
+ setTestSystemProperty(hostVariableName, hostVariableValue);
+ }
+ setTestSystemProperty(portVariableName, portVariableValue);
+
+ String uri = "amqp://${" + hostVariableName + "}:${" + portVariableName + "}?jms.clientID=${" + clientIdVariableName + "}";
+
+ Hashtable<Object, Object> env = new Hashtable<Object, Object>();
+ env.put(environmentProperty, uri);
+ env.put(clientIdVariableName, clientIdVariableValue);
+
+ Context ctx = createInitialContext(env);
+
+ Object o = ctx.lookup(factoryName);
+
+ assertNotNull("No object returned", o);
+ assertEquals("Unexpected class type for returned object", JmsConnectionFactory.class, o.getClass());
+
+ assertEquals("Unexpected ClientID for returned factory", clientIdVariableValue, ((JmsConnectionFactory) o).getClientID());
+
+ String expectedURI = "amqp://" + hostVariableValue + ":" + portVariableValue;
+ assertEquals("Unexpected URI for returned factory", expectedURI, ((JmsConnectionFactory) o).getRemoteURI());
+ }
+
+ @Test
+ public void testVariableExpansionQueue() throws Exception {
+ String lookupName = "myQueueLookup";
+ String variableName = "myQueueVariable";
+ String variableValue = "myQueueName";
+
+ Hashtable<Object, Object> env = new Hashtable<Object, Object>();
+ env.put("queue." + lookupName, "${" + variableName +"}");
+
+ setTestSystemProperty(variableName, variableValue);
+
+ Context ctx = createInitialContext(env);
+
+ Object o = ctx.lookup(lookupName);
+
+ assertNotNull("No object returned", o);
+ assertEquals("Unexpected class type for returned object", JmsQueue.class, o.getClass());
+
+ assertEquals("Unexpected name for returned queue", variableValue, ((JmsQueue) o).getQueueName());
+ }
+
+ @Test
+ public void testVariableExpansionTopic() throws Exception {
+ String lookupName = "myTopicLookup";
+ String variableName = "myTopicVariable";
+ String variableValue = "myTopicName";
+
+ Hashtable<Object, Object> env = new Hashtable<Object, Object>();
+ env.put("topic." + lookupName, "${" + variableName +"}");
+
+ setTestSystemProperty(variableName, variableValue);
+
+ Context ctx = createInitialContext(env);
+
+ Object o = ctx.lookup(lookupName);
+
+ assertNotNull("No object returned", o);
+ assertEquals("Unexpected class type for returned object", JmsTopic.class, o.getClass());
+
+ assertEquals("Unexpected name for returned queue", variableValue, ((JmsTopic) o).getTopicName());
+ }
}
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/VariableExpansionTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/VariableExpansionTest.java
new file mode 100644
index 0000000..d13aa89
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/util/VariableExpansionTest.java
@@ -0,0 +1,302 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.jms.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.qpid.jms.test.QpidJmsTestCase;
+import org.apache.qpid.jms.util.VariableExpansion.Resolver;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class VariableExpansionTest extends QpidJmsTestCase {
+
+ // Environment variable name+value for test, configured in Surefire config
+ private static final String TEST_ENV_VARIABLE_NAME = "VAR_EXPANSION_TEST_ENV_VAR";
+ private static final String TEST_ENV_VARIABLE_VALUE = "TestEnvVariableValue123";
+
+ private static final String TEST_ENV_VARIABLE_NAME_NOT_SET = "VAR_EXPANSION_TEST_ENV_VAR_NOT_SET";
+ private static final String ESCAPE = "$";
+
+ private String testNamePrefix;
+ private String testPropName;
+ private String testPropValue;
+ private String testVariableForExpansion;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ testNamePrefix = getTestName() + ".";
+
+ testPropName = testNamePrefix + "myPropKey";
+ testPropValue = testNamePrefix + "myPropValue";
+ testVariableForExpansion = "${" + testPropName + "}";
+ }
+
+ // ===== Resolver tests =====
+
+ @Test
+ public void testResolveWithSysPropResolver() {
+ Resolver sysPropResolver = VariableExpansion.SYS_PROP_RESOLVER;
+
+ assertNull("System property value unexpectedly set already", System.getProperty(testPropName));
+ assertNull("Expected resolve to return null as property not set", sysPropResolver.resolve(testPropName));
+
+ setTestSystemProperty(testPropName, testPropValue);
+
+ assertEquals("System property value not as expected", testPropValue, System.getProperty(testPropName));
+ assertEquals("Resolved variable not as expected", testPropValue, sysPropResolver.resolve(testPropName));
+ }
+
+ @Test
+ public void testResolveWithEnvVarResolver() {
+ // Verify variable is set (by Surefire config),
+ // prevents spurious failure if not manually configured when run in IDE.
+ assumeTrue("Environment variable not set as required", System.getenv().containsKey(TEST_ENV_VARIABLE_NAME));
+ assumeFalse("Environment variable unexpectedly set", System.getenv().containsKey(TEST_ENV_VARIABLE_NAME_NOT_SET));
+
+ assertEquals("Environment variable value not as expected", TEST_ENV_VARIABLE_VALUE, System.getenv(TEST_ENV_VARIABLE_NAME));
+
+ final Resolver envVarResolver = VariableExpansion.ENV_VAR_RESOLVER;
+
+ assertNull("Expected resolve to return null as property not set", envVarResolver.resolve(TEST_ENV_VARIABLE_NAME_NOT_SET));
+
+ assertEquals("Resolved variable not as expected", TEST_ENV_VARIABLE_VALUE, envVarResolver.resolve(TEST_ENV_VARIABLE_NAME));
+ }
+
+ // ===== Expansion tests =====
+
+ @Test
+ public void testExpandWithResolverNotProvided() {
+ try {
+ VariableExpansion.expand("no-expansion-needed", null);
+ fail("Should have failed to expand,resolver not given");
+ } catch (NullPointerException npe) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testExpandNull() {
+ assertNull("Expected null", VariableExpansion.expand(null, variable -> "foo"));
+ }
+
+ @Test
+ public void testExpandWithSysPropResolver() {
+ final Resolver resolver = VariableExpansion.SYS_PROP_RESOLVER;
+
+ try {
+ VariableExpansion.expand(testVariableForExpansion, resolver);
+ fail("Should have failed to expand, property not set");
+ } catch (IllegalArgumentException iae) {
+ // Expected
+ }
+
+ setTestSystemProperty(testPropName, testPropValue);
+
+ assertEquals("System property value not as expected", testPropValue, System.getProperty(testPropName));
+
+ String expanded = VariableExpansion.expand(testVariableForExpansion, resolver);
+ assertEquals("Expanded variable not as expected", testPropValue, expanded);
+ }
+
+ @Test
+ public void testExpandWithEnvVarResolver() {
+ // Verify variable is set (by Surefire config),
+ // prevents spurious failure if not manually configured when run in IDE.
+ assumeTrue("Environment variable not set as required", System.getenv().containsKey(TEST_ENV_VARIABLE_NAME));
+
+ assertEquals("Environment variable value not as expected", TEST_ENV_VARIABLE_VALUE, System.getenv(TEST_ENV_VARIABLE_NAME));
+
+ final Resolver resolver = VariableExpansion.ENV_VAR_RESOLVER;
+
+ try {
+ VariableExpansion.expand("${" + TEST_ENV_VARIABLE_NAME + "_NOT_SET" + "}", resolver);
+ fail("Should have failed to expand unset env variable");
+ } catch (IllegalArgumentException iae) {
+ // Expected
+ }
+
+ String expanded = VariableExpansion.expand("${" + TEST_ENV_VARIABLE_NAME + "}", resolver);
+
+ assertEquals("Expanded variable not as expected", TEST_ENV_VARIABLE_VALUE, expanded);
+ }
+
+ @Test
+ public void testExpandBasicWithMapResolver() {
+ Map<String,String> propsMap = new HashMap<>();
+ propsMap.put(testPropName, testPropValue);
+ Resolver resolver = new VariableExpansion.MapResolver(propsMap);
+
+ try {
+ VariableExpansion.expand("${" + testNamePrefix + "-not-set" + "}", resolver);
+ fail("Should have failed to expand, property not set");
+ } catch (IllegalArgumentException iae) {
+ // Expected
+ }
+
+ String expanded = VariableExpansion.expand(testVariableForExpansion, resolver);
+
+ assertEquals("Expanded variable not as expected", testPropValue, expanded);
+ }
+
+ @Test
+ public void testExpandBasic() {
+ // Variable is the full input
+ doBasicExpansionTestImpl(testVariableForExpansion, testPropValue);
+
+ // Variable trails a prefix
+ String prefix = "prefix";
+ doBasicExpansionTestImpl(prefix + testVariableForExpansion, prefix + testPropValue);
+
+ // Variable precedes a suffix
+ String suffix = "suffix";
+ doBasicExpansionTestImpl(testVariableForExpansion + suffix, testPropValue + suffix);
+
+ // Variable is between prefix and suffix
+ doBasicExpansionTestImpl(prefix + testVariableForExpansion + suffix, prefix + testPropValue + suffix);
+ }
+
+ @Test
+ public void testExpandMultipleVariables() {
+ String propName2 = "propName2";
+ String propValue2 = "propValue2";
+ String propName3 = "propName3";
+ String propValue3 = "propValue3";
+
+ Map<String,String> propsMap = new HashMap<>();
+ propsMap.put(testPropName, testPropValue);
+ propsMap.put(propName2, propValue2);
+ propsMap.put(propName3, propValue3);
+
+ Resolver resolver = new VariableExpansion.MapResolver(propsMap);
+
+ // Variables are the full input
+ String toExpand = testVariableForExpansion + "${" + propName2 +"}${" + propName3 + "}";
+ String expected = testPropValue + propValue2 + propValue3;
+
+ doBasicExpansionTestImpl(toExpand, expected, resolver);
+
+ // Variable internal to overall input
+ toExpand = "prefix" + testVariableForExpansion + "-foo-${" + propName2 +"}-bar-${" + propName3 + "}" + "suffix";
+ expected = "prefix" + testPropValue + "-foo-" + propValue2 +"-bar-" + propValue3 + "suffix";
+
+ doBasicExpansionTestImpl(toExpand, expected, resolver);
+ }
+
+ @Test
+ public void testExpandMultipleInstancesOfVariable() {
+ Map<String,String> propsMap = new HashMap<>();
+ propsMap.put(testPropName, testPropValue);
+
+ Resolver resolver = new VariableExpansion.MapResolver(propsMap);
+
+ String toExpand = "1-" + testVariableForExpansion + "2-" + testVariableForExpansion + "3-" + testVariableForExpansion;
+ String expected = "1-" + testPropValue + "2-" + testPropValue + "3-" + testPropValue;
+
+ doBasicExpansionTestImpl(toExpand, expected, resolver);
+ }
+
+ @Test
+ public void testExpandRecursiveThrows() {
+ String propName1 = "propName1";
+ String propName2 = "propName2";
+ String propValue1 = "propValue1-${" + propName2 + "}";
+ String propValue2 = "recursive-${" + propName1 + "}";
+
+ Map<String,String> propsMap = new HashMap<>();
+ propsMap.put(propName1, propValue1);
+ propsMap.put(propName2, propValue2);
+ try {
+ VariableExpansion.expand("${" + propName1 + "}", new VariableExpansion.MapResolver(propsMap));
+ fail("Expected exception to be thrown");
+ } catch (IllegalArgumentException iae) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testExpandWithoutVariable() {
+ doBasicExpansionTestImpl("no-expansion-needed", "no-expansion-needed");
+ doBasicExpansionTestImpl(ESCAPE + "no-expansion-needed", ESCAPE + "no-expansion-needed");
+ doBasicExpansionTestImpl("no-expansion-needed" + ESCAPE, "no-expansion-needed" + ESCAPE);
+ doBasicExpansionTestImpl(ESCAPE + "no-expansion-needed" + ESCAPE, ESCAPE + "no-expansion-needed" + ESCAPE);
+ doBasicExpansionTestImpl("no" + ESCAPE + "-expansion-needed", "no" + ESCAPE + "-expansion-needed");
+ }
+
+ @Test
+ public void testExpandSkipsEscapedVariables() {
+ doBasicExpansionTestImpl(ESCAPE + testVariableForExpansion, testVariableForExpansion);
+
+ String prefix = "prefix";
+ doBasicExpansionTestImpl(prefix + ESCAPE + testVariableForExpansion, prefix + testVariableForExpansion);
+
+ String suffix = "suffix";
+ doBasicExpansionTestImpl(ESCAPE + testVariableForExpansion + suffix, testVariableForExpansion + suffix);
+
+ doBasicExpansionTestImpl(prefix + ESCAPE + testVariableForExpansion + suffix, prefix + testVariableForExpansion + suffix);
+ }
+
+
+ private void doBasicExpansionTestImpl(String toExpand, String expectedExpansion) {
+ final Resolver mockResolver = Mockito.mock(Resolver.class);
+
+ Mockito.when(mockResolver.resolve(testPropName)).thenReturn(testPropValue);
+
+ doBasicExpansionTestImpl(toExpand, expectedExpansion, mockResolver);
+ }
+
+ private void doBasicExpansionTestImpl(String toExpand, String expectedExpansion, Resolver resolver) {
+ String expanded = VariableExpansion.expand(toExpand, resolver);
+ assertEquals("Expanded variable not as expected", expectedExpansion, expanded);
+ }
+
+ @Test
+ public void testExpandFailsToResolve() {
+ doBasicExpansionTestImpl(testVariableForExpansion);
+ }
+
+ private void doBasicExpansionTestImpl(String toExpand) {
+ final Resolver mockResolver = Mockito.mock(Resolver.class);
+
+ // Check when resolution fails
+ Mockito.when(mockResolver.resolve(testPropName)).thenReturn(null);
+ try {
+ VariableExpansion.expand(toExpand, mockResolver);
+ fail("Should have failed to expand, property not resolve");
+ } catch (IllegalArgumentException iae) {
+ // Expected
+ }
+
+ Mockito.verify(mockResolver).resolve(testPropName);
+ Mockito.verifyNoMoreInteractions(mockResolver);
+ }
+}
diff --git a/qpid-jms-docs/Configuration.md b/qpid-jms-docs/Configuration.md
index ee51c44..7d9b520 100644
--- a/qpid-jms-docs/Configuration.md
+++ b/qpid-jms-docs/Configuration.md
@@ -43,11 +43,11 @@ Applications use a JNDI InitialContext, itself obtained from an InitialContextFa
The property syntax used in the properties file or environment Hashtable is as follows:
-+ To define a ConnectionFactory, use format: *connectionfactory.lookupName = URI*
-+ To define a Queue, use format: *queue.lookupName = queueName*
-+ To define a Topic use format: *topic.lookupName = topicName*
++ To define a ConnectionFactory, use format: *connectionfactory.<lookup-name> = <connection-uri>*
++ To define a Queue, use format: *queue.<lookup-name> = <queue-name>*
++ To define a Topic use format: *topic.<lookup-name> = <topic-name>*
-For more details of the Connection URI, see the next section.
+The property values which which constitute the connection URI, queue name, or topic name can also utilise simple *${variable}* expansion resolved in order from system properties, environment variables, or the properties file / environment Hashtable. For more details of the Connection URI, see the next section.
As an example, consider the following properties used to define a ConnectionFactory, Queue, and Topic:
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org