Introduce transaction working copy abstraction
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Jun 2022 05:57:08 +0000 (07:57 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Jun 2022 05:57:08 +0000 (07:57 +0200)
23 files changed:
org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserWorkingCopy.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/FunctionalGroup.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/Organization.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java
org.argeo.util/src/org/argeo/osgi/useradmin/Person.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/SystemPermissions.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java
org.argeo.util/src/org/argeo/util/directory/FunctionalGroup.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/directory/Organization.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/directory/Person.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/directory/SystemPermissions.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/transaction/AbstractWorkingCopy.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/transaction/WorkingCopy.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/transaction/WorkingCopyProcessor.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/transaction/XAResourceProvider.java [new file with mode: 0644]

index e5576b91d31e3c1b44a3f76986137a007f057c09..838c2ce0b15f886234136ff1275c24de880e8b48 100644 (file)
@@ -30,10 +30,14 @@ import javax.naming.directory.BasicAttribute;
 import javax.naming.directory.BasicAttributes;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
+import javax.transaction.xa.XAResource;
 
 import org.argeo.util.naming.LdapAttrs;
 import org.argeo.util.naming.LdapObjs;
 import org.argeo.util.transaction.WorkControl;
+import org.argeo.util.transaction.WorkingCopyProcessor;
+import org.argeo.util.transaction.WorkingCopyXaResource;
+import org.argeo.util.transaction.XAResourceProvider;
 import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -43,7 +47,8 @@ import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
 
 /** Base class for a {@link UserDirectory}. */
-abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
+abstract class AbstractUserDirectory
+               implements UserAdmin, UserDirectory, WorkingCopyProcessor<DirectoryUserWorkingCopy>, XAResourceProvider {
        static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
        static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
 
@@ -71,7 +76,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        // Transaction
 //     private TransactionManager transactionManager;
        private WorkControl transactionControl;
-       private WcXaResource xaResource = new WcXaResource(this);
+       private WorkingCopyXaResource<DirectoryUserWorkingCopy> xaResource = new WorkingCopyXaResource<>(this);
 
        private String forcedPassword;
 
@@ -248,8 +253,8 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return xaResource.wc() != null;
        }
 
-       protected UserDirectoryWorkingCopy getWorkingCopy() {
-               UserDirectoryWorkingCopy wc = xaResource.wc();
+       protected DirectoryUserWorkingCopy getWorkingCopy() {
+               DirectoryUserWorkingCopy wc = xaResource.wc();
                if (wc == null)
                        return null;
                return wc;
@@ -317,7 +322,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        }
 
        protected DirectoryUser doGetRole(LdapName dn) {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                DirectoryUser user;
                try {
                        user = daoGetRole(dn);
@@ -325,9 +330,9 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        user = null;
                }
                if (wc != null) {
-                       if (user == null && wc.getNewUsers().containsKey(dn))
-                               user = wc.getNewUsers().get(dn);
-                       else if (wc.getDeletedUsers().containsKey(dn))
+                       if (user == null && wc.getNewData().containsKey(dn))
+                               user = wc.getNewData().get(dn);
+                       else if (wc.getDeletedData().containsKey(dn))
                                user = null;
                }
                return user;
@@ -340,17 +345,17 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        }
 
        List<DirectoryUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
                List<DirectoryUser> res = doGetRoles(searchBase, f, deep);
                if (wc != null) {
                        for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
                                DirectoryUser user = it.next();
                                LdapName dn = user.getDn();
-                               if (wc.getDeletedUsers().containsKey(dn))
+                               if (wc.getDeletedData().containsKey(dn))
                                        it.remove();
                        }
-                       for (DirectoryUser user : wc.getNewUsers().values()) {
+                       for (DirectoryUser user : wc.getNewData().values()) {
                                if (f == null || f.match(user.getProperties()))
                                        res.add(user);
                        }
@@ -427,23 +432,23 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @Override
        public Role createRole(String name, int type) {
                checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                LdapName dn = toLdapName(name);
-               if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
+               if ((daoHasRole(dn) && !wc.getDeletedData().containsKey(dn)) || wc.getNewData().containsKey(dn))
                        throw new IllegalArgumentException("Already a role " + name);
                BasicAttributes attrs = new BasicAttributes(true);
                // attrs.put(LdifName.dn.name(), dn.toString());
                Rdn nameRdn = dn.getRdn(dn.size() - 1);
                // TODO deal with multiple attr RDN
                attrs.put(nameRdn.getType(), nameRdn.getValue());
-               if (wc.getDeletedUsers().containsKey(dn)) {
-                       wc.getDeletedUsers().remove(dn);
-                       wc.getModifiedUsers().put(dn, attrs);
+               if (wc.getDeletedData().containsKey(dn)) {
+                       wc.getDeletedData().remove(dn);
+                       wc.getModifiedData().put(dn, attrs);
                        return getRole(name);
                } else {
-                       wc.getModifiedUsers().put(dn, attrs);
+                       wc.getModifiedData().put(dn, attrs);
                        DirectoryUser newRole = newRole(dn, type, attrs);
-                       wc.getNewUsers().put(dn, newRole);
+                       wc.getNewData().put(dn, newRole);
                        return newRole;
                }
        }
@@ -479,12 +484,12 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @Override
        public boolean removeRole(String name) {
                checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                LdapName dn = toLdapName(name);
                boolean actuallyDeleted;
-               if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
+               if (daoHasRole(dn) || wc.getNewData().containsKey(dn)) {
                        DirectoryUser user = (DirectoryUser) getRole(name);
-                       wc.getDeletedUsers().put(dn, user);
+                       wc.getDeletedData().put(dn, user);
                        actuallyDeleted = true;
                } else {// just removing from groups (e.g. system roles)
                        actuallyDeleted = false;
@@ -496,17 +501,12 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return actuallyDeleted;
        }
 
-       // TRANSACTION
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void commit(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-
+       /*
+        * TRANSACTION
+        */
+       @Override
+       public DirectoryUserWorkingCopy newWorkingCopy() {
+               return new DirectoryUserWorkingCopy();
        }
 
        /*
@@ -684,7 +684,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                this.transactionControl = transactionControl;
        }
 
-       public WcXaResource getXaResource() {
+       public XAResource getXaResource() {
                return xaResource;
        }
 
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserWorkingCopy.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserWorkingCopy.java
new file mode 100644 (file)
index 0000000..2aed145
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.osgi.useradmin;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.transaction.AbstractWorkingCopy;
+
+/** Working copy for a user directory being edited. */
+class DirectoryUserWorkingCopy extends AbstractWorkingCopy<DirectoryUser, Attributes, LdapName> {
+       @Override
+       protected LdapName getId(DirectoryUser user) {
+               return user.getDn();
+       }
+
+       @Override
+       protected Attributes cloneAttributes(DirectoryUser user) {
+               return (Attributes) user.getAttributes().clone();
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/FunctionalGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/FunctionalGroup.java
deleted file mode 100644 (file)
index 5f17d8b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-public interface FunctionalGroup {
-
-}
index 1eaf1e7d65470e8377e072727b0fb8069f375ebd..1fe7eb9df60dcab1347475ade7bcec231fe28356 100644 (file)
@@ -98,23 +98,23 @@ class LdapConnection {
                }
        }
 
-       synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException {
+       synchronized void prepareChanges(DirectoryUserWorkingCopy wc) throws NamingException {
                // make sure connection will work
                reconnect();
 
                // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+               for (LdapName dn : wc.getDeletedData().keySet()) {
                        if (!entryExists(dn))
                                throw new IllegalStateException("User to delete no found " + dn);
                }
                // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
+               for (LdapName dn : wc.getNewData().keySet()) {
                        if (entryExists(dn))
                                throw new IllegalStateException("User to create found " + dn);
                }
                // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       if (!wc.getNewData().containsKey(dn) && !entryExists(dn))
                                throw new IllegalStateException("User to modify not found " + dn);
                }
 
@@ -128,19 +128,19 @@ class LdapConnection {
                }
        }
 
-       synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException {
+       synchronized void commitChanges(DirectoryUserWorkingCopy wc) throws NamingException {
                // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+               for (LdapName dn : wc.getDeletedData().keySet()) {
                        getLdapContext().destroySubcontext(dn);
                }
                // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       DirectoryUser user = wc.getNewUsers().get(dn);
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       DirectoryUser user = wc.getNewData().get(dn);
                        getLdapContext().createSubcontext(dn, user.getAttributes());
                }
                // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
                        getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
                }
        }
index 82e890b7cae577c53bd928f8b3d185118e532ce2..879d5da04991b0113383771ee43ae8aee4ac0829 100644 (file)
@@ -165,7 +165,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void prepare(UserDirectoryWorkingCopy wc) {
+       public void prepare(DirectoryUserWorkingCopy wc) {
                try {
                        ldapConnection.prepareChanges(wc);
                } catch (NamingException e) {
@@ -174,7 +174,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void commit(UserDirectoryWorkingCopy wc) {
+       public void commit(DirectoryUserWorkingCopy wc) {
                try {
                        ldapConnection.commitChanges(wc);
                } catch (NamingException e) {
@@ -183,7 +183,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void rollback(UserDirectoryWorkingCopy wc) {
+       public void rollback(DirectoryUserWorkingCopy wc) {
                // prepare not impacting
        }
 
index 80bff59472a7799ca5fdd1ada85ebd91215ac2cb..72b08a8c3d0fc59c78fd3a0f3dc35ec45a0ffbd1 100644 (file)
@@ -10,6 +10,9 @@ import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.util.directory.FunctionalGroup;
+import org.argeo.util.directory.Organization;
+import org.argeo.util.directory.SystemPermissions;
 import org.osgi.service.useradmin.Role;
 
 /** Directory group implementation */
index a20ad85c11fbc9a65e24e3a3921aae5a5a8a0f67..aaac50272a02cb605ec962521fcf77f9f873a06a 100644 (file)
@@ -21,6 +21,7 @@ import javax.naming.directory.Attributes;
 import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.util.directory.Person;
 import org.argeo.util.naming.LdapAttrs;
 import org.argeo.util.naming.LdapObjs;
 import org.argeo.util.naming.SharedSecret;
@@ -182,14 +183,14 @@ abstract class LdifUser implements DirectoryUser {
        /** Should only be called from working copy thread. */
        private synchronized Attributes getModifiedAttributes() {
                assert getWc() != null;
-               return getWc().getAttributes(getDn());
+               return getWc().getModifiedData().get(getDn());
        }
 
        protected synchronized boolean isEditing() {
                return getWc() != null && getModifiedAttributes() != null;
        }
 
-       private synchronized UserDirectoryWorkingCopy getWc() {
+       private synchronized DirectoryUserWorkingCopy getWc() {
                return userAdmin.getWorkingCopy();
        }
 
index bf455949d52af549983bd18536b9d9b5c35500bd..26d3d134c511561016333f9273458967d7446fc9 100644 (file)
@@ -264,9 +264,9 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void prepare(UserDirectoryWorkingCopy wc) {
+       public void prepare(DirectoryUserWorkingCopy wc) {
                // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+               for (LdapName dn : wc.getDeletedData().keySet()) {
                        if (users.containsKey(dn))
                                users.remove(dn);
                        else if (groups.containsKey(dn))
@@ -275,8 +275,8 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                                throw new IllegalStateException("User to delete not found " + dn);
                }
                // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       DirectoryUser user = wc.getNewUsers().get(dn);
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       DirectoryUser user = wc.getNewData().get(dn);
                        if (users.containsKey(dn) || groups.containsKey(dn))
                                throw new IllegalStateException("User to create found " + dn);
                        else if (Role.USER == user.getType())
@@ -287,8 +287,8 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                                throw new IllegalStateException("Unsupported role type " + user.getType() + " for new user " + dn);
                }
                // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
                        DirectoryUser user;
                        if (users.containsKey(dn))
                                user = users.get(dn);
@@ -301,12 +301,12 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void commit(UserDirectoryWorkingCopy wc) {
+       public void commit(DirectoryUserWorkingCopy wc) {
                save();
        }
 
        @Override
-       protected void rollback(UserDirectoryWorkingCopy wc) {
+       public void rollback(DirectoryUserWorkingCopy wc) {
                init();
        }
 
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/Organization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/Organization.java
deleted file mode 100644 (file)
index 85b1280..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-public interface Organization {
-
-}
index 3ded7a7a6a6ce28cfd277663b6efbb9e238a4810..69c06c8483a2d73f9735f06b3700705d32907079 100644 (file)
@@ -75,4 +75,17 @@ public class OsUserDirectory extends AbstractUserDirectory {
                return new ArrayList<>();
        }
 
+       public void prepare(DirectoryUserWorkingCopy wc) {
+
+       }
+
+       public void commit(DirectoryUserWorkingCopy wc) {
+
+       }
+
+       public void rollback(DirectoryUserWorkingCopy wc) {
+
+       }
+
+
 }
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/Person.java b/org.argeo.util/src/org/argeo/osgi/useradmin/Person.java
deleted file mode 100644 (file)
index 8f6980b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-public interface Person {
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/SystemPermissions.java b/org.argeo.util/src/org/argeo/osgi/useradmin/SystemPermissions.java
deleted file mode 100644 (file)
index c386a41..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-public interface SystemPermissions {
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java
deleted file mode 100644 (file)
index 35a34bd..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** {@link XAResource} for a user directory being edited. */
-class UserDirectoryWorkingCopy {
-       // private final static Log log = LogFactory
-       // .getLog(UserDirectoryWorkingCopy.class);
-
-       private Map<LdapName, DirectoryUser> newUsers = new HashMap<LdapName, DirectoryUser>();
-       private Map<LdapName, Attributes> modifiedUsers = new HashMap<LdapName, Attributes>();
-       private Map<LdapName, DirectoryUser> deletedUsers = new HashMap<LdapName, DirectoryUser>();
-
-       void cleanUp() {
-               // clean collections
-               newUsers.clear();
-               newUsers = null;
-               modifiedUsers.clear();
-               modifiedUsers = null;
-               deletedUsers.clear();
-               deletedUsers = null;
-       }
-
-       public boolean noModifications() {
-               return newUsers.size() == 0 && modifiedUsers.size() == 0 && deletedUsers.size() == 0;
-       }
-
-       public Attributes getAttributes(LdapName dn) {
-               if (modifiedUsers.containsKey(dn))
-                       return modifiedUsers.get(dn);
-               return null;
-       }
-
-       public void startEditing(DirectoryUser user) {
-               LdapName dn = user.getDn();
-               if (modifiedUsers.containsKey(dn))
-                       throw new IllegalStateException("Already editing " + dn);
-               modifiedUsers.put(dn, (Attributes) user.getAttributes().clone());
-       }
-
-       public Map<LdapName, DirectoryUser> getNewUsers() {
-               return newUsers;
-       }
-
-       public Map<LdapName, DirectoryUser> getDeletedUsers() {
-               return deletedUsers;
-       }
-
-       public Map<LdapName, Attributes> getModifiedUsers() {
-               return modifiedUsers;
-       }
-}
index af0f351c77e6b9b9105ed8f0ef3e9a0f009837d2..32bb401bc4695f997f021ebd190406308a538348 100644 (file)
@@ -11,7 +11,7 @@ import javax.transaction.xa.Xid;
 class WcXaResource implements XAResource {
        private final AbstractUserDirectory userDirectory;
 
-       private Map<Xid, UserDirectoryWorkingCopy> workingCopies = new HashMap<Xid, UserDirectoryWorkingCopy>();
+       private Map<Xid, DirectoryUserWorkingCopy> workingCopies = new HashMap<Xid, DirectoryUserWorkingCopy>();
        private Xid editingXid = null;
        private int transactionTimeout = 0;
 
@@ -23,7 +23,7 @@ class WcXaResource implements XAResource {
        public synchronized void start(Xid xid, int flags) throws XAException {
                if (editingXid != null)
                        throw new IllegalStateException("Already editing " + editingXid);
-               UserDirectoryWorkingCopy wc = workingCopies.put(xid, new UserDirectoryWorkingCopy());
+               DirectoryUserWorkingCopy wc = workingCopies.put(xid, new DirectoryUserWorkingCopy());
                if (wc != null)
                        throw new IllegalStateException("There is already a working copy for " + xid);
                this.editingXid = xid;
@@ -34,14 +34,14 @@ class WcXaResource implements XAResource {
                checkXid(xid);
        }
 
-       private UserDirectoryWorkingCopy wc(Xid xid) {
+       private DirectoryUserWorkingCopy wc(Xid xid) {
                return workingCopies.get(xid);
        }
 
-       synchronized UserDirectoryWorkingCopy wc() {
+       synchronized DirectoryUserWorkingCopy wc() {
                if (editingXid == null)
                        return null;
-               UserDirectoryWorkingCopy wc = workingCopies.get(editingXid);
+               DirectoryUserWorkingCopy wc = workingCopies.get(editingXid);
                if (wc == null)
                        throw new IllegalStateException("No working copy found for " + editingXid);
                return wc;
@@ -56,7 +56,7 @@ class WcXaResource implements XAResource {
        @Override
        public int prepare(Xid xid) throws XAException {
                checkXid(xid);
-               UserDirectoryWorkingCopy wc = wc(xid);
+               DirectoryUserWorkingCopy wc = wc(xid);
                if (wc.noModifications())
                        return XA_RDONLY;
                try {
@@ -72,7 +72,7 @@ class WcXaResource implements XAResource {
        public void commit(Xid xid, boolean onePhase) throws XAException {
                try {
                        checkXid(xid);
-                       UserDirectoryWorkingCopy wc = wc(xid);
+                       DirectoryUserWorkingCopy wc = wc(xid);
                        if (wc.noModifications())
                                return;
                        if (onePhase)
diff --git a/org.argeo.util/src/org/argeo/util/directory/FunctionalGroup.java b/org.argeo.util/src/org/argeo/util/directory/FunctionalGroup.java
new file mode 100644 (file)
index 0000000..89511ab
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.util.directory;
+
+public interface FunctionalGroup {
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/directory/Organization.java b/org.argeo.util/src/org/argeo/util/directory/Organization.java
new file mode 100644 (file)
index 0000000..bbbdcd9
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.util.directory;
+
+public interface Organization {
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/directory/Person.java b/org.argeo.util/src/org/argeo/util/directory/Person.java
new file mode 100644 (file)
index 0000000..d782ee4
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.util.directory;
+
+public interface Person {
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/directory/SystemPermissions.java b/org.argeo.util/src/org/argeo/util/directory/SystemPermissions.java
new file mode 100644 (file)
index 0000000..3ab16b8
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.util.directory;
+
+public interface SystemPermissions {
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/transaction/AbstractWorkingCopy.java b/org.argeo.util/src/org/argeo/util/transaction/AbstractWorkingCopy.java
new file mode 100644 (file)
index 0000000..0da35ac
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.util.transaction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractWorkingCopy<DATA, ATTR, ID> implements WorkingCopy<DATA, ATTR, ID> {
+       private Map<ID, DATA> newData = new HashMap<ID, DATA>();
+       private Map<ID, ATTR> modifiedData = new HashMap<ID, ATTR>();
+       private Map<ID, DATA> deletedData = new HashMap<ID, DATA>();
+
+       protected abstract ID getId(DATA data);
+
+       protected abstract ATTR cloneAttributes(DATA data);
+
+       public void cleanUp() {
+               // clean collections
+               newData.clear();
+               newData = null;
+               modifiedData.clear();
+               modifiedData = null;
+               deletedData.clear();
+               deletedData = null;
+       }
+
+       public boolean noModifications() {
+               return newData.size() == 0 && modifiedData.size() == 0 && deletedData.size() == 0;
+       }
+
+       public void startEditing(DATA user) {
+               ID id = getId(user);
+               if (modifiedData.containsKey(id))
+                       throw new IllegalStateException("Already editing " + id);
+               modifiedData.put(id, cloneAttributes(user));
+       }
+
+       public Map<ID, DATA> getNewData() {
+               return newData;
+       }
+
+       public Map<ID, DATA> getDeletedData() {
+               return deletedData;
+       }
+
+       public Map<ID, ATTR> getModifiedData() {
+               return modifiedData;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopy.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopy.java
new file mode 100644 (file)
index 0000000..9dd3fc5
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.util.transaction;
+
+import java.util.Map;
+
+public interface WorkingCopy<DATA, ATTR, ID> {
+       void startEditing(DATA user);
+
+       boolean noModifications();
+
+       void cleanUp();
+
+       Map<ID, DATA> getNewData();
+
+       Map<ID, DATA> getDeletedData();
+
+       Map<ID, ATTR> getModifiedData();
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyProcessor.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyProcessor.java
new file mode 100644 (file)
index 0000000..cdd6404
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.util.transaction;
+
+public interface WorkingCopyProcessor<WC extends WorkingCopy<?, ?, ?>> {
+       void prepare(WC wc);
+
+       void commit(WC wc);
+
+       void rollback(WC wc);
+       
+       WC newWorkingCopy();
+}
diff --git a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java
new file mode 100644 (file)
index 0000000..ebafd26
--- /dev/null
@@ -0,0 +1,135 @@
+package org.argeo.util.transaction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** {@link XAResource} for a user directory being edited. */
+public class WorkingCopyXaResource<WC extends WorkingCopy<?, ?, ?>> implements XAResource {
+       private final WorkingCopyProcessor<WC> processor;
+
+       private Map<Xid, WC> workingCopies = new HashMap<Xid, WC>();
+       private Xid editingXid = null;
+       private int transactionTimeout = 0;
+
+       public WorkingCopyXaResource(WorkingCopyProcessor<WC> processor) {
+               this.processor = processor;
+       }
+
+       @Override
+       public synchronized void start(Xid xid, int flags) throws XAException {
+               if (editingXid != null)
+                       throw new IllegalStateException("Already editing " + editingXid);
+               WC wc = workingCopies.put(xid, processor.newWorkingCopy());
+               if (wc != null)
+                       throw new IllegalStateException("There is already a working copy for " + xid);
+               this.editingXid = xid;
+       }
+
+       @Override
+       public void end(Xid xid, int flags) throws XAException {
+               checkXid(xid);
+       }
+
+       private WC wc(Xid xid) {
+               return workingCopies.get(xid);
+       }
+
+       public synchronized WC wc() {
+               if (editingXid == null)
+                       return null;
+               WC wc = workingCopies.get(editingXid);
+               if (wc == null)
+                       throw new IllegalStateException("No working copy found for " + editingXid);
+               return wc;
+       }
+
+       private synchronized void cleanUp(Xid xid) {
+               wc(xid).cleanUp();
+               workingCopies.remove(xid);
+               editingXid = null;
+       }
+
+       @Override
+       public int prepare(Xid xid) throws XAException {
+               checkXid(xid);
+               WC wc = wc(xid);
+               if (wc.noModifications())
+                       return XA_RDONLY;
+               try {
+                       processor.prepare(wc);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               }
+               return XA_OK;
+       }
+
+       @Override
+       public void commit(Xid xid, boolean onePhase) throws XAException {
+               try {
+                       checkXid(xid);
+                       WC wc = wc(xid);
+                       if (wc.noModifications())
+                               return;
+                       if (onePhase)
+                               processor.prepare(wc);
+                       processor.commit(wc);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               } finally {
+                       cleanUp(xid);
+               }
+       }
+
+       @Override
+       public void rollback(Xid xid) throws XAException {
+               try {
+                       checkXid(xid);
+                       processor.rollback(wc(xid));
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               } finally {
+                       cleanUp(xid);
+               }
+       }
+
+       @Override
+       public void forget(Xid xid) throws XAException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean isSameRM(XAResource xares) throws XAException {
+               return xares == this;
+       }
+
+       @Override
+       public Xid[] recover(int flag) throws XAException {
+               return new Xid[0];
+       }
+
+       @Override
+       public int getTransactionTimeout() throws XAException {
+               return transactionTimeout;
+       }
+
+       @Override
+       public boolean setTransactionTimeout(int seconds) throws XAException {
+               transactionTimeout = seconds;
+               return true;
+       }
+
+       private void checkXid(Xid xid) throws XAException {
+               if (xid == null)
+                       throw new XAException(XAException.XAER_OUTSIDE);
+               if (!xid.equals(xid))
+                       throw new XAException(XAException.XAER_NOTA);
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/transaction/XAResourceProvider.java b/org.argeo.util/src/org/argeo/util/transaction/XAResourceProvider.java
new file mode 100644 (file)
index 0000000..b0b211b
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.util.transaction;
+
+import javax.transaction.xa.XAResource;
+
+public interface XAResourceProvider {
+       XAResource getXaResource();
+}