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
.acr
.xml
.DomContentProvider
;
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
) {
140 if (provider
instanceof DomContentProvider
) {
141 ((DomContentProvider
) provider
).persist(s
);
144 modifiedProviders
.clear();
148 return edition
.minimalCompletionStage();
152 public boolean isEditing() {
153 return edition
!= null && !edition
.isDone();
157 public synchronized void notifyModification(ProvidedContent content
) {
158 ContentProvider contentProvider
= content
.getProvider();
159 modifiedProviders
.add(contentProvider
);
163 public UUID
getUuid() {
168 public Content
getSessionRunDir() {
169 if (sessionRunDir
== null) {
170 String runDirPath
= CmsContentRepository
.RUN_BASE
+ '/' + uuid
.toString();
171 if (exists(runDirPath
))
172 sessionRunDir
= get(runDirPath
);
174 Content runDir
= get(CmsContentRepository
.RUN_BASE
);
175 // TODO deal with no run dir available?
176 sessionRunDir
= runDir
.add(uuid
.toString(), DName
.collection
.qName());
179 return sessionRunDir
;
186 public Stream
<Content
> search(Consumer
<BasicSearch
> search
) {
187 BasicSearch s
= new BasicSearch();
189 NavigableMap
<String
, SearchPartition
> searchPartitions
= new TreeMap
<>();
190 for (Scope scope
: s
.getFrom()) {
191 String scopePath
= scope
.getUri().getPath();
192 NavigableMap
<String
, ContentProvider
> contentProviders
= contentRepository
.getMountManager()
193 .findContentProviders(scopePath
);
194 for (Map
.Entry
<String
, ContentProvider
> contentProvider
: contentProviders
.entrySet()) {
195 // TODO deal with depth
197 if (scopePath
.startsWith(contentProvider
.getKey())) {
198 relPath
= scopePath
.substring(contentProvider
.getKey().length());
202 SearchPartition searchPartition
= new SearchPartition(s
, relPath
, contentProvider
.getValue());
203 searchPartitions
.put(contentProvider
.getKey(), searchPartition
);
206 return StreamSupport
.stream(new SearchPartitionsSpliterator(searchPartitions
), true);
209 class SearchPartition
{
212 ContentProvider contentProvider
;
214 public SearchPartition(BasicSearch search
, String relPath
, ContentProvider contentProvider
) {
216 this.search
= search
;
217 this.relPath
= relPath
;
218 this.contentProvider
= contentProvider
;
221 public BasicSearch
getSearch() {
225 public String
getRelPath() {
229 public ContentProvider
getContentProvider() {
230 return contentProvider
;
235 class SearchPartitionsSpliterator
implements Spliterator
<Content
> {
236 NavigableMap
<String
, SearchPartition
> searchPartitions
;
238 Spliterator
<Content
> currentSpliterator
;
240 public SearchPartitionsSpliterator(NavigableMap
<String
, SearchPartition
> searchPartitions
) {
242 this.searchPartitions
= searchPartitions
;
243 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
244 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
245 searchPartition
.getSearch(), searchPartition
.getRelPath());
249 public boolean tryAdvance(Consumer
<?
super Content
> action
) {
250 boolean remaining
= currentSpliterator
.tryAdvance(action
);
253 if (searchPartitions
.isEmpty())
255 SearchPartition searchPartition
= searchPartitions
.pollFirstEntry().getValue();
256 currentSpliterator
= searchPartition
.getContentProvider().search(CmsContentSession
.this,
257 searchPartition
.getSearch(), searchPartition
.getRelPath());
262 public Spliterator
<Content
> trySplit() {
263 if (searchPartitions
.isEmpty()) {
265 } else if (searchPartitions
.size() == 1) {
266 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>(searchPartitions
);
267 searchPartitions
.clear();
268 return new SearchPartitionsSpliterator(newSearchPartitions
);
270 NavigableMap
<String
, SearchPartition
> newSearchPartitions
= new TreeMap
<>();
271 for (int i
= 0; i
< searchPartitions
.size() / 2; i
++) {
272 Map
.Entry
<String
, SearchPartition
> searchPartition
= searchPartitions
.pollLastEntry();
273 newSearchPartitions
.put(searchPartition
.getKey(), searchPartition
.getValue());
275 return new SearchPartitionsSpliterator(newSearchPartitions
);
280 public long estimateSize() {
281 return Long
.MAX_VALUE
;
285 public int characteristics() {