ACE-452 / ACE-531 - fixed additional issues:

- renamed the implementation to UserAdminRepository, it is an implementation
  detail that it is based on ACE's repository mechanism;
- reworked the implementation to no longer register and use its own remote
  repository, but let this be injected when needed. This allows us to use the
  same bundle for *both* the server and client side without difficult
  configuration issues. The only assumption we now have to think of is that a
  local and remote repository cannot be used at the same time in a single JVM;
- some other tests and utilities needed some attention to make sure that we do
  not try to run a local and remote repository in the same JVM.



git-svn-id: https://svn.apache.org/repos/asf/ace/trunk@1731750 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java b/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java
index f6ed34b..e04e28f 100644
--- a/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java
+++ b/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java
@@ -122,11 +122,7 @@
             RepositoryConstants.REPOSITORY_CUSTOMER, "apache",
             RepositoryConstants.REPOSITORY_MASTER, "true");
 
-        configure("org.apache.ace.repository.servlet.RepositoryServlet",
-            HttpConstants.ENDPOINT, "/repository", "authentication.enabled", "false");
-
         configure("org.apache.ace.useradmin.repository",
-            "repositoryLocation", "http://localhost:" + TestConstants.PORT + "/repository",
             "repositoryCustomer", "apache",
             "repositoryName", "users");
 
diff --git a/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java b/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java
index 770c20b..5de92d1 100644
--- a/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java
+++ b/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java
@@ -45,7 +45,20 @@
 
     static void closeSilently(HttpURLConnection resource) {
         if (resource != null) {
-            resource.disconnect();
+            try {
+                flushStream(resource.getInputStream());
+            }
+            catch (IOException exception) {
+                // Ignore...
+            }
+            try {
+                InputStream es = resource.getErrorStream();
+                if (es != null) {
+                    flushStream(es);
+                }
+            } finally {
+                resource.disconnect();
+            }
         }
     }
 
@@ -79,20 +92,17 @@
 
         URL url = new URL(host, endpoint + "?customer=" + customer + "&name=" + name + "&version=" + version);
 
-        InputStream input = null;
-        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-        try {
-            responseCode = connection.getResponseCode();
-            input = connection.getInputStream();
-
+        HttpURLConnection connection = openConnection(url);
+        try (InputStream input = connection.getInputStream()) {
             copy(input, out);
             out.flush();
+
+            responseCode = connection.getResponseCode();
         }
         catch (IOException e) {
             responseCode = handleIOException(connection);
         }
         finally {
-            closeSilently(input);
             closeSilently(connection);
         }
 
@@ -103,13 +113,14 @@
      * @see http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html
      */
     static int handleIOException(HttpURLConnection conn) {
-        int respCode = -1;
+        int respCode = -2;
         try {
             respCode = conn.getResponseCode();
             flushStream(conn.getErrorStream());
         }
         catch (IOException ex) {
             // deal with the exception
+            ex.printStackTrace();
         }
         return respCode;
     }
@@ -117,36 +128,32 @@
     static int put(URL host, String endpoint, String customer, String name, String version, InputStream in) throws IOException {
         URL url = new URL(host, endpoint + "?customer=" + customer + "&name=" + name + "&version=" + version);
 
-        int responseCode;
-        HttpURLConnection connection = null;
-        OutputStream out = null;
+        int rc;
+        HttpURLConnection connection = openConnection(url);
+        connection.setDoOutput(true);
+        // ACE-294: enable streaming mode causing only small amounts of memory to be
+        // used for this commit. Otherwise, the entire input stream is cached into
+        // memory prior to sending it to the server...
+        connection.setChunkedStreamingMode(8192);
+        connection.setRequestProperty("Content-Type", MIME_APPLICATION_OCTET_STREAM);
 
-        try {
-            connection = (HttpURLConnection) url.openConnection();
-            connection.setDoOutput(true);
-            // ACE-294: enable streaming mode causing only small amounts of memory to be
-            // used for this commit. Otherwise, the entire input stream is cached into
-            // memory prior to sending it to the server...
-            connection.setChunkedStreamingMode(8192);
-            connection.setRequestProperty("Content-Type", MIME_APPLICATION_OCTET_STREAM);
-            out = connection.getOutputStream();
-
+        try (OutputStream out = connection.getOutputStream()) {
             copy(in, out);
+
             out.flush();
 
-            responseCode = connection.getResponseCode();
+            rc = connection.getResponseCode();
             flushStream(connection.getInputStream());
         }
         catch (IOException e) {
-            responseCode = handleIOException(connection);
+            rc = handleIOException(connection);
         }
         finally {
             closeSilently(in);
-            closeSilently(out);
             closeSilently(connection);
         }
 
-        return responseCode;
+        return rc;
     }
 
     static int query(URL host, String endpoint, String customer, String name, OutputStream out) throws IOException {
@@ -156,22 +163,18 @@
         URL url = new URL(host, endpoint + filter);
 
         int responseCode;
-        HttpURLConnection connection = null;
-        InputStream input = null;
+        HttpURLConnection connection = openConnection(url);
 
-        try {
-            connection = (HttpURLConnection) url.openConnection();
-            responseCode = connection.getResponseCode();
-            input = connection.getInputStream();
-
+        try (InputStream input = connection.getInputStream()) {
             copy(input, out);
             out.flush();
+
+            responseCode = connection.getResponseCode();
         }
         catch (IOException e) {
             responseCode = handleIOException(connection);
         }
         finally {
-            closeSilently(input);
             closeSilently(out);
             closeSilently(connection);
         }
@@ -182,12 +185,10 @@
     static void waitForWebserver(URL host) throws IOException {
         int retries = 1, rc = -1;
         IOException ioe = null;
-        HttpURLConnection conn = null;
         while (retries++ < 10) {
+            HttpURLConnection connection = openConnection(host);
             try {
-                conn = (HttpURLConnection) host.openConnection();
-
-                rc = conn.getResponseCode();
+                rc = connection.getResponseCode();
                 if (rc >= 0) {
                     return;
                 }
@@ -203,17 +204,28 @@
                 }
             }
             catch (IOException e) {
-                rc = handleIOException(conn);
+                rc = handleIOException(connection);
             }
             finally {
-                if (conn != null) {
-                    conn.disconnect();
+                if (connection != null) {
+                    connection.disconnect();
                 }
-                conn = null;
             }
         }
         if (ioe != null) {
             throw ioe;
         }
     }
+
+    private static HttpURLConnection openConnection(URL url) throws IOException {
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setInstanceFollowRedirects(false);
+        conn.setAllowUserInteraction(false);
+        conn.setDefaultUseCaches(false);
+        conn.setUseCaches(false);
+        conn.setConnectTimeout(1000);
+        conn.setReadTimeout(1000);
+
+        return conn;
+    }
 }
diff --git a/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java b/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java
index c24fc95..180cec5 100644
--- a/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java
+++ b/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java
@@ -132,7 +132,7 @@
             }
             else {
                 if ((name != null) && (customer != null)) {
-                    handleQuery("(&(customer=" + customer + ")(name=" + name + "))", response);
+                    handleQuery(getRepositoryFilter(customer, name), response);
                 }
                 else if (name != null) {
                     handleQuery("(name=" + name + ")", response);
@@ -288,7 +288,7 @@
     private void handleCheckout(String customer, String name, long version, HttpServletResponse response) throws IOException {
         List<ServiceReference<REPO_TYPE>> refs;
         try {
-            refs = getRepositories("(&(customer=" + customer + ")(name=" + name + "))");
+            refs = getRepositories(getRepositoryFilter(customer, name));
         }
         catch (InvalidSyntaxException e) {
             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid filter syntax: " + e.getMessage());
@@ -336,7 +336,7 @@
     private void handleCommit(String customer, String name, long version, InputStream data, HttpServletResponse response) throws IOException {
         List<ServiceReference<REPO_TYPE>> refs;
         try {
-            refs = getRepositories("(&(customer=" + customer + ")(name=" + name + "))");
+            refs = getRepositories(getRepositoryFilter(customer, name));
         }
         catch (InvalidSyntaxException e) {
             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid filter syntax: " + e.getMessage());
@@ -363,7 +363,7 @@
                     response.sendError(HttpServletResponse.SC_NOT_MODIFIED, "Could not commit");
                 }
                 else {
-                    response.sendError(HttpServletResponse.SC_OK);
+                    response.setStatus(HttpServletResponse.SC_OK);
                 }
             }
             catch (IllegalArgumentException e) {
@@ -381,6 +381,10 @@
         }
     }
 
+    private String getRepositoryFilter(String customer, String name) {
+        return "(&(customer=" + customer + ")(name=" + name + ")(master=*))";
+    }
+
     /**
      * Handles a query command and sends back the response.
      */
diff --git a/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java b/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java
similarity index 65%
rename from org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java
rename to org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java
index 00e3774..4342cd2 100644
--- a/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java
+++ b/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java
@@ -22,7 +22,10 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
 import java.net.URL;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.ace.http.listener.constants.HttpConstants;
 import org.apache.ace.it.IntegrationTestBase;
@@ -33,129 +36,160 @@
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.UserAdmin;
 
-public class RepositoryBasedRoleRepositoryStoreTest extends IntegrationTestBase {
+public class UserAdminRepositoryTest extends IntegrationTestBase {
 
     private URL m_host;
 
     private volatile UserAdmin m_userAdmin;
     private volatile Repository m_repository;
 
+    public void testUpdateUserFetchedBeforeRepoSync() throws Exception {
+        long high = m_repository.getRange().getHigh();
+        Role user1 = m_userAdmin.createRole("user1", Role.USER);
+        high = waitForRepoChange(high);
+        user1.getProperties().put("test", "property");
+        high = waitForRepoChange(high);
+
+        // Write a new version directly to the repository
+        long latest = m_repository.getRange().getHigh();
+        // Use *remote* repository...
+        URL remoteURL = new URL("http://localhost:" + TestConstants.PORT + "/repository/commit?customer=apache&name=user&version=" + latest);
+        HttpURLConnection conn = (HttpURLConnection) remoteURL.openConnection();
+        try {
+            conn.setDoOutput(true);
+            conn.setChunkedStreamingMode(8192);
+            conn.setRequestProperty("Content-Type", "application/octet-stream");
+            
+            try (OutputStream os = conn.getOutputStream()) {
+                os.write("<roles><user name=\"user1\"><properties><test>changed</test></properties></user></roles>".getBytes());
+            }
+            assertEquals(200, conn.getResponseCode());
+        } finally {
+            conn.disconnect();
+        }
+
+        try {
+            // Expect that updating properties fails as the user was fetched before the repository was refreshed
+            user1.getProperties().put("this", "fails");
+            fail("Expected IllegalStateException");
+        }
+        catch (IllegalStateException e) {
+            // expected
+        }
+
+        Role role = m_userAdmin.getRole("user1");
+        assertEquals("changed", role.getProperties().get("test"));
+    }
+
+    public void testAddUser() throws Exception {
+        long high = m_repository.getRange().getHigh();
+        m_userAdmin.createRole("user1", Role.USER);
+        waitForRepoChange(high);
+
+        String repoContentsAsString = getRepoContentsAsString();
+        assertTrue(repoContentsAsString.contains("<user name=\"user1\">"));
+    }
+
+    public void testDuplicateAddUser() throws Exception {
+        Role role = m_userAdmin.createRole("user1", Role.USER);
+        assertEquals("user1", role.getName());
+        Role dup = m_userAdmin.createRole("user1", Role.USER);
+        assertNull(dup);
+    }
+
+    public void testRemoveUser() throws Exception {
+        // Write a new version directly to the repository
+        SortedRangeSet range = m_repository.getRange();
+        try (InputStream is = new ByteArrayInputStream("<roles><user name=\"user1\"><properties><test>changed</test></properties></user></roles>".getBytes())) {
+            m_repository.commit(is, range.getHigh());
+        }
+
+        Role role = m_userAdmin.getRole("user1");
+        assertEquals("user1", role.getName());
+
+        boolean removeRole = m_userAdmin.removeRole("user1");
+        assertTrue(removeRole);
+
+        Role afterRemove = m_userAdmin.getRole("user1");
+        assertNull(afterRemove);
+
+        boolean removeRoleAgain = m_userAdmin.removeRole("user1");
+        assertFalse(removeRoleAgain);
+    }
+
+    public void testUpdateUser() throws Exception {
+        long high = m_repository.getRange().getHigh();
+        Role user1 = m_userAdmin.createRole("user1", Role.USER);
+
+        high = waitForRepoChange(high);
+
+        user1.getProperties().put("test", "property");
+        waitForRepoChange(high);
+
+        String repoContentsAsString = getRepoContentsAsString();
+        assertTrue(repoContentsAsString.contains("<test>property</test>"));
+    }
+
+    public void testUpdateUserRepoOutOfSync() throws Exception {
+        long high = m_repository.getRange().getHigh();
+        Role user1 = m_userAdmin.createRole("user1", Role.USER);
+        high = waitForRepoChange(high);
+        user1.getProperties().put("test", "property");
+        waitForRepoChange(high);
+
+        // Write a new version directly to the repository
+        SortedRangeSet range = m_repository.getRange();
+        try (InputStream is = new ByteArrayInputStream("<roles><user name=\"user1\"><properties><test>changed</test></properties></user></roles>".getBytes())) {
+            m_repository.commit(is, range.getHigh());
+        }
+
+        try {
+            // Expect that updating properties fails as the user object is out of date
+            user1.getProperties().put("this", "fails");
+            fail("Expected IllegalStateException");
+        }
+        catch (IllegalStateException e) {
+            // expected
+        }
+
+        Role role = m_userAdmin.getRole("user1");
+        assertEquals("changed", role.getProperties().get("test"));
+    }
+
+    protected void configureProvisionedServices() throws Exception {
+        m_host = new URL("http://localhost:" + TestConstants.PORT);
+
+        configure("org.apache.ace.repository.servlet.RepositoryServlet",
+            HttpConstants.ENDPOINT, "/repository", "authentication.enabled", "false");
+
+        configureFactory("org.apache.ace.server.repository.factory",
+            "customer", "apache",
+            "name", "user",
+            "master", "true",
+            "initial", "<roles></roles>");
+
+        configure("org.apache.ace.useradmin.repository",
+            "repositoryLocation", "http://localhost:" + TestConstants.PORT + "/repository",
+            "repositoryCustomer", "apache",
+            "repositoryName", "user");
+
+        Utils.waitForWebserver(m_host);
+    }
+
     @Override
     protected Component[] getDependencies() {
         return new Component[] {
             createComponent()
                 .setImplementation(this)
                 .add(createServiceDependency().setService(UserAdmin.class).setRequired(true))
-                .add(createServiceDependency().setService(Repository.class, "(&(customer=apache)(name=user))").setRequired(true))
+                .add(createServiceDependency().setService(Repository.class, "(&(customer=apache)(name=user)(!(remote=*)))").setRequired(true))
         };
     }
-    
-    public void testAddUser() throws Exception {
-        long high = m_repository.getRange().getHigh();
-        m_userAdmin.createRole("Piet", Role.USER);
-        waitForRepoChange(high);
-        
-        String repoContentsAsString = getRepoContentsAsString();
-        assertTrue(repoContentsAsString.contains("<user name=\"Piet\">"));
-    }
-    
-    public void testDuplicateAddUser() throws Exception {
-        Role role = m_userAdmin.createRole("Piet", Role.USER);
-        assertEquals("Piet", role.getName());
-        Role dup = m_userAdmin.createRole("Piet", Role.USER);
-        assertNull(dup);
-    }
-    
-    public void testRemoveUser() throws Exception {
-        // Write a new version directly to the repository
-        SortedRangeSet range = m_repository.getRange();
-        try (InputStream is = new ByteArrayInputStream("<roles><user name=\"Piet\"><properties><test>changed</test></properties></user></roles>".getBytes())){
-            m_repository.commit(is, range.getHigh());
-        }
-        
-        Role role = m_userAdmin.getRole("Piet");
-        assertEquals("Piet", role.getName());
-        
-        boolean removeRole = m_userAdmin.removeRole("Piet");
-        assertTrue(removeRole);
-        
-        Role afterRemove = m_userAdmin.getRole("Piet");
-        assertNull(afterRemove);
-        
-        boolean removeRoleAgain = m_userAdmin.removeRole("Piet");
-        assertFalse(removeRoleAgain);
-    }
-    
-    public void testUpdateUser() throws Exception {
-        long high = m_repository.getRange().getHigh();
-        Role piet = m_userAdmin.createRole("Piet", Role.USER);
-
-        high = waitForRepoChange(high);
-        
-        piet.getProperties().put("test", "property");
-        waitForRepoChange(high);
-        
-        String repoContentsAsString = getRepoContentsAsString();
-        assertTrue(repoContentsAsString.contains("<test>property</test>"));
-    }
-    
-    public void testUpdateUserRepoOutOfSync() throws Exception {
-        long high = m_repository.getRange().getHigh();
-        Role piet = m_userAdmin.createRole("Piet", Role.USER);
-        high = waitForRepoChange(high);
-        piet.getProperties().put("test", "property");
-        waitForRepoChange(high);
-        
-        // Write a new version directly to the repository
-        SortedRangeSet range = m_repository.getRange();
-        try (InputStream is = new ByteArrayInputStream("<roles><user name=\"Piet\"><properties><test>changed</test></properties></user></roles>".getBytes())){
-            m_repository.commit(is, range.getHigh());
-        }
-        
-        try {
-            // Expect that updating properties fails as the user object is out of date
-            piet.getProperties().put("this", "fails");
-            fail("Expected IllegalStateException");
-        } catch (IllegalStateException e) {
-            //expected
-        }
-        
-        Role role = m_userAdmin.getRole("Piet");
-        assertEquals("changed", role.getProperties().get("test"));
-    }
-    
-    public void testUpdateUserFetchedBeforeRepoSync() throws Exception {
-        long high = m_repository.getRange().getHigh();
-        Role piet = m_userAdmin.createRole("Piet", Role.USER);
-        high = waitForRepoChange(high);
-        piet.getProperties().put("test", "property");
-        high = waitForRepoChange(high);
-        
-        // Write a new version directly to the repository
-        SortedRangeSet range = m_repository.getRange();
-        try (InputStream is = new ByteArrayInputStream("<roles><user name=\"Piet\"><properties><test>changed</test></properties></user></roles>".getBytes())){
-            m_repository.commit(is, range.getHigh());
-        }
-        
-        // try to get a Role just to trigger the RepositoryBasedRoleRepositoryStore to refresh from the repository 
-        m_userAdmin.getRole("JustATrigger"); 
-        
-        try {
-            // Expect that updating properties fails as the user was fetched before the repository was refreshed
-            piet.getProperties().put("this", "fails");
-            fail("Expected IllegalStateException");
-        } catch (IllegalStateException e) {
-//            expected
-        }
-        
-        Role role = m_userAdmin.getRole("Piet");
-        assertEquals("changed", role.getProperties().get("test"));
-    }
 
     private String getRepoContentsAsString() throws IOException {
         String repoContentsAsString;
-        try(InputStream repoInputStream = m_repository.checkout(m_repository.getRange().getHigh());
-                ByteArrayOutputStream out = new ByteArrayOutputStream()){
+        try (InputStream repoInputStream = m_repository.checkout(m_repository.getRange().getHigh());
+            ByteArrayOutputStream out = new ByteArrayOutputStream()) {
             byte[] buf = new byte[4096];
             int bytesRead = -1;
             while ((bytesRead = repoInputStream.read(buf)) >= 0) {
@@ -169,38 +203,12 @@
     private long waitForRepoChange(long high) throws IOException, InterruptedException {
         int i = 0;
         while (m_repository.getRange().getHigh() <= high) {
-            Thread.sleep(10l);
+            TimeUnit.MILLISECONDS.sleep(50L);
             i++;
-            if (i > 250){
+            if (i > 25) {
                 fail("Repo didn't update in time");
             }
-        } 
+        }
         return m_repository.getRange().getHigh();
     }
-   
-    protected void configureProvisionedServices() throws Exception {
-        m_host = new URL("http://localhost:" + TestConstants.PORT);
-
-        configure("org.apache.ace.repository.servlet.RepositoryServlet",
-            HttpConstants.ENDPOINT, "/repository", "authentication.enabled", "false");
-        
-        configureFactory("org.apache.ace.server.repository.factory", 
-            "customer", "apache",
-            "name", "user", 
-            "master", "true",
-            "initial", "<roles></roles>"
-            );
-        
-        configure("org.apache.ace.useradmin.repository",
-            "repositoryLocation", "http://localhost:" + TestConstants.PORT + "/repository",
-            "repositoryCustomer", "apache",
-            "repositoryName", "user");
-
-        Utils.waitForWebserver(m_host);
-    }
-
-    @Override
-    protected void doTearDown() throws Exception {
-    }
-
 }
diff --git a/org.apache.ace.useradmin/repository.bnd b/org.apache.ace.useradmin/repository.bnd
index 3d3fd15..4d621fc 100644
--- a/org.apache.ace.useradmin/repository.bnd
+++ b/org.apache.ace.useradmin/repository.bnd
@@ -66,5 +66,5 @@
     *
 Bundle-Activator: org.apache.ace.useradmin.repository.Activator
 
-Bundle-Name: Apache ACE UserAdmin RoleRepositoryStore
-Bundle-Description: Felix UserAdmin RoleRepositoryStore implementation backed by an ACE Repository
\ No newline at end of file
+Bundle-Name: Apache ACE UserAdmin RepositoryStore
+Bundle-Description: Felix UserAdmin RepositoryStore implementation backed by an ACE Repository
\ No newline at end of file
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java
index e07e4af..add55d2 100644
--- a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java
+++ b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java
@@ -23,15 +23,12 @@
 
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.Dictionary;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Properties;
 
 import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.ace.repository.Repository;
 import org.apache.ace.repository.ext.impl.RemoteRepository;
-import org.apache.felix.dm.Component;
 import org.apache.felix.dm.DependencyActivatorBase;
 import org.apache.felix.dm.DependencyManager;
 import org.apache.felix.useradmin.RoleRepositoryStore;
@@ -41,92 +38,68 @@
 import org.osgi.service.log.LogService;
 import org.osgi.service.useradmin.UserAdminListener;
 
-public class Activator extends DependencyActivatorBase {
-
+public class Activator extends DependencyActivatorBase implements ManagedService {
     private static final String PID = "org.apache.ace.useradmin.repository";
+
     public static final String KEY_REPOSITORY_CUSTOMER = "repositoryCustomer";
     public static final String KEY_REPOSITORY_NAME = "repositoryName";
     public static final String KEY_REPOSITORY_LOCATION = "repositoryLocation";
-    
+
+    private volatile DependencyManager m_manager;
+
     @Override
     public void init(BundleContext context, DependencyManager manager) throws Exception {
-        
-        manager.add(createComponent().setImplementation(new RemoteRepositoryManager())
-            .add(createConfigurationDependency().setPid(PID))
-            );
+        manager.add(createComponent()
+            .setImplementation(this)
+            .add(createConfigurationDependency().setPid(PID)));
     }
-    
-    private static class RemoteRepositoryManager implements ManagedService {
-        
-        private volatile DependencyManager m_manager;
-        private final List<Component> m_components = new ArrayList<>();
-    
-        @Override
-        public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
-            if (properties == null) {
-                Iterator<Component> iterator = m_components.iterator();
-                while (iterator.hasNext()) {
-                    Component component = (Component) iterator.next();
-                    m_manager.remove(component);
-                    iterator.remove();
-                }
-                return;
-            }
-            
-            String customer = (String) properties.get(KEY_REPOSITORY_CUSTOMER);
-            if ((customer == null) || "".equals(customer)) {
-                throw new ConfigurationException(KEY_REPOSITORY_CUSTOMER, "Repository customer has to be specified.");
-            }
 
-            String name = (String) properties.get(KEY_REPOSITORY_NAME);
-            if ((name == null) || "".equals(name)) {
-                throw new ConfigurationException(KEY_REPOSITORY_NAME, "Repository name has to be specified.");
-            }
-            
-            String repositoryUrl = (String) properties.get(KEY_REPOSITORY_LOCATION);
-            if ((repositoryUrl == null) || "".equals(repositoryUrl)) {
-                throw new ConfigurationException(KEY_REPOSITORY_LOCATION, "Repository location has to be specified.");
-            }
-            
-            try {
-                //CachedRepo
-                RemoteRepository remoteRepository = new RemoteRepository(new URL(repositoryUrl), customer, name);
-                Properties repoProps = new Properties();
-                repoProps.put(REPOSITORY_CUSTOMER, customer);
-                repoProps.put(REPOSITORY_NAME, name);
-                
-                Component repositoryComponent = m_manager.createComponent()
-                    .setInterface(RemoteRepository.class.getName(), repoProps)
-                    .setImplementation(remoteRepository)
-                    .add(m_manager.createServiceDependency()
-                        .setService(ConnectionFactory.class)
-                        .setRequired(true)
-                    );
-    
-                m_manager.add(repositoryComponent);
-                m_components.add(repositoryComponent);
-                
-            } catch (MalformedURLException e) {
-                throw new ConfigurationException(KEY_REPOSITORY_LOCATION, "Repository location has to be a valid URL.");
-            }
-            
-            String repoFilter = String.format("(&(customer=%s)(name=%s))", customer, name);
-            Component storeComponent = m_manager.createComponent()
-                .setInterface(new String[]{ RoleRepositoryStore.class.getName(), UserAdminListener.class.getName() }, null)
-                .setImplementation(RepositoryBasedRoleRepositoryStore.class)
-                .add(m_manager.createServiceDependency()
-                    .setService(RemoteRepository.class, repoFilter)
-                    .setRequired(true)
-                )
-                .add(m_manager.createServiceDependency()
-                    .setService(LogService.class)
-                    .setRequired(false)
-                );
-            
-            m_manager.add(storeComponent);
-            m_components.add(storeComponent);
+    @Override
+    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
+        String customer = (String) properties.get(KEY_REPOSITORY_CUSTOMER);
+        if ((customer == null) || "".equals(customer.trim())) {
+            throw new ConfigurationException(KEY_REPOSITORY_CUSTOMER, "Repository customer has to be specified.");
         }
-        
-    } 
 
+        String name = (String) properties.get(KEY_REPOSITORY_NAME);
+        if ((name == null) || "".equals(name.trim())) {
+            throw new ConfigurationException(KEY_REPOSITORY_NAME, "Repository name has to be specified.");
+        }
+
+        URL repositoryUrl = null;
+        String repositoryUrlStr = (String) properties.get(KEY_REPOSITORY_LOCATION);
+        if (repositoryUrlStr != null) {
+            if (!"".equals(repositoryUrlStr.trim())) {
+                try {
+                    repositoryUrl = new URL(repositoryUrlStr);
+                }
+                catch (MalformedURLException exception) {
+                    throw new ConfigurationException(KEY_REPOSITORY_LOCATION, "Repository location has to be a valid URL.");
+                }
+            }
+        }
+
+        String repoFilter = String.format("(&(customer=%s)(name=%s)(|(master=true)(remote=true)))", customer, name);
+
+        m_manager.add(m_manager.createComponent()
+            .setInterface(new String[] { RoleRepositoryStore.class.getName(), UserAdminListener.class.getName() }, null)
+            .setImplementation(UserAdminRepository.class)
+            .add(m_manager.createServiceDependency().setService(Repository.class, repoFilter).setRequired(true))
+            .add(m_manager.createServiceDependency().setService(LogService.class).setRequired(false)));
+
+        if (repositoryUrl != null) {
+            // Remote version...
+            Properties repoProps = new Properties();
+            repoProps.put(REPOSITORY_CUSTOMER, customer);
+            repoProps.put(REPOSITORY_NAME, name);
+            repoProps.put("remote", "true");
+
+            m_manager.add(m_manager.createComponent()
+                .setInterface(Repository.class.getName(), repoProps)
+                .setImplementation(new RemoteRepository(repositoryUrl, customer, name))
+                .add(m_manager.createServiceDependency()
+                    .setService(ConnectionFactory.class)
+                    .setRequired(true)));
+        }
+    }
 }
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java
new file mode 100644
index 0000000..aa6657b
--- /dev/null
+++ b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ace.useradmin.repository;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.osgi.service.useradmin.Role;
+
+/**
+ * 
+ */
+public interface RepoCurrentChecker {
+    
+    void checkRepoUpToDate(Role context, AtomicLong expectedVersion) throws IllegalStateException;
+}
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java
deleted file mode 100644
index cefea74..0000000
--- a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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.ace.useradmin.repository;
-
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.ace.repository.ext.CachedRepository;
-import org.apache.ace.repository.ext.impl.CachedRepositoryImpl;
-import org.apache.ace.repository.ext.impl.FilebasedBackupRepository;
-import org.apache.ace.repository.ext.impl.RemoteRepository;
-import org.apache.ace.useradmin.repository.xstream.GroupDTO;
-import org.apache.ace.useradmin.repository.xstream.RoleDTO;
-import org.apache.ace.useradmin.repository.xstream.UserDTO;
-import org.apache.ace.useradmin.repository.xstream.XStreamFactory;
-import org.apache.felix.useradmin.RoleFactory;
-import org.apache.felix.useradmin.RoleRepositoryStore;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.log.LogService;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-import com.thoughtworks.xstream.XStream;
-
-/**
- * Felix UserAdmin RoleRepositoryStore implementation that's backed by an ACE Repository
- *
- */
-public class RepositoryBasedRoleRepositoryStore implements RoleRepositoryStore, UserAdminListener {
-    
-    private volatile BundleContext m_BundleContext;
-    private volatile LogService m_log;
-    private volatile RemoteRepository m_repository;    
-    private volatile CachedRepository m_cachedRepository;
-
-    private volatile AtomicLong m_version;
-    private final Map<String, Role> m_roleMap = new ConcurrentHashMap<>();
-    
-    @SuppressWarnings("unused" /* dependency manager callback */)
-    private void start() throws IOException {
-        File currentFile = m_BundleContext.getDataFile("current.xml");
-        File backupFile = m_BundleContext.getDataFile("backup.xml");
-        
-        if (currentFile.exists()) {
-            currentFile.delete();
-        }
-        
-        if (backupFile.exists()) {
-            backupFile.delete();
-        }
-        
-        FilebasedBackupRepository backupRepo = new FilebasedBackupRepository(currentFile, backupFile);
-        m_cachedRepository = new CachedRepositoryImpl(m_repository, backupRepo, CachedRepositoryImpl.UNCOMMITTED_VERSION);
-    }
-
-    private void refreshRoleMap() throws Exception {
-        m_roleMap.clear();
-        XStream instance = XStreamFactory.getInstance();
-        
-        try (InputStream inputStream = m_cachedRepository.checkout(true);
-                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
-                ObjectInputStream objectInputStream = instance.createObjectInputStream(inputStreamReader)){
-            
-            RoleDTO roleDto;
-            List<RoleDTO> rolesWithMemberships = new ArrayList<>();
-            m_version = new AtomicLong(m_cachedRepository.getMostRecentVersion());
-            try {
-                while ((roleDto = (RoleDTO) objectInputStream.readObject()) != null) {
-                    User role;
-                    if (roleDto.type == Role.USER) {
-                        role = RoleFactory.createUser(roleDto.name);
-                    } else if (roleDto.type == Role.GROUP) {
-                        role = RoleFactory.createGroup(roleDto.name);
-                    } else {
-                        throw new IllegalStateException("");
-                    }
-                    if (roleDto.properties != null){
-                        for (Entry<Object, Object> entry : roleDto.properties.entrySet()) {
-                            role.getProperties().put(entry.getKey(), entry.getValue());
-                        }
-                    }
-                    if (roleDto.credentials != null){
-                        for (Entry<Object, Object> entry : roleDto.credentials.entrySet()) {
-                            role.getCredentials().put(entry.getKey(), entry.getValue());
-                        }
-                    }
-                    if (roleDto.memberOf != null && !roleDto.memberOf.isEmpty()){
-                        rolesWithMemberships.add(roleDto);
-                    }
-                    
-                    m_roleMap.put(role.getName(), role);
-                }
-            }catch (EOFException e) {
-                // Ignore, this is the way XStream let's us know we're done reading
-            }
-            
-            for (RoleDTO role : rolesWithMemberships) {
-                Role memberRole = m_roleMap.get(role.name);
-                for (String memberOf : role.memberOf) {
-                    Role groupRole = m_roleMap.get(memberOf);
-                    if (groupRole == null){
-                        throw new IllegalStateException("Target group not found");
-                    }
-                    
-                    if (groupRole.getType() != Role.GROUP) {
-                        throw new IllegalStateException("Target is not a group");
-                    }
-                    
-                    Group group = (Group) groupRole;
-                    group.addMember(memberRole);
-                }
-            }
-            
-            // Wrap users and groups in repository user / group types 
-            for (Entry<String, Role> roleMapEntry : m_roleMap.entrySet()) {
-                m_roleMap.put(roleMapEntry.getKey(), wrapRole(roleMapEntry.getValue()));
-            }
-        }
-    }
-    
-    /**
-     * Add a wrapper around a Role that prevents changes to Users / Groups when the repository is out of sync
-     * 
-     * @param role User or Group role to be wrapped
-     * @return a wrapped Role
-     */
-    private Role wrapRole(Role role) {
-        if (role.getType() == Role.USER) {
-            return new RepositoryUser((User)role, m_cachedRepository, m_version);
-        } else if (role.getType() == Role.GROUP) {
-            return new RepositoryGroup((Group)role, m_cachedRepository, m_version);
-        }else {
-            throw new IllegalStateException("");
-        }
-    }
-
-    @Override
-    public Role getRoleByName(String name) throws Exception {
-        if (name == null) {
-            return null;
-        }
-        
-        synchronized (m_roleMap) {
-            if (!m_cachedRepository.isCurrent()) {
-                refreshRoleMap(); 
-            }
-            return m_roleMap.get(name);
-        }
-    }
-
-    @Override
-    public Role[] getRoles(String filterString) throws Exception {
-        synchronized (m_roleMap) {
-            if (!m_cachedRepository.isCurrent()) {
-                refreshRoleMap();
-            }
-        
-            if (filterString == null) {
-                return m_roleMap.values().toArray(new Role[0]);
-            }
-            
-            Filter filter = FrameworkUtil.createFilter(filterString);
-            
-            List<Role> matchingRoles = new ArrayList<>();
-            for (Role role: m_roleMap.values()){
-                if (filter.match(role.getProperties())){
-                    matchingRoles.add(role);
-                }
-            }
-            
-            return matchingRoles.toArray(new Role[matchingRoles.size()]);
-        }
-    }
-
-    @Override
-    public Role addRole(String name, int type) throws Exception {
-        Role role;
-        switch (type) {
-            case Role.USER:
-                role = RoleFactory.createUser(name);                
-                break;
-            case Role.GROUP:
-                role = RoleFactory.createGroup(name);
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid group type " + type);
-        }
-        synchronized (m_roleMap) {
-            if (m_cachedRepository.getMostRecentVersion() == -1) {
-                refreshRoleMap();
-            }
-            
-            if (m_roleMap.containsKey(name)){
-                return null;
-            }
-            role = wrapRole(role);
-            m_roleMap.put(name, role);
-            roleChanged(null);
-        }
-        return role;
-    }
-    
-    @Override
-    public Role removeRole(String name) throws Exception {
-        Role removedRole;
-        synchronized (m_roleMap) {
-            removedRole = m_roleMap.remove(name);
-            if (removedRole != null){
-                roleChanged(null);
-            }
-        }
-        return removedRole;
-    }
-
-    List<String> memberOf(Role role) {
-        List<String> memberOf = new ArrayList<>();
-        for (Role r: m_roleMap.values()) {
-            if (r instanceof Group) {
-                Group group = (Group) r;
-                Role[] members = group.getMembers();
-                if (members != null) {
-                    if (contains(role, members)) {
-                        memberOf.add(group.getName());
-                    }
-                }
-            }
-        }
-        return memberOf; 
-    }
-    
-    /**
-     * Helper method that checks the presence of an object in an array. Returns <code>true</code> if <code>t</code> is
-     * in <code>ts</code>, <code>false</code> otherwise.
-     */
-    private <T> boolean contains(T t, T[] ts) {
-        for (T current : ts) {
-            if (current.equals(t)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void roleChanged(UserAdminEvent event) {
-        synchronized (m_roleMap) {
-            XStream instance = XStreamFactory.getInstance();
-            try (StringWriter writer = new StringWriter();
-                            ObjectOutputStream stream =
-                                instance.createObjectOutputStream(writer, "roles");) {
-                
-                for (Role role : m_roleMap.values()) {
-                    List<String> memberOf = memberOf(role);
-                    if (role.getType() == Role.USER) {
-                        stream.writeObject(new UserDTO((User) role, memberOf));
-                    } else if (role.getType() == Role.GROUP) {
-                        GroupDTO obj = new GroupDTO((Group) role, memberOf);
-                        stream.writeObject(obj);
-                    } else {
-                        throw new IllegalStateException("Unsupported role type");
-                    }
-                }
-    
-                stream.flush();
-                stream.close();
-                writer.flush();
-                
-                try (ByteArrayInputStream inputStream = new ByteArrayInputStream(writer.toString().getBytes())){
-                    m_cachedRepository.writeLocal(inputStream);
-                }
-                
-                m_cachedRepository.commit();
-                m_version.set(m_cachedRepository.getMostRecentVersion());
-            } catch (IOException e) {
-                m_log.log(LogService.LOG_ERROR, "Failed to commit role changes to the main role repository", e);
-            } 
-        }
-    }
-    
-}
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java
index 9e0c35d..aec7f47 100644
--- a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java
+++ b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java
@@ -20,48 +20,44 @@
 
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.ace.repository.ext.CachedRepository;
 import org.osgi.service.useradmin.Group;
 import org.osgi.service.useradmin.Role;
 
 /**
  * Wrapper for {@link Group} that prevents changes to the group when the store is out of sync with the main repository
  */
-public class RepositoryGroup extends RepositoryUser implements Group{
+public class RepositoryGroup extends RepositoryUser implements Group {
 
-    private Group m_delegate;                           
-    
-    public RepositoryGroup(Group group, CachedRepository cachedRepository, AtomicLong version) {
-        super(group, cachedRepository, version);
-        m_delegate = group;
+    public RepositoryGroup(Group group, AtomicLong version, RepoCurrentChecker repoCurrentChecker) {
+        super(group, version, repoCurrentChecker);
     }
-    
+
     @Override
     public boolean addMember(Role role) {
         checkRepoUpToDate();
-        return m_delegate.addMember(role);
+        return ((Group) m_delegate).addMember(role);
     }
 
     @Override
     public boolean addRequiredMember(Role role) {
         checkRepoUpToDate();
-        return m_delegate.addMember(role);
+        return ((Group) m_delegate).addMember(role);
     }
 
     @Override
     public boolean removeMember(Role role) {
         checkRepoUpToDate();
-        return m_delegate.removeMember(role);
+        return ((Group) m_delegate).removeMember(role);
     }
 
     @Override
     public Role[] getMembers() {
-        return m_delegate.getMembers();
+        return ((Group) m_delegate).getMembers();
     }
 
     @Override
     public Role[] getRequiredMembers() {
-        return m_delegate.getRequiredMembers();
+        return ((Group) m_delegate).getRequiredMembers();
     }
 
 }
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java
index cdf2c71..41730da 100644
--- a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java
+++ b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java
@@ -18,67 +18,32 @@
  */
 package org.apache.ace.useradmin.repository;
 
-import java.io.IOException;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.ace.repository.ext.CachedRepository;
 import org.osgi.service.useradmin.User;
 
 /**
  * Wrapper for {@link User} that prevents changes to the user when the store is out of sync with the main repository
  */
 public class RepositoryUser implements User {
-
-    private User m_delegate;
-    private CachedRepository m_cachedRepository;
-    private AtomicLong m_version;
-
-    public RepositoryUser(User user, CachedRepository cachedRepository, AtomicLong version) {
-        m_delegate = user;
-        m_cachedRepository = cachedRepository;
-        m_version = version;
-    }
-
-    @Override
-    public String getName() {
-        return m_delegate.getName();
-    }
-
-    @Override
-    public int getType() {
-        return m_delegate.getType();
-    }
-
-    @Override
-    public Dictionary getProperties() {
-        return new RepoProperties(m_delegate.getProperties());
-    }
-
-    @Override
-    public Dictionary getCredentials() {
-        return new RepoProperties(m_delegate.getCredentials());
-    }
-
-    @Override
-    public boolean hasCredential(String key, Object value) {
-        return m_delegate.hasCredential(key, value);
-    }
-
     @SuppressWarnings("rawtypes")
     private class RepoProperties extends Dictionary {
+        private Dictionary<Object, Object> m_delegate;
 
-        private Dictionary m_delegate;
-
-        public RepoProperties(Dictionary dictionary) {
-            this.m_delegate = dictionary;
-
+        public RepoProperties(Dictionary<Object, Object> dictionary) {
+            m_delegate = dictionary;
         }
 
         @Override
-        public int size() {
-            return m_delegate.size();
+        public Enumeration elements() {
+            return m_delegate.elements();
+        }
+
+        @Override
+        public Object get(Object key) {
+            return m_delegate.get(key);
         }
 
         @Override
@@ -92,17 +57,6 @@
         }
 
         @Override
-        public Enumeration elements() {
-            return m_delegate.elements();
-        }
-
-        @Override
-        public Object get(Object key) {
-            return m_delegate.get(key);
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
         public Object put(Object key, Object value) {
             checkRepoUpToDate();
             return m_delegate.put(key, value);
@@ -114,19 +68,76 @@
             return m_delegate.remove(key);
         }
 
-    }
-
-    protected void checkRepoUpToDate() {
-        try {
-            if (!m_cachedRepository.isCurrent()) {
-                throw new IllegalStateException("Repository out of date, refresh first");
-            }
-            if (m_version.get() != m_cachedRepository.getMostRecentVersion()) {
-                throw new IllegalStateException("User out of date, refresh first");
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
+        @Override
+        public int size() {
+            return m_delegate.size();
         }
     }
+    private final RepoCurrentChecker m_repoCurrentChecker;
+    protected final User m_delegate;
 
+    protected final AtomicLong m_version;
+
+    public RepositoryUser(User user, AtomicLong version, RepoCurrentChecker repoCurrentChecker) {
+        m_delegate = user;
+        m_version = version;
+        m_repoCurrentChecker = repoCurrentChecker;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        RepositoryUser other = (RepositoryUser) obj;
+        if (!m_delegate.equals(other.m_delegate)) {
+            return false;
+        }
+        if (!m_version.equals(other.m_version)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public Dictionary getCredentials() {
+        return new RepoProperties(m_delegate.getCredentials());
+    }
+
+    @Override
+    public String getName() {
+        return m_delegate.getName();
+    }
+
+    @Override
+    public Dictionary getProperties() {
+        return new RepoProperties(m_delegate.getProperties());
+    }
+
+    @Override
+    public int getType() {
+        return m_delegate.getType();
+    }
+
+    @Override
+    public boolean hasCredential(String key, Object value) {
+        return m_delegate.hasCredential(key, value);
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((m_delegate == null) ? 0 : m_delegate.hashCode());
+        result = prime * result + ((m_version == null) ? 0 : m_version.hashCode());
+        return result;
+    }
+
+    protected final void checkRepoUpToDate() {
+        m_repoCurrentChecker.checkRepoUpToDate(this, m_version);
+    }
 }
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java
new file mode 100644
index 0000000..fb40f3f
--- /dev/null
+++ b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java
@@ -0,0 +1,421 @@
+/*
+ * 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.ace.useradmin.repository;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.ace.repository.Repository;
+import org.apache.ace.useradmin.repository.xstream.GroupDTO;
+import org.apache.ace.useradmin.repository.xstream.RoleDTO;
+import org.apache.ace.useradmin.repository.xstream.UserDTO;
+import org.apache.ace.useradmin.repository.xstream.XStreamFactory;
+import org.apache.felix.useradmin.RoleFactory;
+import org.apache.felix.useradmin.RoleRepositoryStore;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+import com.thoughtworks.xstream.XStream;
+
+/**
+ * Felix UserAdmin RoleRepositoryStore implementation that's backed by an ACE Repository
+ */
+public class UserAdminRepository implements RoleRepositoryStore, UserAdminListener, RepoCurrentChecker {
+    private final ConcurrentMap<String, Role> m_roleMap;
+    private final AtomicLong m_version;
+    private final ReadWriteLock m_rw;
+
+    private volatile Repository m_repository;
+    private volatile LogService m_log;
+
+    public UserAdminRepository() {
+        m_roleMap = new ConcurrentHashMap<>();
+        m_version = new AtomicLong(-1L);
+        m_rw = new ReentrantReadWriteLock(true /* fair */);
+    }
+
+    UserAdminRepository(Repository repo, LogService log) {
+        this();
+        m_repository = repo;
+        m_log = log;
+    }
+
+    @Override
+    public Role addRole(String name, int type) throws Exception {
+        ensureRoleMapIsCurrent();
+
+        m_rw.readLock().lock();
+        try {
+            if (m_roleMap.containsKey(name)) {
+                return null;
+            }
+        }
+        finally {
+            m_rw.readLock().unlock();
+        }
+
+        Role role;
+        switch (type) {
+            case Role.USER:
+                role = wrapRole(RoleFactory.createUser(name));
+                break;
+            case Role.GROUP:
+                role = wrapRole(RoleFactory.createGroup(name));
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid group type " + type);
+        }
+
+        m_rw.writeLock().lock();
+        try {
+            m_roleMap.put(name, role);
+            // Make sure the repository is correctly synchronized...
+            m_log.log(LogService.LOG_DEBUG, "Writing role map due to adding of " + ((type == Role.USER) ? "user" : "group") + " role: " + name);
+
+            writeRoleMap();
+        }
+        finally {
+            m_rw.writeLock().unlock();
+        }
+
+        return role;
+    }
+
+    public void checkRepoUpToDate(Role context, AtomicLong version) throws IllegalStateException {
+        long currentVersion = getMostRecentVersion();
+        // NOTE: do not use local variable for `version.get()` as it appears that javac otherwise replaces it with a
+        // constant...
+        if ((version.get() > 0) && currentVersion != version.get()) {
+            m_rw.writeLock().lock();
+            try {
+                m_roleMap.clear();
+            }
+            finally {
+                m_rw.writeLock().unlock();
+            }
+
+            throw new IllegalStateException(context + " out of sync. Please refresh first!");
+        }
+    }
+
+    @Override
+    public Role getRoleByName(String name) throws Exception {
+        if (name == null) {
+            return null;
+        }
+
+        ensureRoleMapIsCurrent();
+
+        m_rw.readLock().lock();
+        try {
+            return m_roleMap.get(name);
+        }
+        finally {
+            m_rw.readLock().unlock();
+        }
+    }
+
+    @Override
+    public Role[] getRoles(String filterString) throws Exception {
+        Filter filter = null;
+        if (filterString != null) {
+            filter = FrameworkUtil.createFilter(filterString);
+        }
+
+        ensureRoleMapIsCurrent();
+
+        List<Role> allRoles;
+        m_rw.readLock().lock();
+        try {
+            allRoles = new ArrayList<>(m_roleMap.values());
+        }
+        finally {
+            m_rw.readLock().unlock();
+        }
+
+        List<Role> matchingRoles = new ArrayList<>();
+        for (Role role : allRoles) {
+            if (filter == null || filter.match(role.getProperties())) {
+                matchingRoles.add(role);
+            }
+        }
+
+        return matchingRoles.toArray(new Role[matchingRoles.size()]);
+    }
+
+    @Override
+    public Role removeRole(String name) throws Exception {
+        m_rw.writeLock().lock();
+        try {
+            Role role = m_roleMap.remove(name);
+            if (role != null) {
+                // Make sure the repository is correctly synchronized...
+                m_log.log(LogService.LOG_DEBUG, "Writing role map due to removal of " + ((role.getType() == Role.USER) ? "user" : "group") + " role: " + role.getName());
+
+                writeRoleMap();
+            }
+            return role;
+        }
+        finally {
+            m_rw.writeLock().unlock();
+        }
+    }
+
+    @Override
+    public void roleChanged(UserAdminEvent event) {
+        m_rw.writeLock().lock();
+        try {
+            // Make sure the repository is correctly synchronized...
+            Role role = event.getRole();
+            m_log.log(LogService.LOG_DEBUG, "Writing role map due to change of " + ((role.getType() == Role.USER) ? "user" : "group") + " role: " + role.getName());
+
+            writeRoleMap();
+        }
+        finally {
+            m_rw.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Add a wrapper around a Role that prevents changes to Users / Groups when the repository is out of sync
+     * 
+     * @param role
+     *            User or Group role to be wrapped
+     * @return a wrapped Role
+     */
+    protected Role wrapRole(Role role) {
+        if (role.getType() == Role.USER) {
+            return new RepositoryUser((User) role, m_version, this);
+        }
+        else if (role.getType() == Role.GROUP) {
+            return new RepositoryGroup((Group) role, m_version, this);
+        }
+        else {
+            throw new IllegalStateException("Invalid role type: " + role.getType());
+        }
+    }
+
+    /**
+     * Helper method that checks the presence of an object in an array. Returns <code>true</code> if <code>t</code> is
+     * in <code>ts</code>, <code>false</code> otherwise.
+     */
+    private <T> boolean contains(T t, T[] ts) {
+        for (T current : ts) {
+            if (current.equals(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean ensureRoleMapIsCurrent() throws Exception {
+        long actualVersion;
+        long localVersion;
+
+        boolean isCurrent;
+        m_rw.readLock().lock();
+        try {
+            actualVersion = getMostRecentVersion();
+            localVersion = m_version.longValue();
+
+            isCurrent = !m_roleMap.isEmpty() && localVersion == actualVersion;
+        }
+        finally {
+            m_rw.readLock().unlock();
+        }
+
+        if (!isCurrent) {
+            m_log.log(LogService.LOG_DEBUG, "Reading role map as we're no longer current (" + localVersion + " <=> " + actualVersion + " )...");
+
+            readRoleMap(actualVersion);
+        }
+
+        return isCurrent;
+    }
+
+    private long getMostRecentVersion() {
+        m_rw.readLock().lock();
+        try {
+            return m_repository.getRange().getHigh();
+        }
+        catch (IOException exception) {
+            m_log.log(LogService.LOG_WARNING, "Unable to query repository for most recent version!", exception);
+            return -1L;
+        }
+        finally {
+            m_rw.readLock().unlock();
+        }
+    }
+
+    private List<String> memberOf(List<Role> roles, Role role) {
+        List<String> memberOf = new ArrayList<>();
+        for (Role r : roles) {
+            if (r instanceof Group) {
+                Group group = (Group) r;
+                Role[] members = group.getMembers();
+                if (members != null) {
+                    for (Role member : members) {
+                        if (member.getType() == role.getType() && member.getName().equals(role.getName())) {
+                            memberOf.add(group.getName());
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return memberOf;
+    }
+
+    final void readRoleMap(long version) throws Exception {
+        XStream instance = XStreamFactory.getInstance();
+
+        Map<String, Role> newRoles = new HashMap<>();
+
+        try (Reader r = new InputStreamReader(m_repository.checkout(version));
+            ObjectInputStream objectInputStream = instance.createObjectInputStream(r)) {
+
+            RoleDTO roleDto;
+            List<RoleDTO> rolesWithMemberships = new ArrayList<>();
+
+            try {
+                while ((roleDto = (RoleDTO) objectInputStream.readObject()) != null) {
+                    User role;
+                    if (roleDto.type == Role.USER) {
+                        role = RoleFactory.createUser(roleDto.name);
+                    }
+                    else if (roleDto.type == Role.GROUP) {
+                        role = RoleFactory.createGroup(roleDto.name);
+                    }
+                    else {
+                        throw new IllegalStateException("");
+                    }
+                    if (roleDto.properties != null) {
+                        for (Entry<Object, Object> entry : roleDto.properties.entrySet()) {
+                            role.getProperties().put(entry.getKey(), entry.getValue());
+                        }
+                    }
+                    if (roleDto.credentials != null) {
+                        for (Entry<Object, Object> entry : roleDto.credentials.entrySet()) {
+                            role.getCredentials().put(entry.getKey(), entry.getValue());
+                        }
+                    }
+                    if (roleDto.memberOf != null && !roleDto.memberOf.isEmpty()) {
+                        rolesWithMemberships.add(roleDto);
+                    }
+
+                    newRoles.put(role.getName(), role);
+                }
+            }
+            catch (EOFException e) {
+                // Ignore, this is the way XStream let's us know we're done reading
+            }
+
+            for (RoleDTO role : rolesWithMemberships) {
+                Role memberRole = newRoles.get(role.name);
+                for (String memberOf : role.memberOf) {
+                    Role groupRole = newRoles.get(memberOf);
+                    if (groupRole == null) {
+                        throw new IllegalStateException("Target group not found");
+                    }
+
+                    if (groupRole.getType() != Role.GROUP) {
+                        throw new IllegalStateException("Target is not a group");
+                    }
+
+                    ((Group) groupRole).addMember(memberRole);
+                }
+            }
+        }
+
+        m_rw.writeLock().lock();
+        try {
+            // "Commit" everything...
+            m_roleMap.clear();
+            for (Map.Entry<String, Role> entry : newRoles.entrySet()) {
+                m_roleMap.put(entry.getKey(), wrapRole(entry.getValue()));
+            }
+
+            m_version.set(version);
+        }
+        finally {
+            m_rw.writeLock().unlock();
+        }
+    }
+
+    final void writeRoleMap() {
+        StringWriter writer = new StringWriter();
+
+        List<Role> roles = new ArrayList<>(m_roleMap.values());
+
+        XStream instance = XStreamFactory.getInstance();
+        try (ObjectOutputStream stream = instance.createObjectOutputStream(writer, "roles")) {
+            for (Role role : roles) {
+                List<String> memberOf = memberOf(roles, role);
+                if (role.getType() == Role.USER) {
+                    stream.writeObject(new UserDTO((User) role, memberOf));
+                }
+                else if (role.getType() == Role.GROUP) {
+                    GroupDTO obj = new GroupDTO((Group) role, memberOf);
+                    stream.writeObject(obj);
+                }
+                else {
+                    throw new IllegalStateException("Unsupported role type");
+                }
+            }
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "Failed to write role changes to the main role repository", e);
+            return;
+        }
+
+        try (InputStream inputStream = new ByteArrayInputStream(writer.toString().getBytes())) {
+            long fromVersion = m_version.longValue();
+
+            if (m_repository.commit(inputStream, fromVersion)) {
+                m_version.set(getMostRecentVersion());
+            }
+        }
+        catch (Exception e) {
+            m_log.log(LogService.LOG_ERROR, "Failed to commit role changes to the main role repository", e);
+        }
+    }
+}
diff --git a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java
index d4ac1fe..a9f93d3 100644
--- a/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java
+++ b/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java
@@ -42,10 +42,9 @@
         this.memberOf = memberOf;
     }
     
-    @SuppressWarnings("rawtypes")
-    static Properties toProperties(Dictionary dict) {
+    static Properties toProperties(Dictionary<Object, Object> dict) {
         Properties properties = new Properties();
-        Enumeration keys = dict.keys();
+        Enumeration<Object> keys = dict.keys();
         while (keys.hasMoreElements()) {
             Object key = (Object) keys.nextElement();
             properties.put(key, dict.get(key));
diff --git a/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java b/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java
index bf05efc..9ee0894 100644
--- a/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java
+++ b/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java
@@ -40,60 +40,56 @@
     @Test
     public void testRead() throws Exception {
         XStream xStream = XStreamFactory.getInstance();
-        
-        Reader reader = new FileReader(new File("test/valid.xml"));
-        ObjectInputStream objectInputStream = xStream.createObjectInputStream(reader);
-        GroupDTO testgroup = (GroupDTO) objectInputStream.readObject();
-        assertEquals(testgroup.name, "testgroup");
-        assertEquals(testgroup.properties.get("type"), "testGroupType");
-        assertEquals(testgroup.properties.get("other"), "otherTestProperty");
-        
-        GroupDTO testgroup2 = (GroupDTO) objectInputStream.readObject();
-        assertEquals(testgroup2.name, "testgroup2");
-        assertEquals(testgroup2.properties.get("type"), "otherGroupType");
-        assertEquals(testgroup2.memberOf, Arrays.asList("testgroup"));        
-        
-        UserDTO testuser = (UserDTO) objectInputStream.readObject();
-        assertEquals(testuser.name, "testuser");
-        assertEquals(testuser.properties.get("username"), "testuser");
-        assertEquals(testuser.credentials.get("password"), "test");
-        assertEquals(testuser.memberOf, Arrays.asList("testgroup2"));
-        
+
+        try (Reader reader = new FileReader(new File("test/valid.xml"));
+            ObjectInputStream objectInputStream = xStream.createObjectInputStream(reader)) {
+
+            GroupDTO testgroup = (GroupDTO) objectInputStream.readObject();
+            assertEquals(testgroup.name, "testgroup");
+            assertEquals(testgroup.properties.get("type"), "testGroupType");
+            assertEquals(testgroup.properties.get("other"), "otherTestProperty");
+
+            GroupDTO testgroup2 = (GroupDTO) objectInputStream.readObject();
+            assertEquals(testgroup2.name, "testgroup2");
+            assertEquals(testgroup2.properties.get("type"), "otherGroupType");
+            assertEquals(testgroup2.memberOf, Arrays.asList("testgroup"));
+
+            UserDTO testuser = (UserDTO) objectInputStream.readObject();
+            assertEquals(testuser.name, "testuser");
+            assertEquals(testuser.properties.get("username"), "testuser");
+            assertEquals(testuser.credentials.get("password"), "test");
+            assertEquals(testuser.memberOf, Arrays.asList("testgroup2"));
+        }
     }
-    
+
     @Test
     public void testWrite() throws Exception {
         XStream xStream = XStreamFactory.getInstance();
-        
         StringWriter sw = new StringWriter();
-        ObjectOutputStream objectOutputStream = xStream.createObjectOutputStream(sw, "roles");
-        
-        objectOutputStream.writeObject(
-            new GroupDTO("testgroup", properties("type", "testGroupType", "other", "otherTestProperty"), null, null));
-        objectOutputStream.writeObject(
-            new GroupDTO("testgroup2", properties("type", "otherGroupType"),  null, Arrays.asList("testgroup")));
-        objectOutputStream.writeObject(
-            new UserDTO("testuser", properties("username", "testuser"),  properties("password", "test"), Arrays.asList("testgroup2")));
 
-        objectOutputStream.flush();
-        objectOutputStream.close();
-        
+        try (ObjectOutputStream objectOutputStream = xStream.createObjectOutputStream(sw, "roles")) {
+
+            objectOutputStream.writeObject(
+                new GroupDTO("testgroup", properties("type", "testGroupType", "other", "otherTestProperty"), null, null));
+            objectOutputStream.writeObject(
+                new GroupDTO("testgroup2", properties("type", "otherGroupType"), null, Arrays.asList("testgroup")));
+            objectOutputStream.writeObject(
+                new UserDTO("testuser", properties("username", "testuser"), properties("password", "test"), Arrays.asList("testgroup2")));
+        }
+
         String outputString = sw.toString();
-        
-        byte[] encoded = Files.readAllBytes(Paths.get("test/valid.xml"));
-        String validXmlFileString = new String(encoded);
-        
+
+        String validXmlFileString = new String(Files.readAllBytes(Paths.get("test/valid.xml")));
+
         assertEquals(outputString, validXmlFileString);
     }
-    
-   
-    
+
     private static Properties properties(String... pairs) {
         Properties properties = new Properties();
-        for (int i = 0; i < pairs.length - 1; i += 2){
-            properties.put(pairs[i], pairs[i +1]);
+        for (int i = 0; i < pairs.length - 1; i += 2) {
+            properties.put(pairs[i], pairs[i + 1]);
         }
         return properties;
     }
-    
+
 }