1 package org
.argeo
.cms
.acr
;
3 import java
.util
.HashSet
;
4 import java
.util
.Locale
;
6 import java
.util
.NavigableMap
;
8 import java
.util
.Spliterator
;
9 import java
.util
.TreeMap
;
10 import java
.util
.UUID
;
11 import java
.util
.concurrent
.CompletableFuture
;
12 import java
.util
.concurrent
.CompletionStage
;
13 import java
.util
.function
.Consumer
;
14 import java
.util
.stream
.Stream
;
15 import java
.util
.stream
.StreamSupport
;
17 import javax
.security
.auth
.Subject
;
19 import org
.argeo
.api
.acr
.Content
;
20 import org
.argeo
.api
.acr
.ContentSession
;
21 import org
.argeo
.api
.acr
.DName
;
22 import org
.argeo
.api
.acr
.search
.BasicSearch
;
23 import org
.argeo
.api
.acr
.search
.BasicSearch
.Scope
;
24 import org
.argeo
.api
.acr
.spi
.ContentProvider
;
25 import org
.argeo
.api
.acr
.spi
.ProvidedContent
;
26 import org
.argeo
.api
.acr
.spi
.ProvidedRepository
;
27 import org
.argeo
.api
.acr
.spi
.ProvidedSession
;
28 import org
.argeo
.api
.uuid
.UuidFactory
;
29 import org
.argeo
.api
.uuid
.UuidIdentified
;
30 import org
.argeo
.cms
.CurrentUser
;
32 /** Implements {@link ProvidedSession}. */
33 class CmsContentSession
implements ProvidedSession
, UuidIdentified
{
34 final private AbstractContentRepository contentRepository
;
36 private final UUID uuid
;
37 private Subject subject
;
38 private Locale locale
;
40 private UuidFactory uuidFactory
;
42 private CompletableFuture
<ProvidedSession
> closed
= new CompletableFuture
<>();
44 private CompletableFuture
<ContentSession
> edition
;
46 private Set
<ContentProvider
> modifiedProviders
= new HashSet
<>();
48 private Content sessionRunDir
;
50 public CmsContentSession(AbstractContentRepository contentRepository
, UUID uuid
, Subject subject
, Locale locale
,
51 UuidFactory uuidFactory
) {
52 this.contentRepository
= contentRepository
;
53 this.subject
= subject
;
56 this.uuidFactory
= uuidFactory
;
60 closed
.complete(this);
62 if (sessionRunDir
!= null)
63 sessionRunDir
.remove();
67 public CompletionStage
<ProvidedSession
> onClose() {
68 return closed
.minimalCompletionStage();
72 public Content
get(String path
) {
73 if (!path
.startsWith(Content
.ROOT_PATH
))
74 throw new IllegalArgumentException(path
+ " is not an absolute path");
75 ContentProvider contentProvider
= contentRepository
.getMountManager().findContentProvider(path
);
76 String mountPath
= contentProvider
.getMountPath();
77 String relativePath
= ContentUtils
.relativize(mountPath
, path
);
78 ProvidedContent content
= contentProvider
.get(CmsContentSession
.this, relativePath
);
83 public boolean exists(String path
) {
84 if (!path
.startsWith(Content
.ROOT_PATH
))
85 throw new IllegalArgumentException(path
+ " is not an absolute path");
86 ContentProvider contentProvider
= contentRepository
.getMountManager().findContentProvider(path
);
87 String mountPath
= contentProvider
.getMountPath();
88 String relativePath
= ContentUtils
.relativize(mountPath
, path
);
89 return contentProvider
.exists(this, relativePath
);
93 public Subject
getSubject() {
98 public Locale
getLocale() {
103 public ProvidedRepository
getRepository() {
104 return contentRepository
;
107 public UuidFactory
getUuidFactory() {
115 public Content
getMountPoint(String path
) {
116 String
[] parent
= ContentUtils
.getParentPath(path
);
117 ProvidedContent mountParent
= (ProvidedContent
) get(parent
[0]);
118 // Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
119 return mountParent
.getMountPoint(parent
[1]);
126 public CompletionStage
<ContentSession
> edit(Consumer
<ContentSession
> work
) {
127 edition
= CompletableFuture
.supplyAsync(() -> {
130 }).thenApply((s
) -> {
131 synchronized (CmsContentSession
.this) {
133 for (ContentProvider provider
: modifiedProviders
) {
135 // if (provider instanceof DomContentProvider) {
136 // ((DomContentProvider) provider).persist(s);
139 modifiedProviders
.clear();
143 return edition
.minimalCompletionStage();
147 public boolean isEditing() {
148 return edition
!= null && !edition
.isDone();
152 public synchronized void notifyModification(ProvidedContent content
) {
153 ContentProvider contentProvider
= content
.getProvider();
154 modifiedProviders
.add(contentProvider
);
163 public Content
getSessionRunDir() {
164 if (sessionRunDir
== null) {
165 String runDirPath
= CmsContentRepository
.RUN_BASE
+ '/' + uuid
.toString();
166 if (exists(runDirPath
))
167 sessionRunDir
= get(runDirPath
);
169 Content runDir
= get(CmsContentRepository
.RUN_BASE
);
170 // TODO deal with no run dir available?
171 sessionRunDir
= runDir
.add(uuid
.toString(), DName
.collection
.qName());
174 return sessionRunDir
;
182 public boolean equals(Object o
) {
183 return UuidIdentified
.equals(this, o
);
187 public int hashCode() {
188 return UuidIdentified
.hashCode(this);
192 public String
toString() {
193 return "Content Session " + uuid
+ " (" + CurrentUser
.getUsername(subject
) + ")";
200 public Stream
<Content
> search(Consumer
<BasicSearch
> search
) {
201 BasicSearch s
= new BasicSearch();
203 NavigableMap
<String
, SearchPartition
> searchPartitions
= new TreeMap
<>();
204 for (Scope scope
: s
.getFrom()) {
205 String scopePath
= scope
.getUri().getPath();
206 NavigableMap
<String
, ContentProvider
> contentProviders
= contentRepository
.getMountManager()
207 .findContentProviders(scopePath
);
208 for (Map
.Entry
<String
, ContentProvider
> contentProvider
: contentProviders
.entrySet()) {
209 assert scopePath
.startsWith(contentProvider
.getKey())
210 : "scopePath=" + scopePath
+ ", contentProvider path=" + contentProvider
.getKey();
211 // TODO deal with depth
213 if (!scopePath
.equals(contentProvider
.getKey())) {
214 relPath
= scopePath
.substring(contentProvider
.getKey().length() + 1, scopePath
.length());
218 SearchPartition searchPartition
= new SearchPartition(s
, relPath
, contentProvider
.getValue());
219 searchPartitions
.put(contentProvider
.getKey(), searchPartition
);
222 if (searchPartitions
.isEmpty())
223 return Stream
.empty();
224 return StreamSupport
.stream(new SearchPartitionsSpliterator(searchPartitions
), true);
227 class SearchPartition
{
230 ContentProvider contentProvider
;
232 public SearchPartition(BasicSearch search
, String relPath
, ContentProvider contentProvider
) {
234 this.search
= search
;
235 this.relPath
= relPath
;
236 this.contentProvider
= contentProvider
;
239 public BasicSearch
getSearch() {
243 public String
getRelPath() {
247 public ContentProvider
getContentProvider() {
248 return contentProvider
;
253 class SearchPartitionsSpliterator
implements Spliterator
<Content
> {
254 NavigableMap
<String
, SearchPartition
> searchPartitions
;
256 Spliterator
<Content
> currentSpliterator
;
258 public SearchPartitionsSpliterator(NavigableMap
<String
, SearchPartition
> searchPartitions
) {
260 this.searchPartitions
= searchPartitions
;
261 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
262 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
263 searchPartition
.getSearch(), searchPartition
.getRelPath());
267 public boolean tryAdvance(Consumer
<?
super Content
> action
) {
268 boolean remaining
= currentSpliterator
.tryAdvance(action
);
271 if (searchPartitions
.isEmpty())
273 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
274 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
275 searchPartition
.getSearch(), searchPartition
.getRelPath());
280 public Spliterator
<Content
> trySplit() {
281 if (searchPartitions
.isEmpty()) {
283 } else if (searchPartitions
.size() == 1) {
284 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>(searchPartitions
);
285 searchPartitions
.clear();
286 return new SearchPartitionsSpliterator(newSearchPartitions
);
288 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>();
289 for (int i
= 0; i
< searchPartitions
.size() / 2; i
++) {
290 Map
.Entry
<String
, SearchPartition
> searchPartition
= searchPartitions
.pollLastEntry();
291 newSearchPartitions
.put(searchPartition
.getKey(), searchPartition
.getValue());
293 return new SearchPartitionsSpliterator(newSearchPartitions
);
298 public long estimateSize() {
299 return Long
.MAX_VALUE
;
303 public int characteristics() {