Introduce directory content provider
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 17 Jun 2022 10:12:25 +0000 (12:12 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 17 Jun 2022 10:12:25 +0000 (12:12 +0200)
31 files changed:
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtContentHierarchicalPart.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtHierarchicalPart.java
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/CrName.java
org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java
org.argeo.cms/OSGI-INF/acrContentRepository.xml
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java
org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java
org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java
org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java
org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdapNameUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java

index 4deef49c1bd0e813651bc91def456d6248d93a25..98fabf0600089ee8d369f3e90e71156b4f8e2540 100644 (file)
@@ -1,28 +1,21 @@
 package org.argeo.cms.swt.acr;
 
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
 import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
-import org.argeo.cms.acr.fs.FsContentProvider;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableColumn;
 import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeItem;
 
 public class AcrContentTreeView extends Composite {
-       private Tree tree;
+       private static final long serialVersionUID = -3707881216246077323L;
+       private SwtContentHierarchicalPart hPart;
+//     private Tree tree;
        private Table table;
        private Content rootContent;
 
@@ -36,44 +29,52 @@ public class AcrContentTreeView extends Composite {
                initTree();
                GridData treeGd = CmsSwtUtils.fillHeight();
                treeGd.widthHint = 300;
-               tree.setLayoutData(treeGd);
+               hPart.getArea().setLayoutData(treeGd);
                initTable();
 
                table.setLayoutData(CmsSwtUtils.fillAll());
        }
 
        protected void initTree() {
-               tree = new Tree(this, 0);
-               for (Content c : rootContent) {
-                       TreeItem root = new TreeItem(tree, 0);
-                       root.setText(c.getName().toString());
-                       root.setData(c);
-                       new TreeItem(root, 0);
-               }
-               tree.addListener(SWT.Expand, event -> {
-                       final TreeItem root = (TreeItem) event.item;
-                       TreeItem[] items = root.getItems();
-                       for (TreeItem item : items) {
-                               if (item.getData() != null)
-                                       return;
-                               item.dispose();
-                       }
-                       Content content = (Content) root.getData();
-                       for (Content c : content) {
-                               TreeItem item = new TreeItem(root, 0);
-                               item.setText(c.getName().toString());
-                               item.setData(c);
-                               boolean hasChildren = true;
-                               if (hasChildren) {
-                                       new TreeItem(item, 0);
-                               }
-                       }
-               });
-               tree.addListener(SWT.Selection, event -> {
-                       TreeItem item = (TreeItem) event.item;
-                       selected = (Content) item.getData();
+               hPart = new SwtContentHierarchicalPart(this, getStyle());
+               hPart.setInput(rootContent);
+               hPart.onSelected((o) -> {
+                       Content content = (Content) o;
+                       selected = content;
                        refreshTable();
+
                });
+//             tree = new Tree(this, 0);
+//             for (Content c : rootContent) {
+//                     TreeItem root = new TreeItem(tree, 0);
+//                     root.setText(c.getName().toString());
+//                     root.setData(c);
+//                     new TreeItem(root, 0);
+//             }
+//             tree.addListener(SWT.Expand, event -> {
+//                     final TreeItem root = (TreeItem) event.item;
+//                     TreeItem[] items = root.getItems();
+//                     for (TreeItem item : items) {
+//                             if (item.getData() != null)
+//                                     return;
+//                             item.dispose();
+//                     }
+//                     Content content = (Content) root.getData();
+//                     for (Content c : content) {
+//                             TreeItem item = new TreeItem(root, 0);
+//                             item.setText(c.getName().toString());
+//                             item.setData(c);
+//                             boolean hasChildren = true;
+//                             if (hasChildren) {
+//                                     new TreeItem(item, 0);
+//                             }
+//                     }
+//             });
+//             tree.addListener(SWT.Selection, event -> {
+//                     TreeItem item = (TreeItem) event.item;
+//                     selected = (Content) item.getData();
+//                     refreshTable();
+//             });
        }
 
        protected void initTable() {
@@ -103,28 +104,28 @@ public class AcrContentTreeView extends Composite {
                table.getColumn(1).pack();
        }
 
-       public static void main(String[] args) {
-               Path basePath;
-               if (args.length > 0) {
-                       basePath = Paths.get(args[0]);
-               } else {
-                       basePath = Paths.get(System.getProperty("user.home"));
-               }
-
-               final Display display = new Display();
-               final Shell shell = new Shell(display);
-               shell.setText(basePath.toString());
-               shell.setLayout(new FillLayout());
-
-               FsContentProvider contentSession = new FsContentProvider("/", basePath);
-//             GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
-
-               shell.setSize(shell.computeSize(800, 600));
-               shell.open();
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-               display.dispose();
-       }
+//     public static void main(String[] args) {
+//             Path basePath;
+//             if (args.length > 0) {
+//                     basePath = Paths.get(args[0]);
+//             } else {
+//                     basePath = Paths.get(System.getProperty("user.home"));
+//             }
+//
+//             final Display display = new Display();
+//             final Shell shell = new Shell(display);
+//             shell.setText(basePath.toString());
+//             shell.setLayout(new FillLayout());
+//
+//             FsContentProvider contentSession = new FsContentProvider("/", basePath);
+////           GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
+//
+//             shell.setSize(shell.computeSize(800, 600));
+//             shell.open();
+//             while (!shell.isDisposed()) {
+//                     if (!display.readAndDispatch())
+//                             display.sleep();
+//             }
+//             display.dispose();
+//     }
 }
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtContentHierarchicalPart.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtContentHierarchicalPart.java
new file mode 100644 (file)
index 0000000..230cca4
--- /dev/null
@@ -0,0 +1,55 @@
+package org.argeo.cms.swt.acr;
+
+import java.util.Iterator;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.widgets.SwtHierarchicalPart;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class SwtContentHierarchicalPart extends SwtHierarchicalPart {
+
+       public SwtContentHierarchicalPart(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       public Content getContent() {
+               return (Content) getInput();
+       }
+
+       @Override
+       protected void refreshRootItem(TreeItem item) {
+               refreshItem(null, item);
+       }
+
+       @Override
+       protected void refreshItem(TreeItem parentItem, TreeItem item) {
+               int index = getTree().indexOf(item);
+               Content parentContent = parentItem == null ? getContent() : (Content) parentItem.getData();
+               Content content = null;
+               int count = 0;
+               children: for (Content c : parentContent) {
+                       if (count == index) {
+                               content = c;
+                               break children;
+                       }
+                       count++;
+               }
+               item.setData(content);
+               item.setText(content.getName().toString());
+               item.setItemCount(getChildrenCount(content));
+       }
+
+       @Override
+       protected int getRootItemCount() {
+               return getChildrenCount(getContent());
+       }
+
+       static int getChildrenCount(Content content) {
+               int count = 0;
+               for (Iterator<Content> it = content.iterator(); it.hasNext();it.next()) {
+                       count++;
+               }
+               return count;
+       }
+}
index 08c85b1657af0a9a01a0449aa4880d9cb16bb5cd..a4ca061e69fd6a1f25f6e7e50ec2e434b137d57c 100644 (file)
@@ -95,4 +95,8 @@ public class SwtHierarchicalPart implements HierarchicalPart {
                this.onAction = onAction;
        }
 
+       public Composite getArea() {
+               return area;
+       }
+
 }
index 116e45c7bf09fc0d546aeadc216a7239bcab4b9b..a7a9ecce0746f85a9e7967112c93b425e46a2b89 100644 (file)
@@ -12,6 +12,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ForkJoinPool;
 
 import javax.jcr.Node;
@@ -42,13 +43,12 @@ public class JcrContent extends AbstractContent {
 //     private Node jcrNode;
 
        private JcrContentProvider provider;
-       private ProvidedSession session;
 
        private String jcrWorkspace;
        private String jcrPath;
 
        protected JcrContent(ProvidedSession session, JcrContentProvider provider, String jcrWorkspace, String jcrPath) {
-               this.session = session;
+               super(session);
                this.provider = provider;
                this.jcrWorkspace = jcrWorkspace;
                this.jcrPath = jcrPath;
@@ -103,7 +103,7 @@ public class JcrContent extends AbstractContent {
        public Node getJcrNode() {
                try {
                        // TODO caching?
-                       return provider.getJcrSession(session, jcrWorkspace).getNode(jcrPath);
+                       return provider.getJcrSession(getSession(), jcrWorkspace).getNode(jcrPath);
                } catch (RepositoryException e) {
                        throw new JcrException("Cannot retrieve " + jcrPath + " from workspace " + jcrWorkspace, e);
                }
@@ -165,7 +165,7 @@ public class JcrContent extends AbstractContent {
 
                @Override
                public Content next() {
-                       current = new JcrContent(session, provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
+                       current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
                        return current;
                }
 
@@ -182,7 +182,7 @@ public class JcrContent extends AbstractContent {
        public Content getParent() {
                if (Jcr.isRoot(getJcrNode())) // root
                        return null;
-               return new JcrContent(session, provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
+               return new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
        }
 
        @Override
@@ -224,7 +224,7 @@ public class JcrContent extends AbstractContent {
 
        boolean exists() {
                try {
-                       return provider.getJcrSession(session, jcrWorkspace).itemExists(jcrPath);
+                       return provider.getJcrSession(getSession(), jcrWorkspace).itemExists(jcrPath);
                } catch (RepositoryException e) {
                        throw new JcrException("Cannot check whether " + jcrPath + " exists", e);
                }
@@ -241,7 +241,7 @@ public class JcrContent extends AbstractContent {
 
                        ForkJoinPool.commonPool().execute(() -> {
                                try (PipedOutputStream out = new PipedOutputStream(in)) {
-                                       provider.getJcrSession(session, jcrWorkspace).exportDocumentView(jcrPath, out, true, false);
+                                       provider.getJcrSession(getSession(), jcrWorkspace).exportDocumentView(jcrPath, out, true, false);
                                        out.flush();
                                } catch (IOException | RepositoryException e) {
                                        throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
@@ -273,11 +273,6 @@ public class JcrContent extends AbstractContent {
                return super.open(clss);
        }
 
-       @Override
-       public ProvidedSession getSession() {
-               return session;
-       }
-
        @Override
        public ContentProvider getProvider() {
                return provider;
@@ -292,6 +287,60 @@ public class JcrContent extends AbstractContent {
                }
        }
 
+       /*
+        * TYPING
+        */
+       @Override
+       public List<QName> getTypes() {
+               try {
+//                     Node node = getJcrNode();
+//                     List<QName> res = new ArrayList<>();
+//                     res.add(nodeTypeToQName(node.getPrimaryNodeType()));
+//                     for (NodeType mixin : node.getMixinNodeTypes()) {
+//                             res.add(nodeTypeToQName(mixin));
+//                     }
+//                     return res;
+                       Node context = getJcrNode();
+
+                       List<QName> res = new ArrayList<>();
+                       // primary node type
+                       NodeType primaryType = context.getPrimaryNodeType();
+                       res.add(nodeTypeToQName(primaryType));
+
+                       Set<QName> secondaryTypes = new TreeSet<>();
+                       for (NodeType mixinType : context.getMixinNodeTypes()) {
+                               secondaryTypes.add(nodeTypeToQName(mixinType));
+                       }
+                       for (NodeType superType : primaryType.getDeclaredSupertypes()) {
+                               secondaryTypes.add(nodeTypeToQName(superType));
+                       }
+                       // mixins
+                       for (NodeType mixinType : context.getMixinNodeTypes()) {
+                               for (NodeType superType : mixinType.getDeclaredSupertypes()) {
+                                       secondaryTypes.add(nodeTypeToQName(superType));
+                               }
+                       }
+//             // entity type
+//             if (context.isNodeType(EntityType.entity.get())) {
+//                     if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
+//                             String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
+//                             if (byType.containsKey(entityTypeName)) {
+//                                     types.add(entityTypeName);
+//                             }
+//                     }
+//             }
+                       res.addAll(secondaryTypes);
+                       return res;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot list node types from " + getJcrNode(), e);
+               }
+       }
+
+       private QName nodeTypeToQName(NodeType nodeType) {
+               String name = nodeType.getName();
+               return QName.valueOf(name);
+       }
+
        /*
         * STATIC UTLITIES
         */
index c2202b0fa31e14ad7db87a0f104390beaa792a7f..cd03b16a828906dff4990ad62a7b77621c9f96cd 100644 (file)
@@ -89,10 +89,15 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
 
        void remove();
 
+       /*
+        * TYPING
+        */
+       List<QName> getTypes();
+
        /*
         * DEFAULT METHODS
         */
-       default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+       default <A> A adapt(Class<A> clss) {
                throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
        }
 
index 1b29b366132eae40ff88c5e73a564b98357608b1..c772daca32a7964ac576e87d7bafad420532b393 100644 (file)
@@ -39,6 +39,11 @@ public enum CrName implements ContentNameSupplier {
 //     public final static String CR_POSIX_NAMESPACE_URI = CR_NAMESPACE_URI + "/posix";
 
        public final static String CR_DEFAULT_PREFIX = "cr";
+
+       public final static String LDAP_NAMESPACE_URI = "http://argeo.org/ns/ldap";
+       public final static String LDAP_DEFAULT_PREFIX = "ldap";
+
+       
        private final ContentName value;
 
        CrName() {
index a1b4062aa3e4277e5b47606d81a2d992a1766b7f..9b5034b559c5692b3ec119f13e2b3ae5eed9c7ef 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.api.acr;
 
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.Objects;
 import java.util.Set;
@@ -39,6 +40,19 @@ public class NamespaceUtils {
                return prefix + ":" + name.getLocalPart();
        }
 
+       public final static Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
+       
+               @Override
+               public int compare(QName qn1, QName qn2) {
+                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+                       } else {
+                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+                       }
+               }
+       
+       };
+
        /** singleton */
        private NamespaceUtils() {
        }
index d0d31db9ce4f69d27f313a470362ec2c28388142..befc95889a6f0efd196761387b77f46ee515edcd 100644 (file)
@@ -8,4 +8,5 @@
    </service>
    <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
    <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
+   <reference bind="setUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
 </scr:component>
index c51d21caa30505129f01476db05647ffb02d0586..5256dbfed5901eb507e9ddaf8583edd1ab628b3f 100644 (file)
@@ -80,6 +80,8 @@ public interface CmsUserManager {
        void expireAuthToken(String token);
 
        void expireAuthTokens(Subject subject);
+       
+       UserDirectory getUserDirectory(User user);
 
 //     User createUserFromPerson(Node person);
 
index fa8062f7c12ea4fa7c596df69ffb89541112444d..18c6724ace740d6a5df99e5cf43e9126cd6c9eaf 100644 (file)
@@ -15,16 +15,22 @@ import javax.xml.namespace.QName;
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
 
 /** Partial reference implementation of a {@link ProvidedContent}. */
 public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+       private final ProvidedSession session;
+
+       public AbstractContent(ProvidedSession session) {
+               this.session = session;
+       }
 
        /*
         * ATTRIBUTES OPERATIONS
         */
-       protected abstract Iterable<QName> keys();
-
-       protected abstract void removeAttr(QName key);
+//     protected abstract Iterable<QName> keys();
+//
+//     protected abstract void removeAttr(QName key);
 
        @Override
        public Set<Entry<QName, Object>> entrySet() {
@@ -106,12 +112,30 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
                collectAncestors(ancestors, this);
                return ancestors.size();
        }
-       
+
        @Override
        public String getSessionLocalId() {
                return getPath();
        }
 
+       /*
+        * SESSION
+        */
+
+       @Override
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       /*
+        * TYPING
+        */
+
+       @Override
+       public List<QName> getTypes() {
+               return new ArrayList<>();
+       }
+
        /*
         * UTILITIES
         */
@@ -125,6 +149,40 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
 //             return "content " + getPath();
 //     }
 
+       /*
+        * DEFAULTS
+        */
+       //      - no children
+       //      - no attributes
+       //      - cannot be modified
+       @Override
+       public Iterator<Content> iterator() {
+               return Collections.emptyIterator();
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               throw new UnsupportedOperationException("Content cannot be added.");
+       }
+
+       @Override
+       public void remove() {
+               throw new UnsupportedOperationException("Content cannot be removed.");
+       }
+
+       protected Iterable<QName> keys() {
+               return Collections.emptySet();
+       }
+
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               return null;
+       }
+
+       protected void removeAttr(QName key) {
+               throw new UnsupportedOperationException("Attributes cannot be removed.");
+       }
+
        /*
         * SUB CLASSES
         */
index cfffea027855b2fcd3a8e3301fa7f80bc6dfc267..1481b3a40ff4125c32f94eaa3adda8c7f08364d9 100644 (file)
@@ -5,6 +5,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.HashSet;
 import java.util.Set;
 
 import javax.xml.namespace.QName;
@@ -40,6 +41,8 @@ public abstract class AbstractContentRepository implements ProvidedRepository {
 
        private CmsContentSession systemSession;
 
+       private Set<ContentProvider> providersToAdd = new HashSet<>();
+
        // utilities
        /** Should be used only to copy source and results. */
        private TransformerFactory identityTransformerFactory = TransformerFactory.newInstance();
@@ -76,7 +79,10 @@ public abstract class AbstractContentRepository implements ProvidedRepository {
         */
 
        public void addProvider(ContentProvider provider) {
-               mountManager.addStructuralContentProvider(provider);
+               if (mountManager == null)
+                       providersToAdd.add(provider);
+               else
+                       mountManager.addStructuralContentProvider(provider);
        }
 
        public void registerTypes(String prefix, String namespaceURI, String schemaSystemId) {
@@ -128,6 +134,10 @@ public abstract class AbstractContentRepository implements ProvidedRepository {
                        throw new IllegalStateException("Cannot init ACR root " + path, e);
                }
 
+               // add content providers already notified
+               for (ContentProvider contentProvider : providersToAdd)
+                       addProvider(contentProvider);
+               providersToAdd.clear();
        }
 
        public void writeDom(Document document, OutputStream out) throws IOException {
index 46222ce1ac0d2248b58f67ed8bdb3e13b8a6e68a..3e01aee8b235ff9d1d0a94d30174d1afa6d6e6f4 100644 (file)
@@ -22,6 +22,7 @@ import org.argeo.cms.internal.runtime.CmsContextImpl;
  */
 public class CmsContentRepository extends AbstractContentRepository {
        public final static String RUN_BASE = "/run";
+       public final static String DIRECTORY_BASE = "/directory";
 
        private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
 
index 5609cf7778ca65555824c500e48d3769be944f69..272a5c5a148d3f51b3315fad79918e4837aed3c9 100644 (file)
@@ -1,12 +1,13 @@
 package org.argeo.cms.acr;
 
 import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.BiConsumer;
 
 import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsSession;
 
 /** Utilities and routines around {@link Content}. */
 public class ContentUtils {
@@ -50,18 +51,56 @@ public class ContentUtils {
 //             return t instanceof String;
 //     }
 
+       public static final char SLASH = '/';
+       public static final String ROOT_SLASH = "" + SLASH;
+
        /**
         * Split a path (with '/' separator) in an array of length 2, the first part
         * being the parent path (which could be either absolute or relative), the
-        * second one being the last segment, (guaranteed to be with '/').
+        * second one being the last segment, (guaranteed to be without a '/').
         */
        public static String[] getParentPath(String path) {
-               int parentIndex = path.lastIndexOf('/');
-               // TODO make it more robust
-               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "/",
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               if (path.length() == 0)
+                       throw new IllegalArgumentException("Path cannot be empty");
+               checkDoubleSlash(path);
+               int parentIndex = path.lastIndexOf(SLASH);
+               if (parentIndex == path.length() - 1) {// trailing '/'
+                       path = path.substring(0, path.length() - 1);
+                       parentIndex = path.lastIndexOf(SLASH);
+               }
+
+               if (parentIndex == -1) // no '/'
+                       return new String[] { "", path };
+
+               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
                                path.substring(parentIndex + 1) };
        }
 
+       public static List<String> toPathSegments(String path) {
+               List<String> res = new ArrayList<>();
+               if ("".equals(path) || ROOT_SLASH.equals(path))
+                       return res;
+               collectPathSegments(path, res);
+               return res;
+       }
+
+       private static void collectPathSegments(String path, List<String> segments) {
+               String[] parent = getParentPath(path);
+               if ("".equals(parent[1])) // root
+                       return;
+               segments.add(0, parent[1]);
+               if ("".equals(parent[0])) // end
+                       return;
+               collectPathSegments(parent[0], segments);
+       }
+
+       public static void checkDoubleSlash(String path) {
+               if (path.contains(SLASH + "" + SLASH))
+                       throw new IllegalArgumentException("Path " + path + " contains //");
+       }
+
        /** Singleton. */
        private ContentUtils() {
 
index 62335d3617a1cc47d8d22111139a3ac3358d2252..731c6d5624488d07ac04277471fe815c52489954 100644 (file)
@@ -42,6 +42,7 @@ import org.apache.xerces.xs.XSSimpleTypeDefinition;
 import org.apache.xerces.xs.XSTerm;
 import org.apache.xerces.xs.XSTypeDefinition;
 import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.cms.CmsLog;
 import org.xml.sax.ErrorHandler;
 import org.xml.sax.SAXException;
@@ -72,13 +73,7 @@ class TypesManager {
                schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 
                // types
-               types = new TreeMap<>((qn1, qn2) -> {
-                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
-                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
-                       } else {
-                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
-                       }
-               });
+               types = new TreeMap<>(NamespaceUtils.QNAME_COMPARATOR);
 
        }
 
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java
new file mode 100644 (file)
index 0000000..60ef675
--- /dev/null
@@ -0,0 +1,145 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.osgi.useradmin.HierarchyUnit;
+import org.argeo.osgi.useradmin.LdapNameUtils;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.osgi.service.useradmin.User;
+
+public class DirectoryContentProvider implements ContentProvider {
+       private String mountPath;
+       private String mountName;
+
+       private CmsUserManager userManager;
+
+       public DirectoryContentProvider(String mountPath, CmsUserManager userManager) {
+               this.mountPath = mountPath;
+               List<String> mountSegments = ContentUtils.toPathSegments(mountPath);
+               this.mountName = mountSegments.get(mountSegments.size() - 1);
+               this.userManager = userManager;
+       }
+
+       @Override
+       public ProvidedContent get(ProvidedSession session, String relativePath) {
+               List<String> segments = ContentUtils.toPathSegments(relativePath);
+               if (segments.size() == 0)
+                       return new UserManagerContent(session);
+               String userDirectoryDn = segments.get(0);
+               UserDirectory userDirectory = null;
+               userDirectories: for (UserDirectory ud : userManager.getUserDirectories()) {
+                       if (userDirectoryDn.equals(ud.getBasePath())) {
+                               userDirectory = ud;
+                               break userDirectories;
+                       }
+               }
+               if (userDirectory == null)
+                       throw new ContentNotFoundException("Cannot find user directory " + userDirectoryDn);
+               if (segments.size() == 1) {
+                       return new HierarchyUnitContent(session, this, userDirectory);
+               } else {
+                       LdapName dn;
+                       try {
+                               dn = LdapNameUtils.toLdapName(userDirectoryDn);
+                               for (int i = 1; i < segments.size(); i++) {
+                                       dn.add(segments.get(i));
+                               }
+                       } catch (InvalidNameException e) {
+                               throw new IllegalStateException("Cannot interpret " + segments + " as DN", e);
+                       }
+                       User user = userManager.getUser(dn.toString());
+                       if (user != null) {
+                               HierarchyUnit parent = userDirectory.getHierarchyUnit(user);
+                               return new RoleContent(session, this, new HierarchyUnitContent(session, this, parent), user);
+                       }
+                       HierarchyUnit hierarchyUnit = userDirectory.getHierarchyUnit(dn.toString());
+                       if (hierarchyUnit == null)
+                               throw new ContentNotFoundException("Cannot find " + dn);
+                       return new HierarchyUnitContent(session, this, hierarchyUnit);
+               }
+       }
+
+       @Override
+       public boolean exists(ProvidedSession session, String relativePath) {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
+       @Override
+       public String getNamespaceURI(String prefix) {
+               if (CrName.LDAP_DEFAULT_PREFIX.equals(prefix))
+                       return CrName.LDAP_NAMESPACE_URI;
+               throw new IllegalArgumentException("Only prefix " + CrName.LDAP_DEFAULT_PREFIX + " is supported");
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               if (CrName.LDAP_NAMESPACE_URI.equals(namespaceURI))
+                       return Collections.singletonList(CrName.LDAP_DEFAULT_PREFIX).iterator();
+               throw new IllegalArgumentException("Only namespace URI " + CrName.LDAP_NAMESPACE_URI + " is supported");
+       }
+
+       public void setUserManager(CmsUserManager userManager) {
+               this.userManager = userManager;
+       }
+       
+       UserManagerContent getRootContent(ProvidedSession session) {
+               return new UserManagerContent(session);
+       }
+
+       class UserManagerContent extends AbstractContent {
+
+               public UserManagerContent(ProvidedSession session) {
+                       super(session);
+               }
+
+               @Override
+               public ContentProvider getProvider() {
+                       return DirectoryContentProvider.this;
+               }
+
+               @Override
+               public QName getName() {
+                       return new ContentName(mountName);
+               }
+
+               @Override
+               public Content getParent() {
+                       return null;
+               }
+
+               @Override
+               public Iterator<Content> iterator() {
+                       List<Content> res = new ArrayList<>();
+                       for (UserDirectory userDirectory : userManager.getUserDirectories()) {
+                               HierarchyUnitContent content = new HierarchyUnitContent(getSession(), DirectoryContentProvider.this,
+                                               userDirectory);
+                               res.add(content);
+                       }
+                       return res.iterator();
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java
new file mode 100644 (file)
index 0000000..9184c63
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.osgi.useradmin.HierarchyUnit;
+import org.osgi.service.useradmin.Role;
+
+public class HierarchyUnitContent extends AbstractContent {
+       private HierarchyUnit hierarchyUnit;
+
+       private DirectoryContentProvider provider;
+
+       public HierarchyUnitContent(ProvidedSession session, DirectoryContentProvider provider,
+                       HierarchyUnit hierarchyUnit) {
+               super(session);
+               Objects.requireNonNull(hierarchyUnit);
+               this.provider = provider;
+               this.hierarchyUnit = hierarchyUnit;
+       }
+
+       @Override
+       public ContentProvider getProvider() {
+               return provider;
+       }
+
+       @Override
+       public QName getName() {
+               if (hierarchyUnit.getParent() == null) {// base DN
+                       String baseDn = hierarchyUnit.getBasePath();
+                       return new ContentName(baseDn);
+               }
+               String name = hierarchyUnit.getHierarchyUnitName();
+               return new ContentName(name);
+       }
+
+       @Override
+       public Content getParent() {
+               HierarchyUnit parentHu = hierarchyUnit.getParent();
+               if (parentHu == null) {
+                       return provider.getRootContent(getSession());
+               }
+               return new HierarchyUnitContent(getSession(), provider, parentHu);
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               List<Content> lst = new ArrayList<>();
+               for (int i = 0; i < hierarchyUnit.getHierarchyChildCount(); i++)
+                       lst.add(new HierarchyUnitContent(getSession(), provider, hierarchyUnit.getHierarchyChild(i)));
+
+               for (Role role : hierarchyUnit.getRoles(null, false))
+                       lst.add(new RoleContent(getSession(), provider, this, role));
+               return lst.iterator();
+       }
+
+       /*
+        * TYPING
+        */
+
+       @Override
+       public List<QName> getTypes() {
+               List<QName> res = new ArrayList<>();
+               res.add(CrName.COLLECTION.get());
+               return res;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java
new file mode 100644 (file)
index 0000000..8043205
--- /dev/null
@@ -0,0 +1,125 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.ldap.LdapName;
+import javax.swing.GroupLayout.Group;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.osgi.useradmin.LdapNameUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+public class RoleContent extends AbstractContent {
+
+       private DirectoryContentProvider provider;
+       private HierarchyUnitContent parent;
+       private Role role;
+
+       public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent,
+                       Role role) {
+               super(session);
+               this.provider = provider;
+               this.parent = parent;
+               this.role = role;
+       }
+
+       @Override
+       public ContentProvider getProvider() {
+               return provider;
+       }
+
+       @Override
+       public QName getName() {
+               LdapName dn = LdapNameUtils.toLdapName(role.getName());
+               String name = LdapNameUtils.getLastRdnAsString(dn);
+               return new ContentName(name);
+       }
+
+       @Override
+       public Content getParent() {
+               return parent;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               String attrName = key.getLocalPart();
+               Object value = role.getProperties().get(attrName);
+               if (value == null)
+                       return Optional.empty();
+               // TODO deal with type and multiple
+               return Optional.of((A) value);
+       }
+
+       @Override
+       protected Iterable<QName> keys() {
+               Set<QName> keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
+               keys: for (Enumeration<String> it = role.getProperties().keys(); it.hasMoreElements();) {
+                       String key = it.nextElement();
+                       if (key.equalsIgnoreCase(LdapAttrs.objectClass.name()))
+                               continue keys;
+                       ContentName name = new ContentName(CrName.LDAP_NAMESPACE_URI, key, provider);
+                       keys.add(name);
+               }
+               return keys;
+       }
+
+       @Override
+       public List<QName> getTypes() {
+               List<QName> contentClasses = new ArrayList<>();
+               keys: for (Enumeration<String> it = role.getProperties().keys(); it.hasMoreElements();) {
+                       String key = it.nextElement();
+                       if (key.equalsIgnoreCase(LdapAttrs.objectClass.name())) {
+                               String[] objectClasses = role.getProperties().get(key).toString().split("\\n");
+                               objectClasses: for (String objectClass : objectClasses) {
+                                       if (LdapObjs.top.name().equalsIgnoreCase(objectClass))
+                                               continue objectClasses;
+                                       contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, objectClass, provider));
+                               }
+                               break keys;
+                       }
+               }
+               return contentClasses;
+       }
+
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               // TODO deal with typing
+               role.getProperties().put(key.getLocalPart(), value);
+               return previous;
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               role.getProperties().remove(key.getLocalPart());
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> A adapt(Class<A> clss) {
+               if (clss.equals(Group.class))
+                       return (A) role;
+               else if (clss.equals(User.class))
+                       return (A) role;
+               else if (clss.equals(Role.class))
+                       return (A) role;
+               return super.adapt(clss);
+       }
+
+}
index 5c9c1096e69722cf65ac634a8cde86d8cacb110d..d0df8845f308a4fa5befbef1c60ef5ca953e2265 100644 (file)
@@ -11,10 +11,12 @@ import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -52,14 +54,13 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
        }
 
-       private final ProvidedSession session;
        private final FsContentProvider provider;
        private final Path path;
        private final boolean isRoot;
        private final QName name;
 
        protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
-               this.session = session;
+               super(session);
                this.provider = contentProvider;
                this.path = path;
                this.isRoot = contentProvider.isMountRoot(path);
@@ -159,7 +160,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        try {
                                for (String name : udfav.list()) {
                                        QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
-                                       QName sessionName = new ContentName(providerName, session);
+                                       QName sessionName = new ContentName(providerName, getSession());
                                        result.add(sessionName);
                                }
                        } catch (IOException e) {
@@ -211,9 +212,9 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                                        Optional<String> isMount = fsContent.get(CrName.MOUNT.get(), String.class);
                                        if (isMount.orElse("false").equals("true")) {
                                                QName[] classes = null;
-                                               ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent,
-                                                               false, classes);
-                                               Content mountedContent = contentProvider.get(session, "");
+                                               ContentProvider contentProvider = getSession().getRepository()
+                                                               .getMountContentProvider(fsContent, false, classes);
+                                               Content mountedContent = contentProvider.get(getSession(), "");
                                                return mountedContent;
                                        } else {
                                                return (Content) fsContent;
@@ -245,9 +246,10 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        throw new ContentResourceException("Cannot create new content", e);
                }
 
-               if (session.getRepository().shouldMount(classes)) {
-                       ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent, true, classes);
-                       Content mountedContent = contentProvider.get(session, "");
+               if (getSession().getRepository().shouldMount(classes)) {
+                       ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
+                                       classes);
+                       Content mountedContent = contentProvider.get(getSession(), "");
                        fsContent.put(CrName.MOUNT.get(), "true");
                        return mountedContent;
 
@@ -268,7 +270,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        if (mountPath == null || mountPath.equals("/"))
                                return null;
                        String[] parent = ContentUtils.getParentPath(mountPath);
-                       return session.get(parent[0]);
+                       return getSession().get(parent[0]);
                }
                return new FsContent(this, path.getParent());
        }
@@ -299,13 +301,22 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        }
 
        /*
-        * ACCESSORS
+        * TYPING
         */
+
        @Override
-       public ProvidedSession getSession() {
-               return session;
+       public List<QName> getTypes() {
+               List<QName> res = new ArrayList<>();
+               if (Files.isDirectory(path))
+                       res.add(CrName.COLLECTION.get());
+               // TODO add other types
+               return res;
        }
 
+       /*
+        * ACCESSORS
+        */
+
        @Override
        public FsContentProvider getProvider() {
                return provider;
index ac863403deed8138a298cb25a0efd5ce098c7d0a..15dfe291b701a1f395adb8aad35b6a0bc18cb45b 100644 (file)
@@ -1,8 +1,11 @@
 package org.argeo.cms.acr.xml;
 
 import java.nio.CharBuffer;
+import java.nio.file.Files;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -13,6 +16,7 @@ import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
@@ -28,7 +32,6 @@ import org.w3c.dom.Text;
 /** Content persisted as a DOM element. */
 public class DomContent extends AbstractContent implements ProvidedContent {
 
-       private final ProvidedSession session;
        private final DomContentProvider provider;
        private final Element element;
 
@@ -36,7 +39,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
        private Boolean hasText = null;
 
        public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
-               this.session = session;
+               super(session);
                this.provider = contentProvider;
                this.element = element;
        }
@@ -50,7 +53,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                if (element.getParentNode() == null) {// root
                        String mountPath = provider.getMountPath();
                        if (mountPath != null) {
-                               Content mountPoint = session.getMountPoint(mountPath);
+                               Content mountPoint = getSession().getMountPoint(mountPath);
                                return mountPoint.getName();
                        }
                }
@@ -198,7 +201,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
        @Override
        public Iterator<Content> iterator() {
                NodeList nodeList = element.getChildNodes();
-               return new ElementIterator(this, session, provider, nodeList);
+               return new ElementIterator(this, getSession(), provider, nodeList);
        }
 
        @Override
@@ -209,7 +212,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                        if (mountPath == null)
                                return null;
                        String[] parent = ContentUtils.getParentPath(mountPath);
-                       return session.get(parent[0]);
+                       return getSession().get(parent[0]);
                }
                if (parentNode instanceof Document)
                        return null;
@@ -263,7 +266,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                if (String.class.isAssignableFrom(clss)) {
                        CompletableFuture<String> res = new CompletableFuture<>();
                        res.thenAccept((s) -> {
-                               session.notifyModification(this);
+                               getSession().notifyModification(this);
                                element.setTextContent(s);
                        });
                        return (CompletableFuture<A>) res;
@@ -271,6 +274,16 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                return super.write(clss);
        }
 
+       /*
+        * TYPING
+        */
+       @Override
+       public List<QName> getTypes() {
+               List<QName> res = new ArrayList<>();
+               res.add(getName());
+               return res;
+       }
+
        /*
         * MOUNT MANAGEMENT
         */
@@ -282,10 +295,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                return new DomContent(this, childElement);
        }
 
-       public ProvidedSession getSession() {
-               return session;
-       }
-
+       @Override
        public DomContentProvider getProvider() {
                return provider;
        }
index 84562ebd16660a860df397778421037581f9ba64..135bf50c8ebac917827a31c8405d719dccc4e71d 100644 (file)
@@ -16,7 +16,9 @@ import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.UUID;
 
@@ -438,6 +440,20 @@ public class CmsUserManagerImpl implements CmsUserManager {
                }
        }
 
+       @Override
+       public UserDirectory getUserDirectory(User user) {
+               String name = user.getName();
+               NavigableMap<String, UserDirectory> possible = new TreeMap<>();
+               for (UserDirectory userDirectory : userDirectories.keySet()) {
+                       if (name.endsWith(userDirectory.getBasePath())) {
+                               possible.put(userDirectory.getBasePath(), userDirectory);
+                       }
+               }
+               if (possible.size() == 0)
+                       throw new IllegalStateException("No user directory found for user " + name);
+               return possible.lastEntry().getValue();
+       }
+
 //     public User createUserFromPerson(Node person) {
 //             String email = JcrUtils.get(person, LdapAttrs.mail.property());
 //             String dn = buildDefaultDN(email, Role.USER);
index cff9bc5148a3464678dc98ae310f93d035332724..4a4a4d9867863a4e4bf4ecf3887ba63ecdc37ab3 100644 (file)
@@ -6,13 +6,16 @@ import java.nio.file.Path;
 import java.util.Map;
 
 import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.acr.CmsContentRepository;
+import org.argeo.cms.acr.directory.DirectoryContentProvider;
 import org.argeo.cms.acr.fs.FsContentProvider;
-import org.argeo.util.OS;
 
 public class DeployedContentRepository extends CmsContentRepository {
        private final static String ROOT_XML = "cr:root.xml";
 
+       private CmsUserManager userManager;
+
        @Override
        public void start() {
                try {
@@ -24,10 +27,16 @@ public class DeployedContentRepository extends CmsContentRepository {
 //             FsContentProvider srvContentProvider = new FsContentProvider("/" + CmsConstants.SRV_WORKSPACE, srvPath, false);
 //             addProvider(srvContentProvider);
 
-                       Path runDirPath =  KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE);
+                       // run dir
+                       Path runDirPath = KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE);
                        Files.createDirectories(runDirPath);
                        FsContentProvider runContentProvider = new FsContentProvider(CmsContentRepository.RUN_BASE, runDirPath);
                        addProvider(runContentProvider);
+
+                       // users
+                       DirectoryContentProvider directoryContentProvider = new DirectoryContentProvider(
+                                       CmsContentRepository.DIRECTORY_BASE, userManager);
+                       addProvider(directoryContentProvider);
                } catch (IOException e) {
                        throw new IllegalStateException("Cannot start content repository", e);
                }
@@ -46,4 +55,8 @@ public class DeployedContentRepository extends CmsContentRepository {
        public void removeContentProvider(ContentProvider provider, Map<String, Object> properties) {
        }
 
+       public void setUserManager(CmsUserManager userManager) {
+               this.userManager = userManager;
+       }
+
 }
index e4087e13903ee00f707ea5ec159f9f5335c86b37..a44c5d4fdade72c495ed52e430ae033716ffbf1c 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.cms.runtime;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.concurrent.CompletableFuture;
 
 import org.argeo.api.acr.ContentRepository;
@@ -11,7 +12,9 @@ import org.argeo.api.cms.CmsContext;
 import org.argeo.api.cms.CmsDeployment;
 import org.argeo.api.cms.CmsState;
 import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.acr.CmsUuidFactory;
+import org.argeo.cms.internal.auth.CmsUserManagerImpl;
 import org.argeo.cms.internal.osgi.DeployConfig;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.internal.runtime.CmsDeploymentImpl;
@@ -21,6 +24,7 @@ import org.argeo.cms.internal.runtime.DeployedContentRepository;
 import org.argeo.osgi.transaction.SimpleTransactionManager;
 import org.argeo.osgi.transaction.WorkControl;
 import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.osgi.useradmin.UserDirectory;
 import org.argeo.util.register.Component;
 import org.argeo.util.register.SimpleRegister;
 import org.osgi.service.useradmin.UserAdmin;
@@ -77,7 +81,6 @@ public class StaticCms {
 
                // User Admin
                CmsUserAdmin userAdmin = new CmsUserAdmin();
-
                Component<CmsUserAdmin> userAdminC = new Component.Builder<>(userAdmin) //
                                .addType(UserAdmin.class) //
                                .addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) //
@@ -88,6 +91,19 @@ public class StaticCms {
                                }, null) //
                                .build(register);
 
+               // User manager
+               CmsUserManagerImpl userManager = new CmsUserManagerImpl();
+               for (UserDirectory userDirectory : userAdmin.getUserDirectories()) {
+                       // FIXME deal with properties
+                       userManager.addUserDirectory(userDirectory, new HashMap<>());
+               }
+               Component<CmsUserManagerImpl> userManagerC = new Component.Builder<>(userManager) //
+                               .addType(CmsUserManager.class) //
+                               .addDependency(userAdminC.getType(UserAdmin.class), userManager::setUserAdmin, null) //
+                               .addDependency(transactionManagerC.getType(WorkTransaction.class), userManager::setUserTransaction,
+                                               null) //
+                               .build(register);
+
                // Content Repository
                DeployedContentRepository contentRepository = new DeployedContentRepository();
                Component<DeployedContentRepository> contentRepositoryC = new Component.Builder<>(contentRepository) //
@@ -97,6 +113,7 @@ public class StaticCms {
                                .addDeactivation(contentRepository::stop) //
                                .addDependency(cmsStateC.getType(CmsState.class), contentRepository::setCmsState, null) //
                                .addDependency(uuidFactoryC.getType(UuidFactory.class), contentRepository::setUuidFactory, null) //
+                               .addDependency(userManagerC.getType(CmsUserManager.class), contentRepository::setUserManager, null) //
                                .build(register);
 
                // CMS Context
index 716ddb5edfa72975fecfd3fef81812d4e7554505..889f9cfa79fd3829bf4ab6ed6aee79b09626fc7b 100644 (file)
@@ -444,6 +444,11 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                throw new IllegalArgumentException("No child hierarchy unit available");
        }
 
+       @Override
+       public HierarchyUnit getParent() {
+               return null;
+       }
+
        @Override
        public int getHierarchyUnitType() {
                return 0;
@@ -451,11 +456,21 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        @Override
        public String getHierarchyUnitName() {
-               String name = baseDn.getRdn(baseDn.size() - 1).getValue().toString();
+               String name = LdapNameUtils.getLastRdnAsString(baseDn);
                // TODO check ou, o, etc.
                return name;
        }
 
+       @Override
+       public HierarchyUnit getHierarchyUnit(String path) {
+               return null;
+       }
+
+       @Override
+       public HierarchyUnit getHierarchyUnit(Role role) {
+               return null;
+       }
+
        @Override
        public List<? extends Role> getRoles(String filter, boolean deep) {
                try {
index 5613c28484ac4dff444505d5b96f3e1ab6e74434..ad6a83fb5d437d5b519c662b15623dc3523abe3e 100644 (file)
@@ -9,6 +9,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
@@ -270,4 +271,9 @@ public class AggregatingUserAdmin implements UserAdmin {
        protected void preDestroy(UserDirectory userDirectory) {
        }
 
+       public Set<UserDirectory> getUserDirectories() {
+               TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBasePath().compareTo(o2.getBasePath()));
+               res.addAll(businessRoles.values());
+               return res;
+       }
 }
index c01ea15f418e52ef5f134a3c335575ee7a1aeeb2..24943463139baa20772c928bdef48acccac0aa24 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.osgi.useradmin;
 
 import java.util.List;
+import java.util.Map;
 
 import org.osgi.service.useradmin.Role;
 
@@ -14,6 +15,8 @@ public interface HierarchyUnit {
 
        int getHierarchyChildCount();
 
+       HierarchyUnit getParent();
+
        HierarchyUnit getHierarchyChild(int i);
 
        int getHierarchyUnitType();
@@ -21,4 +24,6 @@ public interface HierarchyUnit {
        String getBasePath();
 
        List<? extends Role> getRoles(String filter, boolean deep);
+       
+//     Map<String,Object> getHierarchyProperties();
 }
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapNameUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapNameUtils.java
new file mode 100644 (file)
index 0000000..b98c8bf
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.StringJoiner;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+       public static String toRevertPath(String dn, String prefix) {
+               if (!dn.endsWith(prefix))
+                       throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+               String relativeName = dn.substring(0, dn.length() - prefix.length() - 1);
+               LdapName name = toLdapName(relativeName);
+               StringJoiner path = new StringJoiner("/");
+               for (int i = 0; i < name.size(); i++) {
+                       path.add(name.get(i));
+               }
+               return path.toString();
+       }
+
+       public static LdapName getParent(LdapName dn) {
+               try {
+                       LdapName parent = (LdapName) dn.clone();
+                       parent.remove(parent.size() - 1);
+                       return parent;
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+               }
+       }
+
+       public static LdapName toLdapName(String distinguishedName) {
+               try {
+                       return new LdapName(distinguishedName);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+               }
+       }
+
+       public static Rdn getLastRdn(LdapName dn) {
+               return dn.getRdn(dn.size() - 1);
+       }
+
+       public static String getLastRdnAsString(LdapName dn) {
+               return getLastRdn(dn).toString();
+       }
+
+       public static String getLastRdnValue(LdapName dn) {
+               return getLastRdn(dn).getValue().toString();
+       }
+
+       /** singleton */
+       private LdapNameUtils() {
+
+       }
+}
index 623ca2a929b7ad8c0b8e981576cfe9a52bb99e33..af75bf03e524c7ed4b4f6d143fe74a4c6036ed1c 100644 (file)
@@ -187,4 +187,11 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                // prepare not impacting
        }
 
+//     @Override
+//     public HierarchyUnit getHierarchyUnit(String path) {
+//             LdapName dn = LdapNameUtils.toLdapName(path);
+//             Attributes attrs = ldapConnection.getAttributes(dn);
+//             
+//     }
+
 }
index 5cf52b9a2f34038bdf299b65683456bf1ce31668..593416259c0fba213001d9085710169da8cb7c86 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.osgi.useradmin;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import javax.naming.directory.Attributes;
@@ -36,6 +37,11 @@ class LdifHierarchyUnit implements HierarchyUnit {
                return children.size();
        }
 
+       @Override
+       public HierarchyUnit getParent() {
+               return parent;
+       }
+
        @Override
        public HierarchyUnit getHierarchyChild(int i) {
                return children.get(i);
@@ -48,7 +54,7 @@ class LdifHierarchyUnit implements HierarchyUnit {
 
        @Override
        public String getHierarchyUnitName() {
-               String name = dn.getRdn(dn.size() - 1).getValue().toString();
+               String name = LdapNameUtils.getLastRdnAsString(dn);
                // TODO check ou, o, etc.
                return name;
        }
index 135645a1233a8e1940dfeb20015a81bfdb984057..c03465b7058b318afecd63c689677c846ade3b4a 100644 (file)
@@ -14,6 +14,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.StringJoiner;
 
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
@@ -156,7 +157,7 @@ class LdifUser implements DirectoryUser {
        byte[] sha1hash(char[] password) {
                byte[] hashedPassword = ("{SHA}"
                                + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
-                                               .getBytes(StandardCharsets.UTF_8);
+                               .getBytes(StandardCharsets.UTF_8);
                return hashedPassword;
        }
 
@@ -307,24 +308,25 @@ class LdifUser implements DirectoryUser {
                                }
                                if (attr.size() == 1)
                                        return value;
-                               if (!attr.getID().equals(LdapAttrs.objectClass.name()))
-                                       return value;
+//                             if (!attr.getID().equals(LdapAttrs.objectClass.name()))
+//                                     return value;
                                // special case for object class
                                NamingEnumeration<?> en = attr.getAll();
-                               Set<String> objectClasses = new HashSet<String>();
+                               StringJoiner values = new StringJoiner("\n");
+                               // Set<String> values = new HashSet<String>();
                                while (en.hasMore()) {
-                                       String objectClass = en.next().toString();
-                                       objectClasses.add(objectClass);
+                                       String v = en.next().toString();
+                                       values.add(v);
                                }
-
-                               if (objectClasses.contains(userAdmin.getUserObjectClass()))
-                                       return userAdmin.getUserObjectClass();
-                               else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
-                                       return userAdmin.getGroupObjectClass();
-                               else
-                                       return value;
+                               return values.toString();
+//                             if (objectClasses.contains(userAdmin.getUserObjectClass()))
+//                                     return userAdmin.getUserObjectClass();
+//                             else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
+//                                     return userAdmin.getGroupObjectClass();
+//                             else
+//                                     return value;
                        } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
+                               throw new IllegalStateException("Cannot get value for attribute " + key, e);
                        }
                }
 
index f9163d7e2af20dfd43caa759443241ee464b74c3..6d9305a176e29e5e9edab2e40ba93f0e5713a7a0 100644 (file)
@@ -308,6 +308,9 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                init();
        }
 
+       /*
+        * HIERARCHY
+        */
        @Override
        public int getHierarchyChildCount() {
                return rootHierarchyUnits.size();
@@ -318,8 +321,21 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                return rootHierarchyUnits.get(i);
        }
 
-       /*
-        * HIERARCHY
-        */
+       @Override
+       public HierarchyUnit getHierarchyUnit(String path) {
+               LdapName dn = LdapNameUtils.toLdapName(path);
+               return hierarchy.get(dn);
+       }
+
+       @Override
+       public HierarchyUnit getHierarchyUnit(Role role) {
+               LdapName dn = LdapNameUtils.toLdapName(role.getName());
+               // 2 levels
+               LdapName huDn = LdapNameUtils.getParent(LdapNameUtils.getParent(dn));
+               HierarchyUnit hierarchyUnit = hierarchy.get(huDn);
+               if (hierarchyUnit == null)
+                       throw new IllegalStateException("No hierarchy unit found for " + role);
+               return hierarchyUnit;
+       }
 
 }
index 5a69f1a169b00177738a65019f0f68d275749fbc..781b7855a34c08b973d49fd0be9273b7d07cddc4 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.osgi.useradmin;
 import java.util.Optional;
 
 import org.argeo.osgi.transaction.WorkControl;
+import org.osgi.service.useradmin.Role;
 
 /** Information about a user directory. */
 public interface UserDirectory extends HierarchyUnit {
@@ -31,7 +32,11 @@ public interface UserDirectory extends HierarchyUnit {
        String getGroupBase();
 
        Optional<String> getRealm();
-       
+
+       HierarchyUnit getHierarchyUnit(String path);
+
+       HierarchyUnit getHierarchyUnit(Role role);
+
        @Deprecated
        void setTransactionControl(WorkControl transactionControl);
 }