diff --git a/projects/cadcAccessControl-Admin/build.xml b/projects/cadcAccessControl-Admin/build.xml
index 69fca321f6832fdd7e56ab423a3c3b0051a8b49b..7d6e61846b6dadf85504be14efa70f0a910642bb 100644
--- a/projects/cadcAccessControl-Admin/build.xml
+++ b/projects/cadcAccessControl-Admin/build.xml
@@ -144,8 +144,7 @@
                 <pathelement path="${build}/test/class"/>
                 <pathelement path="${jars}:${testingJars}"/>
             </classpath>
-            <sysproperty key="ca.nrc.cadc.util.PropertiesReader.dir" value="test"/>
-            <test name="ca.nrc.cadc.ac.admin.integration.AdminIntTest" />
+            <test name="ca.nrc.cadc.ac.admin.UserAdminTest" />
             <formatter type="plain" usefile="false" />
         </junit>
     </target>
diff --git a/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/AbstractCommand.java b/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/AbstractCommand.java
index 9cda6f89fa9ab2a05d0e94c3a7bb9a19502620ef..5740a43e15bf4c3ecc81eeb0c19b227241dda2b8 100644
--- a/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/AbstractCommand.java
+++ b/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/AbstractCommand.java
@@ -136,7 +136,9 @@ public abstract class AbstractCommand implements PrivilegedAction<Object>
 
     protected <T extends Principal> UserPersistence<T> getUserPersistence()
     {
+        System.setProperty("java.naming.factory.initial", ContextFactoryImpl.class.getName());
+
         PluginFactory pluginFactory = new PluginFactory();
-        return pluginFactory.getUserPersistence();
+        return pluginFactory.createUserPersistence();
     }
 }
diff --git a/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/ContextFactoryImpl.java b/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/ContextFactoryImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbfb8efa2fd6a4a40ab9525959fd1e3e3b111058
--- /dev/null
+++ b/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/ContextFactoryImpl.java
@@ -0,0 +1,25 @@
+package ca.nrc.cadc.ac.admin;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+import java.util.Hashtable;
+
+/**
+ * A Simple ContextFactory.
+ */
+public class ContextFactoryImpl implements InitialContextFactory
+{
+
+    public ContextFactoryImpl()
+    {
+    }
+
+    @Override
+    public Context getInitialContext(Hashtable environment)
+        throws NamingException
+    {
+        return new ContextImpl();
+    }
+
+}
diff --git a/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/ContextImpl.java b/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/ContextImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0e09fcda743ef609677efbbdce523641b6bccb9
--- /dev/null
+++ b/projects/cadcAccessControl-Admin/src/ca/nrc/cadc/ac/admin/ContextImpl.java
@@ -0,0 +1,266 @@
+/*
+************************************************************************
+*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+*
+*  (c) 2011.                            (c) 2011.
+*  Government of Canada                 Gouvernement du Canada
+*  National Research Council            Conseil national de recherches
+*  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+*  All rights reserved                  Tous droits réservés
+*
+*  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+*  expressed, implied, or               énoncée, implicite ou légale,
+*  statutory, of any kind with          de quelque nature que ce
+*  respect to the software,             soit, concernant le logiciel,
+*  including without limitation         y compris sans restriction
+*  any warranty of merchantability      toute garantie de valeur
+*  or fitness for a particular          marchande ou de pertinence
+*  purpose. NRC shall not be            pour un usage particulier.
+*  liable in any event for any          Le CNRC ne pourra en aucun cas
+*  damages, whether direct or           être tenu responsable de tout
+*  indirect, special or general,        dommage, direct ou indirect,
+*  consequential or incidental,         particulier ou général,
+*  arising from the use of the          accessoire ou fortuit, résultant
+*  software.  Neither the name          de l'utilisation du logiciel. Ni
+*  of the National Research             le nom du Conseil National de
+*  Council of Canada nor the            Recherches du Canada ni les noms
+*  names of its contributors may        de ses  participants ne peuvent
+*  be used to endorse or promote        être utilisés pour approuver ou
+*  products derived from this           promouvoir les produits dérivés
+*  software without specific prior      de ce logiciel sans autorisation
+*  written permission.                  préalable et particulière
+*                                       par écrit.
+*
+*  This file is part of the             Ce fichier fait partie du projet
+*  OpenCADC project.                    OpenCADC.
+*
+*  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+*  you can redistribute it and/or       vous pouvez le redistribuer ou le
+*  modify it under the terms of         modifier suivant les termes de
+*  the GNU Affero General Public        la “GNU Affero General Public
+*  License as published by the          License” telle que publiée
+*  Free Software Foundation,            par la Free Software Foundation
+*  either version 3 of the              : soit la version 3 de cette
+*  License, or (at your option)         licence, soit (à votre gré)
+*  any later version.                   toute version ultérieure.
+*
+*  OpenCADC is distributed in the       OpenCADC est distribué
+*  hope that it will be useful,         dans l’espoir qu’il vous
+*  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+*  without even the implied             GARANTIE : sans même la garantie
+*  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+*  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+*  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+*  General Public License for           Générale Publique GNU Affero
+*  more details.                        pour plus de détails.
+*
+*  You should have received             Vous devriez avoir reçu une
+*  a copy of the GNU Affero             copie de la Licence Générale
+*  General Public License along         Publique GNU Affero avec
+*  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+*  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+*                                       <http://www.gnu.org/licenses/>.
+*
+*  $Revision: 5 $
+*
+************************************************************************
+*/
+
+package ca.nrc.cadc.ac.admin;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * A Simple JNDI context.
+ */
+public class ContextImpl implements Context
+{
+    Map<String,Object> map = new HashMap<String,Object>(1);
+
+    @Override
+    public Object lookup(String name) throws NamingException
+    {
+        return map.get(name);
+    }
+
+    @Override
+    public void bind(String name, Object value) throws NamingException
+    {
+        map.put(name,  value);
+    }
+
+    @Override
+    public Object addToEnvironment(String arg0, Object arg1)
+        throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public void bind(Name arg0, Object arg1) throws NamingException
+    {
+    }
+
+    @Override
+    public void close() throws NamingException
+    {
+    }
+
+    @Override
+    public Name composeName(Name arg0, Name arg1) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public String composeName(String arg0, String arg1)
+        throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public Context createSubcontext(Name arg0) throws NamingException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Context createSubcontext(String arg0) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public void destroySubcontext(Name arg0) throws NamingException
+    {
+    }
+
+    @Override
+    public void destroySubcontext(String arg0) throws NamingException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public Hashtable<?, ?> getEnvironment() throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public String getNameInNamespace() throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public NameParser getNameParser(Name arg0) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public NameParser getNameParser(String arg0) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public NamingEnumeration<NameClassPair> list(Name arg0)
+        throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public NamingEnumeration<NameClassPair> list(String arg0)
+        throws NamingException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public NamingEnumeration<Binding> listBindings(Name arg0)
+        throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public NamingEnumeration<Binding> listBindings(String arg0)
+        throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public Object lookup(Name arg0) throws NamingException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Object lookupLink(Name arg0) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public Object lookupLink(String arg0) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public void rebind(Name arg0, Object arg1) throws NamingException
+    {
+    }
+
+    @Override
+    public void rebind(String arg0, Object arg1) throws NamingException
+    {
+    }
+
+    @Override
+    public Object removeFromEnvironment(String arg0) throws NamingException
+    {
+        return null;
+    }
+
+    @Override
+    public void rename(Name arg0, Name arg1) throws NamingException
+    {
+    }
+
+    @Override
+    public void rename(String arg0, String arg1) throws NamingException
+    {
+    }
+
+    @Override
+    public void unbind(Name arg0) throws NamingException
+    {
+    }
+
+    @Override
+    public void unbind(String arg0) throws NamingException
+    {
+    }
+
+}
\ No newline at end of file
diff --git a/projects/cadcAccessControl-Admin/test/LdapConfig.properties b/projects/cadcAccessControl-Admin/test/LdapConfig.properties
index 57fdb97105d8df8f1a4fb538f734a7045feadbfb..27f519ac7bb015646dcdabdf763c2a6ebcf8c447 100644
--- a/projects/cadcAccessControl-Admin/test/LdapConfig.properties
+++ b/projects/cadcAccessControl-Admin/test/LdapConfig.properties
@@ -1,8 +1,48 @@
-# This are the configuration fields required by the unit tests
-server = proc5-03.cadc.dao.nrc.ca
+###############################################################
+#
+# LDAP Connection and Pool Configuration
+#
+#
+###############################################################
+
+# Read-only connection pool
+readOnly.servers = proc5-03.cadc.dao.nrc.ca
+readOnly.poolInitSize = 1
+readOnly.poolMaxSize = 2
+readOnly.poolPolicy = roundRobin
+readOnly.maxWait = 30000
+readOnly.createIfNeeded = false
+
+# Read-write connection pool
+readWrite.servers = proc5-03.cadc.dao.nrc.ca
+readWrite.poolInitSize = 1
+readWrite.poolMaxSize = 2
+readWrite.poolPolicy = roundRobin
+readWrite.maxWait = 30000
+readWrite.createIfNeeded = false
+
+# Unbound-Read-only connection pool
+unboundReadOnly.servers = proc5-03.cadc.dao.nrc.ca
+unboundReadOnly.poolInitSize = 1
+unboundReadOnly.poolMaxSize = 2
+unboundReadOnly.poolPolicy = roundRobin
+unboundReadOnly.maxWait = 30000
+unboundReadOnly.createIfNeeded = false
+
+# server configuration -- applies to all servers
+#dbrcHost = devLdap
+#port = 636
+#proxyUser = uid=webproxy,ou=SpecialUsers,dc=canfar,dc=net
+#usersDN = ou=Users,ou=ds,dc=canfar,dc=net
+#userRequestsDN = ou=userRequests,ou=ds,dc=canfar,dc=net
+#groupsDN = ou=Groups,ou=ds,dc=canfar,dc=net
+#adminGroupsDN = ou=adminGroups,ou=ds,dc=canfar,dc=net
+
+# tree without aci's
+dbrcHost = devLdap
 port = 389
-proxyUser = testproxy
-usersDn = ou=Users,ou=ds,dc=testcanfar
+proxyUser = uid=testproxy,ou=SpecialUsers,dc=testcanfar
+usersDN = ou=Users,ou=ds,dc=testcanfar
 userRequestsDN = ou=UserRequests,ou=ds,dc=testcanfar
-groupsDn = ou=Groups,ou=ds,dc=testcanfar
-adminGroupsDn = ou=adminGroups,ou=ds,dc=testcanfar
\ No newline at end of file
+groupsDN = ou=Groups,ou=ds,dc=testcanfar
+adminGroupsDN = ou=adminGroups,ou=ds,dc=testcanfar
\ No newline at end of file
diff --git a/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/CmdLineParserTest.java b/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/CmdLineParserTest.java
index 010dc0f64adbb2836a7bd610b80610210774a64d..c9c28332d14f22d5869334e70c7d035c5314cb50 100644
--- a/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/CmdLineParserTest.java
+++ b/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/CmdLineParserTest.java
@@ -209,7 +209,7 @@ public class CmdLineParserTest
     	}
     	catch (UsageException e)
     	{
-            String expected = "Missing command or ommand is not supported";
+            String expected = "Missing command or command is not supported";
             Assert.assertTrue(e.getMessage().contains(expected));
     	}
     	catch (Exception e)
diff --git a/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/integration/AdminIntTest.java b/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/UserAdminTest.java
similarity index 96%
rename from projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/integration/AdminIntTest.java
rename to projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/UserAdminTest.java
index ff8a4262841842871a8cbc45a1bdc81af9a44ca8..938f8b4a42ed8b44c7f2dd7312829c3d80571994 100644
--- a/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/integration/AdminIntTest.java
+++ b/projects/cadcAccessControl-Admin/test/src/ca/nrc/cadc/ac/admin/UserAdminTest.java
@@ -66,7 +66,7 @@
  *
  ************************************************************************
  */
-package ca.nrc.cadc.ac.admin.integration;
+package ca.nrc.cadc.ac.admin;
 
 import ca.nrc.cadc.ac.PersonalDetails;
 import ca.nrc.cadc.ac.User;
@@ -79,6 +79,7 @@ import ca.nrc.cadc.auth.DNPrincipal;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import ca.nrc.cadc.net.TransientException;
 import ca.nrc.cadc.util.Log4jInit;
+import ca.nrc.cadc.util.PropertiesReader;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.junit.BeforeClass;
@@ -99,9 +100,9 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-public class AdminIntTest
+public class UserAdminTest
 {
-    private static final Logger log = Logger.getLogger(AdminIntTest.class);
+    private static final Logger log = Logger.getLogger(UserAdminTest.class);
 
     static final String EXEC_CMD = "./test/scripts/userAdminTest";
 
@@ -117,6 +118,8 @@ public class AdminIntTest
         testCert = "build/test/class/cadcauthtest1.pem";
 
         config = LdapConfig.getLdapConfig();
+
+        System.setProperty(PropertiesReader.class.getName() + ".dir", "test");
     }
 
     @Test
@@ -350,15 +353,18 @@ public class AdminIntTest
                         if (isPending)
                         {
                             userDAO.addPendingUser(userRequest);
+                            log.debug("added pending user: " + username);
                         }
                         else
                         {
                             userDAO.addUser(userRequest);
+                            log.debug("added user: " + username);
                         }
                         return null;
                     }
                     catch (Exception e)
                     {
+                        log.error("Exception adding user: " + e.getMessage());
                         throw new Exception("Problems", e);
                     }
                 }
@@ -411,6 +417,7 @@ public class AdminIntTest
 
     <T extends Principal> LdapUserPersistence<T> getUserPersistence()
     {
+        System.setProperty("java.naming.factory.initial", ContextFactoryImpl.class.getName());
         return new LdapUserPersistence<T>();
     }
 
diff --git a/projects/cadcAccessControl-Server/config/LdapConfig.dev.properties b/projects/cadcAccessControl-Server/config/LdapConfig.dev.properties
index fe5eb3da0a422192242954a5dd0e31066c751052..63709c4b7827ffb0905633352bbb9f6830c82e9f 100644
--- a/projects/cadcAccessControl-Server/config/LdapConfig.dev.properties
+++ b/projects/cadcAccessControl-Server/config/LdapConfig.dev.properties
@@ -20,6 +20,14 @@ readWrite.poolPolicy = roundRobin
 readWrite.maxWait = 30000
 readWrite.createIfNeeded = false
 
+# Unbound-Read-write connection pool
+unboundReadOnly.servers = proc5-03.cadc.dao.nrc.ca
+unboundReadOnly.poolInitSize = 1
+unboundReadOnly.poolMaxSize = 1
+unboundReadOnly.poolPolicy = roundRobin
+unboundReadOnly.maxWait = 30000
+unboundReadOnly.createIfNeeded = false
+
 # server configuration -- applies to all servers
 dbrcHost = devLdap
 port = 636
diff --git a/projects/cadcAccessControl-Server/config/LdapConfig.properties b/projects/cadcAccessControl-Server/config/LdapConfig.properties
index 92e89644d9f1b14674eee4d90907e7e07c0e6d3f..da1d558032e275768f341552c67ed37aad431722 100644
--- a/projects/cadcAccessControl-Server/config/LdapConfig.properties
+++ b/projects/cadcAccessControl-Server/config/LdapConfig.properties
@@ -21,6 +21,14 @@ readWrite.poolPolicy = <roundRobin || fewestConnections>
 readWrite.maxWait = <timeout wait time in milliseconds>
 readWrite.createIfNeeded = <true || false> Go beyond poolMaxSize
 
+# Unbound-Read-only connection pool
+unboundReadOnly.servers = <list of ldap servers for readonly unbound access>
+unboundReadOnly.poolInitSize = <number of initial connections in the readonly pool>
+unboundReadOnly.poolMaxSize = <maximum number of connections in the readonly pool>
+unboundReadOnly.poolPolicy = <roundRobin || fewestConnections>
+unboundReadOnly.maxWait = <timeout wait time in milliseconds>
+unboundReadOnly.createIfNeeded = <true || false> Go beyond poolMaxSize
+
 # server configuration -- applies to all servers
 dbrcHost = <prodLdap || devLdap>
 port = <389 or 636>
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/ConnectionPools.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/ConnectionPools.java
new file mode 100644
index 0000000000000000000000000000000000000000..79956002b7e1ccdaa285cd6b932c1316220cdb2f
--- /dev/null
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/ConnectionPools.java
@@ -0,0 +1,100 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  Government of Canada                 Gouvernement du Canada
+ *  National Research Council            Conseil national de recherches
+ *  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+ *  All rights reserved                  Tous droits réservés
+ *
+ *  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+ *  expressed, implied, or               énoncée, implicite ou légale,
+ *  statutory, of any kind with          de quelque nature que ce
+ *  respect to the software,             soit, concernant le logiciel,
+ *  including without limitation         y compris sans restriction
+ *  any warranty of merchantability      toute garantie de valeur
+ *  or fitness for a particular          marchande ou de pertinence
+ *  purpose. NRC shall not be            pour un usage particulier.
+ *  liable in any event for any          Le CNRC ne pourra en aucun cas
+ *  damages, whether direct or           être tenu responsable de tout
+ *  indirect, special or general,        dommage, direct ou indirect,
+ *  consequential or incidental,         particulier ou général,
+ *  arising from the use of the          accessoire ou fortuit, résultant
+ *  software.  Neither the name          de l'utilisation du logiciel. Ni
+ *  of the National Research             le nom du Conseil National de
+ *  Council of Canada nor the            Recherches du Canada ni les noms
+ *  names of its contributors may        de ses  participants ne peuvent
+ *  be used to endorse or promote        être utilisés pour approuver ou
+ *  products derived from this           promouvoir les produits dérivés
+ *  software without specific prior      de ce logiciel sans autorisation
+ *  written permission.                  préalable et particulière
+ *                                       par écrit.
+ *
+ *  This file is part of the             Ce fichier fait partie du projet
+ *  OpenCADC project.                    OpenCADC.
+ *
+ *  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+ *  you can redistribute it and/or       vous pouvez le redistribuer ou le
+ *  modify it under the terms of         modifier suivant les termes de
+ *  the GNU Affero General Public        la “GNU Affero General Public
+ *  License as published by the          License” telle que publiée
+ *  Free Software Foundation,            par la Free Software Foundation
+ *  either version 3 of the              : soit la version 3 de cette
+ *  License, or (at your option)         licence, soit (à votre gré)
+ *  any later version.                   toute version ultérieure.
+ *
+ *  OpenCADC is distributed in the       OpenCADC est distribué
+ *  hope that it will be useful,         dans l’espoir qu’il vous
+ *  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+ *  without even the implied             GARANTIE : sans même la garantie
+ *  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+ *  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+ *  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+ *  General Public License for           Générale Publique GNU Affero
+ *  more details.                        pour plus de détails.
+ *
+ *  You should have received             Vous devriez avoir reçu une
+ *  a copy of the GNU Affero             copie de la Licence Générale
+ *  General Public License along         Publique GNU Affero avec
+ *  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+ *  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+ *                                       <http://www.gnu.org/licenses/>.
+ *
+ *  $Revision: 4 $
+ *
+ ************************************************************************
+ */
+
+package ca.nrc.cadc.ac.server.ldap;
+
+import java.util.Map;
+
+/**
+ * The object that is bound in JNDI to hold the LDAP pools.
+ */
+public class ConnectionPools
+{
+
+    private LdapConfig config;
+
+    private Map<String,LdapConnectionPool> pools;
+
+    public ConnectionPools(Map<String,LdapConnectionPool> pools, LdapConfig config)
+    {
+        this.pools = pools;
+        this.config = config;
+    }
+
+    public Map<String,LdapConnectionPool> getPools()
+    {
+        return pools;
+    }
+
+    public LdapConfig getConfig()
+    {
+        return config;
+    }
+
+}
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java
index a58366053d277cb3e038844c02688fcabe1b2131..1cd4d5205bff0bc8db1797f2564be840d99712e4 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConfig.java
@@ -81,7 +81,7 @@ import ca.nrc.cadc.util.MultiValuedProperties;
 import ca.nrc.cadc.util.PropertiesReader;
 
 /**
- * Reads and stores the LDAP configuration information. The information
+ * Reads and stores the LDAP configuration information.
  *
  * @author adriand
  *
@@ -94,6 +94,7 @@ public class LdapConfig
 
     public static final String READONLY_PREFIX = "readOnly.";
     public static final String READWRITE_PREFIX = "readWrite.";
+    public static final String UB_READONLY_PREFIX = "unboundReadOnly.";
     public static final String POOL_SERVERS = "servers";
     public static final String POOL_INIT_SIZE = "poolInitSize";
     public static final String POOL_MAX_SIZE = "poolMaxSize";
@@ -151,6 +152,23 @@ public class LdapConfig
             return createIfNeeded;
         }
 
+        @Override
+        public String toString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.append(" Servers: ");
+            for (String server : servers)
+            {
+                sb.append(" [" + server + "]");
+            }
+            sb.append(" initSize: " + initSize);
+            sb.append(" maxSize: " + maxSize);
+            sb.append(" policy: " + policy);
+            sb.append(" maxWait: " + maxWait);
+            sb.append(" createIfNeeded: " + createIfNeeded);
+            return sb.toString();
+        }
+
         @Override
         public boolean equals(Object other)
         {
@@ -183,6 +201,7 @@ public class LdapConfig
 
     private LdapPool readOnlyPool = new LdapPool();
     private LdapPool readWritePool = new LdapPool();
+    private LdapPool unboundReadOnlyPool = new LdapPool();
     private int port;
     private String usersDN;
     private String userRequestsDN;
@@ -204,16 +223,15 @@ public class LdapConfig
 
     public static LdapConfig getLdapConfig()
     {
-        return getLdapConfig(CONFIG);
+        return loadLdapConfig(CONFIG);
     }
 
-    public static LdapConfig getLdapConfig(String ldapProperties)
+    public static LdapConfig loadLdapConfig(String ldapProperties)
     {
         logger.debug("Reading LDAP properties from: " + ldapProperties);
         PropertiesReader pr = new PropertiesReader(ldapProperties);
 
         MultiValuedProperties config = pr.getAllProperties();
-
         if (config == null || config.keySet() == null)
         {
             throw new RuntimeException("failed to read any LDAP property ");
@@ -221,19 +239,9 @@ public class LdapConfig
 
         LdapConfig ldapConfig = new LdapConfig();
 
-        ldapConfig.readOnlyPool.servers = getMultiProperty(pr, READONLY_PREFIX + POOL_SERVERS);
-        ldapConfig.readOnlyPool.initSize = Integer.valueOf(getProperty(pr, READONLY_PREFIX + POOL_INIT_SIZE));
-        ldapConfig.readOnlyPool.maxSize = Integer.valueOf(getProperty(pr, READONLY_PREFIX + POOL_MAX_SIZE));
-        ldapConfig.readOnlyPool.policy = PoolPolicy.valueOf(getProperty(pr, READONLY_PREFIX + POOL_POLICY));
-        ldapConfig.readOnlyPool.maxWait = Long.valueOf(getProperty(pr, READONLY_PREFIX + MAX_WAIT));
-        ldapConfig.readOnlyPool.createIfNeeded = Boolean.valueOf(getProperty(pr, READONLY_PREFIX + CREATE_IF_NEEDED));
-
-        ldapConfig.readWritePool.servers = getMultiProperty(pr, READWRITE_PREFIX + POOL_SERVERS);
-        ldapConfig.readWritePool.initSize = Integer.valueOf(getProperty(pr, READWRITE_PREFIX + POOL_INIT_SIZE));
-        ldapConfig.readWritePool.maxSize = Integer.valueOf(getProperty(pr, READWRITE_PREFIX + POOL_MAX_SIZE));
-        ldapConfig.readWritePool.policy = PoolPolicy.valueOf(getProperty(pr, READWRITE_PREFIX + POOL_POLICY));
-        ldapConfig.readWritePool.maxWait = Long.valueOf(getProperty(pr, READONLY_PREFIX + MAX_WAIT));
-        ldapConfig.readWritePool.createIfNeeded = Boolean.valueOf(getProperty(pr, READONLY_PREFIX + CREATE_IF_NEEDED));
+        loadPoolConfig(ldapConfig.readOnlyPool, pr, READONLY_PREFIX);
+        loadPoolConfig(ldapConfig.readWritePool, pr, READWRITE_PREFIX);
+        loadPoolConfig(ldapConfig.unboundReadOnlyPool, pr, UB_READONLY_PREFIX);
 
         ldapConfig.dbrcHost = getProperty(pr, LDAP_DBRC_ENTRY);
         ldapConfig.port = Integer.valueOf(getProperty(pr, LDAP_PORT));
@@ -265,6 +273,16 @@ public class LdapConfig
         return ldapConfig;
     }
 
+    private static void loadPoolConfig(LdapPool pool, PropertiesReader pr, String prefix)
+    {
+        pool.servers = getMultiProperty(pr, prefix + POOL_SERVERS);
+        pool.initSize = Integer.valueOf(getProperty(pr, prefix + POOL_INIT_SIZE));
+        pool.maxSize = Integer.valueOf(getProperty(pr, prefix + POOL_MAX_SIZE));
+        pool.policy = PoolPolicy.valueOf(getProperty(pr, prefix + POOL_POLICY));
+        pool.maxWait = Long.valueOf(getProperty(pr, prefix + MAX_WAIT));
+        pool.createIfNeeded = Boolean.valueOf(getProperty(pr, prefix + CREATE_IF_NEEDED));
+    }
+
     private static String getProperty(PropertiesReader properties, String key)
     {
         String prop = properties.getFirstPropertyValue(key);
@@ -321,6 +339,9 @@ public class LdapConfig
         if ( !(l.readWritePool.equals(readWritePool)))
             return false;
 
+        if ( !(l.unboundReadOnlyPool.equals(unboundReadOnlyPool)))
+            return false;
+
         return true;
     }
 
@@ -338,6 +359,11 @@ public class LdapConfig
         return readWritePool;
     }
 
+    public LdapPool getUnboundReadOnlyPool()
+    {
+        return unboundReadOnlyPool;
+    }
+
     public String getUsersDN()
     {
         return this.usersDN;
@@ -386,12 +412,13 @@ public class LdapConfig
     public String toString()
     {
         StringBuilder sb = new StringBuilder();
-        sb.append("ldap dbrc host = ");
-        sb.append(dbrcHost);
-        sb.append(" port = ");
-        sb.append(port);
-        sb.append(" proxyUserDN = ");
-        sb.append(proxyUserDN);
+        sb.append(" ReadOnlyPool: [" + readOnlyPool + "]");
+        sb.append(" ReadWritePool: [" + readWritePool + "]");
+        sb.append(" UnboundReadOnlyPool: [" + unboundReadOnlyPool + "]");
+        sb.append(" Port: " + port);
+        sb.append(" dbrcHost: " + dbrcHost);
+        sb.append(" proxyUserDN: " + proxyUserDN);
+
         return sb.toString();
     }
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionPool.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionPool.java
index 3a41fff06b74d891d6fb20849e7f8884cfce2270..b17dfa6932eec9aaa91a0950a391650505630af9 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionPool.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionPool.java
@@ -81,7 +81,6 @@ import com.unboundid.ldap.sdk.LDAPConnection;
 import com.unboundid.ldap.sdk.LDAPConnectionOptions;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.LDAPReadWriteConnectionPool;
 import com.unboundid.ldap.sdk.RoundRobinServerSet;
 import com.unboundid.ldap.sdk.ServerSet;
 import com.unboundid.ldap.sdk.SimpleBindRequest;
@@ -98,51 +97,50 @@ public class LdapConnectionPool
 {
     private static final Logger logger = Logger.getLogger(LdapConnectionPool.class);
 
-    private static final int POOL_CHECK_INTERVAL_MILLESCONDS = 10000; // 10 seconds
-
     Profiler profiler = new Profiler(LdapConnectionPool.class);
 
     protected LdapConfig currentConfig;
-    private LDAPReadWriteConnectionPool pool;
+    private String poolName;
+    private LDAPConnectionPool pool;
     private Object poolMonitor = new Object();
     private LDAPConnectionOptions connectionOptions;
 
-    private long lastPoolCheck = System.currentTimeMillis();
-
-    public LdapConnectionPool()
-    {
-        this(LdapConfig.getLdapConfig());
-    }
-
-    public LdapConnectionPool(LdapConfig config)
+    public LdapConnectionPool(LdapConfig config, LdapPool poolConfig, String poolName, boolean boundPool)
     {
         if (config == null)
             throw new IllegalArgumentException("config required");
+        if (poolConfig == null)
+            throw new IllegalArgumentException("poolConfig required");
+        if (poolName == null)
+            throw new IllegalArgumentException("poolName required");
 
         connectionOptions = new LDAPConnectionOptions();
         connectionOptions.setUseSynchronousMode(true);
         connectionOptions.setAutoReconnect(true);
         currentConfig = config;
+        this.poolName = poolName;
         synchronized (poolMonitor)
         {
-            pool = createPool(currentConfig);
-            profiler.checkpoint("Create pool");
+            if (!boundPool)
+                pool = createPool(config, poolConfig, poolName, null, null);
+            else
+                pool = createPool(config, poolConfig, poolName, config.getAdminUserDN(), config.getAdminPasswd());
+            logger.debug(poolName + " statistics after create:\n" + pool.getConnectionPoolStatistics());
+            profiler.checkpoint("Create read only pool.");
         }
     }
 
-    public LDAPConnection getReadOnlyConnection() throws TransientException
+    public LDAPConnection getConnection() throws TransientException
     {
-        poolCheck();
-
         try
         {
             LDAPConnection conn = null;
             synchronized (poolMonitor)
             {
-                conn = pool.getReadConnection();
+                conn = pool.getConnection();
             }
-            logger.debug("Read pool statistics after borrow:\n" + pool.getReadPoolStatistics());
-            profiler.checkpoint("get read only connection");
+            logger.debug(poolName + " pool statistics after borrow:\n" + pool.getConnectionPoolStatistics());
+            profiler.checkpoint("get " + poolName + " only connection");
             conn.setConnectionOptions(connectionOptions);
 
             return conn;
@@ -153,40 +151,10 @@ public class LdapConnectionPool
         }
     }
 
-    public LDAPConnection getReadWriteConnection() throws TransientException
-    {
-        poolCheck();
-
-        try
-        {
-            LDAPConnection conn = null;
-            synchronized (poolMonitor)
-            {
-                conn = pool.getWriteConnection();
-            }
-
-            logger.debug("write pool statistics after borrow:\n" + pool.getWritePoolStatistics());
-            profiler.checkpoint("get read write connection");
-            conn.setConnectionOptions(connectionOptions);
-
-            return conn;
-        }
-        catch (LDAPException e)
-        {
-            throw new TransientException("Failed to get read write connection", e);
-        }
-    }
-
-    public void releaseReadOnlyConnection(LDAPConnection conn)
+    public void releaseConnection(LDAPConnection conn)
     {
-        pool.releaseReadConnection(conn);
-        logger.debug("Read pool statistics after release:\n" + pool.getReadPoolStatistics());
-    }
-
-    public void releaseReadWriteConnection(LDAPConnection conn)
-    {
-        pool.releaseWriteConnection(conn);
-        logger.debug("write pool statistics after release:\n" + pool.getWritePoolStatistics());
+        pool.releaseConnection(conn);
+        logger.debug(poolName + " pool statistics after release:\n" + pool.getConnectionPoolStatistics());
     }
 
     public LdapConfig getCurrentConfig()
@@ -196,9 +164,9 @@ public class LdapConnectionPool
 
     public void shutdown()
     {
-        logger.debug("Shutting down pool");
+        logger.debug("Closing pool...");
         pool.close();
-        profiler.checkpoint("Shutdown pool");
+        profiler.checkpoint("Pool closed.");
     }
 
     @Override
@@ -209,104 +177,50 @@ public class LdapConnectionPool
             pool.close();
     }
 
-    private void poolCheck()
-    {
-        if (timeToCheckPool())
-        {
-            // check to see if the configuration has changed
-            logger.debug("checking for ldap config change");
-            LdapConfig newConfig = LdapConfig.getLdapConfig();
-            if (!newConfig.equals(currentConfig))
-            {
-                logger.debug("Detected ldap configuration change, rebuilding pools");
-                boolean poolRecreated = false;
-                final LDAPReadWriteConnectionPool oldPool = pool;
-
-                synchronized (poolMonitor)
-                {
-                    // check to see if another thread has already
-                    // done the work
-                    if (timeToCheckPool())
-                    {
-                        this.currentConfig = newConfig;
-                        pool = createPool(currentConfig);
-                        profiler.checkpoint("Rebuild pool");
-                        lastPoolCheck = System.currentTimeMillis();
-                        poolRecreated = true;
-                    }
-                }
-
-                if (poolRecreated)
-                {
-                    // close the old pool in a separate thread
-                    Runnable closeOldPool = new Runnable()
-                    {
-                        public void run()
-                        {
-                            logger.debug("Closing old pool...");
-                            oldPool.close();
-                            logger.debug("Old pool closed.");
-                        }
-                    };
-                    Thread closePoolThread = new Thread(closeOldPool);
-                    closePoolThread.start();
-                }
+    private LDAPConnectionPool createPool(LdapConfig config, LdapPool poolConfig, String poolName, String bindID, String bindPW)
 
-            }
-            else
-            {
-                lastPoolCheck = System.currentTimeMillis();
-            }
-        }
-    }
-
-    private boolean timeToCheckPool()
-    {
-        return (System.currentTimeMillis() - lastPoolCheck) > POOL_CHECK_INTERVAL_MILLESCONDS;
-    }
-
-    private LDAPReadWriteConnectionPool createPool(LdapConfig config)
-    {
-        LDAPConnectionPool ro = createPool(config.getReadOnlyPool(), config);
-        LDAPConnectionPool rw = createPool(config.getReadOnlyPool(), config);
-        LDAPReadWriteConnectionPool pool = new LDAPReadWriteConnectionPool(ro, rw);
-        logger.debug("Read pool statistics after create:\n" + pool.getReadPoolStatistics());
-        logger.debug("Write pool statistics after create:\n" + pool.getWritePoolStatistics());
-        return pool;
-    }
-
-    private synchronized LDAPConnectionPool createPool(LdapPool pool, LdapConfig config)
     {
         try
         {
             logger.debug("LDAP Config: " + config);
-            String[] hosts = pool.getServers().toArray(new String[0]);
-            int[] ports = new int[pool.getServers().size()];
-            for (int i=0; i<pool.getServers().size(); i++)
+            String[] hosts = poolConfig.getServers().toArray(new String[0]);
+            int[] ports = new int[poolConfig.getServers().size()];
+            for (int i=0; i<poolConfig.getServers().size(); i++)
             {
                 ports[i] = config.getPort();
             }
 
             ServerSet serverSet = null;
-            if (pool.getPolicy().equals(PoolPolicy.roundRobin))
+            if (poolConfig.getPolicy().equals(PoolPolicy.roundRobin))
             {
                 serverSet = new RoundRobinServerSet(hosts, ports, LdapDAO.getSocketFactory(config));
             }
-            else if (pool.getPolicy().equals(PoolPolicy.fewestConnections))
+            else if (poolConfig.getPolicy().equals(PoolPolicy.fewestConnections))
             {
                 serverSet = new FewestConnectionsServerSet(hosts, ports, LdapDAO.getSocketFactory(config));
             }
             else
             {
-                throw new IllegalStateException("Unconfigured pool policy: " + pool.getPolicy());
+                throw new IllegalStateException("Unconfigured pool policy: " + poolConfig.getPolicy());
             }
 
-            SimpleBindRequest bindRequest = new SimpleBindRequest(config.getAdminUserDN(), config.getAdminPasswd());
+            SimpleBindRequest bindRequest = null;
+            if (bindID != null && bindPW != null)
+            {
+                logger.debug("Binding pool as " + bindID);
+                bindRequest = new SimpleBindRequest(bindID, bindPW);
+            }
+            else
+            {
+                logger.debug("Binding pool annonymously");
+                bindRequest = new SimpleBindRequest();
+            }
             LDAPConnectionPool connectionPool = new LDAPConnectionPool(
-                serverSet, bindRequest, pool.getInitSize(), pool.getMaxSize());
+                serverSet, bindRequest, poolConfig.getInitSize(), poolConfig.getMaxSize());
 
-            connectionPool.setCreateIfNecessary(pool.getCreateIfNeeded());
-            connectionPool.setMaxWaitTimeMillis(pool.getMaxWait());
+            connectionPool.setCreateIfNecessary(poolConfig.getCreateIfNeeded());
+            connectionPool.setMaxWaitTimeMillis(poolConfig.getMaxWait());
+            connectionPool.setConnectionPoolName(poolName);
 
             return connectionPool;
         }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnections.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnections.java
index 93d579c55a2e670507e4ff048d8482acbb833f61..efe43639ca42ca78f975e6cdfa17248ad5106710 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnections.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapConnections.java
@@ -75,6 +75,7 @@ import ca.nrc.cadc.net.TransientException;
 import ca.nrc.cadc.profiler.Profiler;
 
 import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
 
 /**
@@ -95,14 +96,19 @@ class LdapConnections
     Profiler profiler = new Profiler(LdapConnections.class);
 
     private LdapPersistence persistence;
+    private LdapConfig config;
+
+    private LdapConnectionPool readOnlyPool;
+    private LdapConnectionPool readWritePool;
+    private LdapConnectionPool unboundReadOnlyPool;
 
     private LDAPConnection autoConfigReadOnlyConn;
     private LDAPConnection autoConfigReadWriteConn;
-
-    private LdapConnectionPool pool;
+    private LDAPConnection autoConfigUnboundReadOnlyConn;
 
     private LDAPConnection manualConfigReadOnlyConn;
     private LDAPConnection manualConfigReadWriteConn;
+    private LDAPConnection manualConfigUnboundReadOnlyConn;
 
     LdapConnections(LdapPersistence persistence)
     {
@@ -111,11 +117,12 @@ class LdapConnections
         this.persistence = persistence;
     }
 
-    LdapConnections(LdapConnectionPool pool)
+    LdapConnections(LdapConfig config)
     {
-        if (pool == null)
-            throw new RuntimeException("pool object is required");
-        this.pool = pool;
+        if (config == null)
+            throw new RuntimeException("config object is required");
+
+        this.config = config;
     }
 
     LDAPConnection getReadOnlyConnection() throws TransientException
@@ -125,7 +132,7 @@ class LdapConnections
             if (autoConfigReadOnlyConn == null)
             {
                 log.debug("Getting new auto config read only connection.");
-                autoConfigReadOnlyConn = persistence.getReadOnlyConnection();
+                autoConfigReadOnlyConn = persistence.getConnection(LdapPersistence.POOL_READONLY);
                 profiler.checkpoint("Get read only connection");
             }
             else
@@ -136,10 +143,14 @@ class LdapConnections
         }
         else
         {
+            if (readOnlyPool == null)
+            {
+                readOnlyPool = new LdapConnectionPool(config, config.getReadOnlyPool(), LdapPersistence.POOL_READONLY, true);
+            }
             if (manualConfigReadOnlyConn == null)
             {
                 log.debug("Getting new manual config read only connection.");
-                manualConfigReadOnlyConn = pool.getReadOnlyConnection();
+                manualConfigReadOnlyConn = readOnlyPool.getConnection();
             }
             else
             {
@@ -156,7 +167,7 @@ class LdapConnections
             if (autoConfigReadWriteConn == null)
             {
                 log.debug("Getting new auto config read write connection.");
-                autoConfigReadWriteConn = persistence.getReadWriteConnection();
+                autoConfigReadWriteConn = persistence.getConnection(LdapPersistence.POOL_READWRITE);
                 profiler.checkpoint("Get read write connection");
             }
             else
@@ -167,10 +178,14 @@ class LdapConnections
         }
         else
         {
+            if (readWritePool == null)
+            {
+                readWritePool = new LdapConnectionPool(config, config.getReadWritePool(), LdapPersistence.POOL_READWRITE, true);
+            }
             if (manualConfigReadWriteConn == null)
             {
                 log.debug("Getting new manual config read write connection.");
-                manualConfigReadWriteConn = pool.getReadWriteConnection();
+                manualConfigReadWriteConn = readWritePool.getConnection();
             }
             else
             {
@@ -180,66 +195,80 @@ class LdapConnections
         }
     }
 
-    void releaseConnections()
+    LDAPConnection getUnboundReadOnlyConnection() throws TransientException
     {
         if (persistence != null)
         {
-            if (autoConfigReadOnlyConn != null)
+            if (autoConfigUnboundReadOnlyConn == null)
             {
-                log.debug("Releasing read only auto config connection.");
-                persistence.releaseReadOnlyConnection(autoConfigReadOnlyConn);
-                profiler.checkpoint("Release read only connection");
+                log.debug("Getting new auto config unbound read only connection.");
+                autoConfigUnboundReadOnlyConn = persistence.getConnection(LdapPersistence.POOL_UNBOUNDREADONLY);
+                profiler.checkpoint("Get read write connection");
             }
-            if (autoConfigReadWriteConn != null)
+            else
             {
-                log.debug("Releasing read write auto config connection.");
-                persistence.releaseReadWriteConnection(autoConfigReadWriteConn);
-                profiler.checkpoint("Release read write connection");
+                log.debug("Getting reused auto config unbound read only connection.");
             }
+            return autoConfigUnboundReadOnlyConn;
         }
         else
         {
-            if (manualConfigReadOnlyConn != null)
+            if (unboundReadOnlyPool == null)
             {
-                log.debug("Releasing read only manual config connection.");
-                pool.releaseReadOnlyConnection(manualConfigReadOnlyConn);
+                unboundReadOnlyPool = new LdapConnectionPool(config, config.getUnboundReadOnlyPool(), LdapPersistence.POOL_UNBOUNDREADONLY, false);
             }
-            if (manualConfigReadWriteConn != null)
+            if (manualConfigUnboundReadOnlyConn == null)
             {
-                log.debug("Releasing read write manual config connection.");
-                pool.releaseReadWriteConnection(manualConfigReadWriteConn);
+                log.debug("Getting new manual config unbound read only connection.");
+                manualConfigUnboundReadOnlyConn = unboundReadOnlyPool.getConnection();
+            }
+            else
+            {
+                log.debug("Getting reused manual config unbound read only connection.");
             }
+            return manualConfigUnboundReadOnlyConn;
         }
     }
 
-    void releaseConnectionsAfterError()
+    void releaseConnections()
     {
         if (persistence != null)
         {
             if (autoConfigReadOnlyConn != null)
             {
                 log.debug("Releasing read only auto config connection.");
-                persistence.releaseReadOnlyConnection(autoConfigReadOnlyConn);
+                persistence.releaseConnection(LdapPersistence.POOL_READONLY, autoConfigReadOnlyConn);
                 profiler.checkpoint("Release read only connection");
             }
             if (autoConfigReadWriteConn != null)
             {
                 log.debug("Releasing read write auto config connection.");
-                persistence.releaseReadWriteConnection(autoConfigReadWriteConn);
+                persistence.releaseConnection(LdapPersistence.POOL_READWRITE, autoConfigReadWriteConn);
                 profiler.checkpoint("Release read write connection");
             }
+            if (autoConfigUnboundReadOnlyConn != null)
+            {
+                log.debug("Releasing read only auto config connection.");
+                persistence.releaseConnection(LdapPersistence.POOL_UNBOUNDREADONLY, autoConfigUnboundReadOnlyConn);
+                profiler.checkpoint("Release read only connection");
+            }
         }
         else
         {
             if (manualConfigReadOnlyConn != null)
             {
                 log.debug("Releasing read only manual config connection.");
-                pool.releaseReadOnlyConnection(manualConfigReadOnlyConn);
+                readOnlyPool.releaseConnection(manualConfigReadOnlyConn);
             }
             if (manualConfigReadWriteConn != null)
             {
                 log.debug("Releasing read write manual config connection.");
-                pool.releaseReadWriteConnection(manualConfigReadWriteConn);
+                readWritePool.releaseConnection(manualConfigReadWriteConn);
+            }
+            if (manualConfigUnboundReadOnlyConn != null)
+            {
+                log.debug("Releasing read only manual config connection.");
+                unboundReadOnlyPool.releaseConnection(manualConfigUnboundReadOnlyConn);
             }
         }
     }
@@ -250,11 +279,23 @@ class LdapConnections
     @Override
     public void finalize()
     {
-        if (pool != null)
+        if (readOnlyPool != null)
         {
-            log.debug("Closing manual config connection pool--should only see this " +
+            log.debug("Closing manual config readonly connection pool--should only see this " +
             		"message when running unit tests.");
-            pool.shutdown();
+            readOnlyPool.shutdown();
+        }
+        if (readWritePool != null)
+        {
+            log.debug("Closing manual config readwrite connection pool--should only see this " +
+                    "message when running unit tests.");
+            readWritePool.shutdown();
+        }
+        if (unboundReadOnlyPool != null)
+        {
+            log.debug("Closing manual config unboundreadonly connection pool--should only see this " +
+                    "message when running unit tests.");
+            unboundReadOnlyPool.shutdown();
         }
     }
 
@@ -263,8 +304,7 @@ class LdapConnections
         if (persistence != null)
             return persistence.getCurrentConfig();
         else
-            return pool.getCurrentConfig();
-
+            return config;
     }
 
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
index 2c9d3080846f23c5f2a51ed81fed35f36f5cb563..98cf2297f9a15681fe4f9accadec345004a9a49d 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapDAO.java
@@ -109,6 +109,7 @@ public abstract class LdapDAO
     {
         this.connections = connections;
         config = connections.getCurrentConfig();
+        logger.debug("New LdapDAO instance, config: " + config);
     }
 
     public LDAPConnection getReadOnlyConnection() throws TransientException
@@ -121,6 +122,11 @@ public abstract class LdapDAO
         return connections.getReadWriteConnection();
     }
 
+    public LDAPConnection getUnboundReadConnection() throws TransientException
+    {
+        return connections.getUnboundReadOnlyConnection();
+    }
+
     public void close()
     {
         connections.releaseConnections();
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapPersistence.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapPersistence.java
index 484c2ab483fdb948b5d6ccc09930450831520bb8..045e5a806871d4e7ada090bd81e8220665024edd 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapPersistence.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapPersistence.java
@@ -69,6 +69,9 @@
 
 package ca.nrc.cadc.ac.server.ldap;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import javax.naming.InitialContext;
 import javax.naming.NameNotFoundException;
 import javax.naming.NamingException;
@@ -76,8 +79,10 @@ import javax.naming.NamingException;
 import org.apache.log4j.Logger;
 
 import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
 
+import ca.nrc.cadc.ac.server.ldap.LdapConfig.LdapPool;
 import ca.nrc.cadc.net.TransientException;
 import ca.nrc.cadc.profiler.Profiler;
 
@@ -87,12 +92,20 @@ import ca.nrc.cadc.profiler.Profiler;
  */
 public class LdapPersistence
 {
+
+    // pool names
+    public static final String POOL_READONLY = "readOnly";
+    public static final String POOL_READWRITE = "readWrite";
+    public static final String POOL_UNBOUNDREADONLY = "unboundReadOnly";
+
     private static final Logger logger = Logger.getLogger(LdapPersistence.class);
-    private static final String LDAP_POOL_JNDI_NAME = LdapConnectionPool.class.getName();
+    private static final String LDAP_POOL_JNDI_NAME = ConnectionPools.class.getName();
+    private static final int POOL_CHECK_INTERVAL_MILLESCONDS = 10000; // 10 seconds
 
     Profiler profiler = new Profiler(LdapPersistence.class);
 
-    private LdapConnectionPool pool;
+    private long lastPoolCheck = System.currentTimeMillis();
+    private ConnectionPools pools;
 
     // static monitor is required for when multiple LdapPersistence objects
     // are created.
@@ -100,38 +113,31 @@ public class LdapPersistence
 
     LdapPersistence()
     {
-        initPool();
-    }
-
-    protected LDAPConnection getReadOnlyConnection() throws TransientException
-    {
-        return pool.getReadOnlyConnection();
-    }
-
-    protected LDAPConnection getReadWriteConnection() throws TransientException
-    {
-        return pool.getReadWriteConnection();
+        initPools();
     }
 
-    protected void releaseReadOnlyConnection(LDAPConnection conn)
+    protected LDAPConnection getConnection(String poolName) throws TransientException
     {
-        pool.releaseReadOnlyConnection(conn);
+        poolCheck();
+        return pools.getPools().get(poolName).getConnection();
     }
 
-    protected void releaseReadWriteConnection(LDAPConnection conn)
+    protected void releaseConnection(String poolName, LDAPConnection conn)
     {
-        pool.releaseReadWriteConnection(conn);
+        pools.getPools().get(poolName).releaseConnection(conn);
     }
 
     protected LdapConfig getCurrentConfig()
     {
-        return pool.currentConfig;
+        return pools.getConfig();
     }
 
     protected void shutdown()
     {
-        // shutdown the pool
-        pool.shutdown();
+        // shutdown the pools
+        pools.getPools().get(POOL_READONLY).shutdown();
+        pools.getPools().get(POOL_READWRITE).shutdown();
+        pools.getPools().get(POOL_UNBOUNDREADONLY).shutdown();
 
         // unbind the pool
         try
@@ -141,31 +147,31 @@ public class LdapPersistence
         }
         catch (NamingException e)
         {
-            logger.warn("Could not unbind ldap pool", e);
+            logger.warn("Could not unbind ldap pools", e);
         }
     }
 
-    private void initPool()
+    private void initPools()
     {
         try
         {
-            pool = lookupPool();
-            logger.debug("Pool from JNDI lookup: " + pool);
+            pools = lookupPool();
+            logger.debug("Pool from JNDI lookup: " + pools);
 
-            if (pool == null)
+            if (pools == null)
             {
                 synchronized (jndiMonitor)
                 {
-                    pool = lookupPool();
-                    logger.debug("Pool from second JNDI lookup: " + pool);
-                    if (pool == null)
+                    pools = lookupPool();
+                    logger.debug("Pool from second JNDI lookup: " + pools);
+                    if (pools == null)
                     {
-                        pool = new LdapConnectionPool();
-                        profiler.checkpoint("Created LDAP connection pool");
+                        LdapConfig config = LdapConfig.getLdapConfig();
+                        pools = createPools(config);
                         InitialContext ic = new InitialContext();
-                        ic.bind(LDAP_POOL_JNDI_NAME, pool);
-                        profiler.checkpoint("Bound LDAP pool to JNDI");
-                        logger.debug("Bound LDAP pool to JNDI");
+                        ic.bind(LDAP_POOL_JNDI_NAME, pools);
+                        profiler.checkpoint("Bound LDAP pools to JNDI");
+                        logger.debug("Bound LDAP pools to JNDI");
                     }
                 }
             }
@@ -177,12 +183,25 @@ public class LdapPersistence
         }
     }
 
-    private LdapConnectionPool lookupPool() throws NamingException
+    private ConnectionPools createPools(LdapConfig config)
+    {
+        Map<String,LdapConnectionPool> poolMap = new HashMap<String,LdapConnectionPool>(3);
+        poolMap.put(POOL_READONLY, new LdapConnectionPool(
+            config, config.getReadOnlyPool(), POOL_READONLY, true));
+        poolMap.put(POOL_READWRITE, new LdapConnectionPool(
+            config, config.getReadWritePool(), POOL_READWRITE, true));
+        poolMap.put(POOL_UNBOUNDREADONLY, new LdapConnectionPool(
+            config, config.getUnboundReadOnlyPool(), POOL_UNBOUNDREADONLY, false));
+        profiler.checkpoint("Created 3 LDAP connection pools");
+        return new ConnectionPools(poolMap, config);
+    }
+
+    private ConnectionPools lookupPool() throws NamingException
     {
         try
         {
             InitialContext ic = new InitialContext();
-            return (LdapConnectionPool) ic.lookup(LDAP_POOL_JNDI_NAME);
+            return (ConnectionPools) ic.lookup(LDAP_POOL_JNDI_NAME);
         }
         catch (NameNotFoundException e)
         {
@@ -190,4 +209,79 @@ public class LdapPersistence
         }
     }
 
+    private void poolCheck() throws TransientException
+    {
+        if (timeToCheckPool())
+        {
+            // check to see if the configuration has changed
+            logger.debug("checking for ldap config change");
+            LdapConfig newConfig = LdapConfig.getLdapConfig();
+            if (!newConfig.equals(pools.getConfig()))
+            {
+                logger.debug("Detected ldap configuration change, rebuilding pools");
+                boolean poolRecreated = false;
+                final ConnectionPools oldPools = pools;
+
+                synchronized (jndiMonitor)
+                {
+                    // check to see if another thread has already
+                    // done the work
+                    if (timeToCheckPool())
+                    {
+                        try
+                        {
+                            ConnectionPools newPools = createPools(newConfig);
+                            InitialContext ic = new InitialContext();
+                            try
+                            {
+                                ic.unbind(LDAP_POOL_JNDI_NAME);
+                            }
+                            catch (NamingException e)
+                            {
+                                logger.warn("Could not unbind previous JNDI instance", e);
+                            }
+                            ic.bind(LDAP_POOL_JNDI_NAME, pools);
+                            profiler.checkpoint("Rebuild pools");
+                            lastPoolCheck = System.currentTimeMillis();
+                            pools = newPools;
+                            poolRecreated = true;
+                        }
+                        catch (NamingException e)
+                        {
+                            logger.debug("JNDI Naming Exception: " + e.getMessage());
+                            throw new TransientException("JNDI Naming Exception", e);
+                        }
+                    }
+                }
+
+                if (poolRecreated)
+                {
+                    // close the old pool in a separate thread
+                    Runnable closeOldPools = new Runnable()
+                    {
+                        public void run()
+                        {
+                            logger.debug("Closing old pools...");
+                            oldPools.getPools().get(POOL_READONLY).shutdown();
+                            oldPools.getPools().get(POOL_READWRITE).shutdown();
+                            oldPools.getPools().get(POOL_UNBOUNDREADONLY).shutdown();
+                            logger.debug("Old pools closed.");
+                        }
+                    };
+                    Thread closePoolsThread = new Thread(closeOldPools);
+                    closePoolsThread.start();
+                }
+            }
+            else
+            {
+                lastPoolCheck = System.currentTimeMillis();
+            }
+        }
+    }
+
+    private boolean timeToCheckPool()
+    {
+        return (System.currentTimeMillis() - lastPoolCheck) > POOL_CHECK_INTERVAL_MILLESCONDS;
+    }
+
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java
index 3940f5382b89cb503c3f38b2de1ce65304dcd0aa..414d1539849364204fc9f50525a5ae441de1ef45 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAO.java
@@ -213,11 +213,14 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         {
             BindRequest bindRequest = new SimpleBindRequest(
                 getUserDN(username, config.getUsersDN()), password);
-
-            String server = config.getReadOnlyPool().getServers().get(0);
-            int port = config.getPort();
-            LDAPConnection conn = new LDAPConnection(LdapDAO.getSocketFactory(config), server,
-                    config.getPort());
+//
+//            String server = config.getReadOnlyPool().getServers().get(0);
+//            int port = config.getPort();
+//            LDAPConnection conn = new LDAPConnection(LdapDAO.getSocketFactory(config), server,
+//                    config.getPort());
+//            BindResult bindResult = conn.bind(bindRequest);
+
+            LDAPConnection conn = this.getUnboundReadConnection();
             BindResult bindResult = conn.bind(bindRequest);
 
             if (bindResult != null && bindResult.getResultCode() == ResultCode.SUCCESS)
@@ -472,9 +475,10 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
         }
 
         SearchResultEntry searchResult = null;
+        Filter filter = null;
         try
         {
-            Filter filter = Filter.createEqualityFilter(searchField, userID.getName());
+            filter = Filter.createEqualityFilter(searchField, userID.getName());
             logger.debug("search filter: " + filter);
 
             SearchRequest searchRequest =
@@ -496,9 +500,26 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
 
         if (searchResult == null)
         {
-            String msg = "User not found " + userID.toString();
-            logger.debug(msg);
-            throw new UserNotFoundException(msg);
+            // determine if the user is not there of if the calling user
+            // doesn't have permission to see it
+            SearchRequest searchRequest =
+                    new SearchRequest(usersDN, SearchScope.ONE, filter, userAttribs);
+            try
+            {
+                searchResult = getReadOnlyConnection().searchForEntry(searchRequest);
+            }
+            catch (LDAPException e)
+            {
+                LdapDAO.checkLdapResult(e.getResultCode());
+            }
+
+            if (searchResult == null)
+            {
+                String msg = "User not found " + userID.toString();
+                logger.debug(msg);
+                throw new UserNotFoundException(msg);
+            }
+            throw new AccessControlException("Permission denied");
         }
 
         User<T> user = new User<T>(userID);
@@ -795,9 +816,10 @@ public class LdapUserDAO<T extends Principal> extends LdapDAO
                 new PasswordModifyExtendedRequest(
                     userDN.toNormalizedString(), oldPassword, newPassword, controls);
 
-            String server = config.getReadWritePool().getServers().get(0);
-            int port = config.getPort();
-            LDAPConnection conn = new LDAPConnection(LdapDAO.getSocketFactory(config), server, port);
+            LdapConfig ldapConfig = LdapConfig.getLdapConfig();
+            String server = ldapConfig.getReadWritePool().getServers().get(0);
+            int port = ldapConfig.getPort();
+            LDAPConnection conn = new LDAPConnection(LdapDAO.getSocketFactory(ldapConfig), server, port);
 
             PasswordModifyExtendedResult passwordModifyResult = (PasswordModifyExtendedResult)
                     conn.processExtendedOperation(passwordModifyRequest);
diff --git a/projects/cadcAccessControl-Server/test/LdapConfig.test.properties b/projects/cadcAccessControl-Server/test/LdapConfig.test.properties
index f2d9b8ba536fc48563dd990c9621678edaa1571b..9b64724758556ec0e03cb3a716a052fb5a3622f3 100644
--- a/projects/cadcAccessControl-Server/test/LdapConfig.test.properties
+++ b/projects/cadcAccessControl-Server/test/LdapConfig.test.properties
@@ -19,6 +19,14 @@ readWrite.poolPolicy = roundRobin
 readWrite.maxWait = 30000
 readWrite.createIfNeeded = false
 
+# Unbound-Read-write connection pool
+unboundReadOnly.servers = proc5-03.cadc.dao.nrc.ca
+unboundReadOnly.poolInitSize = 1
+unboundReadOnly.poolMaxSize = 1
+unboundReadOnly.poolPolicy = roundRobin
+unboundReadOnly.maxWait = 30000
+unboundReadOnly.createIfNeeded = false
+
 # server configuration -- applies to all servers
 dbrcHost = devLdap
 port = 636
diff --git a/projects/cadcAccessControl-Server/test/config/testConfig1.properties b/projects/cadcAccessControl-Server/test/config/testConfig1.properties
index b427f660db38b420ef89b256d11469f4b17ce954..130f087baee17061e35c408b69648943efffb899 100644
--- a/projects/cadcAccessControl-Server/test/config/testConfig1.properties
+++ b/projects/cadcAccessControl-Server/test/config/testConfig1.properties
@@ -21,6 +21,14 @@ readWrite.poolPolicy = fewestConnections
 readWrite.maxWait = 30000
 readWrite.createIfNeeded = false
 
+# Unbound-Read-only connection pool
+unboundReadOnly.servers = server1 server2 server3
+unboundReadOnly.poolInitSize = 3
+unboundReadOnly.poolMaxSize = 8
+unboundReadOnly.poolPolicy = roundRobin
+unboundReadOnly.maxWait = 30000
+unboundReadOnly.createIfNeeded = false
+
 # server configuration -- applies to all servers
 dbrcHost = devLdap
 port = 389
diff --git a/projects/cadcAccessControl-Server/test/config/testConfig2.properties b/projects/cadcAccessControl-Server/test/config/testConfig2.properties
index 32e494320625d42dbe8cd161309cc207ee612e9b..67c822c2fe523cca7e38e363a89f827e5a74ca6a 100644
--- a/projects/cadcAccessControl-Server/test/config/testConfig2.properties
+++ b/projects/cadcAccessControl-Server/test/config/testConfig2.properties
@@ -21,6 +21,14 @@ readWrite.poolPolicy = fewestConnections
 readWrite.maxWait = 30000
 readWrite.createIfNeeded = false
 
+# Unbound-Read-only connection pool
+unboundReadOnly.servers = serverA serverB serverC
+unboundReadOnly.poolInitSize = 0
+unboundReadOnly.poolMaxSize = 1
+unboundReadOnly.poolPolicy = fewestConnections
+unboundReadOnly.maxWait = 30000
+unboundReadOnly.createIfNeeded = false
+
 # server configuration -- applies to all servers
 dbrcHost = devLdap
 port = 389
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java
index 87abcdac7c5d70b2aadf0aca0aba438ae04ecbdd..0b50c482739c15ae84a1862ca8930ad79a10f075 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/RequestValidatorTest.java
@@ -70,7 +70,6 @@ package ca.nrc.cadc.ac.server;
 
 import ca.nrc.cadc.ac.Role;
 import ca.nrc.cadc.ac.server.web.groups.AddUserMemberActionTest;
-import ca.nrc.cadc.auth.AuthenticationUtil;
 import ca.nrc.cadc.auth.IdentityType;
 import ca.nrc.cadc.util.Log4jInit;
 import ca.nrc.cadc.uws.Parameter;
@@ -92,7 +91,7 @@ import static org.junit.Assert.*;
 public class RequestValidatorTest
 {
     private final static Logger log = Logger.getLogger(AddUserMemberActionTest.class);
-    
+
     @BeforeClass
     public static void setUpClass()
     {
@@ -106,16 +105,16 @@ public class RequestValidatorTest
     public void testValidate()
     {
         try
-        {   
+        {
             RequestValidator rv = new RequestValidator();
-            
+
             try
             {
                 rv.validate(null);
                 fail("null parameter list should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             List<Parameter> paramList = new ArrayList<Parameter>();
             try
             {
@@ -123,7 +122,7 @@ public class RequestValidatorTest
                 fail("empty parameter list should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             paramList.add(new Parameter("IDTYPE", "idtype"));
             paramList.add(new Parameter("ROLE", "role"));
             try
@@ -132,7 +131,7 @@ public class RequestValidatorTest
                 fail("missing ID parameter should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             paramList.clear();
             paramList.add(new Parameter("ID", "foo"));
             paramList.add(new Parameter("ROLE", "role"));
@@ -142,7 +141,7 @@ public class RequestValidatorTest
                 fail("missing IDTYPE parameter should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             paramList.clear();
             paramList.add(new Parameter("ID", "foo"));
             paramList.add(new Parameter("IDTYPE", "idtype"));
@@ -152,7 +151,7 @@ public class RequestValidatorTest
                 fail("missing ROLE parameter should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             paramList.clear();
             paramList.add(new Parameter("ID", "foo"));
             paramList.add(new Parameter("IDTYPE", IdentityType.USERNAME.getValue()));
@@ -163,7 +162,7 @@ public class RequestValidatorTest
                 fail("invalid ROLE parameter should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             paramList.clear();
             paramList.add(new Parameter("ID", "foo"));
             paramList.add(new Parameter("IDTYPE", IdentityType.USERNAME.getValue()));
@@ -175,20 +174,20 @@ public class RequestValidatorTest
                 fail("empty GROUPID parameter value should throw IllegalArgumentException");
             }
             catch (IllegalArgumentException ignore) {}
-            
+
             paramList.clear();
             paramList.add(new Parameter("ID", "foo"));
             paramList.add(new Parameter("IDTYPE", IdentityType.USERNAME.getValue()));
             paramList.add(new Parameter("ROLE", Role.MEMBER.getValue()));
             rv.validate(paramList);
-            
+
             assertNotNull(rv.getPrincipal());
             assertNotNull(rv.getRole());
             assertNull(rv.getGroupID());
-            
+
             paramList.add(new Parameter("GROUPID", "bar"));
             rv.validate(paramList);
-            
+
             assertNotNull(rv.getPrincipal());
             assertNotNull(rv.getRole());
             assertNotNull(rv.getGroupID());
@@ -199,5 +198,5 @@ public class RequestValidatorTest
             fail("unexpected error: " + t.getMessage());
         }
     }
-    
+
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java
index 5801981ac380b8f2314d4b321952cd86d15789ea..361fdd4fdff8de3c692a0eda21ab4b193272f7ef 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/AbstractLdapDAOTest.java
@@ -76,7 +76,7 @@ public class AbstractLdapDAOTest
 
     static protected LdapConfig getLdapConfig()
     {
-        return LdapConfig.getLdapConfig(CONFIG);
+        return LdapConfig.loadLdapConfig(CONFIG);
     }
 
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConfigTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConfigTest.java
index 32085f9ce85a0d65fb0299a6831768801e3dbd8a..2602442019ec617c1b11835c08b1625d7b0061ca 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConfigTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConfigTest.java
@@ -98,7 +98,7 @@ public class LdapConfigTest
         {
             System.setProperty(PropertiesReader.class.getName() + ".dir", "test/config");
 
-            LdapConfig c = LdapConfig.getLdapConfig("testConfig1.properties");
+            LdapConfig c = LdapConfig.loadLdapConfig("testConfig1.properties");
             Assert.assertEquals("devLdap", c.getDbrcHost());
             Assert.assertEquals(389, c.getPort());
             Assert.assertEquals("uid=testproxy,ou=SpecialUsers,dc=testcanfar", c.getProxyUserDN());
@@ -139,7 +139,7 @@ public class LdapConfigTest
         {
             System.setProperty(PropertiesReader.class.getName() + ".dir", "test/config");
 
-            LdapConfig c = LdapConfig.getLdapConfig("testConfig2.properties");
+            LdapConfig c = LdapConfig.loadLdapConfig("testConfig2.properties");
             Assert.assertEquals("devLdap", c.getDbrcHost());
             Assert.assertEquals(389, c.getPort());
             Assert.assertEquals("uid=testproxy,ou=SpecialUsers,dc=testcanfar", c.getProxyUserDN());
@@ -180,8 +180,8 @@ public class LdapConfigTest
         {
             System.setProperty(PropertiesReader.class.getName() + ".dir", "test/config");
 
-            LdapConfig c1 = LdapConfig.getLdapConfig("testConfig1.properties");
-            LdapConfig c2 = LdapConfig.getLdapConfig("testConfig1.properties");
+            LdapConfig c1 = LdapConfig.loadLdapConfig("testConfig1.properties");
+            LdapConfig c2 = LdapConfig.loadLdapConfig("testConfig1.properties");
             Assert.assertEquals(c1, c2);
         }
         catch (Throwable t)
@@ -202,8 +202,8 @@ public class LdapConfigTest
         {
             System.setProperty(PropertiesReader.class.getName() + ".dir", "test/config");
 
-            LdapConfig c1 = LdapConfig.getLdapConfig("testConfig2.properties");
-            LdapConfig c2 = LdapConfig.getLdapConfig("testConfig2.properties");
+            LdapConfig c1 = LdapConfig.loadLdapConfig("testConfig2.properties");
+            LdapConfig c2 = LdapConfig.loadLdapConfig("testConfig2.properties");
             Assert.assertEquals(c1, c2);
         }
         catch (Throwable t)
@@ -224,8 +224,8 @@ public class LdapConfigTest
         {
             System.setProperty(PropertiesReader.class.getName() + ".dir", "test/config");
 
-            LdapConfig c1 = LdapConfig.getLdapConfig("testConfig1.properties");
-            LdapConfig c2 = LdapConfig.getLdapConfig("testConfig2.properties");
+            LdapConfig c1 = LdapConfig.loadLdapConfig("testConfig1.properties");
+            LdapConfig c2 = LdapConfig.loadLdapConfig("testConfig2.properties");
             Assert.assertTrue(!c1.equals(c2));
         }
         catch (Throwable t)
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionsTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionsTest.java
index 5be2dedf121c0a0ff54e572e4a52256d5ad12c71..ef287d7dd11f8bbb7cfe24935fdacda00aefa9af 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionsTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapConnectionsTest.java
@@ -97,16 +97,21 @@ public class LdapConnectionsTest
         {
             LDAPConnection readConn = new LDAPConnection();
             LDAPConnection writeConn = new LDAPConnection();
+            LDAPConnection unReadConn = new LDAPConnection();
             LdapPersistence persistence = EasyMock.createMock(LdapPersistence.class);
 
-            EasyMock.expect(persistence.getReadOnlyConnection()).andReturn(readConn).once();
-            EasyMock.expect(persistence.getReadWriteConnection()).andReturn(writeConn).once();
+            EasyMock.expect(persistence.getConnection(LdapPersistence.POOL_READONLY)).andReturn(readConn).once();
+            EasyMock.expect(persistence.getConnection(LdapPersistence.POOL_READWRITE)).andReturn(writeConn).once();
+            EasyMock.expect(persistence.getConnection(LdapPersistence.POOL_UNBOUNDREADONLY)).andReturn(unReadConn).once();
             EasyMock.expect(persistence.getCurrentConfig()).andReturn(null).once();
 
-            persistence.releaseReadOnlyConnection(readConn);
+            persistence.releaseConnection(LdapPersistence.POOL_READONLY, readConn);
             EasyMock.expectLastCall().once();
 
-            persistence.releaseReadWriteConnection(writeConn);
+            persistence.releaseConnection(LdapPersistence.POOL_READWRITE, writeConn);
+            EasyMock.expectLastCall().once();
+
+            persistence.releaseConnection(LdapPersistence.POOL_UNBOUNDREADONLY, unReadConn);
             EasyMock.expectLastCall().once();
 
             EasyMock.replay(persistence);
@@ -122,57 +127,15 @@ public class LdapConnectionsTest
             connections.getReadWriteConnection();
             connections.getReadWriteConnection();
 
-            connections.getCurrentConfig();
-
-            connections.releaseConnections();
-
-            EasyMock.verify(persistence);
-
-        }
-        catch (Exception e)
-        {
-            log.error("Unexpected exception", e);
-            Assert.fail("Unexpected exception");
-        }
-    }
-
-    @Test
-    public void testManualConfig()
-    {
-        try
-        {
-            LDAPConnection readConn = new LDAPConnection();
-            LDAPConnection writeConn = new LDAPConnection();
-            LdapConnectionPool pool = EasyMock.createMock(LdapConnectionPool.class);
-
-            EasyMock.expect(pool.getReadOnlyConnection()).andReturn(readConn).once();
-            EasyMock.expect(pool.getReadWriteConnection()).andReturn(writeConn).once();
-            EasyMock.expect(pool.getCurrentConfig()).andReturn(null).once();
-
-            pool.releaseReadOnlyConnection(readConn);
-            EasyMock.expectLastCall().once();
-
-            pool.releaseReadWriteConnection(writeConn);
-            EasyMock.expectLastCall().once();
-
-            EasyMock.replay(pool);
-
-            LdapConnections connections = new LdapConnections(pool);
-
-            // multiple calls to get connections should only go to the pool once
-            connections.getReadOnlyConnection();
-            connections.getReadOnlyConnection();
-            connections.getReadOnlyConnection();
-
-            connections.getReadWriteConnection();
-            connections.getReadWriteConnection();
-            connections.getReadWriteConnection();
+            connections.getUnboundReadOnlyConnection();
+            connections.getUnboundReadOnlyConnection();
+            connections.getUnboundReadOnlyConnection();
 
             connections.getCurrentConfig();
 
             connections.releaseConnections();
 
-            EasyMock.verify(pool);
+            EasyMock.verify(persistence);
 
         }
         catch (Exception e)
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java
index 3ab3c073f4f4401154dada9c05caeafe7820cd97..f2df46ef4b392aa38d19427bcc83d2caba314f00 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapDAOTest.java
@@ -117,8 +117,7 @@ public class LdapDAOTest extends AbstractLdapDAOTest
 
         subject.getPrincipals().add(httpPrincipal);
 
-        LdapConnectionPool pool = new LdapConnectionPool(config);
-        LdapConnections connections = new LdapConnections(pool);
+        LdapConnections connections = new LdapConnections(config);
         final LdapDAOTestImpl ldapDao = new LdapDAOTestImpl(connections);
 
         Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
@@ -191,9 +190,8 @@ public class LdapDAOTest extends AbstractLdapDAOTest
         subject.getPrincipals().add(new X500Principal("uid=foo,o=bar"));
         subject.getPrincipals().add(dnPrincipal);
 
-        LdapConfig config = LdapConfig.getLdapConfig("LdapConfig.test.properties");
-        LdapConnectionPool pool = new LdapConnectionPool(config);
-        LdapConnections conn = new LdapConnections(pool);
+        LdapConfig config = LdapConfig.loadLdapConfig("LdapConfig.test.properties");
+        LdapConnections conn = new LdapConnections(config);
         final LdapDAO ldapDAO = new LdapDAO(conn) { }; // abstract
 
         DN actual = Subject.doAs(subject, new PrivilegedAction<DN>()
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java
index f559a730ec1a77c3f7a4c5e07b48bbb3501f40be..e67fa02bf086c2bc0c1af69061093e02a0172010 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapGroupDAOTest.java
@@ -164,8 +164,7 @@ public class LdapGroupDAOTest extends AbstractLdapDAOTest
 
     LdapGroupDAO<X500Principal> getGroupDAO() throws Exception
     {
-        LdapConnectionPool pool = new LdapConnectionPool(config);
-        LdapConnections connections = new LdapConnections(pool);
+        LdapConnections connections = new LdapConnections(config);
         return new LdapGroupDAO<X500Principal>(connections,
                 new LdapUserDAO<X500Principal>(connections));
     }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java
index 86249c43c143b83815870691bd4e622970dea81a..df58629d3c93b3d6de4251b3c2cb8712f3032035 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/ldap/LdapUserDAOTest.java
@@ -162,8 +162,7 @@ public class LdapUserDAOTest extends AbstractLdapDAOTest
 
     <T extends Principal> LdapUserDAO<T> getUserDAO() throws Exception
     {
-        LdapConnectionPool pool = new LdapConnectionPool(config);
-        LdapConnections connections = new LdapConnections(pool);
+        LdapConnections connections = new LdapConnections(config);
         return new LdapUserDAO(connections){
             protected int genNextNumericId()
             {