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
.cms
.CurrentUser
;
31 /** Implements {@link ProvidedSession}. */
32 class CmsContentSession
implements ProvidedSession
{
33 final private AbstractContentRepository contentRepository
;
35 private final UUID uuid
;
36 private Subject subject
;
37 private Locale locale
;
39 private UuidFactory uuidFactory
;
41 private CompletableFuture
<ProvidedSession
> closed
= new CompletableFuture
<>();
43 private CompletableFuture
<ContentSession
> edition
;
45 private Set
<ContentProvider
> modifiedProviders
= new HashSet
<>();
47 private Content sessionRunDir
;
49 public CmsContentSession(AbstractContentRepository contentRepository
, UUID uuid
, Subject subject
, Locale locale
,
50 UuidFactory uuidFactory
) {
51 this.contentRepository
= contentRepository
;
52 this.subject
= subject
;
55 this.uuidFactory
= uuidFactory
;
59 closed
.complete(this);
61 if (sessionRunDir
!= null)
62 sessionRunDir
.remove();
66 public CompletionStage
<ProvidedSession
> onClose() {
67 return closed
.minimalCompletionStage();
71 public Content
get(String path
) {
72 if (!path
.startsWith(ContentUtils
.ROOT_SLASH
))
73 throw new IllegalArgumentException(path
+ " is not an absolute path");
74 ContentProvider contentProvider
= contentRepository
.getMountManager().findContentProvider(path
);
75 String mountPath
= contentProvider
.getMountPath();
76 String relativePath
= extractRelativePath(mountPath
, path
);
77 ProvidedContent content
= contentProvider
.get(CmsContentSession
.this, relativePath
);
82 public boolean exists(String path
) {
83 if (!path
.startsWith(ContentUtils
.ROOT_SLASH
))
84 throw new IllegalArgumentException(path
+ " is not an absolute path");
85 ContentProvider contentProvider
= contentRepository
.getMountManager().findContentProvider(path
);
86 String mountPath
= contentProvider
.getMountPath();
87 String relativePath
= extractRelativePath(mountPath
, path
);
88 return contentProvider
.exists(this, relativePath
);
91 private String
extractRelativePath(String mountPath
, String path
) {
92 String relativePath
= path
.substring(mountPath
.length());
93 if (relativePath
.length() > 0 && relativePath
.charAt(0) == '/')
94 relativePath
= relativePath
.substring(1);
99 public Subject
getSubject() {
104 public Locale
getLocale() {
109 public ProvidedRepository
getRepository() {
110 return contentRepository
;
113 public UuidFactory
getUuidFactory() {
121 public Content
getMountPoint(String path
) {
122 String
[] parent
= ContentUtils
.getParentPath(path
);
123 ProvidedContent mountParent
= (ProvidedContent
) get(parent
[0]);
124 // Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
125 return mountParent
.getMountPoint(parent
[1]);
132 public CompletionStage
<ContentSession
> edit(Consumer
<ContentSession
> work
) {
133 edition
= CompletableFuture
.supplyAsync(() -> {
136 }).thenApply((s
) -> {
137 synchronized (CmsContentSession
.this) {
139 for (ContentProvider provider
: modifiedProviders
) {
141 // if (provider instanceof DomContentProvider) {
142 // ((DomContentProvider) provider).persist(s);
145 modifiedProviders
.clear();
149 return edition
.minimalCompletionStage();
153 public boolean isEditing() {
154 return edition
!= null && !edition
.isDone();
158 public synchronized void notifyModification(ProvidedContent content
) {
159 ContentProvider contentProvider
= content
.getProvider();
160 modifiedProviders
.add(contentProvider
);
164 public UUID
getUuid() {
169 public Content
getSessionRunDir() {
170 if (sessionRunDir
== null) {
171 String runDirPath
= CmsContentRepository
.RUN_BASE
+ '/' + uuid
.toString();
172 if (exists(runDirPath
))
173 sessionRunDir
= get(runDirPath
);
175 Content runDir
= get(CmsContentRepository
.RUN_BASE
);
176 // TODO deal with no run dir available?
177 sessionRunDir
= runDir
.add(uuid
.toString(), DName
.collection
.qName());
180 return sessionRunDir
;
184 public boolean equals(Object o
) {
185 if (o
instanceof CmsContentSession session
)
186 return uuid
.equals(session
.uuid
);
191 public String
toString() {
192 return "Content Session " + uuid
+ " (" + CurrentUser
.getUsername(subject
) + ")";
199 public Stream
<Content
> search(Consumer
<BasicSearch
> search
) {
200 BasicSearch s
= new BasicSearch();
202 NavigableMap
<String
, SearchPartition
> searchPartitions
= new TreeMap
<>();
203 for (Scope scope
: s
.getFrom()) {
204 String scopePath
= scope
.getUri().getPath();
205 NavigableMap
<String
, ContentProvider
> contentProviders
= contentRepository
.getMountManager()
206 .findContentProviders(scopePath
);
207 for (Map
.Entry
<String
, ContentProvider
> contentProvider
: contentProviders
.entrySet()) {
208 // TODO deal with depth
210 if (scopePath
.startsWith(contentProvider
.getKey())) {
211 relPath
= scopePath
.substring(contentProvider
.getKey().length());
215 SearchPartition searchPartition
= new SearchPartition(s
, relPath
, contentProvider
.getValue());
216 searchPartitions
.put(contentProvider
.getKey(), searchPartition
);
219 return StreamSupport
.stream(new SearchPartitionsSpliterator(searchPartitions
), true);
222 class SearchPartition
{
225 ContentProvider contentProvider
;
227 public SearchPartition(BasicSearch search
, String relPath
, ContentProvider contentProvider
) {
229 this.search
= search
;
230 this.relPath
= relPath
;
231 this.contentProvider
= contentProvider
;
234 public BasicSearch
getSearch() {
238 public String
getRelPath() {
242 public ContentProvider
getContentProvider() {
243 return contentProvider
;
248 class SearchPartitionsSpliterator
implements Spliterator
<Content
> {
249 NavigableMap
<String
, SearchPartition
> searchPartitions
;
251 Spliterator
<Content
> currentSpliterator
;
253 public SearchPartitionsSpliterator(NavigableMap
<String
, SearchPartition
> searchPartitions
) {
255 this.searchPartitions
= searchPartitions
;
256 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
257 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
258 searchPartition
.getSearch(), searchPartition
.getRelPath());
262 public boolean tryAdvance(Consumer
<?
super Content
> action
) {
263 boolean remaining
= currentSpliterator
.tryAdvance(action
);
266 if (searchPartitions
.isEmpty())
268 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
269 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
270 searchPartition
.getSearch(), searchPartition
.getRelPath());
275 public Spliterator
<Content
> trySplit() {
276 if (searchPartitions
.isEmpty()) {
278 } else if (searchPartitions
.size() == 1) {
279 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>(searchPartitions
);
280 searchPartitions
.clear();
281 return new SearchPartitionsSpliterator(newSearchPartitions
);
283 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>();
284 for (int i
= 0; i
< searchPartitions
.size() / 2; i
++) {
285 Map
.Entry
<String
, SearchPartition
> searchPartition
= searchPartitions
.pollLastEntry();
286 newSearchPartitions
.put(searchPartition
.getKey(), searchPartition
.getValue());
288 return new SearchPartitionsSpliterator(newSearchPartitions
);
293 public long estimateSize() {
294 return Long
.MAX_VALUE
;
298 public int characteristics() {