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(ContentUtils
.ROOT_SLASH
))
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(ContentUtils
.ROOT_SLASH
))
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 // TODO deal with depth
211 if (scopePath
.startsWith(contentProvider
.getKey())) {
212 relPath
= scopePath
.substring(contentProvider
.getKey().length());
216 SearchPartition searchPartition
= new SearchPartition(s
, relPath
, contentProvider
.getValue());
217 searchPartitions
.put(contentProvider
.getKey(), searchPartition
);
220 return StreamSupport
.stream(new SearchPartitionsSpliterator(searchPartitions
), true);
223 class SearchPartition
{
226 ContentProvider contentProvider
;
228 public SearchPartition(BasicSearch search
, String relPath
, ContentProvider contentProvider
) {
230 this.search
= search
;
231 this.relPath
= relPath
;
232 this.contentProvider
= contentProvider
;
235 public BasicSearch
getSearch() {
239 public String
getRelPath() {
243 public ContentProvider
getContentProvider() {
244 return contentProvider
;
249 class SearchPartitionsSpliterator
implements Spliterator
<Content
> {
250 NavigableMap
<String
, SearchPartition
> searchPartitions
;
252 Spliterator
<Content
> currentSpliterator
;
254 public SearchPartitionsSpliterator(NavigableMap
<String
, SearchPartition
> searchPartitions
) {
256 this.searchPartitions
= searchPartitions
;
257 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
258 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
259 searchPartition
.getSearch(), searchPartition
.getRelPath());
263 public boolean tryAdvance(Consumer
<?
super Content
> action
) {
264 boolean remaining
= currentSpliterator
.tryAdvance(action
);
267 if (searchPartitions
.isEmpty())
269 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
270 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
271 searchPartition
.getSearch(), searchPartition
.getRelPath());
276 public Spliterator
<Content
> trySplit() {
277 if (searchPartitions
.isEmpty()) {
279 } else if (searchPartitions
.size() == 1) {
280 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>(searchPartitions
);
281 searchPartitions
.clear();
282 return new SearchPartitionsSpliterator(newSearchPartitions
);
284 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>();
285 for (int i
= 0; i
< searchPartitions
.size() / 2; i
++) {
286 Map
.Entry
<String
, SearchPartition
> searchPartition
= searchPartitions
.pollLastEntry();
287 newSearchPartitions
.put(searchPartition
.getKey(), searchPartition
.getValue());
289 return new SearchPartitionsSpliterator(newSearchPartitions
);
294 public long estimateSize() {
295 return Long
.MAX_VALUE
;
299 public int characteristics() {