Continue framework clean up.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 10 Aug 2016 17:42:14 +0000 (17:42 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 10 Aug 2016 17:42:14 +0000 (17:42 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@9083 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java [deleted file]
org.argeo.security.core/src/org/argeo/osgi/transaction/simple/SimpleTransaction.java [new file with mode: 0644]
org.argeo.security.core/src/org/argeo/osgi/transaction/simple/SimpleTransactionManager.java [new file with mode: 0644]
org.argeo.security.core/src/org/argeo/osgi/transaction/simple/UuidXid.java [new file with mode: 0644]

diff --git a/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminUtils.java b/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminUtils.java
deleted file mode 100644 (file)
index 3a67717..0000000
+++ /dev/null
@@ -1,243 +0,0 @@
-package org.argeo.cms.util.useradmin;
-
-import java.security.AccessController;
-import java.util.List;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-import javax.security.auth.Subject;
-import javax.security.auth.x500.X500Principal;
-
-import org.argeo.ArgeoException;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.CmsView;
-import org.argeo.cms.auth.AuthConstants;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.util.CmsUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.osgi.useradmin.LdifName;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Centralise common patterns to manage roles with a user admin */
-public class UserAdminUtils {
-
-       /** Retrieves a {@link Role} given a LDAP name */
-       public final static Role getRole(UserAdmin userAdmin, LdapName dn) {
-               Role role = userAdmin.getRole(dn.toString());
-               return role;
-       }
-
-       /** Retrieves the unique local username given a {@link User}. */
-       public final static String getUsername(User user) {
-               String username = null;
-               if (user instanceof Group)
-                       username = getProperty(user, LdifName.cn.name());
-               else
-                       username = getProperty(user, LdifName.uid.name());
-               return username;
-       }
-
-       /**
-        * Easily retrieves one of the {@link Role}'s property or an empty String if
-        * the requested property is not defined
-        */
-       public final static String getProperty(Role role, String key) {
-               Object obj = role.getProperties().get(key);
-               if (obj != null)
-                       return (String) obj;
-               else
-                       return "";
-       }
-
-       // CENTRALIZE SOME METHODS UNTIL API IS STABLE
-       /** Simply checks if current user is registered */
-       public static boolean isRegistered() {
-               return !CurrentUser.isAnonymous();
-       }
-
-       /** Simply checks if current user as a home */
-       public static boolean hasHome() {
-               return isRegistered();
-       }
-
-       // SELF HELPERS
-       /** Simply retrieves the current logged-in user display name. */
-       public static User getCurrentUser(UserAdmin userAdmin) {
-               return (User) getRole(userAdmin, getCurrentUserLdapName());
-       }
-
-       /** Simply retrieves the current logged-in user display name. */
-       public static String getCurrentUserDisplayName(UserAdmin userAdmin) {
-               String username = getCurrentUsername();
-               return getUserDisplayName(userAdmin, username);
-       }
-
-       /** Simply retrieves the current logged-in user display name. */
-       public static String getCurrentUserMail(UserAdmin userAdmin) {
-               String username = getCurrentUsername();
-               return getUserMail(userAdmin, username);
-       }
-
-       /** Returns the local name of the current connected user */
-       public final static String getUsername(UserAdmin userAdmin) {
-               LdapName dn = getCurrentUserLdapName();
-               return getUsername((User) getRole(userAdmin, dn));
-       }
-
-       /** Returns true if the current user is in the specified role */
-       public static boolean isUserInRole(String role) {
-               Set<String> roles = CurrentUser.roles();
-               return roles.contains(role);
-       }
-
-       /** Simply checks if current user is the same as the passed one */
-       public static boolean isCurrentUser(User user) {
-               String userName = getProperty(user, LdifName.dn.name());
-               try {
-                       LdapName selfUserName = getCurrentUserLdapName();
-                       LdapName userLdapName = new LdapName(userName);
-                       if (userLdapName.equals(selfUserName))
-                               return true;
-                       else
-                               return false;
-               } catch (InvalidNameException e) {
-                       throw new ArgeoException("User " + user + " has an unvalid dn: "
-                                       + userName, e);
-               }
-       }
-
-       public final static LdapName getCurrentUserLdapName() {
-               String name = getCurrentUsername();
-               return getLdapName(name);
-       }
-
-       /** Simply retrieves username for current user, generally a LDAP dn */
-       public static String getCurrentUsername() {
-               Subject subject = currentSubject();
-               String name = subject.getPrincipals(X500Principal.class).iterator()
-                               .next().toString();
-               return name;
-       }
-
-       /**
-        * Fork of the {@link CurrentUser#currentSubject} method that is private.
-        * TODO Enhance and factorize
-        */
-       private static Subject currentSubject() {
-               CmsView cmsView = CmsUtils.getCmsView();
-               if (cmsView != null)
-                       return cmsView.getSubject();
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               if (subject != null)
-                       return subject;
-               throw new CmsException("Cannot find related subject");
-       }
-
-       // HOME MANAGEMENT
-       /**
-        * Simply retrieves the *relative* path to the current user home node from
-        * the base home node
-        */
-       public static String getCurrentUserHomeRelPath() {
-               return getHomeRelPath(getCurrentUsername());
-       }
-
-       /**
-        * Simply retrieves the *relative* path to the home node of a user given its
-        * userName
-        */
-       public static String getHomeRelPath(String userName) {
-               String id = getUserUid(userName);
-               String currHomePath = JcrUtils.firstCharsToPath(id, 2) + "/" + id;
-               return currHomePath;
-       }
-
-       // HELPERS TO RETRIEVE REMARKABLE PROPERTIES
-       /** Simply retrieves the user uid from his dn with no useradmin */
-       public static String getUserUid(String dn) {
-               LdapName ldapName = getLdapName(dn);
-               Rdn last = ldapName.getRdn(ldapName.size() - 1);
-               if (last.getType().toLowerCase().equals(LdifName.uid.name())
-                               || last.getType().toLowerCase().equals(LdifName.cn.name()))
-                       return (String) last.getValue();
-               else
-                       throw new ArgeoException("Cannot retrieve user uid, "
-                                       + "non valid dn: " + dn);
-       }
-
-       /**
-        * Returns the local username if no user with this dn is found or if the
-        * found user has no defined display name
-        */
-       public static String getUserDisplayName(UserAdmin userAdmin, String dn) {
-               Role user = getRole(userAdmin, getLdapName(dn));
-               if (user == null)
-                       return getUserUid(dn);
-               String displayName = getProperty(user, LdifName.displayName.name());
-               if (EclipseUiUtils.isEmpty(displayName))
-                       displayName = getProperty(user, LdifName.cn.name());
-               if (EclipseUiUtils.isEmpty(displayName))
-                       return getUserUid(dn);
-               else
-                       return displayName;
-       }
-
-       /**
-        * Returns null if no user with this dn is found or if the found user has no
-        * defined mail
-        */
-       public static String getUserMail(UserAdmin userAdmin, String dn) {
-               Role user = getRole(userAdmin, getLdapName(dn));
-               if (user == null)
-                       return null;
-               else
-                       return getProperty(user, LdifName.mail.name());
-       }
-
-       // VARIOUS UI HELPERS
-       public final static String buildDefaultCn(String firstName, String lastName) {
-               return (firstName.trim() + " " + lastName.trim() + " ").trim();
-       }
-
-       /** Simply retrieves a display name of the relevant domain */
-       public final static String getDomainName(User user) {
-               String dn = user.getName();
-               if (dn.endsWith(AuthConstants.ROLES_BASEDN))
-                       return "System roles";
-               try {
-                       LdapName name = new LdapName(dn);
-                       List<Rdn> rdns = name.getRdns();
-                       String dname = null;
-                       int i = 0;
-                       loop: while (i < rdns.size()) {
-                               Rdn currrRdn = rdns.get(i);
-                               if (!LdifName.dc.name().equals(currrRdn.getType()))
-                                       break loop;
-                               else {
-                                       String currVal = (String) currrRdn.getValue();
-                                       dname = dname == null ? currVal : currVal + "." + dname;
-                               }
-                               i++;
-                       }
-                       return dname;
-               } catch (InvalidNameException e) {
-                       throw new ArgeoException("Unable to get domain name for " + dn, e);
-               }
-       }
-
-       // Local Helpers
-       /** Simply retrieves a LDAP name from a dn with no exception */
-       public static LdapName getLdapName(String dn) {
-               try {
-                       return new LdapName(dn);
-               } catch (InvalidNameException e) {
-                       throw new ArgeoException("Cannot parse LDAP name " + dn, e);
-               }
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java b/org.argeo.cms/src/org/argeo/cms/util/useradmin/UserAdminWrapper.java
deleted file mode 100644 (file)
index 8d91c71..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.argeo.cms.util.useradmin;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.transaction.Status;
-import javax.transaction.UserTransaction;
-
-import org.argeo.ArgeoException;
-import org.argeo.cms.auth.AuthConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-/**
- * Base useradmin wrapper. Implementing application might extends to add
- * business specific behaviour
- */
-public class UserAdminWrapper {
-       // private Log log = LogFactory.getLog(UserAdminWrapper.class);
-
-       private UserAdmin userAdmin;
-       private ServiceReference<UserAdmin> userAdminServiceReference;
-       private UserTransaction userTransaction;
-
-       /* USER ADMIN LISTENER MANAGEMENT */
-       List<UserAdminListener> listeners = new ArrayList<UserAdminListener>();
-
-       // TODO implement safer mechanism
-       public void addListener(UserAdminListener userAdminListener) {
-               if (!listeners.contains(userAdminListener))
-                       listeners.add(userAdminListener);
-       }
-
-       /**
-        * Starts a transaction if none already exists and notify the userAdmin
-        * listeners.Must be called from the UI Thread.
-        */
-       public UserTransaction beginTransactionIfNeeded() {
-               try {
-                       if (userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION) {
-                               userTransaction.begin();
-                       }
-                       return userTransaction;
-               } catch (Exception e) {
-                       throw new ArgeoException("Unable to begin transaction", e);
-               }
-       }
-
-       // Expose this?
-       public void removeListener(UserAdminListener userAdminListener) {
-               if (listeners.contains(userAdminListener))
-                       listeners.remove(userAdminListener);
-       }
-
-       public void notifyListeners(UserAdminEvent event) {
-               for (UserAdminListener listener : listeners)
-                       listener.roleChanged(event);
-       }
-
-       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-               Map<String, String> dns = new HashMap<String, String>();
-               for (String uri : userAdminServiceReference.getPropertyKeys()) {
-                       if (!uri.startsWith("/"))
-                               continue;
-                       Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
-                       String readOnly = UserAdminConf.readOnly.getValue(props);
-                       String baseDn = UserAdminConf.baseDn.getValue(props);
-
-                       if (onlyWritable && "true".equals(readOnly))
-                               continue;
-                       if (baseDn.equalsIgnoreCase(AuthConstants.ROLES_BASEDN))
-                               continue;
-                       dns.put(baseDn, uri);
-               }
-               return dns;
-       }
-
-       public UserAdmin getUserAdmin() {
-               return userAdmin;
-       }
-
-       public UserTransaction getUserTransaction() {
-               return userTransaction;
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdmin(UserAdmin userAdmin) {
-               this.userAdmin = userAdmin;
-       }
-
-       public void setUserTransaction(UserTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-       public void setUserAdminServiceReference(
-                       ServiceReference<UserAdmin> userAdminServiceReference) {
-               this.userAdminServiceReference = userAdminServiceReference;
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.security.core/src/org/argeo/osgi/transaction/simple/SimpleTransaction.java b/org.argeo.security.core/src/org/argeo/osgi/transaction/simple/SimpleTransaction.java
new file mode 100644 (file)
index 0000000..0e4a588
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.osgi.transaction.simple;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+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;
+
+class SimpleTransaction implements Transaction, Status {
+       private final static Log log = LogFactory.getLog(SimpleTransaction.class);
+
+       private final Xid xid;
+       private int status = Status.STATUS_ACTIVE;
+       private final List<XAResource> xaResources = new ArrayList<XAResource>();
+
+       private final SimpleTransactionManager transactionManager;
+
+       public SimpleTransaction(SimpleTransactionManager transactionManager) {
+               this.xid = new UuidXid();
+               this.transactionManager = transactionManager;
+       }
+
+       @Override
+       public synchronized void commit() throws RollbackException,
+                       HeuristicMixedException, HeuristicRollbackException,
+                       SecurityException, IllegalStateException, SystemException {
+               status = STATUS_PREPARING;
+               for (XAResource xaRes : xaResources) {
+                       if (status == STATUS_MARKED_ROLLBACK)
+                               break;
+                       try {
+                               xaRes.prepare(xid);
+                       } catch (XAException e) {
+                               status = STATUS_MARKED_ROLLBACK;
+                               log.error("Cannot prepare " + xaRes + " for " + xid, e);
+                       }
+               }
+               if (status == STATUS_MARKED_ROLLBACK) {
+                       rollback();
+                       throw new RollbackException();
+               }
+               status = STATUS_PREPARED;
+
+               status = STATUS_COMMITTING;
+               for (XAResource xaRes : xaResources) {
+                       if (status == STATUS_MARKED_ROLLBACK)
+                               break;
+                       try {
+                               xaRes.commit(xid, false);
+                       } catch (XAException e) {
+                               status = STATUS_MARKED_ROLLBACK;
+                               log.error("Cannot prepare " + xaRes + " for " + xid, e);
+                       }
+               }
+               if (status == STATUS_MARKED_ROLLBACK) {
+                       rollback();
+                       throw new RollbackException();
+               }
+
+               // complete
+               status = STATUS_COMMITTED;
+               if (log.isDebugEnabled())
+                       log.debug("COMMITTED  " + xid);
+               clearResources(XAResource.TMSUCCESS);
+               transactionManager.unregister(xid);
+       }
+
+       @Override
+       public synchronized void rollback() throws IllegalStateException,
+                       SystemException {
+               status = STATUS_ROLLING_BACK;
+               for (XAResource xaRes : xaResources) {
+                       try {
+                               xaRes.rollback(xid);
+                       } catch (XAException e) {
+                               log.error("Cannot rollback " + xaRes + " for " + xid, e);
+                       }
+               }
+
+               // complete
+               status = STATUS_ROLLEDBACK;
+               if (log.isDebugEnabled())
+                       log.debug("ROLLEDBACK " + xid);
+               clearResources(XAResource.TMFAIL);
+               transactionManager.unregister(xid);
+       }
+
+       @Override
+       public synchronized boolean enlistResource(XAResource xaRes)
+                       throws RollbackException, IllegalStateException, SystemException {
+               if (xaResources.add(xaRes)) {
+                       try {
+                               xaRes.start(getXid(), XAResource.TMNOFLAGS);
+                               return true;
+                       } catch (XAException e) {
+                               log.error("Cannot enlist " + xaRes, e);
+                               return false;
+                       }
+               } else
+                       return false;
+       }
+
+       @Override
+       public synchronized boolean delistResource(XAResource xaRes, int flag)
+                       throws IllegalStateException, SystemException {
+               if (xaResources.remove(xaRes)) {
+                       try {
+                               xaRes.end(getXid(), flag);
+                       } catch (XAException e) {
+                               log.error("Cannot delist " + xaRes, e);
+                               return false;
+                       }
+                       return true;
+               } else
+                       return false;
+       }
+
+       protected void clearResources(int flag) {
+               for (XAResource xaRes : xaResources)
+                       try {
+                               xaRes.end(getXid(), flag);
+                       } catch (XAException e) {
+                               log.error("Cannot end " + xaRes, e);
+                       }
+               xaResources.clear();
+       }
+
+       @Override
+       public synchronized int getStatus() throws SystemException {
+               return status;
+       }
+
+       @Override
+       public void registerSynchronization(Synchronization sync)
+                       throws RollbackException, IllegalStateException, SystemException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void setRollbackOnly() throws IllegalStateException, SystemException {
+               status = STATUS_MARKED_ROLLBACK;
+       }
+
+       @Override
+       public int hashCode() {
+               return xid.hashCode();
+       }
+
+       Xid getXid() {
+               return xid;
+       }
+
+}
diff --git a/org.argeo.security.core/src/org/argeo/osgi/transaction/simple/SimpleTransactionManager.java b/org.argeo.security.core/src/org/argeo/osgi/transaction/simple/SimpleTransactionManager.java
new file mode 100644 (file)
index 0000000..696f0c0
--- /dev/null
@@ -0,0 +1,173 @@
+package org.argeo.osgi.transaction.simple;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionSynchronizationRegistry;
+import javax.transaction.UserTransaction;
+import javax.transaction.xa.Xid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class SimpleTransactionManager implements TransactionManager, UserTransaction {
+       private final static Log log = LogFactory.getLog(SimpleTransactionManager.class);
+
+       private ThreadLocal<SimpleTransaction> current = new ThreadLocal<SimpleTransaction>();
+
+       private Map<Xid, SimpleTransaction> knownTransactions = Collections
+                       .synchronizedMap(new HashMap<Xid, SimpleTransaction>());
+       private SyncRegistry syncRegistry = new SyncRegistry();
+
+       @Override
+       public void begin() throws NotSupportedException, SystemException {
+               if (getCurrent() != null)
+                       throw new NotSupportedException("Nested transactions are not supported");
+               SimpleTransaction transaction = new SimpleTransaction(this);
+               knownTransactions.put(transaction.getXid(), transaction);
+               current.set(transaction);
+               if (log.isDebugEnabled())
+                       log.debug("STARTED    " + transaction.getXid());
+       }
+
+       @Override
+       public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+                       SecurityException, IllegalStateException, SystemException {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().commit();
+       }
+
+       @Override
+       public int getStatus() throws SystemException {
+               if (getCurrent() == null)
+                       return Status.STATUS_NO_TRANSACTION;
+               return getTransaction().getStatus();
+       }
+
+       @Override
+       public Transaction getTransaction() throws SystemException {
+               return getCurrent();
+       }
+
+       protected SimpleTransaction getCurrent() throws SystemException {
+               SimpleTransaction transaction = current.get();
+               if (transaction == null)
+                       return null;
+               int status = transaction.getStatus();
+               if (Status.STATUS_COMMITTED == status || Status.STATUS_ROLLEDBACK == status) {
+                       current.remove();
+                       return null;
+               }
+               return transaction;
+       }
+
+       void unregister(Xid xid) {
+               knownTransactions.remove(xid);
+       }
+
+       @Override
+       public void resume(Transaction tobj) throws InvalidTransactionException, IllegalStateException, SystemException {
+               if (getCurrent() != null)
+                       throw new IllegalStateException("Transaction " + current.get() + " already registered");
+               current.set((SimpleTransaction) tobj);
+       }
+
+       @Override
+       public void rollback() throws IllegalStateException, SecurityException, SystemException {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().rollback();
+       }
+
+       @Override
+       public void setRollbackOnly() throws IllegalStateException, SystemException {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().setRollbackOnly();
+       }
+
+       @Override
+       public void setTransactionTimeout(int seconds) throws SystemException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public Transaction suspend() throws SystemException {
+               Transaction transaction = getCurrent();
+               current.remove();
+               return transaction;
+       }
+
+       public TransactionSynchronizationRegistry getTsr() {
+               return syncRegistry;
+       }
+
+       private class SyncRegistry implements TransactionSynchronizationRegistry {
+               @Override
+               public Object getTransactionKey() {
+                       try {
+                               SimpleTransaction transaction = getCurrent();
+                               if (transaction == null)
+                                       return null;
+                               return getCurrent().getXid();
+                       } catch (SystemException e) {
+                               throw new RuntimeException("Cannot get transaction key", e);
+                       }
+               }
+
+               @Override
+               public void putResource(Object key, Object value) {
+                       throw new UnsupportedOperationException();
+               }
+
+               @Override
+               public Object getResource(Object key) {
+                       throw new UnsupportedOperationException();
+               }
+
+               @Override
+               public void registerInterposedSynchronization(Synchronization sync) {
+                       throw new UnsupportedOperationException();
+               }
+
+               @Override
+               public int getTransactionStatus() {
+                       try {
+                               return getStatus();
+                       } catch (SystemException e) {
+                               throw new RuntimeException("Cannot get status", e);
+                       }
+               }
+
+               @Override
+               public boolean getRollbackOnly() {
+                       try {
+                               return getStatus() == Status.STATUS_MARKED_ROLLBACK;
+                       } catch (SystemException e) {
+                               throw new RuntimeException("Cannot get status", e);
+                       }
+               }
+
+               @Override
+               public void setRollbackOnly() {
+                       try {
+                               getCurrent().setRollbackOnly();
+                       } catch (Exception e) {
+                               throw new RuntimeException("Cannot set rollback only", e);
+                       }
+               }
+
+       }
+}
diff --git a/org.argeo.security.core/src/org/argeo/osgi/transaction/simple/UuidXid.java b/org.argeo.security.core/src/org/argeo/osgi/transaction/simple/UuidXid.java
new file mode 100644 (file)
index 0000000..cf35555
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.osgi.transaction.simple;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * Implementation of {@link Xid} based on {@link UUID}, using max significant
+ * bits as global transaction id, and least significant bits as branch
+ * qualifier.
+ */
+public class UuidXid implements Xid, Serializable {
+       private static final long serialVersionUID = -5380531989917886819L;
+       public final static int FORMAT = (int) serialVersionUID;
+
+       private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
+
+       private final int format;
+       private final byte[] globalTransactionId;
+       private final byte[] branchQualifier;
+       private final String uuid;
+       private final int hashCode;
+
+       public UuidXid() {
+               this(UUID.randomUUID());
+       }
+
+       public UuidXid(UUID uuid) {
+               this.format = FORMAT;
+               this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
+               this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
+               this.uuid = uuid.toString();
+               this.hashCode = uuid.hashCode();
+       }
+
+       public UuidXid(Xid xid) {
+               this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
+                               .getBranchQualifier());
+       }
+
+       private UuidXid(int format, byte[] globalTransactionId,
+                       byte[] branchQualifier) {
+               this.format = format;
+               this.globalTransactionId = globalTransactionId;
+               this.branchQualifier = branchQualifier;
+               this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
+                               .toString();
+               this.hashCode = uuid.hashCode();
+       }
+
+       @Override
+       public int getFormatId() {
+               return format;
+       }
+
+       @Override
+       public byte[] getGlobalTransactionId() {
+               return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
+       }
+
+       @Override
+       public byte[] getBranchQualifier() {
+               return Arrays.copyOf(branchQualifier, branchQualifier.length);
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj instanceof UuidXid) {
+                       UuidXid that = (UuidXid) obj;
+                       return Arrays.equals(globalTransactionId, that.globalTransactionId)
+                                       && Arrays.equals(branchQualifier, that.branchQualifier);
+               }
+               if (obj instanceof Xid) {
+                       Xid that = (Xid) obj;
+                       return Arrays.equals(globalTransactionId,
+                                       that.getGlobalTransactionId())
+                                       && Arrays
+                                                       .equals(branchQualifier, that.getBranchQualifier());
+               }
+               return uuid.equals(obj.toString());
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new UuidXid(format, globalTransactionId, branchQualifier);
+       }
+
+       @Override
+       public String toString() {
+               return uuid;
+       }
+
+       public UUID asUuid() {
+               return bytesToUUID(globalTransactionId, branchQualifier);
+       }
+
+       public static byte[] uuidToBytes(long bits) {
+               ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
+               buffer.putLong(0, bits);
+               return buffer.array();
+       }
+
+       public static UUID bytesToUUID(byte[] most, byte[] least) {
+               if (most.length < BYTES_PER_LONG)
+                       most = Arrays.copyOf(most, BYTES_PER_LONG);
+               if (least.length < BYTES_PER_LONG)
+                       least = Arrays.copyOf(least, BYTES_PER_LONG);
+               ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
+               buffer.put(most, 0, BYTES_PER_LONG);
+               buffer.put(least, 0, BYTES_PER_LONG);
+               buffer.flip();
+               return new UUID(buffer.getLong(), buffer.getLong());
+       }
+
+       // public static void main(String[] args) {
+       // UUID uuid = UUID.randomUUID();
+       // System.out.println(uuid);
+       // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
+       // uuidToBytes(uuid.getLeastSignificantBits()));
+       // System.out.println(uuid);
+       // }
+}