QPID-8440: [JMS AMQP 0-x] Add support for user user defined extensions to override client settings
diff --git a/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/client/src/main/java/org/apache/qpid/client/AMQConnection.java
index 4426fb6..7514ff8 100644
--- a/client/src/main/java/org/apache/qpid/client/AMQConnection.java
+++ b/client/src/main/java/org/apache/qpid/client/AMQConnection.java
@@ -151,12 +151,6 @@
 
     private String _clientName;
 
-    /** The user name to use for authentication */
-    private String _username;
-
-    /** The password to use for authentication */
-    private String _password;
-
     /** The virtual path to connect to on the AMQ server */
     private String _virtualHost;
 
@@ -482,8 +476,6 @@
             _connectionURL = connectionURL;
 
             _clientName = connectionURL.getClientName();
-            _username = connectionURL.getUsername();
-            _password = connectionURL.getPassword();
 
             setVirtualHost(connectionURL.getVirtualHost());
 
@@ -1356,17 +1348,17 @@
 
     public String getUsername()
     {
-        return _username;
+        return _connectionURL.getUsername();
     }
 
     public void setUsername(String id)
     {
-        _username = id;
+        _connectionURL.setUsername(id);
     }
 
     public String getPassword()
     {
-        return _password;
+        return _connectionURL.getPassword();
     }
 
     public String getVirtualHost()
diff --git a/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java b/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java
index ba3e4ae..c2e8c34 100644
--- a/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java
+++ b/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java
@@ -240,7 +240,11 @@
             _qpidConnection.connect(conSettings);
 
             _conn.setConnected(true);
-            _conn.setUsername(_qpidConnection.getUserID());
+
+            if (_qpidConnection.getUserID() != null)
+            {
+                _conn.setUsername(_qpidConnection.getUserID());
+            }
             _conn.setMaximumChannelCount(_qpidConnection.getChannelMax());
             _conn.getFailoverPolicy().attainedConnection();
             _conn.logConnected(_qpidConnection.getLocalAddress(), _qpidConnection.getRemoteSocketAddress());
diff --git a/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java b/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java
index a843f8f..066535f 100644
--- a/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java
+++ b/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java
@@ -54,7 +54,8 @@
 import java.util.UUID;
 
 
-public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory,
+public class AMQConnectionFactory extends AbstractConnectionFactory
+                                  implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory,
                                              javax.naming.spi.ObjectFactory, Referenceable, XATopicConnectionFactory,
                                              XAQueueConnectionFactory, XAConnectionFactory, Serializable
 {
@@ -129,7 +130,7 @@
             {
                 _connectionDetails.setClientName(getUniqueClientID());
             }
-            return new AMQConnection(_connectionDetails);
+            return newAMQConnectionInstance(_connectionDetails);
         }
         catch (Exception e)
         {
@@ -161,7 +162,7 @@
                 {
                     connectionDetails.setClientName(getUniqueClientID());
                 }
-                return new AMQConnection(connectionDetails);
+                return newAMQConnectionInstance(connectionDetails);
             }
             catch (Exception e)
             {
diff --git a/client/src/main/java/org/apache/qpid/client/AbstractConnectionFactory.java b/client/src/main/java/org/apache/qpid/client/AbstractConnectionFactory.java
new file mode 100644
index 0000000..ea7549b
--- /dev/null
+++ b/client/src/main/java/org/apache/qpid/client/AbstractConnectionFactory.java
@@ -0,0 +1,74 @@
+/*
+ * 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.client;
+
+import java.net.URI;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import javax.jms.Connection;
+
+import org.apache.qpid.QpidException;
+import org.apache.qpid.jms.ConnectionURL;
+
+public abstract class AbstractConnectionFactory
+{
+    private final Map<ConnectionExtension, BiFunction<Connection, URI, Object>>
+            _extensions = new EnumMap<>(ConnectionExtension.class);
+
+
+    public void setExtension(String extensionName, BiFunction<Connection, URI, Object> extension)
+    {
+        final ConnectionExtension connectionExtension = ConnectionExtension.fromString(extensionName);
+        if (extension == null)
+        {
+            _extensions.remove(connectionExtension);
+        }
+        else
+        {
+            _extensions.put(connectionExtension, extension);
+        }
+    }
+
+    protected CommonConnection newConnectionInstance(final ConnectionURL connectionDetails) throws QpidException
+    {
+        return newAMQConnectionInstance(connectionDetails);
+    }
+
+    protected Map<ConnectionExtension, BiFunction<Connection, URI, Object>> getExtensions()
+    {
+        return new EnumMap<>(_extensions);
+    }
+
+    final AMQConnection newAMQConnectionInstance(final ConnectionURL connectionDetails) throws QpidException
+    {
+        final Map<ConnectionExtension, BiFunction<Connection, URI, Object>> extensions = getExtensions();
+        final ConnectionURL connectionURL =
+                extensions.isEmpty() ? connectionDetails : new ExtensibleConnectionURL(connectionDetails, extensions);
+        final AMQConnection connection = new AMQConnection(connectionURL);
+        if (connectionURL instanceof ExtensibleConnectionURL)
+        {
+            ((ExtensibleConnectionURL) connectionURL).setConnectionSupplier(() -> connection);
+        }
+        return connection;
+    }
+}
diff --git a/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java b/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java
index 12fa91f..cba727b 100644
--- a/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java
+++ b/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java
@@ -193,6 +193,10 @@
         {
             declareDestination(_destination);
         }
+        if (_connection.isPopulateUserId())
+        {
+            _userID = _connection.getUsername();
+        }
     }
 
     abstract void declareDestination(AMQDestination destination) throws QpidException;
diff --git a/client/src/main/java/org/apache/qpid/client/ConnectionExtension.java b/client/src/main/java/org/apache/qpid/client/ConnectionExtension.java
new file mode 100644
index 0000000..364a24a
--- /dev/null
+++ b/client/src/main/java/org/apache/qpid/client/ConnectionExtension.java
@@ -0,0 +1,51 @@
+/*
+ * 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.client;
+
+public enum ConnectionExtension
+{
+    USERNAME_OVERRIDE("username"),
+    PASSWORD_OVERRIDE("password");
+
+    private final String _extensionName;
+
+    ConnectionExtension(final String extensionName)
+    {
+        _extensionName = extensionName;
+    }
+
+    public String getExtensionName()
+    {
+        return _extensionName;
+    }
+
+    public static ConnectionExtension fromString(String name)
+    {
+        for (ConnectionExtension extension : ConnectionExtension.values())
+        {
+            if (extension.getExtensionName().equalsIgnoreCase(name) || extension.name().equalsIgnoreCase(name))
+            {
+                return extension;
+            }
+        }
+        throw new IllegalArgumentException(String.format("Extension with name '%s' is not found", name));
+    }
+}
diff --git a/client/src/main/java/org/apache/qpid/client/ExtensibleConnectionURL.java b/client/src/main/java/org/apache/qpid/client/ExtensibleConnectionURL.java
new file mode 100644
index 0000000..1523bf6
--- /dev/null
+++ b/client/src/main/java/org/apache/qpid/client/ExtensibleConnectionURL.java
@@ -0,0 +1,229 @@
+/*
+ * 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.client;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+import javax.jms.Connection;
+
+import org.apache.qpid.QpidException;
+import org.apache.qpid.jms.ConnectionURL;
+
+public class ExtensibleConnectionURL implements ConnectionURL
+{
+    private final ConnectionURL _connectionURL;
+    private final Map<ConnectionExtension, BiFunction<Connection, URI, Object>> _extensions;
+    private final URI _connectionURI;
+    private volatile Supplier<AMQConnection> _connectionSupplier;
+
+    ExtensibleConnectionURL(final ConnectionURL connectionURL,
+                            final Map<ConnectionExtension, BiFunction<Connection, URI, Object>> extensions)
+            throws QpidException
+    {
+        this(connectionURL, extensions, () -> null);
+    }
+
+    ExtensibleConnectionURL(final ConnectionURL connectionURL,
+                            final Map<ConnectionExtension, BiFunction<Connection, URI, Object>> extensions,
+                            final Supplier<AMQConnection> connectionSupplier) throws QpidException
+    {
+        _connectionURL = connectionURL;
+        _extensions = extensions;
+        _connectionSupplier = connectionSupplier;
+
+        try
+        {
+            _connectionURI = new URI(_connectionURL.getURL());
+        }
+        catch (URISyntaxException e)
+        {
+            throw new QpidException("Unexpected connection URL", e);
+        }
+    }
+
+    @Override
+    public String getURL()
+    {
+        return _connectionURL.getURL();
+    }
+
+    @Override
+    public String getFailoverMethod()
+    {
+        return _connectionURL.getFailoverMethod();
+    }
+
+    @Override
+    public String getFailoverOption(final String key)
+    {
+        return _connectionURL.getFailoverOption(key);
+    }
+
+    @Override
+    public int getBrokerCount()
+    {
+        return _connectionURL.getBrokerCount();
+    }
+
+    @Override
+    public BrokerDetails getBrokerDetails(final int index)
+    {
+        return _connectionURL.getBrokerDetails(index);
+    }
+
+    @Override
+    public void addBrokerDetails(final BrokerDetails broker)
+    {
+        _connectionURL.addBrokerDetails(broker);
+    }
+
+    @Override
+    public void setBrokerDetails(final List<BrokerDetails> brokers)
+    {
+        _connectionURL.setBrokerDetails(brokers);
+    }
+
+    @Override
+    public List<BrokerDetails> getAllBrokerDetails()
+    {
+        return _connectionURL.getAllBrokerDetails();
+    }
+
+    @Override
+    public String getClientName()
+    {
+        return _connectionURL.getClientName();
+    }
+
+    @Override
+    public void setClientName(final String clientName)
+    {
+        _connectionURL.setClientName(clientName);
+    }
+
+    @Override
+    public String getUsername()
+    {
+        if (_extensions.containsKey(ConnectionExtension.USERNAME_OVERRIDE))
+        {
+            final BiFunction<Connection, URI, Object> extension =
+                    _extensions.get(ConnectionExtension.USERNAME_OVERRIDE);
+            final Object userName = extension.apply(_connectionSupplier.get(), _connectionURI);
+            return userName == null ? null : String.valueOf(userName);
+        }
+        else
+        {
+            return _connectionURL.getUsername();
+        }
+    }
+
+    @Override
+    public void setUsername(final String username)
+    {
+        _connectionURL.setUsername(username);
+    }
+
+    @Override
+    public String getPassword()
+    {
+        if (_extensions.containsKey(ConnectionExtension.PASSWORD_OVERRIDE))
+        {
+            final BiFunction<Connection, URI, Object> extension =
+                    _extensions.get(ConnectionExtension.PASSWORD_OVERRIDE);
+            final Object password = extension.apply(_connectionSupplier.get(), _connectionURI);
+            return password == null ? null : String.valueOf(password);
+        }
+        else
+        {
+            return _connectionURL.getPassword();
+        }
+    }
+
+    @Override
+    public void setPassword(final String password)
+    {
+        _connectionURL.setPassword(password);
+    }
+
+    @Override
+    public String getVirtualHost()
+    {
+        return _connectionURL.getVirtualHost();
+    }
+
+    @Override
+    public void setVirtualHost(final String virtualHost)
+    {
+        _connectionURL.setVirtualHost(virtualHost);
+    }
+
+    @Override
+    public String getOption(final String key)
+    {
+        return _connectionURL.getOption(key);
+    }
+
+    @Override
+    public void setOption(final String key, final String value)
+    {
+        _connectionURL.setOption(key, value);
+    }
+
+    @Override
+    public String getDefaultQueueExchangeName()
+    {
+        return _connectionURL.getDefaultQueueExchangeName();
+    }
+
+    @Override
+    public String getDefaultTopicExchangeName()
+    {
+        return _connectionURL.getDefaultTopicExchangeName();
+    }
+
+    @Override
+    public String getTemporaryQueueExchangeName()
+    {
+        return _connectionURL.getTemporaryQueueExchangeName();
+    }
+
+    @Override
+    public String getTemporaryTopicExchangeName()
+    {
+        return _connectionURL.getTemporaryTopicExchangeName();
+    }
+
+    @Override
+    public String toString()
+    {
+        return _connectionURL.toString();
+    }
+
+    void setConnectionSupplier(final Supplier<AMQConnection> connectionSupplier)
+    {
+        _connectionSupplier = connectionSupplier;
+    }
+}
diff --git a/client/src/main/java/org/apache/qpid/client/PooledConnectionFactory.java b/client/src/main/java/org/apache/qpid/client/PooledConnectionFactory.java
index 0f41ab0..abf6fba 100644
--- a/client/src/main/java/org/apache/qpid/client/PooledConnectionFactory.java
+++ b/client/src/main/java/org/apache/qpid/client/PooledConnectionFactory.java
@@ -64,8 +64,8 @@
 import org.apache.qpid.jms.*;
 import org.apache.qpid.url.URLSyntaxException;
 
-public class PooledConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory,
-                                                Referenceable
+public class PooledConnectionFactory extends AbstractConnectionFactory
+        implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, Referenceable
 {
 
     public static final String JNDI_ADDRESS_MAX_POOL_SIZE = "maxPoolSize";
@@ -262,11 +262,6 @@
         }
     }
 
-    protected CommonConnection newConnectionInstance(final ConnectionURL connectionDetails) throws QpidException
-    {
-        return new AMQConnection(connectionDetails);
-    }
-
     private ConnectionURL getConnectionURLOrError() throws IllegalStateException
     {
         final ConnectionURL connectionDetails = _connectionDetails.get();
diff --git a/client/src/main/java/org/apache/qpid/transport/Connection.java b/client/src/main/java/org/apache/qpid/transport/Connection.java
index 13289f1..f270665 100644
--- a/client/src/main/java/org/apache/qpid/transport/Connection.java
+++ b/client/src/main/java/org/apache/qpid/transport/Connection.java
@@ -223,7 +223,6 @@
             conSettings = settings;
             _redirecting.set(false);
             state = OPENING;
-            userID = settings.getUsername();
             connectionLost.set(false);
 
             securityLayer = SecurityLayerFactory.newInstance(getConnectionSettings());
diff --git a/doc/jms-client-0-8/src/docbkx/JMS-Client-JMS-Extensions.xml b/doc/jms-client-0-8/src/docbkx/JMS-Client-JMS-Extensions.xml
index e2a7732..431eb8a 100644
--- a/doc/jms-client-0-8/src/docbkx/JMS-Client-JMS-Extensions.xml
+++ b/doc/jms-client-0-8/src/docbkx/JMS-Client-JMS-Extensions.xml
@@ -22,7 +22,7 @@
 
 <appendix xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="JMS-Client-0-8-Appendix-JMS-Extensions">
   <title>JMS Extensions</title>
-  <para>This section illustrates using Qpid specific extentions to JMS for the managament of queues,
+  <para>This section illustrates using Qpid specific extensions to JMS for the management of connections, queues,
   exchanges and bindings.</para>
   <!-- TODO perhaps mention ConnectionListener?-->
   <important>
@@ -30,6 +30,104 @@
       subject to change and will not be supported in this form for AMQP 1.0. Instead, the reader is
       directed towards the Managment interfaces of the Broker.</para>
   </important>
+  <section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Connection">
+      <title>Connection extensions</title>
+      <para>Connection extensions allows overriding connection configurations like username or password
+          in response to some environment changes like account rotation or authentication token expiration.</para>
+      <para>
+          The extensions take the form of a BiFunction&lt;Connection, URI, Object&gt; passed into the
+          ConnectionFactory using the AMQConnectionFactory#setExtension(String, BiFunction).
+      </para>
+      <para>A table below lists supported extensions.</para>
+      <table pgwide="1">
+      <title>Connection Extensions</title>
+      <tgroup cols="2">
+      <thead>
+          <row>
+              <entry>Extension Name</entry>
+              <entry>Description</entry>
+          </row>
+      </thead>
+      <tbody>
+      <row>
+          <entry>username</entry>
+          <entry><para>Allows to hook a custom code for provisioning of user name which would be used in authentication
+              with a remote host.</para></entry>
+      </row>
+          <row>
+              <entry>password</entry>
+              <entry><para>Allows to hook a custom code for provisioning of user password which would be used in
+                  authentication with a remote host.</para></entry>
+          </row>
+      </tbody>
+      </tgroup>
+      </table>
+      <para>The following example illustrates how expirate OAUTH2 authentication token can be recreated</para>
+      <example>
+          <title>Inject password extension</title>
+          <programlisting>
+final String connectionURL = "...";                                                                    <co xml:id="ext-token-url" linkends="callout-ext-token-url"/>
+final TokenGenerator tokenGenerator = new TokenGenerator(...);                                         <co xml:id="ext-token-generator" linkends="callout-ext-token-generator"/>
+
+final BiFunction&lt;Connection, URI, Object&gt; tokenExtension = new BiFunction&lt;Connection, URI, Object&gt;()   <co xml:id="ext-token-extension" linkends="callout-ext-token-extension"/>
+{
+    private volatile String token;
+    private long currentTokenExpirationTime;
+
+    @Override
+    public Object apply(final Connection connection, final URI uri)
+    {
+        long currentTime = System.currentTimeMillis();
+        if (currentTime &gt; currentTokenExpirationTime)                                                  <co xml:id="ext-token-expiration-check" linkends="callout-ext-token-expiration-check"/>
+        {
+            this.token = tokenGenerator.generateAccessToken();                                         <co xml:id="ext-token-generation" linkends="callout-ext-token-generation"/>
+            this.currentTokenExpirationTime = tokenGenerator.getTokenExpirationTime(token);            <co xml:id="ext-token-exp-time" linkends="callout-ext-token-exp-time"/>
+        }
+        return this.token;
+    }
+};
+final AMQConnectionFactory factory = new AMQConnectionFactory(connectionURL);                          <co xml:id="ext-token-connection-factory" linkends="callout-ext-token-connection-factory"/>
+factory.setExtension(ConnectionExtension.PASSWORD_OVERRIDE.name(), tokenExtension);                    <co xml:id="ext-token-override" linkends="callout-ext-token-override"/>
+
+final Connection connection = factory.createConnection();                                              <co xml:id="ext-token-connection" linkends="callout-ext-token-connection"/>
+          </programlisting>
+      </example><calloutlist>
+          <callout xml:id="callout-ext-token-url" arearefs="ext-token-url">
+              <para>Connection URL</para>
+          </callout>
+          <callout xml:id="callout-ext-token-generator" arearefs="ext-token-generator">
+              <para>Helper object to generate access token for a specific OAUTH2 implementation </para>
+          </callout>
+          <callout xml:id="callout-ext-token-extension" arearefs="ext-token-extension">
+              <para>Password extension for token renewal</para>
+          </callout>
+          <callout xml:id="callout-ext-token-expiration-check" arearefs="ext-token-expiration-check">
+              <para>Check token expiration</para>
+          </callout>
+          <callout xml:id="callout-ext-token-generation" arearefs="ext-token-generation">
+              <para>Get new token</para>
+          </callout>
+          <callout xml:id="callout-ext-token-exp-time" arearefs="ext-token-exp-time">
+              <para>Preserve token expiration time</para>
+          </callout>
+          <callout xml:id="callout-ext-token-connection-factory" arearefs="ext-token-connection-factory">
+              <para>Create connection factory</para>
+          </callout>
+          <callout xml:id="callout-ext-token-override" arearefs="ext-token-override">
+              <para>Register password extension for token regeneration</para>
+          </callout>
+          <callout xml:id="callout-ext-token-connection" arearefs="ext-token-connection">
+              <para>Open connection</para>
+          </callout>
+      </calloutlist>
+      <para>In the snippet above an implementation of  BiFunction&lt;Connection, URI, Object&gt; is created at (3) for
+          access token provisioning. The function implementation checks the token expiration at (4) and regenerate
+          the token at (5) using a helper object (2) implementing calls to OAUTH2 specific API.
+          The token expiration time is preserved at (6) for the following reconnects attempts.
+          An instance of AMQConnectionFactory is created  at (7) for a given connection URL (1).
+          A password extension is registered at (8). JMS connection is open at (9). The example uses a hypothetical
+          class TokenGenerator invoking underlying  OAUTH2 API to generate/renew access token and get token expiration time.</para>
+  </section>
   <section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Queue">
     <title>Queue Management</title>
     <para>These extensions allow queues to be created or removed.</para>
diff --git a/systests/src/test/java/org/apache/qpid/systest/connection/ConnectionFactoryTest.java b/systests/src/test/java/org/apache/qpid/systest/connection/ConnectionFactoryTest.java
index c9eb034..e5db8ee 100644
--- a/systests/src/test/java/org/apache/qpid/systest/connection/ConnectionFactoryTest.java
+++ b/systests/src/test/java/org/apache/qpid/systest/connection/ConnectionFactoryTest.java
@@ -21,47 +21,54 @@
 package org.apache.qpid.systest.connection;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import java.net.InetSocketAddress;
 
-import javax.jms.Connection;
+import javax.jms.JMSException;
 
 import org.junit.Before;
 import org.junit.Test;
 
 import org.apache.qpid.client.AMQConnection;
 import org.apache.qpid.client.AMQConnectionFactory;
+import org.apache.qpid.client.ConnectionExtension;
 import org.apache.qpid.systest.core.BrokerAdmin;
 import org.apache.qpid.systest.core.JmsTestBase;
 
 public class ConnectionFactoryTest extends JmsTestBase
 {
-    private static final String CONNECTION_URL = "amqp://guest:guest@clientID/?brokerlist='tcp://%s:%d'";
-    private String _url;
+    private static final String CONNECTION_URL = "amqp://%s:%s@clientID/?brokerlist='tcp://%s:%d'";
+    private String _urlWithCredentials;
+    private String _urlWithoutCredentials;
+    private String _userName;
+    private String _password;
 
     @Before
     public void setUp()
     {
-        final InetSocketAddress brokerAddress = getBrokerAdmin().getBrokerAddress(BrokerAdmin.PortType.AMQP);
-        _url = String.format(CONNECTION_URL,
-                             brokerAddress.getHostString(),
-                             brokerAddress.getPort());
+        final BrokerAdmin brokerAdmin = getBrokerAdmin();
+        final InetSocketAddress brokerAddress = brokerAdmin.getBrokerAddress(BrokerAdmin.PortType.AMQP);
+        _userName = brokerAdmin.getValidUsername();
+        _password = brokerAdmin.getValidPassword();
+        final String host = brokerAddress.getHostString();
+        final int port = brokerAddress.getPort();
+        _urlWithCredentials = String.format(CONNECTION_URL, _userName, _password, host, port);
+        _urlWithoutCredentials = String.format(CONNECTION_URL, "", "", host, port);
     }
-    /**
-     * The username & password specified should not override the default
-     * specified in the URL.
-     */
+
     @Test
-    public void testCreateConnectionWithUsernamePassword() throws Exception
+    public void testCreateConnectionWithUsernamePasswordSetOnConnectionURL() throws Exception
     {
-        AMQConnectionFactory factory = new AMQConnectionFactory(_url);
+        AMQConnectionFactory factory = new AMQConnectionFactory(_urlWithCredentials);
 
         AMQConnection con = null;
         try
         {
             con = factory.createConnection();
-            assertEquals("Usernames used is different from the one in URL","guest",con.getConnectionURL().getUsername());
-            assertEquals("Password used is different from the one in URL","guest",con.getConnectionURL().getPassword());
+            assertEquals("Usernames used is different from the one in URL", _userName, con.getUsername());
+            assertEquals("Password used is different from the one in URL", _password, con.getPassword());
         }
         finally
         {
@@ -70,17 +77,18 @@
                 con.close();
             }
         }
+    }
 
+    @Test
+    public void testCreateConnectionWithUsernamePasswordPassedIntoCreateConnection() throws Exception
+    {
+        AMQConnectionFactory factory = new AMQConnectionFactory(_urlWithCredentials);
         AMQConnection con2 = null;
         try
         {
-            con2 = factory.createConnection("user", "pass");
-            assertEquals("Usernames used is different from the one in URL","user",con2.getConnectionURL().getUsername());
-            assertEquals("Password used is different from the one in URL","pass",con2.getConnectionURL().getPassword());
-        }
-        catch(Exception e)
-        {
-            // ignore
+            con2 = factory.createConnection("admin", "admin");
+            assertEquals("Usernames used is different from the one in URL", "admin", con2.getUsername());
+            assertEquals("Password used is different from the one in URL", "admin", con2.getPassword());
         }
         finally
         {
@@ -89,13 +97,20 @@
                 con2.close();
             }
         }
+    }
 
+
+    @Test
+    public void testCreateConnectionWithUsernamePasswordPassedIntoCreateConnectionWhenConnectionUrlWithoutCredentials()
+            throws Exception
+    {
+        AMQConnectionFactory factory = new AMQConnectionFactory(_urlWithoutCredentials);
         AMQConnection con3 = null;
         try
         {
-            con3 = factory.createConnection();
-            assertEquals("Usernames used is different from the one in URL","guest",con3.getConnectionURL().getUsername());
-            assertEquals("Password used is different from the one in URL","guest",con3.getConnectionURL().getPassword());
+            con3 = factory.createConnection(_userName, _password);
+            assertEquals("Usernames used is different from the one in URL", _userName, con3.getUsername());
+            assertEquals("Password used is different from the one in URL", _password, con3.getPassword());
         }
         finally
         {
@@ -106,20 +121,73 @@
         }
     }
 
-    /**
-     * Verifies that a connection can be made using an instance of AMQConnectionFactory created with the
-     * default constructor and provided with the connection url via setter.
-     */
     @Test
     public void testCreatingConnectionWithInstanceMadeUsingDefaultConstructor() throws Exception
     {
         AMQConnectionFactory factory = new AMQConnectionFactory();
-        factory.setConnectionURLString(_url);
+        factory.setConnectionURLString(_urlWithCredentials);
 
-        Connection con = null;
+        AMQConnection con = null;
         try
         {
             con = factory.createConnection();
+            assertNotNull(con);
+            assertEquals(_userName, con.getUsername());
+            assertEquals(_password, con.getPassword());
+        }
+        finally
+        {
+            if (con != null)
+            {
+                con.close();
+            }
+        }
+    }
+
+    @Test
+    public void testCreatingConnectionUsingUserNameAndPasswordExtensions() throws Exception
+    {
+        AMQConnectionFactory factory = new AMQConnectionFactory();
+        factory.setConnectionURLString(_urlWithoutCredentials);
+        factory.setExtension(ConnectionExtension.PASSWORD_OVERRIDE.getExtensionName(), (connection, uri) -> _password);
+        factory.setExtension(ConnectionExtension.USERNAME_OVERRIDE.getExtensionName(), (connection, uri) -> _userName);
+
+        AMQConnection con = null;
+        try
+        {
+            con = factory.createConnection();
+            assertNotNull(con);
+            assertEquals(_userName, con.getUsername());
+            assertEquals(_password, con.getPassword());
+        }
+        finally
+        {
+            if (con != null)
+            {
+                con.close();
+            }
+        }
+    }
+
+    @Test
+    public void testCreatingConnectionUsingUserNameAndPasswordExtensionsWhenRuntimeExceptionIsThrown() throws Exception
+    {
+        AMQConnectionFactory factory = new AMQConnectionFactory();
+        factory.setConnectionURLString(_urlWithoutCredentials);
+        factory.setExtension(ConnectionExtension.PASSWORD_OVERRIDE.getExtensionName(), (connection, uri) -> {
+            throw new RuntimeException("Test");
+        });
+        factory.setExtension(ConnectionExtension.USERNAME_OVERRIDE.getExtensionName(), (connection, uri) -> _userName);
+
+        AMQConnection con = null;
+        try
+        {
+            con = factory.createConnection();
+            fail("Exception is expected");
+        }
+        catch (JMSException e)
+        {
+            // pass
         }
         finally
         {