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;
}
-
+
}