LDIF user admin persistence based on transactions.
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / osgi / useradmin / LdifUser.java
index 85d18c082b1d5ad93bd8e78d1740a9f29bd60759..cd5401a55794273ffd350c9e341d025d4bd6425c 100644 (file)
@@ -2,34 +2,43 @@ package org.argeo.osgi.useradmin;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
 import java.util.List;
 
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
-import org.osgi.service.useradmin.User;
-
-class LdifUser implements User {
-       // optimisation
-       //List<LdifGroup> directMemberOf = new ArrayList<LdifGroup>();
+class LdifUser implements DirectoryUser {
+       private final AbstractUserDirectory userAdmin;
 
        private final LdapName dn;
-       private Attributes attributes;
+
+       private final boolean frozen;
+       private Attributes publishedAttributes;
+       private Attributes modifiedAttributes = null;
 
        private final AttributeDictionary properties;
        private final AttributeDictionary credentials;
 
-       private List<String> credentialAttributes = Arrays
-                       .asList(new String[] { "userpassword" });
+       LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               this(userAdmin, dn, attributes, false);
+       }
 
-       LdifUser(LdapName dn, Attributes attributes) {
+       private LdifUser(AbstractUserDirectory userAdmin, LdapName dn,
+                       Attributes attributes, boolean frozen) {
+               this.userAdmin = userAdmin;
                this.dn = dn;
-               this.attributes = attributes;
-               properties = new AttributeDictionary(attributes, credentialAttributes,
-                               false);
-               credentials = new AttributeDictionary(attributes, credentialAttributes,
-                               true);
+               this.publishedAttributes = attributes;
+               properties = new AttributeDictionary(false);
+               credentials = new AttributeDictionary(true);
+               this.frozen = frozen;
        }
 
        @Override
@@ -66,12 +75,38 @@ class LdifUser implements User {
                return false;
        }
 
-       protected LdapName getDn() {
+       @Override
+       public LdapName getDn() {
                return dn;
        }
 
-       protected Attributes getAttributes() {
-               return attributes;
+       @Override
+       public synchronized Attributes getAttributes() {
+               return isEditing() ? modifiedAttributes : publishedAttributes;
+       }
+
+       protected synchronized boolean isEditing() {
+               return userAdmin.isEditing() && modifiedAttributes != null;
+       }
+
+       protected synchronized void startEditing() {
+               if (frozen)
+                       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();
+       }
+
+       protected synchronized void stopEditing(boolean apply) {
+               assert modifiedAttributes != null;
+               if (apply)
+                       publishedAttributes = modifiedAttributes;
+               modifiedAttributes = null;
+       }
+
+       public DirectoryUser getPublished() {
+               return new LdifUser(userAdmin, dn, publishedAttributes, true);
        }
 
        @Override
@@ -94,4 +129,140 @@ class LdifUser implements User {
        public String toString() {
                return dn.toString();
        }
+
+       protected AbstractUserDirectory getUserAdmin() {
+               return userAdmin;
+       }
+
+       private class AttributeDictionary extends Dictionary<String, Object> {
+               private final List<String> effectiveKeys = new ArrayList<String>();
+               private final List<String> attrFilter;
+               private final Boolean includeFilter;
+
+               public AttributeDictionary(Boolean includeFilter) {
+                       this.attrFilter = userAdmin.getCredentialAttributeIds();
+                       this.includeFilter = includeFilter;
+                       try {
+                               NamingEnumeration<String> ids = getAttributes().getIDs();
+                               while (ids.hasMore()) {
+                                       String id = ids.next();
+                                       if (includeFilter && attrFilter.contains(id))
+                                               effectiveKeys.add(id);
+                                       else if (!includeFilter && !attrFilter.contains(id))
+                                               effectiveKeys.add(id);
+                               }
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException(
+                                               "Cannot initialise attribute dictionary", e);
+                       }
+               }
+
+               @Override
+               public int size() {
+                       return effectiveKeys.size();
+               }
+
+               @Override
+               public boolean isEmpty() {
+                       return effectiveKeys.size() == 0;
+               }
+
+               @Override
+               public Enumeration<String> keys() {
+                       return Collections.enumeration(effectiveKeys);
+               }
+
+               @Override
+               public Enumeration<Object> elements() {
+                       final Iterator<String> it = effectiveKeys.iterator();
+                       return new Enumeration<Object>() {
+
+                               @Override
+                               public boolean hasMoreElements() {
+                                       return it.hasNext();
+                               }
+
+                               @Override
+                               public Object nextElement() {
+                                       String key = it.next();
+                                       try {
+                                               return getAttributes().get(key).get();
+                                       } catch (NamingException e) {
+                                               throw new UserDirectoryException(
+                                                               "Cannot get value for key " + key, e);
+                                       }
+                               }
+
+                       };
+               }
+
+               @Override
+               public Object get(Object key) {
+                       try {
+                               Attribute attr = getAttributes().get(key.toString());
+                               if (attr == null)
+                                       return null;
+                               return attr.get();
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException(
+                                               "Cannot get value for attribute " + key, e);
+                       }
+               }
+
+               @Override
+               public Object put(String key, Object value) {
+                       userAdmin.checkEdit();
+                       if (!isEditing())
+                               startEditing();
+
+                       if (!(value instanceof String || value instanceof byte[]))
+                               throw new IllegalArgumentException(
+                                               "Value must be String or byte[]");
+
+                       if (includeFilter && !attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key
+                                               + " not included");
+                       else if (!includeFilter && attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " excluded");
+
+                       try {
+                               Attribute attribute = modifiedAttributes.get(key.toString());
+                               attribute = new BasicAttribute(key.toString());
+                               attribute.add(value);
+                               Attribute previousAttribute = modifiedAttributes.put(attribute);
+                               if (previousAttribute != null)
+                                       return previousAttribute.get();
+                               else
+                                       return null;
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException(
+                                               "Cannot get value for attribute " + key, e);
+                       }
+               }
+
+               @Override
+               public Object remove(Object key) {
+                       userAdmin.checkEdit();
+                       if (!isEditing())
+                               startEditing();
+
+                       if (includeFilter && !attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key
+                                               + " not included");
+                       else if (!includeFilter && attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " excluded");
+
+                       try {
+                               Attribute attr = modifiedAttributes.remove(key.toString());
+                               if (attr != null)
+                                       return attr.get();
+                               else
+                                       return null;
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException("Cannot remove attribute "
+                                               + key, e);
+                       }
+               }
+       }
+
 }