- Improve transaction support
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 15 Sep 2015 07:48:59 +0000 (07:48 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 15 Sep 2015 07:48:59 +0000 (07:48 +0000)
- Can write in LDAP

git-svn-id: https://svn.argeo.org/commons/trunk@8393 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransaction.java
org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/DirectoryUser.java
org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java [deleted file]
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

index a07645f15a9e3746c78733540fef9359ebf5e4a7..f3a27fe534374db532735da1639493c373665a23 100644 (file)
@@ -24,8 +24,11 @@ class SimpleTransaction implements Transaction, Status {
        private int status = Status.STATUS_ACTIVE;
        private final List<XAResource> xaResources = new ArrayList<XAResource>();
 
-       public SimpleTransaction() {
-               xid = new UuidXid();
+       private final SimpleTransactionManager transactionManager;
+
+       public SimpleTransaction(SimpleTransactionManager transactionManager) {
+               this.xid = new UuidXid();
+               this.transactionManager = transactionManager;
        }
 
        @Override
@@ -64,19 +67,73 @@ class SimpleTransaction implements Transaction, Status {
                        rollback();
                        throw new RollbackException();
                }
+
+               // complete
                status = STATUS_COMMITTED;
+               if (log.isDebugEnabled())
+                       log.debug("COMMITTED  " + xid);
+               clearResources(XAResource.TMSUCCESS);
+               transactionManager.unregister(xid);
        }
 
        @Override
-       public synchronized boolean delistResource(XAResource xaRes, int flag)
-                       throws IllegalStateException, SystemException {
-               return xaResources.remove(xaRes);
+       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 {
-               return xaResources.add(xaRes);
+               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
@@ -90,20 +147,6 @@ class SimpleTransaction implements Transaction, Status {
                throw new UnsupportedOperationException();
        }
 
-       @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);
-                       }
-               }
-               status = STATUS_ROLLEDBACK;
-       }
-
        @Override
        public void setRollbackOnly() throws IllegalStateException, SystemException {
                status = STATUS_MARKED_ROLLBACK;
@@ -114,7 +157,7 @@ class SimpleTransaction implements Transaction, Status {
                return xid.hashCode();
        }
 
-       public Xid getXid() {
+       Xid getXid() {
                return xid;
        }
 
index 6261a02655fc74ac06079fd2c8ef95f6d65c99e1..edb57268119d9edd7a4e6b1474a4e6274c0bb571 100644 (file)
@@ -38,11 +38,11 @@ public class SimpleTransactionManager implements TransactionManager,
                if (getCurrent() != null)
                        throw new NotSupportedException(
                                        "Nested transactions are not supported");
-               SimpleTransaction transaction = new SimpleTransaction();
+               SimpleTransaction transaction = new SimpleTransaction(this);
                knownTransactions.put(transaction.getXid(), transaction);
                current.set(transaction);
                if (log.isDebugEnabled())
-                       log.debug("Started transaction " + transaction.getXid());
+                       log.debug("STARTED    " + transaction.getXid());
        }
 
        @Override
@@ -75,17 +75,14 @@ public class SimpleTransactionManager implements TransactionManager,
                if (Status.STATUS_COMMITTED == status
                                || Status.STATUS_ROLLEDBACK == status) {
                        current.remove();
-                       knownTransactions.remove(transaction.getXid());
-                       if (log.isDebugEnabled())
-                               log.debug("Completed transaction "
-                                               + transaction.getXid()
-                                               + " ["
-                                               + (status == Status.STATUS_ROLLEDBACK ? "FAILED" : "OK")
-                                               + "]");
                        return null;
                }
                return transaction;
        }
+       
+       void unregister(Xid xid){
+               knownTransactions.remove(xid);
+       }
 
        @Override
        public void resume(Transaction tobj) throws InvalidTransactionException,
index 4f20dc379895790af0ae2af4435daded268d53a1..77f68463fe4452101d4fe1f4af66e00050cf3d52 100644 (file)
@@ -3,9 +3,16 @@ package org.argeo.osgi.useradmin;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
-import javax.transaction.RollbackException;
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
 import javax.transaction.SystemException;
 import javax.transaction.Transaction;
 import javax.transaction.TransactionManager;
@@ -14,12 +21,20 @@ 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;
+import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.Group;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
 
 public abstract class AbstractUserDirectory implements UserAdmin {
+       private final static Log log = LogFactory
+                       .getLog(AbstractUserDirectory.class);
        private boolean isReadOnly;
        private URI uri;
 
@@ -31,10 +46,12 @@ public abstract class AbstractUserDirectory implements UserAdmin {
        private List<String> credentialAttributeIds = Arrays
                        .asList(new String[] { "userpassword" });
 
-       private TransactionSynchronizationRegistry syncRegistry;
-       private Object editingTransactionKey = null;
+       // private TransactionSynchronizationRegistry syncRegistry;
+       // private Object editingTransactionKey = null;
+
        private TransactionManager transactionManager;
-       private Transaction editingTransaction;
+       private ThreadLocal<WorkingCopy> workingCopy = new ThreadLocal<AbstractUserDirectory.WorkingCopy>();
+       private Xid editingTransactionXid = null;
 
        public AbstractUserDirectory() {
        }
@@ -45,7 +62,16 @@ public abstract class AbstractUserDirectory implements UserAdmin {
        }
 
        /** Returns the {@link Group}s this user is a direct member of. */
-       protected abstract List<? extends Group> getDirectGroups(User user);
+       protected abstract List<? extends DirectoryGroup> getDirectGroups(User user);
+
+       protected abstract Boolean daoHasRole(LdapName dn);
+
+       protected abstract DirectoryUser daoGetRole(LdapName key);
+
+       protected abstract List<DirectoryUser> doGetRoles(Filter f);
+
+       protected abstract void doGetUser(String key, String value,
+                       List<DirectoryUser> collectedUsers);
 
        public void init() {
 
@@ -56,32 +82,53 @@ public abstract class AbstractUserDirectory implements UserAdmin {
        }
 
        boolean isEditing() {
-               if (editingTransactionKey == null)
-                       return false;
-               Object currentTrKey = syncRegistry.getTransactionKey();
-               if (currentTrKey == null)
+               if (editingTransactionXid == null)
                        return false;
-               return editingTransactionKey.equals(currentTrKey);
+               return workingCopy.get() != null;
+               // Object currentTrKey = syncRegistry.getTransactionKey();
+               // if (currentTrKey == null)
+               // return false;
+               // return editingTransactionKey.equals(currentTrKey);
+       }
+
+       protected WorkingCopy getWorkingCopy() {
+               WorkingCopy wc = workingCopy.get();
+               if (wc == null)
+                       return null;
+               if (wc.xid == null) {
+                       workingCopy.set(null);
+                       return null;
+               }
+               return wc;
        }
 
        void checkEdit() {
-               Object currentTrKey = syncRegistry.getTransactionKey();
-               if (currentTrKey == null)
+               Transaction transaction;
+               try {
+                       transaction = transactionManager.getTransaction();
+               } catch (SystemException e) {
+                       throw new UserDirectoryException("Cannot get transaction", e);
+               }
+               if (transaction == null)
                        throw new UserDirectoryException(
                                        "A transaction needs to be active in order to edit");
-               if (editingTransactionKey == null) {
-                       editingTransactionKey = currentTrKey;
-                       XAResource xaRes = getXAResource();
-                       if (xaRes != null)
-                               try {
-                                       transactionManager.getTransaction().enlistResource(xaRes);
-                               } catch (Exception e) {
-                                       throw new UserDirectoryException("Cannot enlist " + this, e);
-                               }
+               if (editingTransactionXid == null) {
+                       WorkingCopy wc = new WorkingCopy();
+                       try {
+                               transaction.enlistResource(wc);
+                               editingTransactionXid = wc.getXid();
+                               workingCopy.set(wc);
+                       } catch (Exception e) {
+                               throw new UserDirectoryException("Cannot enlist " + wc, e);
+                       }
                } else {
-                       if (!editingTransactionKey.equals(currentTrKey))
+                       if (workingCopy.get() == null)
                                throw new UserDirectoryException("Transaction "
-                                               + editingTransactionKey + " already editing");
+                                               + editingTransactionXid + " already editing");
+                       else if (!editingTransactionXid.equals(workingCopy.get().getXid()))
+                               throw new UserDirectoryException("Working copy Xid "
+                                               + workingCopy.get().getXid() + " inconsistent with"
+                                               + editingTransactionXid);
                }
        }
 
@@ -107,10 +154,153 @@ public abstract class AbstractUserDirectory implements UserAdmin {
                // TODO gather anonymous roles
        }
 
-       public XAResource getXAResource() {
+       // USER ADMIN
+       @Override
+       public Role getRole(String name) {
+               LdapName key = toDn(name);
+               WorkingCopy wc = getWorkingCopy();
+               DirectoryUser user = daoGetRole(key);
+               if (wc != null) {
+                       if (user == null && wc.getNewUsers().containsKey(key))
+                               user = wc.getNewUsers().get(key);
+                       else if (wc.getDeletedUsers().containsKey(key))
+                               user = null;
+               }
+               return user;
+       }
+
+       @Override
+       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+               WorkingCopy wc = getWorkingCopy();
+               Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+               List<DirectoryUser> res = doGetRoles(f);
+               if (wc != null) {
+                       for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
+                               DirectoryUser user = it.next();
+                               LdapName dn = user.getDn();
+                               if (wc.getDeletedUsers().containsKey(dn))
+                                       it.remove();
+                       }
+                       for (DirectoryUser user : wc.getNewUsers().values()) {
+                               if (f == null || f.match(user.getProperties()))
+                                       res.add(user);
+                       }
+                       // no need to check modified users,
+                       // since doGetRoles was already based on the modified attributes
+               }
+               return res.toArray(new Role[res.size()]);
+       }
+
+       @Override
+       public User getUser(String key, String value) {
+               // TODO check value null or empty
+               List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>(
+                               getIndexedUserProperties().size());
+               if (key != null) {
+                       doGetUser(key, value, collectedUsers);
+               } else {
+                       // try dn
+                       DirectoryUser user = null;
+                       try {
+                               user = (DirectoryUser) getRole(value);
+                               if (user != null)
+                                       collectedUsers.add(user);
+                       } catch (Exception e) {
+                               // silent
+                       }
+                       // try all indexes
+                       for (String attr : getIndexedUserProperties())
+                               doGetUser(attr, value, collectedUsers);
+               }
+               if (collectedUsers.size() == 1)
+                       return collectedUsers.get(0);
                return null;
        }
 
+       @Override
+       public Authorization getAuthorization(User user) {
+               return new LdifAuthorization((DirectoryUser) user,
+                               getAllRoles((DirectoryUser) user));
+       }
+
+       @Override
+       public Role createRole(String name, int type) {
+               checkEdit();
+               WorkingCopy wc = getWorkingCopy();
+               LdapName dn = toDn(name);
+               if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn))
+                               || wc.getNewUsers().containsKey(dn))
+                       throw new UserDirectoryException("Already a role " + name);
+               BasicAttributes attrs = new BasicAttributes();
+               attrs.put("dn", 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);
+               } else {
+                       wc.getModifiedUsers().put(dn, attrs);
+                       DirectoryUser newRole = newRole(dn, type, attrs);
+                       wc.getNewUsers().put(dn, newRole);
+               }
+               return getRole(name);
+       }
+
+       protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
+               LdifUser newRole;
+               if (type == Role.USER) {
+                       newRole = new LdifUser(this, dn, attrs);
+                       // users.put(dn, newRole);
+               } else if (type == Role.GROUP) {
+                       newRole = new LdifGroup(this, dn, attrs);
+                       // groups.put(dn, (LdifGroup) newRole);
+               } else
+                       throw new UserDirectoryException("Unsupported type " + type);
+               return newRole;
+       }
+
+       @Override
+       public boolean removeRole(String name) {
+               checkEdit();
+               WorkingCopy wc = getWorkingCopy();
+               LdapName dn = toDn(name);
+               if (!daoHasRole(dn) && !wc.getNewUsers().containsKey(dn))
+                       return false;
+               DirectoryUser user = (DirectoryUser) getRole(name);
+               wc.getDeletedUsers().put(dn, user);
+               // FIXME clarify directgroups
+               for (DirectoryGroup group : getDirectGroups(user)) {
+                       group.getAttributes().get(getMemberAttributeId())
+                                       .remove(dn.toString());
+               }
+               return true;
+       }
+
+       // TRANSACTION
+       protected void prepare(WorkingCopy wc) {
+
+       }
+
+       protected void commit(WorkingCopy wc) {
+
+       }
+
+       protected void rollback(WorkingCopy wc) {
+
+       }
+
+       // UTILITIES
+       protected LdapName toDn(String name) {
+               try {
+                       return new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new UserDirectoryException("Badly formatted name", e);
+               }
+       }
+
+       // GETTERS
+
        String getMemberAttributeId() {
                return memberAttributeId;
        }
@@ -152,11 +342,163 @@ public abstract class AbstractUserDirectory implements UserAdmin {
        }
 
        public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) {
-               this.syncRegistry = syncRegistry;
+               // this.syncRegistry = syncRegistry;
        }
 
        public void setTransactionManager(TransactionManager transactionManager) {
                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 05107ab56ed7e34e8a4f9ce088f03205512ad837..34988cf893cc55f99436d87111d2876b7201007a 100644 (file)
@@ -9,4 +9,6 @@ interface DirectoryUser extends User {
        LdapName getDn();
 
        Attributes getAttributes();
+
+       void publishAttributes(Attributes modifiedAttributes);
 }
diff --git a/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java b/org.argeo.security.core/src/org/argeo/osgi/useradmin/EditorRole.java
deleted file mode 100644 (file)
index d99fc33..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import org.osgi.service.useradmin.Role;
-
-public interface EditorRole extends Role {
-       public static final int EDITOR = -1;
-
-       
-}
index 9bb8fbc7d44eadc5f0facbc8bb16a210bae0c74d..0cb435f073e39e25b24efde6a49ac696badd0c85 100644 (file)
@@ -11,15 +11,20 @@ import javax.naming.InvalidNameException;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
 import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
 import javax.naming.ldap.InitialLdapContext;
 import javax.naming.ldap.LdapContext;
 import javax.naming.ldap.LdapName;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.Xid;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
+import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.Group;
@@ -73,26 +78,21 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       public Role createRole(String name, int type) {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               // TODO Auto-generated method stub
-               return false;
+       protected Boolean daoHasRole(LdapName dn) {
+               return daoGetRole(dn) != null;
        }
 
        @Override
-       public Role getRole(String name) {
+       protected DirectoryUser daoGetRole(LdapName name) {
                try {
                        Attributes attrs = initialLdapContext.getAttributes(name);
+                       if (attrs.size() == 0)
+                               return null;
                        LdifUser res;
                        if (attrs.get("objectClass").contains("groupOfNames"))
-                               res = new LdifGroup(this, new LdapName(name), attrs);
+                               res = new LdifGroup(this, name, attrs);
                        else if (attrs.get("objectClass").contains("inetOrgPerson"))
-                               res = new LdifUser(this, new LdapName(name), attrs);
+                               res = new LdifUser(this, name, attrs);
                        else
                                throw new UserDirectoryException("Unsupported LDAP type for "
                                                + name);
@@ -103,11 +103,11 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+       protected List<DirectoryUser> doGetRoles(Filter f) {
+               // TODO Auto-generated method stub
                try {
-                       String searchFilter = filter;
-                       if (searchFilter == null)
-                               searchFilter = "(|(objectClass=inetOrgPerson)(objectClass=groupOfNames))";
+                       String searchFilter = f != null ? f.toString()
+                                       : "(|(objectClass=inetOrgPerson)(objectClass=groupOfNames))";
                        SearchControls searchControls = new SearchControls();
                        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
 
@@ -115,7 +115,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                        NamingEnumeration<SearchResult> results = initialLdapContext
                                        .search(searchBase, searchFilter, searchControls);
 
-                       ArrayList<Role> res = new ArrayList<Role>();
+                       ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
                        while (results.hasMoreElements()) {
                                SearchResult searchResult = results.next();
                                Attributes attrs = searchResult.getAttributes();
@@ -132,13 +132,43 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                                                                        + searchResult.getName());
                                res.add(role);
                        }
-                       return res.toArray(new Role[res.size()]);
+                       return res;
                } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot get roles for filter "
-                                       + filter, e);
+                       throw new UserDirectoryException(
+                                       "Cannot get roles for filter " + f, e);
                }
        }
 
+       @Override
+       protected void doGetUser(String key, String value,
+                       List<DirectoryUser> collectedUsers) {
+               try {
+                       String searchFilter = "(&(objectClass=inetOrgPerson)(" + key + "="
+                                       + value + "))";
+
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+                       String searchBase = baseDn;
+                       NamingEnumeration<SearchResult> results = initialLdapContext
+                                       .search(searchBase, searchFilter, searchControls);
+
+                       SearchResult searchResult = null;
+                       if (results.hasMoreElements()) {
+                               searchResult = (SearchResult) results.nextElement();
+                               if (results.hasMoreElements())
+                                       searchResult = null;
+                       }
+                       if (searchResult != null)
+                               collectedUsers.add(new LdifUser(this, toDn(searchBase,
+                                               searchResult), searchResult.getAttributes()));
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot get user with " + key
+                                       + "=" + value, e);
+               }
+
+       }
+
        @Override
        public User getUser(String key, String value) {
                if (key == null) {
@@ -181,13 +211,6 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                }
        }
 
-       @Override
-       public Authorization getAuthorization(User user) {
-               LdifUser u = (LdifUser) user;
-               // populateDirectMemberOf(u);
-               return new LdifAuthorization(u, getAllRoles(u));
-       }
-
        private LdapName toDn(String baseDn, Binding binding)
                        throws InvalidNameException {
                return new LdapName(binding.isRelative() ? binding.getName() + ","
@@ -224,8 +247,8 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        // }
 
        @Override
-       protected List<? extends Group> getDirectGroups(User user) {
-               List<Group> directGroups = new ArrayList<Group>();
+       protected List<DirectoryGroup> getDirectGroups(User user) {
+               List<DirectoryGroup> directGroups = new ArrayList<DirectoryGroup>();
                try {
                        String searchFilter = "(&(objectClass=groupOfNames)(member="
                                        + user.getName() + "))";
@@ -255,4 +278,66 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                // TODO configure group search base
                return baseDn;
        }
+
+       @Override
+       protected void prepare(WorkingCopy wc) {
+               try {
+                       initialLdapContext.reconnect(initialLdapContext
+                                       .getConnectControls());
+                       // delete
+                       for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                               if (!entryExists(dn))
+                                       throw new UserDirectoryException("User to delete no found "
+                                                       + dn);
+                       }
+                       // add
+                       for (LdapName dn : wc.getNewUsers().keySet()) {
+                               if (!entryExists(dn))
+                                       throw new UserDirectoryException("User to create found "
+                                                       + dn);
+                       }
+                       // modify
+                       for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                               if (!entryExists(dn))
+                                       throw new UserDirectoryException("User to modify no found "
+                                                       + dn);
+                       }
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot prepare LDAP", e);
+               }
+       }
+
+       private boolean entryExists(LdapName dn) throws NamingException {
+               return initialLdapContext.getAttributes(dn).size() != 0;
+       }
+
+       @Override
+       protected void commit(WorkingCopy wc) {
+               try {
+                       // delete
+                       for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                               initialLdapContext.destroySubcontext(dn);
+                       }
+                       // add
+                       for (LdapName dn : wc.getNewUsers().keySet()) {
+                               DirectoryUser user = wc.getNewUsers().get(dn);
+                               initialLdapContext.createSubcontext(dn, user.getAttributes());
+                       }
+                       // modify
+                       for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                               Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+                               initialLdapContext.modifyAttributes(dn,
+                                               DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
+                       }
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot commit LDAP", e);
+               }
+       }
+
+       @Override
+       protected void rollback(WorkingCopy wc) {
+               // TODO Auto-generated method stub
+               super.rollback(wc);
+       }
+
 }
index cd5401a55794273ffd350c9e341d025d4bd6425c..304dda9b1002a926664df181a50b51e0f93f3185 100644 (file)
@@ -15,6 +15,8 @@ import javax.naming.directory.Attributes;
 import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.osgi.useradmin.AbstractUserDirectory.WorkingCopy;
+
 class LdifUser implements DirectoryUser {
        private final AbstractUserDirectory userAdmin;
 
@@ -22,7 +24,6 @@ class LdifUser implements DirectoryUser {
 
        private final boolean frozen;
        private Attributes publishedAttributes;
-       private Attributes modifiedAttributes = null;
 
        private final AttributeDictionary properties;
        private final AttributeDictionary credentials;
@@ -82,11 +83,21 @@ class LdifUser implements DirectoryUser {
 
        @Override
        public synchronized Attributes getAttributes() {
-               return isEditing() ? modifiedAttributes : publishedAttributes;
+               return isEditing() ? getModifiedAttributes() : publishedAttributes;
+       }
+
+       /** Should only be called from working copy thread. */
+       private synchronized Attributes getModifiedAttributes() {
+               assert getWc() != null;
+               return getWc().getAttributes(getDn());
        }
 
-       protected synchronized boolean isEditing() {
-               return userAdmin.isEditing() && modifiedAttributes != null;
+       private synchronized boolean isEditing() {
+               return getWc() != null && getModifiedAttributes() != null;
+       }
+
+       private synchronized WorkingCopy getWc() {
+               return userAdmin.getWorkingCopy();
        }
 
        protected synchronized void startEditing() {
@@ -94,17 +105,22 @@ class LdifUser implements DirectoryUser {
                        throw new UserDirectoryException("Cannot edit frozen view");
                if (getUserAdmin().isReadOnly())
                        throw new UserDirectoryException("User directory is read-only");
-               assert modifiedAttributes == null;
-               modifiedAttributes = (Attributes) publishedAttributes.clone();
+               assert getModifiedAttributes() == null;
+               getWc().startEditing(this);
+               // modifiedAttributes = (Attributes) publishedAttributes.clone();
        }
 
-       protected synchronized void stopEditing(boolean apply) {
-               assert modifiedAttributes != null;
-               if (apply)
-                       publishedAttributes = modifiedAttributes;
-               modifiedAttributes = null;
+       public synchronized void publishAttributes(Attributes modifiedAttributes) {
+               publishedAttributes = modifiedAttributes;
        }
 
+       // protected synchronized void stopEditing(boolean apply) {
+       // assert getModifiedAttributes() != null;
+       // if (apply)
+       // publishedAttributes = getModifiedAttributes();
+       // // modifiedAttributes = null;
+       // }
+
        public DirectoryUser getPublished() {
                return new LdifUser(userAdmin, dn, publishedAttributes, true);
        }
@@ -226,10 +242,12 @@ class LdifUser implements DirectoryUser {
                                throw new IllegalArgumentException("Key " + key + " excluded");
 
                        try {
-                               Attribute attribute = modifiedAttributes.get(key.toString());
+                               Attribute attribute = getModifiedAttributes().get(
+                                               key.toString());
                                attribute = new BasicAttribute(key.toString());
                                attribute.add(value);
-                               Attribute previousAttribute = modifiedAttributes.put(attribute);
+                               Attribute previousAttribute = getModifiedAttributes().put(
+                                               attribute);
                                if (previousAttribute != null)
                                        return previousAttribute.get();
                                else
@@ -253,7 +271,7 @@ class LdifUser implements DirectoryUser {
                                throw new IllegalArgumentException("Key " + key + " excluded");
 
                        try {
-                               Attribute attr = modifiedAttributes.remove(key.toString());
+                               Attribute attr = getModifiedAttributes().remove(key.toString());
                                if (attr != null)
                                        return attr.get();
                                else
index 098243638a9cdba82c8f9f2bf2ede3e2988024e3..608a1f7518edd88eae8450c6e3f2e8fccac51167 100644 (file)
@@ -18,31 +18,22 @@ import java.util.TreeMap;
 import javax.naming.InvalidNameException;
 import javax.naming.NamingEnumeration;
 import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
 import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
 
 import org.apache.commons.io.IOUtils;
 import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 
 /** User admin implementation using LDIF file(s) as backend. */
 public class LdifUserAdmin extends AbstractUserDirectory {
-       SortedMap<LdapName, LdifUser> users = new TreeMap<LdapName, LdifUser>();
-       SortedMap<LdapName, LdifGroup> groups = new TreeMap<LdapName, LdifGroup>();
+       SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
+       SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
 
-       private Map<String, Map<String, LdifUser>> userIndexes = new LinkedHashMap<String, Map<String, LdifUser>>();
+       private Map<String, Map<String, DirectoryUser>> userIndexes = new LinkedHashMap<String, Map<String, DirectoryUser>>();
 
        // private Map<LdapName, List<LdifGroup>> directMemberOf = new
        // TreeMap<LdapName, List<LdifGroup>>();
-       private XaRes xaRes = new XaRes();
 
        public LdifUserAdmin(String uri) {
                this(uri, readOnlyDefault(uri));
@@ -124,6 +115,9 @@ public class LdifUserAdmin extends AbstractUserDirectory {
 
        protected void load(InputStream in) {
                try {
+                       users.clear();
+                       groups.clear();
+
                        LdifParser ldifParser = new LdifParser();
                        SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
                        for (LdapName key : allEntries.keySet()) {
@@ -148,14 +142,14 @@ public class LdifUserAdmin extends AbstractUserDirectory {
 
                        // indexes
                        for (String attr : getIndexedUserProperties())
-                               userIndexes.put(attr, new TreeMap<String, LdifUser>());
+                               userIndexes.put(attr, new TreeMap<String, DirectoryUser>());
 
-                       for (LdifUser user : users.values()) {
+                       for (DirectoryUser user : users.values()) {
                                Dictionary<String, Object> properties = user.getProperties();
                                for (String attr : getIndexedUserProperties()) {
                                        Object value = properties.get(attr);
                                        if (value != null) {
-                                               LdifUser otherUser = userIndexes.get(attr).put(
+                                               DirectoryUser otherUser = userIndexes.get(attr).put(
                                                                value.toString(), user);
                                                if (otherUser != null)
                                                        throw new UserDirectoryException("User " + user
@@ -178,17 +172,7 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                groups = null;
        }
 
-       @Override
-       public Role getRole(String name) {
-               LdapName key;
-               try {
-                       key = new LdapName(name);
-               } catch (InvalidNameException e) {
-                       // TODO implements default base DN
-                       throw new IllegalArgumentException("Badly formatted role name: "
-                                       + name, e);
-               }
-
+       protected DirectoryUser daoGetRole(LdapName key) {
                if (groups.containsKey(key))
                        return groups.get(key);
                if (users.containsKey(key))
@@ -196,148 +180,56 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                return null;
        }
 
-       @Override
-       public Authorization getAuthorization(User user) {
-               return new LdifAuthorization((LdifUser) user,
-                               getAllRoles((LdifUser) user));
+       protected Boolean daoHasRole(LdapName dn) {
+               return users.containsKey(dn) || groups.containsKey(dn);
        }
 
-       @Override
-       public Role createRole(String name, int type) {
-               try {
-                       LdapName dn = new LdapName(name);
-                       if (users.containsKey(dn) || groups.containsKey(dn))
-                               throw new UserDirectoryException("Already a role " + name);
-
-                       BasicAttributes attrs = new BasicAttributes();
-                       attrs.put("dn", dn.toString());
-                       Rdn nameRdn = dn.getRdn(dn.size() - 1);
-                       // TODO deal with multiple attr RDN
-                       attrs.put(nameRdn.getType(), nameRdn.getValue());
-                       LdifUser newRole;
-                       if (type == Role.USER) {
-                               newRole = new LdifUser(this, dn, attrs);
-                               users.put(dn, newRole);
-                       } else if (type == Role.GROUP) {
-                               newRole = new LdifGroup(this, dn, attrs);
-                               groups.put(dn, (LdifGroup) newRole);
-                       } else
-                               throw new UserDirectoryException("Unsupported type " + type);
-                       return newRole;
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Cannot create role " + name, e);
-               }
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               try {
-                       LdapName dn = new LdapName(name);
-                       LdifUser role = null;
-                       if (users.containsKey(dn))
-                               role = users.remove(dn);
-                       else if (groups.containsKey(dn))
-                               role = groups.remove(dn);
-                       else
-                               throw new UserDirectoryException("There is no role " + name);
-                       if (role == null)
-                               return false;
-                       for (LdifGroup group : getDirectGroups(role)) {
-                               // group.directMembers.remove(role);
-                               group.getAttributes().get(getMemberAttributeId())
-                                               .remove(dn.toString());
-                       }
-                       if (role instanceof LdifGroup) {
-                               LdifGroup group = (LdifGroup) role;
-                               // for (Role user : group.directMembers) {
-                               // if (user instanceof LdifUser)
-                               // directMemberOf.get(((LdifUser) user).getDn()).remove(
-                               // group);
-                               // }
-                       }
-                       return true;
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Cannot create role " + name, e);
-               }
-       }
+       // @Override
+       // public boolean removeRole(String name) {
+       // LdapName dn = toDn(name);
+       // LdifUser role = null;
+       // if (users.containsKey(dn))
+       // role = users.remove(dn);
+       // else if (groups.containsKey(dn))
+       // role = groups.remove(dn);
+       // else
+       // throw new UserDirectoryException("There is no role " + name);
+       // if (role == null)
+       // return false;
+       // for (LdifGroup group : getDirectGroups(role)) {
+       // group.getAttributes().get(getMemberAttributeId())
+       // .remove(dn.toString());
+       // }
+       // return true;
+       // }
 
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               ArrayList<Role> res = new ArrayList<Role>();
-               if (filter == null) {
+       protected List<DirectoryUser> doGetRoles(Filter f) {
+               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
+               if (f == null) {
                        res.addAll(users.values());
                        res.addAll(groups.values());
                } else {
-                       Filter f = FrameworkUtil.createFilter(filter);
-                       for (LdifUser user : users.values())
+                       // Filter f = FrameworkUtil.createFilter(filter);
+                       for (DirectoryUser user : users.values())
                                if (f.match(user.getProperties()))
                                        res.add(user);
-                       for (LdifUser group : groups.values())
+                       for (DirectoryUser group : groups.values())
                                if (f.match(group.getProperties()))
                                        res.add(group);
                }
-               return res.toArray(new Role[res.size()]);
+               return res;
        }
 
-       @Override
-       public User getUser(String key, String value) {
-               // TODO check value null or empty
-               if (key != null) {
-                       if (!userIndexes.containsKey(key))
-                               return null;
-                       return userIndexes.get(key).get(value);
-               }
-
-               // Try all indexes
-               List<LdifUser> collectedUsers = new ArrayList<LdifUser>(
-                               getIndexedUserProperties().size());
-               // try dn
-               LdifUser user = null;
-               try {
-                       user = (LdifUser) getRole(value);
-                       if (user != null)
-                               collectedUsers.add(user);
-               } catch (Exception e) {
-                       // silent
-               }
-               for (String attr : userIndexes.keySet()) {
-                       user = userIndexes.get(attr).get(value);
-                       if (user != null)
-                               collectedUsers.add(user);
-               }
-
-               if (collectedUsers.size() == 1)
-                       return collectedUsers.get(0);
-               return null;
-               // throw new UnsupportedOperationException();
+       protected void doGetUser(String key, String value,
+                       List<DirectoryUser> collectedUsers) {
+               assert key != null;
+               DirectoryUser user = userIndexes.get(key).get(value);
+               if (user != null)
+                       collectedUsers.add(user);
        }
 
-       // protected void loadMembers(LdifGroup group) {
-       // group.directMembers = new ArrayList<Role>();
-       // for (LdapName ldapName : group.getMemberNames()) {
-       // LdifUser role = null;
-       // if (groups.containsKey(ldapName))
-       // role = groups.get(ldapName);
-       // else if (users.containsKey(ldapName))
-       // role = users.get(ldapName);
-       // else {
-       // if (getExternalRoles() != null)
-       // role = (LdifUser) getExternalRoles().getRole(
-       // ldapName.toString());
-       // if (role == null)
-       // throw new ArgeoUserAdminException("No role found for "
-       // + ldapName);
-       // }
-       // // role.directMemberOf.add(group);
-       // // if (!directMemberOf.containsKey(role.getDn()))
-       // // directMemberOf.put(role.getDn(), new ArrayList<LdifGroup>());
-       // // directMemberOf.get(role.getDn()).add(group);
-       // group.directMembers.add(role);
-       // }
-       // }
-
        @Override
-       protected List<LdifGroup> getDirectGroups(User user) {
+       protected List<DirectoryGroup> getDirectGroups(User user) {
                LdapName dn;
                if (user instanceof LdifUser)
                        dn = ((LdifUser) user).getDn();
@@ -349,85 +241,61 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                                                + user.getName(), e);
                        }
 
-               List<LdifGroup> directGroups = new ArrayList<LdifGroup>();
+               List<DirectoryGroup> directGroups = new ArrayList<DirectoryGroup>();
                for (LdapName name : groups.keySet()) {
-                       LdifGroup group = groups.get(name);
+                       DirectoryGroup group = groups.get(name);
                        if (group.getMemberNames().contains(dn))
                                directGroups.add(group);
                }
                return directGroups;
-               // if (directMemberOf.containsKey(dn))
-               // return Collections.unmodifiableList(directMemberOf.get(dn));
-               // else
-               // return Collections.EMPTY_LIST;
        }
 
        @Override
-       public XAResource getXAResource() {
-               return xaRes;
-       }
-
-       private class XaRes implements XAResource {
-
-               @Override
-               public void commit(Xid xid, boolean onePhase) throws XAException {
-                       save();
-               }
-
-               @Override
-               public void end(Xid xid, int flags) throws XAException {
-                       // TODO Auto-generated method stub
-
-               }
-
-               @Override
-               public void forget(Xid xid) throws XAException {
-                       // TODO Auto-generated method stub
-
-               }
-
-               @Override
-               public int getTransactionTimeout() throws XAException {
-                       // TODO Auto-generated method stub
-                       return 0;
-               }
-
-               @Override
-               public boolean isSameRM(XAResource xares) throws XAException {
-                       // TODO Auto-generated method stub
-                       return false;
-               }
-
-               @Override
-               public int prepare(Xid xid) throws XAException {
-                       // TODO Auto-generated method stub
-                       return 0;
-               }
-
-               @Override
-               public Xid[] recover(int flag) throws XAException {
-                       // TODO Auto-generated method stub
-                       return null;
+       protected void prepare(WorkingCopy wc) {
+               // delete
+               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                       if (users.containsKey(dn))
+                               users.remove(dn);
+                       else if (groups.containsKey(dn))
+                               groups.remove(dn);
+                       else
+                               throw new UserDirectoryException("User to delete no found "
+                                               + dn);
                }
-
-               @Override
-               public void rollback(Xid xid) throws XAException {
-                       // TODO Auto-generated method stub
-
+               // add
+               for (LdapName dn : wc.getNewUsers().keySet()) {
+                       DirectoryUser user = wc.getNewUsers().get(dn);
+                       if (Role.USER == user.getType())
+                               users.put(dn, user);
+                       else if (Role.GROUP == user.getType())
+                               groups.put(dn, (DirectoryGroup) user);
+                       else
+                               throw new UserDirectoryException("Unsupported role type "
+                                               + user.getType() + " for new user " + dn);
                }
-
-               @Override
-               public boolean setTransactionTimeout(int seconds) throws XAException {
-                       // TODO Auto-generated method stub
-                       return false;
+               // modify
+               for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+                       DirectoryUser user;
+                       if (users.containsKey(dn))
+                               user = users.get(dn);
+                       else if (groups.containsKey(dn))
+                               user = groups.get(dn);
+                       else
+                               throw new UserDirectoryException("User to modify no found "
+                                               + dn);
+                       user.publishAttributes(modifiedAttrs);
                }
+       }
 
-               @Override
-               public void start(Xid xid, int flags) throws XAException {
-                       // TODO Auto-generated method stub
-
-               }
+       @Override
+       protected void commit(WorkingCopy wc) {
+               save();
+       }
 
+       @Override
+       protected void rollback(WorkingCopy wc) {
+               init();
        }
 
 }