From commits-return-5165-apmail-ace-commits-archive=ace.apache.org@ace.apache.org Mon Feb 22 22:18:30 2016 Return-Path: X-Original-To: apmail-ace-commits-archive@www.apache.org Delivered-To: apmail-ace-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id EE5FE18DD7 for ; Mon, 22 Feb 2016 22:18:29 +0000 (UTC) Received: (qmail 70406 invoked by uid 500); 22 Feb 2016 22:18:17 -0000 Delivered-To: apmail-ace-commits-archive@ace.apache.org Received: (qmail 70379 invoked by uid 500); 22 Feb 2016 22:18:17 -0000 Mailing-List: contact commits-help@ace.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ace.apache.org Delivered-To: mailing list commits@ace.apache.org Received: (qmail 70367 invoked by uid 99); 22 Feb 2016 22:18:17 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd4-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 22 Feb 2016 22:18:17 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd4-us-west.apache.org (ASF Mail Server at spamd4-us-west.apache.org) with ESMTP id 85C2AC0E98 for ; Mon, 22 Feb 2016 22:18:16 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd4-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.471 X-Spam-Level: * X-Spam-Status: No, score=1.471 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RP_MATCHES_RCVD=-0.329] autolearn=disabled Received: from mx1-lw-us.apache.org ([10.40.0.8]) by localhost (spamd4-us-west.apache.org [10.40.0.11]) (amavisd-new, port 10024) with ESMTP id sOSU38C8gbMO for ; Mon, 22 Feb 2016 22:18:05 +0000 (UTC) Received: from mailrelay1-us-west.apache.org (mailrelay1-us-west.apache.org [209.188.14.139]) by mx1-lw-us.apache.org (ASF Mail Server at mx1-lw-us.apache.org) with ESMTP id 559BE5FABF for ; Mon, 22 Feb 2016 22:18:05 +0000 (UTC) Received: from svn01-us-west.apache.org (svn.apache.org [10.41.0.6]) by mailrelay1-us-west.apache.org (ASF Mail Server at mailrelay1-us-west.apache.org) with ESMTP id 9577AE00EA for ; Mon, 22 Feb 2016 22:18:04 +0000 (UTC) Received: from svn01-us-west.apache.org (localhost [127.0.0.1]) by svn01-us-west.apache.org (ASF Mail Server at svn01-us-west.apache.org) with ESMTP id 7F5E23A0249 for ; Mon, 22 Feb 2016 22:18:04 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1731750 - in /ace/trunk: org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/ org.apache.ace.repository.itest/src/org/apache/ace/it/repository/ org.apache.ace.repository/src/org/apache/ace/repository/servlet/ org.apach... Date: Mon, 22 Feb 2016 22:18:04 -0000 To: commits@ace.apache.org From: jawi@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20160222221804.7F5E23A0249@svn01-us-west.apache.org> Author: jawi Date: Mon Feb 22 22:18:04 2016 New Revision: 1731750 URL: http://svn.apache.org/viewvc?rev=1731750&view=rev Log: 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. Added: ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java - copied, changed from r1731749, ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java (with props) ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java - copied, changed from r1731749, ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java Removed: ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java Modified: ace/trunk/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java ace/trunk/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java ace/trunk/org.apache.ace.useradmin/ (props changed) ace/trunk/org.apache.ace.useradmin/repository.bnd ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java ace/trunk/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java Modified: ace/trunk/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java (original) +++ ace/trunk/org.apache.ace.authentication.itest/src/org/apache/ace/it/authentication/LogAuthenticationTest.java Mon Feb 22 22:18:04 2016 @@ -122,11 +122,7 @@ public class LogAuthenticationTest exten 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"); Modified: ace/trunk/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java (original) +++ ace/trunk/org.apache.ace.repository.itest/src/org/apache/ace/it/repository/Utils.java Mon Feb 22 22:18:04 2016 @@ -45,7 +45,20 @@ final class Utils { 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 @@ final class Utils { 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 @@ final class Utils { * @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 @@ final class Utils { 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; - - 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(); + 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 (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 @@ final class Utils { URL url = new URL(host, endpoint + filter); int responseCode; - HttpURLConnection connection = null; - InputStream input = null; - - try { - connection = (HttpURLConnection) url.openConnection(); - 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(out); closeSilently(connection); } @@ -182,12 +185,10 @@ final class Utils { 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 @@ final class Utils { } } 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; + } } Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java (original) +++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/servlet/RepositoryServletBase.java Mon Feb 22 22:18:04 2016 @@ -132,7 +132,7 @@ public abstract class RepositoryServletB } 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 @@ public abstract class RepositoryServletB private void handleCheckout(String customer, String name, long version, HttpServletResponse response) throws IOException { List> 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 @@ public abstract class RepositoryServletB private void handleCommit(String customer, String name, long version, InputStream data, HttpServletResponse response) throws IOException { List> 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 @@ public abstract class RepositoryServletB 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 @@ public abstract class RepositoryServletB } } + private String getRepositoryFilter(String customer, String name) { + return "(&(customer=" + customer + ")(name=" + name + ")(master=*))"; + } + /** * Handles a query command and sends back the response. */ Propchange: ace/trunk/org.apache.ace.useradmin/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Mon Feb 22 22:18:04 2016 @@ -0,0 +1,3 @@ +test-output/ +bin/ +bin_test/ Copied: ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java (from r1731749, ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java) URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java?p2=ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java&p1=ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java&r1=1731749&r2=1731750&rev=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/RepositoryBasedRoleRepositoryStoreTest.java (original) +++ ace/trunk/org.apache.ace.useradmin.itest/src/org/apache/ace/it/useradmin/UserAdminRepositoryTest.java Mon Feb 22 22:18:04 2016 @@ -22,7 +22,10 @@ import java.io.ByteArrayInputStream; 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.apache.felix.dm.Component; 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; - @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)) - }; + 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("changed".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("Piet", Role.USER); + m_userAdmin.createRole("user1", Role.USER); waitForRepoChange(high); - + String repoContentsAsString = getRepoContentsAsString(); - assertTrue(repoContentsAsString.contains("")); + assertTrue(repoContentsAsString.contains("")); } - + 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); + 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("changed".getBytes())){ + try (InputStream is = new ByteArrayInputStream("changed".getBytes())) { m_repository.commit(is, range.getHigh()); } - - Role role = m_userAdmin.getRole("Piet"); - assertEquals("Piet", role.getName()); - - boolean removeRole = m_userAdmin.removeRole("Piet"); + + Role role = m_userAdmin.getRole("user1"); + assertEquals("user1", role.getName()); + + boolean removeRole = m_userAdmin.removeRole("user1"); assertTrue(removeRole); - - Role afterRemove = m_userAdmin.getRole("Piet"); + + Role afterRemove = m_userAdmin.getRole("user1"); assertNull(afterRemove); - - boolean removeRoleAgain = m_userAdmin.removeRole("Piet"); + + boolean removeRoleAgain = m_userAdmin.removeRole("user1"); assertFalse(removeRoleAgain); } - + public void testUpdateUser() throws Exception { long high = m_repository.getRange().getHigh(); - Role piet = m_userAdmin.createRole("Piet", Role.USER); + Role user1 = m_userAdmin.createRole("user1", Role.USER); high = waitForRepoChange(high); - - piet.getProperties().put("test", "property"); + + user1.getProperties().put("test", "property"); waitForRepoChange(high); - + String repoContentsAsString = getRepoContentsAsString(); assertTrue(repoContentsAsString.contains("property")); } - + public void testUpdateUserRepoOutOfSync() throws Exception { long high = m_repository.getRange().getHigh(); - Role piet = m_userAdmin.createRole("Piet", Role.USER); + Role user1 = m_userAdmin.createRole("user1", Role.USER); high = waitForRepoChange(high); - piet.getProperties().put("test", "property"); + 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("changed".getBytes())){ + try (InputStream is = new ByteArrayInputStream("changed".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"); + user1.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("changed".getBytes())){ - m_repository.commit(is, range.getHigh()); + catch (IllegalStateException e) { + // expected } - - // 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"); + + 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", ""); + + 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)(!(remote=*)))").setRequired(true)) + }; + } + 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 @@ public class RepositoryBasedRoleReposito 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", "" - ); - - 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 { - } - } Modified: ace/trunk/org.apache.ace.useradmin/repository.bnd URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/repository.bnd?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/repository.bnd (original) +++ ace/trunk/org.apache.ace.useradmin/repository.bnd Mon Feb 22 22:18:04 2016 @@ -66,5 +66,5 @@ Import-Package: !javax.security.auth,\ * 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 Modified: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java (original) +++ ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/Activator.java Mon Feb 22 22:18:04 2016 @@ -23,15 +23,12 @@ import static org.apache.ace.repository. 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.cm.ManagedServic 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 m_components = new ArrayList<>(); - - @Override - public void updated(Dictionary properties) throws ConfigurationException { - if (properties == null) { - Iterator iterator = m_components.iterator(); - while (iterator.hasNext()) { - Component component = (Component) iterator.next(); - m_manager.remove(component); - iterator.remove(); + + @Override + public void updated(Dictionary 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."); } - 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) - ) + 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(LogService.class) - .setRequired(false) - ); - - m_manager.add(storeComponent); - m_components.add(storeComponent); + .setService(ConnectionFactory.class) + .setRequired(true))); } - - } - + } } Added: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java?rev=1731750&view=auto ============================================================================== --- ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java (added) +++ ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java Mon Feb 22 22:18:04 2016 @@ -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; +} Propchange: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepoCurrentChecker.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java (original) +++ ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryGroup.java Mon Feb 22 22:18:04 2016 @@ -20,48 +20,44 @@ package org.apache.ace.useradmin.reposit 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(); } } Modified: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java (original) +++ ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryUser.java Mon Feb 22 22:18:04 2016 @@ -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 m_delegate; - private Dictionary m_delegate; - - public RepoProperties(Dictionary dictionary) { - this.m_delegate = dictionary; + public RepoProperties(Dictionary dictionary) { + m_delegate = dictionary; + } + @Override + public Enumeration elements() { + return m_delegate.elements(); } @Override - public int size() { - return m_delegate.size(); + public Object get(Object key) { + return m_delegate.get(key); } @Override @@ -92,17 +57,6 @@ public class RepositoryUser implements U } @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 @@ public class RepositoryUser implements U return m_delegate.remove(key); } + @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; } - 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 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); + } } Copied: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java (from r1731749, ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java) URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java?p2=ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java&p1=ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java&r1=1731749&r2=1731750&rev=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/RepositoryBasedRoleRepositoryStore.java (original) +++ ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/UserAdminRepository.java Mon Feb 22 22:18:04 2016 @@ -20,31 +20,31 @@ package org.apache.ace.useradmin.reposit 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.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.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.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.BundleContext; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.service.log.LogService; @@ -58,113 +58,82 @@ 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; +public class UserAdminRepository implements RoleRepositoryStore, UserAdminListener, RepoCurrentChecker { + private final ConcurrentMap m_roleMap; + private final AtomicLong m_version; + private final ReadWriteLock m_rw; + + private volatile Repository m_repository; private volatile LogService m_log; - private volatile RemoteRepository m_repository; - private volatile CachedRepository m_cachedRepository; - private volatile AtomicLong m_version; - private final Map 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); + public UserAdminRepository() { + m_roleMap = new ConcurrentHashMap<>(); + m_version = new AtomicLong(-1L); + m_rw = new ReentrantReadWriteLock(true /* fair */); } - 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 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 entry : roleDto.properties.entrySet()) { - role.getProperties().put(entry.getKey(), entry.getValue()); - } - } - if (roleDto.credentials != null){ - for (Entry 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 roleMapEntry : m_roleMap.entrySet()) { - m_roleMap.put(roleMapEntry.getKey(), wrapRole(roleMapEntry.getValue())); + 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; } - - /** - * 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(""); + + 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!"); } } @@ -173,95 +142,98 @@ public class RepositoryBasedRoleReposito if (name == null) { return null; } - - synchronized (m_roleMap) { - if (!m_cachedRepository.isCurrent()) { - refreshRoleMap(); - } + + ensureRoleMapIsCurrent(); + + m_rw.readLock().lock(); + try { return m_roleMap.get(name); } + finally { + m_rw.readLock().unlock(); + } } @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 matchingRoles = new ArrayList<>(); - for (Role role: m_roleMap.values()){ - if (filter.match(role.getProperties())){ - matchingRoles.add(role); - } - } - - return matchingRoles.toArray(new Role[matchingRoles.size()]); + Filter filter = null; + if (filterString != null) { + filter = FrameworkUtil.createFilter(filterString); } - } - @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); + ensureRoleMapIsCurrent(); + + List allRoles; + m_rw.readLock().lock(); + try { + allRoles = new ArrayList<>(m_roleMap.values()); } - synchronized (m_roleMap) { - if (m_cachedRepository.getMostRecentVersion() == -1) { - refreshRoleMap(); - } - - if (m_roleMap.containsKey(name)){ - return null; + finally { + m_rw.readLock().unlock(); + } + + List matchingRoles = new ArrayList<>(); + for (Role role : allRoles) { + if (filter == null || filter.match(role.getProperties())) { + matchingRoles.add(role); } - role = wrapRole(role); - m_roleMap.put(name, role); - roleChanged(null); } - return role; + + return matchingRoles.toArray(new Role[matchingRoles.size()]); } - + @Override public Role removeRole(String name) throws Exception { - Role removedRole; - synchronized (m_roleMap) { - removedRole = m_roleMap.remove(name); - if (removedRole != null){ - roleChanged(null); + 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(); } - return removedRole; } - List memberOf(Role role) { - List 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()); - } - } - } + @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(); } - return memberOf; } - + + /** + * 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 true if t is * in ts, false otherwise. @@ -275,40 +247,175 @@ public class RepositoryBasedRoleReposito 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 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"); + 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 memberOf(List roles, Role role) { + List 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; + } } } - - stream.flush(); - stream.close(); - writer.flush(); - - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(writer.toString().getBytes())){ - m_cachedRepository.writeLocal(inputStream); + } + } + return memberOf; + } + + final void readRoleMap(long version) throws Exception { + XStream instance = XStreamFactory.getInstance(); + + Map newRoles = new HashMap<>(); + + try (Reader r = new InputStreamReader(m_repository.checkout(version)); + ObjectInputStream objectInputStream = instance.createObjectInputStream(r)) { + + RoleDTO roleDto; + List 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 entry : roleDto.properties.entrySet()) { + role.getProperties().put(entry.getKey(), entry.getValue()); + } + } + if (roleDto.credentials != null) { + for (Entry 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 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 roles = new ArrayList<>(m_roleMap.values()); + + XStream instance = XStreamFactory.getInstance(); + try (ObjectOutputStream stream = instance.createObjectOutputStream(writer, "roles")) { + for (Role role : roles) { + List memberOf = memberOf(roles, role); + if (role.getType() == Role.USER) { + stream.writeObject(new UserDTO((User) role, memberOf)); } - - 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); - } + 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); } } - } Modified: ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java (original) +++ ace/trunk/org.apache.ace.useradmin/src/org/apache/ace/useradmin/repository/xstream/RoleDTO.java Mon Feb 22 22:18:04 2016 @@ -42,10 +42,9 @@ public abstract class RoleDTO { this.memberOf = memberOf; } - @SuppressWarnings("rawtypes") - static Properties toProperties(Dictionary dict) { + static Properties toProperties(Dictionary dict) { Properties properties = new Properties(); - Enumeration keys = dict.keys(); + Enumeration keys = dict.keys(); while (keys.hasMoreElements()) { Object key = (Object) keys.nextElement(); properties.put(key, dict.get(key)); Modified: ace/trunk/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java?rev=1731750&r1=1731749&r2=1731750&view=diff ============================================================================== --- ace/trunk/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java (original) +++ ace/trunk/org.apache.ace.useradmin/test/org/apache/ace/useradmin/repository/xstream/XStreamTest.java Mon Feb 22 22:18:04 2016 @@ -40,60 +40,56 @@ public class XStreamTest { @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; } - + }