Clarify ACR API
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / CmsContentSession.java
index 757dad143a7ec3fe59a39d26b7e83b5b9076bf09..04a6fea6cdf15ad231127f66b79c7153981289d1 100644 (file)
@@ -2,26 +2,35 @@ package org.argeo.cms.acr;
 
 import java.util.HashSet;
 import java.util.Locale;
+import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Set;
+import java.util.Spliterator;
+import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import javax.security.auth.Subject;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
-import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.search.BasicSearch;
+import org.argeo.api.acr.search.BasicSearch.Scope;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedRepository;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.api.uuid.UuidFactory;
-import org.argeo.cms.acr.xml.DomContentProvider;
+import org.argeo.api.uuid.UuidIdentified;
+import org.argeo.cms.CurrentUser;
 
 /** Implements {@link ProvidedSession}. */
-class CmsContentSession implements ProvidedSession {
+class CmsContentSession implements ProvidedSession, UuidIdentified {
        final private AbstractContentRepository contentRepository;
 
        private final UUID uuid;
@@ -61,30 +70,25 @@ class CmsContentSession implements ProvidedSession {
 
        @Override
        public Content get(String path) {
-               if (!path.startsWith(ContentUtils.ROOT_SLASH))
+               if (!path.startsWith(Content.ROOT_PATH))
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = extractRelativePath(mountPath, path);
+               String relativePath = CmsContent.relativize(mountPath, path);
                ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath);
                return content;
        }
 
        @Override
        public boolean exists(String path) {
+               if (!path.startsWith(Content.ROOT_PATH))
+                       throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = extractRelativePath(mountPath, path);
+               String relativePath = CmsContent.relativize(mountPath, path);
                return contentProvider.exists(this, relativePath);
        }
 
-       private String extractRelativePath(String mountPath, String path) {
-               String relativePath = path.substring(mountPath.length());
-               if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
-                       relativePath = relativePath.substring(1);
-               return relativePath;
-       }
-
        @Override
        public Subject getSubject() {
                return subject;
@@ -109,30 +113,15 @@ class CmsContentSession implements ProvidedSession {
         */
        @Override
        public Content getMountPoint(String path) {
-               String[] parent = ContentUtils.getParentPath(path);
+               String[] parent = CmsContent.getParentPath(path);
                ProvidedContent mountParent = (ProvidedContent) get(parent[0]);
 //                     Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
                return mountParent.getMountPoint(parent[1]);
        }
 
        /*
-        * NAMESPACE CONTEXT
+        * EDITION
         */
-
-//     @Override
-//     public String getNamespaceURI(String prefix) {
-//             return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix);
-////           return NamespaceUtils.getNamespaceURI((p) -> contentRepository.getTypesManager().getPrefixes().get(p), prefix);
-//     }
-//
-//     @Override
-//     public Iterator<String> getPrefixes(String namespaceURI) {
-//             return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI);
-////           return NamespaceUtils.getPrefixes((ns) -> contentRepository.getTypesManager().getPrefixes().entrySet().stream()
-////                           .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()),
-////                           namespaceURI);
-//     }
-
        @Override
        public CompletionStage<ContentSession> edit(Consumer<ContentSession> work) {
                edition = CompletableFuture.supplyAsync(() -> {
@@ -142,9 +131,10 @@ class CmsContentSession implements ProvidedSession {
                        synchronized (CmsContentSession.this) {
                                // TODO optimise
                                for (ContentProvider provider : modifiedProviders) {
-                                       if (provider instanceof DomContentProvider) {
-                                               ((DomContentProvider) provider).persist(s);
-                                       }
+                                       provider.persist(s);
+//                                     if (provider instanceof DomContentProvider) {
+//                                             ((DomContentProvider) provider).persist(s);
+//                                     }
                                }
                                modifiedProviders.clear();
                                return s;
@@ -165,11 +155,11 @@ class CmsContentSession implements ProvidedSession {
        }
 
        @Override
-       public UUID getUuid() {
+       public UUID uuid() {
                return uuid;
        }
 
-       @Override
+//     @Override
        public Content getSessionRunDir() {
                if (sessionRunDir == null) {
                        String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString();
@@ -178,30 +168,141 @@ class CmsContentSession implements ProvidedSession {
                        else {
                                Content runDir = get(CmsContentRepository.RUN_BASE);
                                // TODO deal with no run dir available?
-                               sessionRunDir = runDir.add(uuid.toString(), CrName.collection.qName());
+                               sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName());
                        }
                }
                return sessionRunDir;
        }
 
-//             @Override
-//             public String findNamespace(String prefix) {
-//                     return prefixes.get(prefix);
-//             }
-//
-//             @Override
-//             public Set<String> findPrefixes(String namespaceURI) {
-//                     Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
-//                                     .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
-//
-//                     return res;
-//             }
-//
-//             @Override
-//             public String findPrefix(String namespaceURI) {
-//                     if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
-//                             return CrName.CR_DEFAULT_PREFIX;
-//                     return ProvidedSession.super.findPrefix(namespaceURI);
-//             }
+       /*
+        * OBJECT METHODS
+        */
+
+       @Override
+       public boolean equals(Object o) {
+               return UuidIdentified.equals(this, o);
+       }
+
+       @Override
+       public int hashCode() {
+               return UuidIdentified.hashCode(this);
+       }
+
+       @Override
+       public String toString() {
+               return "Content Session " + uuid + " (" + CurrentUser.getUsername(subject) + ")";
+       }
+
+       /*
+        * SEARCH
+        */
+       @Override
+       public Stream<Content> search(Consumer<BasicSearch> search) {
+               BasicSearch s = new BasicSearch();
+               search.accept(s);
+               NavigableMap<String, SearchPartition> searchPartitions = new TreeMap<>();
+               for (Scope scope : s.getFrom()) {
+                       String scopePath = scope.getUri().getPath();
+                       NavigableMap<String, ContentProvider> contentProviders = contentRepository.getMountManager()
+                                       .findContentProviders(scopePath);
+                       for (Map.Entry<String, ContentProvider> contentProvider : contentProviders.entrySet()) {
+                               assert scopePath.startsWith(contentProvider.getKey())
+                                               : "scopePath=" + scopePath + ", contentProvider path=" + contentProvider.getKey();
+                               // TODO deal with depth
+                               String relPath;
+                               if (!scopePath.equals(contentProvider.getKey())) {
+                                       relPath = scopePath.substring(contentProvider.getKey().length() + 1, scopePath.length());
+                               } else {
+                                       relPath = null;
+                               }
+                               SearchPartition searchPartition = new SearchPartition(s, relPath, contentProvider.getValue());
+                               searchPartitions.put(contentProvider.getKey(), searchPartition);
+                       }
+               }
+               if (searchPartitions.isEmpty())
+                       return Stream.empty();
+               return StreamSupport.stream(new SearchPartitionsSpliterator(searchPartitions), true);
+       }
+
+       class SearchPartition {
+               BasicSearch search;
+               String relPath;
+               ContentProvider contentProvider;
+
+               public SearchPartition(BasicSearch search, String relPath, ContentProvider contentProvider) {
+                       super();
+                       this.search = search;
+                       this.relPath = relPath;
+                       this.contentProvider = contentProvider;
+               }
+
+               public BasicSearch getSearch() {
+                       return search;
+               }
+
+               public String getRelPath() {
+                       return relPath;
+               }
+
+               public ContentProvider getContentProvider() {
+                       return contentProvider;
+               }
+
+       }
+
+       class SearchPartitionsSpliterator implements Spliterator<Content> {
+               NavigableMap<String, SearchPartition> searchPartitions;
+
+               Spliterator<Content> currentSpliterator;
 
+               public SearchPartitionsSpliterator(NavigableMap<String, SearchPartition> searchPartitions) {
+                       super();
+                       this.searchPartitions = searchPartitions;
+                       SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
+                       currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
+                                       searchPartition.getSearch(), searchPartition.getRelPath());
+               }
+
+               @Override
+               public boolean tryAdvance(Consumer<? super Content> action) {
+                       boolean remaining = currentSpliterator.tryAdvance(action);
+                       if (remaining)
+                               return true;
+                       if (searchPartitions.isEmpty())
+                               return false;
+                       SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
+                       currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
+                                       searchPartition.getSearch(), searchPartition.getRelPath());
+                       return true;
+               }
+
+               @Override
+               public Spliterator<Content> trySplit() {
+                       if (searchPartitions.isEmpty()) {
+                               return null;
+                       } else if (searchPartitions.size() == 1) {
+                               NavigableMap<String, SearchPartition> newSearchPartitions = new TreeMap<>(searchPartitions);
+                               searchPartitions.clear();
+                               return new SearchPartitionsSpliterator(newSearchPartitions);
+                       } else {
+                               NavigableMap<String, SearchPartition> newSearchPartitions = new TreeMap<>();
+                               for (int i = 0; i < searchPartitions.size() / 2; i++) {
+                                       Map.Entry<String, SearchPartition> searchPartition = searchPartitions.pollLastEntry();
+                                       newSearchPartitions.put(searchPartition.getKey(), searchPartition.getValue());
+                               }
+                               return new SearchPartitionsSpliterator(newSearchPartitions);
+                       }
+               }
+
+               @Override
+               public long estimateSize() {
+                       return Long.MAX_VALUE;
+               }
+
+               @Override
+               public int characteristics() {
+                       return NONNULL;
+               }
+
+       }
 }
\ No newline at end of file