Use Bitronix as transaction manager.
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 9 Oct 2015 16:03:22 +0000 (16:03 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 9 Oct 2015 16:03:22 +0000 (16:03 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@8469 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java
org.argeo.security.core/bnd.bnd
org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java [new file with mode: 0644]
pom.xml

index 026b12116159b9667f2c96464f767754bd5d1f3e..32672bef50aeb389a6d9c40681bc8523306e7e0b 100644 (file)
@@ -1,8 +1,14 @@
 package org.argeo.cms.internal.kernel;
 
+import static bitronix.tm.TransactionManagerServices.getTransactionManager;
+import static bitronix.tm.TransactionManagerServices.getTransactionSynchronizationRegistry;
+import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
+import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstancePath;
 import static org.argeo.jcr.ArgeoJcrConstants.ALIAS_NODE;
 import static org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS;
+import static org.osgi.framework.Constants.FRAMEWORK_UUID;
 
+import java.io.File;
 import java.lang.management.ManagementFactory;
 import java.security.PrivilegedAction;
 import java.util.HashMap;
@@ -13,6 +19,7 @@ import javax.jcr.Repository;
 import javax.jcr.RepositoryFactory;
 import javax.security.auth.Subject;
 import javax.transaction.TransactionManager;
+import javax.transaction.TransactionSynchronizationRegistry;
 import javax.transaction.UserTransaction;
 
 import org.apache.commons.logging.Log;
@@ -21,7 +28,6 @@ import org.apache.jackrabbit.util.TransientFileFactory;
 import org.argeo.ArgeoException;
 import org.argeo.ArgeoLogger;
 import org.argeo.cms.CmsException;
-import org.argeo.cms.internal.transaction.SimpleTransactionManager;
 import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory;
 import org.argeo.jcr.ArgeoJcrConstants;
 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
@@ -32,6 +38,11 @@ import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.useradmin.UserAdmin;
 
+import bitronix.tm.BitronixTransactionManager;
+import bitronix.tm.BitronixTransactionSynchronizationRegistry;
+import bitronix.tm.Configuration;
+import bitronix.tm.TransactionManagerServices;
+
 /**
  * Argeo CMS Kernel. Responsible for :
  * <ul>
@@ -43,14 +54,14 @@ import org.osgi.service.useradmin.UserAdmin;
  * <li>OS access</li>
  * </ul>
  */
-final class Kernel implements ServiceListener {
+final class Kernel implements KernelConstants, ServiceListener {
        /*
         * REGISTERED SERVICES
         */
        private ServiceRegistration<ArgeoLogger> loggerReg;
        private ServiceRegistration<TransactionManager> tmReg;
        private ServiceRegistration<UserTransaction> utReg;
-       // private ServiceRegistration<TransactionSynchronizationRegistry> tsrReg;
+       private ServiceRegistration<TransactionSynchronizationRegistry> tsrReg;
        private ServiceRegistration<Repository> repositoryReg;
        private ServiceRegistration<RepositoryFactory> repositoryFactoryReg;
        private ServiceRegistration<UserAdmin> userAdminReg;
@@ -59,7 +70,8 @@ final class Kernel implements ServiceListener {
         * SERVICES IMPLEMENTATIONS
         */
        private NodeLogger logger;
-       private SimpleTransactionManager transactionManager;
+       private BitronixTransactionManager transactionManager;
+       private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry;
        private OsgiJackrabbitRepositoryFactory repositoryFactory;
        NodeRepository repository;
        private NodeUserAdmin userAdmin;
@@ -97,7 +109,8 @@ final class Kernel implements ServiceListener {
                try {
                        // Initialise services
                        logger = new NodeLogger();
-                       transactionManager = new SimpleTransactionManager();
+                       // transactionManager = new SimpleTransactionManager();
+                       initBitronixTransactionManager();
                        repository = new NodeRepository(bc);
                        repositoryFactory = new OsgiJackrabbitRepositoryFactory();
                        userAdmin = new NodeUserAdmin(transactionManager, repository);
@@ -131,10 +144,28 @@ final class Kernel implements ServiceListener {
                directorsCut(initDuration);
        }
 
+       private void initBitronixTransactionManager() {
+               Configuration tmConf = TransactionManagerServices.getConfiguration();
+               tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID));
+
+               File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
+                               getOsgiInstancePath("transactions")));
+               File tmDir1 = new File(tmBaseDir, "btm1");
+               tmDir1.mkdirs();
+               tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog")
+                               .getAbsolutePath());
+               File tmDir2 = new File(tmBaseDir, "btm2");
+               tmDir2.mkdirs();
+               tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog")
+                               .getAbsolutePath());
+               transactionManager = getTransactionManager();
+               transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
+       }
+
        private void publish() {
                // Listen to service publication (also ours)
                bc.addServiceListener(Kernel.this);
-               
+
                // Logging
                loggerReg = bc.registerService(ArgeoLogger.class, logger, null);
                // Transaction
@@ -142,8 +173,8 @@ final class Kernel implements ServiceListener {
                                transactionManager, null);
                utReg = bc.registerService(UserTransaction.class, transactionManager,
                                null);
-               // tsrReg = bc.registerService(TransactionSynchronizationRegistry.class,
-               // transactionManager.getTsr(), null);
+               tsrReg = bc.registerService(TransactionSynchronizationRegistry.class,
+                               transactionSynchronizationRegistry, null);
                // User admin
                userAdminReg = bc.registerService(UserAdmin.class, userAdmin,
                                userAdmin.currentState());
@@ -168,6 +199,8 @@ final class Kernel implements ServiceListener {
                        userAdmin.destroy();
                if (repository != null)
                        repository.destroy();
+               if (transactionManager != null)
+                       transactionManager.shutdown();
 
                bc.removeServiceListener(this);
 
@@ -189,6 +222,7 @@ final class Kernel implements ServiceListener {
                repositoryReg.unregister();
                tmReg.unregister();
                utReg.unregister();
+               tsrReg.unregister();
                loggerReg.unregister();
        }
 
index 663c2b9375e4f1353e80c4ef6bf5f4358aff5a76..f528f6f7007dab15568f6d433f3d343a70fa027e 100644 (file)
@@ -16,6 +16,8 @@ public interface KernelConstants {
        final static String REPO_SEARCH_CACHE_SIZE = "argeo.node.repo.searchCacheSize";
        final static String REPO_MAX_VOLATILE_INDEX_SIZE = "argeo.node.repo.maxVolatileIndexSize";
 
+       final static String TRANSACTIONS_HOME = "argeo.node.transactions.home";
+
        // Node Security
        final static String ROLES_URI = "argeo.node.roles.uri";
        /** URI to an LDIF file or LDAP server used as initialization or backend */
index b2fb03d8eaf7b0cfbc8d426f933e2241c45487c9..c1e20892456fd26792956f3ce2532a3ddce9738c 100644 (file)
@@ -47,6 +47,19 @@ class KernelUtils implements KernelConstants {
                                .getAbsoluteFile();
        }
 
+       static String getOsgiInstancePath(String relativePath) {
+               try {
+                       if (relativePath == null)
+                               return getOsgiInstanceDir().getCanonicalPath();
+                       else
+                               return new File(getOsgiInstanceDir(), relativePath)
+                                               .getCanonicalPath();
+               } catch (IOException e) {
+                       throw new CmsException("Cannot get instance path for "
+                                       + relativePath, e);
+               }
+       }
+
        static File getOsgiConfigurationFile(String relativePath) {
                try {
                        return new File(new URI(Activator.getBundleContext().getProperty(
@@ -74,7 +87,8 @@ class KernelUtils implements KernelConstants {
                Subject subject = new Subject();
                LoginContext lc;
                try {
-                       lc = new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS, subject);
+                       lc = new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS,
+                                       subject);
                        lc.login();
                        return subject;
                } catch (LoginException e) {
index d31b9e77a034b868ffe24d11e2c7489b0210b4f5..6bee2a949bdc2b8c0eef1cab4f26428669353ed8 100644 (file)
@@ -1,4 +1,5 @@
 Import-Package:org.bouncycastle.*;resolution:=optional,\
+bitronix.tm.*;resolution:=optional,\
 javax.jcr.security,\
 org.apache.commons.codec,\
 org.apache.commons.codec.digest,\
index ae931039d17d46ac6442c73e70eaf1262c99d087..e79800f102e1efb46822526a3c098afd0136f3a7 100644 (file)
@@ -13,11 +13,9 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 import javax.naming.InvalidNameException;
 import javax.naming.directory.Attributes;
@@ -28,12 +26,8 @@ import javax.naming.ldap.Rdn;
 import javax.transaction.SystemException;
 import javax.transaction.Transaction;
 import javax.transaction.TransactionManager;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
 import javax.transaction.xa.Xid;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -44,9 +38,6 @@ import org.osgi.service.useradmin.UserAdmin;
 
 /** Base class for a {@link UserDirectory}. */
 abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
-       private final static Log log = LogFactory
-                       .getLog(AbstractUserDirectory.class);
-
        private final Hashtable<String, Object> properties;
        private final String baseDn;
        private final String userObjectClass;
@@ -64,7 +55,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        .asList(new String[] { LdifName.userpassword.name() });
 
        private TransactionManager transactionManager;
-       private ThreadLocal<WorkingCopy> workingCopy = new ThreadLocal<AbstractUserDirectory.WorkingCopy>();
+       private ThreadLocal<UserDirectoryWorkingCopy> workingCopy = new ThreadLocal<UserDirectoryWorkingCopy>();
        private Xid editingTransactionXid = null;
 
        AbstractUserDirectory(Dictionary<String, ?> props) {
@@ -121,11 +112,11 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return workingCopy.get() != null;
        }
 
-       protected WorkingCopy getWorkingCopy() {
-               WorkingCopy wc = workingCopy.get();
+       protected UserDirectoryWorkingCopy getWorkingCopy() {
+               UserDirectoryWorkingCopy wc = workingCopy.get();
                if (wc == null)
                        return null;
-               if (wc.xid == null) {
+               if (wc.getXid() == null) {
                        workingCopy.set(null);
                        return null;
                }
@@ -143,7 +134,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        throw new UserDirectoryException(
                                        "A transaction needs to be active in order to edit");
                if (editingTransactionXid == null) {
-                       WorkingCopy wc = new WorkingCopy();
+                       UserDirectoryWorkingCopy wc = new UserDirectoryWorkingCopy(this);
                        try {
                                transaction.enlistResource(wc);
                                editingTransactionXid = wc.getXid();
@@ -192,7 +183,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        }
 
        protected DirectoryUser doGetRole(LdapName dn) {
-               WorkingCopy wc = getWorkingCopy();
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
                DirectoryUser user = daoGetRole(dn);
                if (wc != null) {
                        if (user == null && wc.getNewUsers().containsKey(dn))
@@ -206,7 +197,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @SuppressWarnings("unchecked")
        @Override
        public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               WorkingCopy wc = getWorkingCopy();
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
                Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
                List<DirectoryUser> res = doGetRoles(f);
                if (wc != null) {
@@ -274,7 +265,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @Override
        public Role createRole(String name, int type) {
                checkEdit();
-               WorkingCopy wc = getWorkingCopy();
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
                LdapName dn = toDn(name);
                if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn))
                                || wc.getNewUsers().containsKey(dn))
@@ -323,7 +314,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @Override
        public boolean removeRole(String name) {
                checkEdit();
-               WorkingCopy wc = getWorkingCopy();
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
                LdapName dn = toDn(name);
                boolean actuallyDeleted;
                if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
@@ -342,16 +333,20 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        }
 
        // TRANSACTION
-       protected void prepare(WorkingCopy wc) {
+       protected void prepare(UserDirectoryWorkingCopy wc) {
 
        }
 
-       protected void commit(WorkingCopy wc) {
+       protected void commit(UserDirectoryWorkingCopy wc) {
 
        }
 
-       protected void rollback(WorkingCopy wc) {
+       protected void rollback(UserDirectoryWorkingCopy wc) {
+
+       }
 
+       void clearEditingTransactionXid() {
+               editingTransactionXid = null;
        }
 
        // UTILITIES
@@ -430,156 +425,4 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                this.transactionManager = transactionManager;
        }
 
-       //
-       // XA RESOURCE
-       //
-       protected class WorkingCopy implements XAResource {
-               private Xid xid;
-               private int transactionTimeout = 0;
-
-               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>();
-
-               @Override
-               public void start(Xid xid, int flags) throws XAException {
-                       if (editingTransactionXid != null)
-                               throw new UserDirectoryException("Transaction "
-                                               + editingTransactionXid + " already editing");
-                       this.xid = xid;
-               }
-
-               @Override
-               public void end(Xid xid, int flags) throws XAException {
-                       checkXid(xid);
-
-                       // clean collections
-                       newUsers.clear();
-                       newUsers = null;
-                       modifiedUsers.clear();
-                       modifiedUsers = null;
-                       deletedUsers.clear();
-                       deletedUsers = null;
-
-                       // clean IDs
-                       this.xid = null;
-                       editingTransactionXid = null;
-               }
-
-               @Override
-               public int prepare(Xid xid) throws XAException {
-                       checkXid(xid);
-                       if (noModifications())
-                               return XA_RDONLY;
-                       try {
-                               AbstractUserDirectory.this.prepare(this);
-                       } catch (Exception e) {
-                               log.error("Cannot prepare " + xid, e);
-                               throw new XAException(XAException.XA_RBOTHER);
-                       }
-                       return XA_OK;
-               }
-
-               @Override
-               public void commit(Xid xid, boolean onePhase) throws XAException {
-                       checkXid(xid);
-                       if (noModifications())
-                               return;
-                       try {
-                               if (onePhase)
-                                       AbstractUserDirectory.this.prepare(this);
-                               AbstractUserDirectory.this.commit(this);
-                       } catch (Exception e) {
-                               log.error("Cannot commit " + xid, e);
-                               throw new XAException(XAException.XA_RBOTHER);
-                       }
-               }
-
-               @Override
-               public void rollback(Xid xid) throws XAException {
-                       checkXid(xid);
-                       try {
-                               AbstractUserDirectory.this.rollback(this);
-                       } catch (Exception e) {
-                               log.error("Cannot rollback " + xid, e);
-                               throw new XAException(XAException.XA_HEURMIX);
-                       }
-               }
-
-               @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 {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public int getTransactionTimeout() throws XAException {
-                       return transactionTimeout;
-               }
-
-               @Override
-               public boolean setTransactionTimeout(int seconds) throws XAException {
-                       transactionTimeout = seconds;
-                       return true;
-               }
-
-               private Xid getXid() {
-                       return xid;
-               }
-
-               private void checkXid(Xid xid) throws XAException {
-                       if (this.xid == null)
-                               throw new XAException(XAException.XAER_OUTSIDE);
-                       if (!this.xid.equals(xid))
-                               throw new XAException(XAException.XAER_NOTA);
-               }
-
-               @Override
-               protected void finalize() throws Throwable {
-                       if (editingTransactionXid != null)
-                               log.warn("Editing transaction still referenced but no working copy "
-                                               + editingTransactionXid);
-                       editingTransactionXid = 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 UserDirectoryException("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 ef212fa27a0160629a592af1c578630699941fbc..1dc4df981840e0608a8e89c5006cb9a9ec6d4afe 100644 (file)
@@ -183,7 +183,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void prepare(WorkingCopy wc) {
+       protected void prepare(UserDirectoryWorkingCopy wc) {
                try {
                        getLdapContext().reconnect(getLdapContext().getConnectControls());
                        // delete
@@ -214,7 +214,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void commit(WorkingCopy wc) {
+       protected void commit(UserDirectoryWorkingCopy wc) {
                try {
                        // delete
                        for (LdapName dn : wc.getDeletedUsers().keySet()) {
@@ -237,7 +237,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void rollback(WorkingCopy wc) {
+       protected void rollback(UserDirectoryWorkingCopy wc) {
                // prepare not impacting
        }
 
index 4f0a56a6075ac7e3b6dbfb120984bc49c61b1e22..41f33c3ba1e36437860ff4eca0635cd83995015e 100644 (file)
@@ -20,7 +20,6 @@ import javax.naming.ldap.LdapName;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.digest.DigestUtils;
-import org.argeo.osgi.useradmin.AbstractUserDirectory.WorkingCopy;
 
 /** Directory user implementation */
 class LdifUser implements DirectoryUser {
@@ -144,7 +143,7 @@ class LdifUser implements DirectoryUser {
                return getWc() != null && getModifiedAttributes() != null;
        }
 
-       private synchronized WorkingCopy getWc() {
+       private synchronized UserDirectoryWorkingCopy getWc() {
                return userAdmin.getWorkingCopy();
        }
 
index 4613ef5289fbb331ecc61e8d982c93c6d3e41c87..bee73e54df97b6e8337027540cb68a00a8b7b04b 100644 (file)
@@ -166,7 +166,7 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void prepare(WorkingCopy wc) {
+       protected void prepare(UserDirectoryWorkingCopy wc) {
                // delete
                for (LdapName dn : wc.getDeletedUsers().keySet()) {
                        if (users.containsKey(dn))
@@ -204,12 +204,12 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected void commit(WorkingCopy wc) {
+       protected void commit(UserDirectoryWorkingCopy wc) {
                save();
        }
 
        @Override
-       protected void rollback(WorkingCopy wc) {
+       protected void rollback(UserDirectoryWorkingCopy wc) {
                init();
        }
 
diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java
new file mode 100644 (file)
index 0000000..d2bd9ff
--- /dev/null
@@ -0,0 +1,185 @@
+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.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import bitronix.tm.resource.ehcache.EhCacheXAResourceProducer;
+
+/** {@link XAResource} for a user directory being edited. */
+class UserDirectoryWorkingCopy implements XAResource {
+       private final static Log log = LogFactory
+                       .getLog(UserDirectoryWorkingCopy.class);
+       private final String cacheName = getClass().getName();
+
+       private final AbstractUserDirectory userDirectory;
+
+       private Xid xid;
+       private int transactionTimeout = 0;
+
+       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>();
+
+       public UserDirectoryWorkingCopy(AbstractUserDirectory userDirectory) {
+               this.userDirectory = userDirectory;
+               try {
+                       // FIXME Make it less bitronix dependant
+                       EhCacheXAResourceProducer.registerXAResource(cacheName, this);
+               } catch (Exception e) {
+                       log.error("Cannot register resource to Bitronix", e);
+               }
+       }
+
+       @Override
+       public void start(Xid xid, int flags) throws XAException {
+               this.xid = xid;
+       }
+
+       @Override
+       public void end(Xid xid, int flags) throws XAException {
+               checkXid(xid);
+
+       }
+
+       private void cleanUp() {
+               // clean collections
+               newUsers.clear();
+               newUsers = null;
+               modifiedUsers.clear();
+               modifiedUsers = null;
+               deletedUsers.clear();
+               deletedUsers = null;
+
+               // clean IDs
+               this.xid = null;
+               userDirectory.clearEditingTransactionXid();
+
+               try {
+                       // FIXME Make it less bitronix dependant
+                       EhCacheXAResourceProducer.unregisterXAResource(cacheName, this);
+               } catch (Exception e) {
+                       log.error("Cannot unregister resource from Bitronix", e);
+               }
+       }
+
+       @Override
+       public int prepare(Xid xid) throws XAException {
+               checkXid(xid);
+               if (noModifications())
+                       return XA_RDONLY;
+               try {
+                       userDirectory.prepare(this);
+               } catch (Exception e) {
+                       log.error("Cannot prepare " + xid, e);
+                       throw new XAException(XAException.XA_RBOTHER);
+               }
+               return XA_OK;
+       }
+
+       @Override
+       public void commit(Xid xid, boolean onePhase) throws XAException {
+               try {
+                       checkXid(xid);
+                       if (noModifications())
+                               return;
+                       if (onePhase)
+                               userDirectory.prepare(this);
+                       userDirectory.commit(this);
+               } catch (Exception e) {
+                       log.error("Cannot commit " + xid, e);
+                       throw new XAException(XAException.XA_RBOTHER);
+               } finally {
+                       cleanUp();
+               }
+       }
+
+       @Override
+       public void rollback(Xid xid) throws XAException {
+               try {
+                       checkXid(xid);
+                       userDirectory.rollback(this);
+               } catch (Exception e) {
+                       log.error("Cannot rollback " + xid, e);
+                       throw new XAException(XAException.XA_HEURMIX);
+               } finally {
+                       cleanUp();
+               }
+       }
+
+       @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;
+       }
+
+       Xid getXid() {
+               return xid;
+       }
+
+       private void checkXid(Xid xid) throws XAException {
+               if (this.xid == null)
+                       throw new XAException(XAException.XAER_OUTSIDE);
+               if (!this.xid.equals(xid))
+                       throw new XAException(XAException.XAER_NOTA);
+       }
+
+       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 UserDirectoryException("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;
+       }
+
+}
diff --git a/pom.xml b/pom.xml
index 7e7ad759188512eeb186c6d3736dfd527169f4aa..04d5d5b6cd9df81772fb147f56ddc4a0d55ab570 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
                <version.argeo-commons>2.1.29-SNAPSHOT</version.argeo-commons>
                <developmentCycle.argeo-commons>2.1</developmentCycle.argeo-commons>
                <developmentCycle.argeo-commons.startDate>2012-12-19</developmentCycle.argeo-commons.startDate>
-               <version.argeo-distribution>2.1.10</version.argeo-distribution>
+               <version.argeo-distribution>2.1.11</version.argeo-distribution>
                <!-- RPM -->
                <rpm.release>6</rpm.release>
                <rpm.stagingRepository>/srv/rpmfactory/argeo-osgi-2-staging/6/x86_64</rpm.stagingRepository>