]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
8d475fd204853737eae48b94629c04fcdf858ea2
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / CmsContentSession.java
1 package org.argeo.cms.acr;
2
3 import java.util.HashSet;
4 import java.util.Locale;
5 import java.util.Map;
6 import java.util.NavigableMap;
7 import java.util.Set;
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;
16
17 import javax.security.auth.Subject;
18
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;
30
31 /** Implements {@link ProvidedSession}. */
32 class CmsContentSession implements ProvidedSession {
33 final private AbstractContentRepository contentRepository;
34
35 private final UUID uuid;
36 private Subject subject;
37 private Locale locale;
38
39 private UuidFactory uuidFactory;
40
41 private CompletableFuture<ProvidedSession> closed = new CompletableFuture<>();
42
43 private CompletableFuture<ContentSession> edition;
44
45 private Set<ContentProvider> modifiedProviders = new HashSet<>();
46
47 private Content sessionRunDir;
48
49 public CmsContentSession(AbstractContentRepository contentRepository, UUID uuid, Subject subject, Locale locale,
50 UuidFactory uuidFactory) {
51 this.contentRepository = contentRepository;
52 this.subject = subject;
53 this.locale = locale;
54 this.uuid = uuid;
55 this.uuidFactory = uuidFactory;
56 }
57
58 public void close() {
59 closed.complete(this);
60
61 if (sessionRunDir != null)
62 sessionRunDir.remove();
63 }
64
65 @Override
66 public CompletionStage<ProvidedSession> onClose() {
67 return closed.minimalCompletionStage();
68 }
69
70 @Override
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);
78 return content;
79 }
80
81 @Override
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);
89 }
90
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);
95 return relativePath;
96 }
97
98 @Override
99 public Subject getSubject() {
100 return subject;
101 }
102
103 @Override
104 public Locale getLocale() {
105 return locale;
106 }
107
108 @Override
109 public ProvidedRepository getRepository() {
110 return contentRepository;
111 }
112
113 public UuidFactory getUuidFactory() {
114 return uuidFactory;
115 }
116
117 /*
118 * MOUNT MANAGEMENT
119 */
120 @Override
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]);
126 }
127
128 /*
129 * EDITION
130 */
131 @Override
132 public CompletionStage<ContentSession> edit(Consumer<ContentSession> work) {
133 edition = CompletableFuture.supplyAsync(() -> {
134 work.accept(this);
135 return this;
136 }).thenApply((s) -> {
137 synchronized (CmsContentSession.this) {
138 // TODO optimise
139 for (ContentProvider provider : modifiedProviders) {
140 provider.persist(s);
141 // if (provider instanceof DomContentProvider) {
142 // ((DomContentProvider) provider).persist(s);
143 // }
144 }
145 modifiedProviders.clear();
146 return s;
147 }
148 });
149 return edition.minimalCompletionStage();
150 }
151
152 @Override
153 public boolean isEditing() {
154 return edition != null && !edition.isDone();
155 }
156
157 @Override
158 public synchronized void notifyModification(ProvidedContent content) {
159 ContentProvider contentProvider = content.getProvider();
160 modifiedProviders.add(contentProvider);
161 }
162
163 @Override
164 public UUID getUuid() {
165 return uuid;
166 }
167
168 // @Override
169 public Content getSessionRunDir() {
170 if (sessionRunDir == null) {
171 String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString();
172 if (exists(runDirPath))
173 sessionRunDir = get(runDirPath);
174 else {
175 Content runDir = get(CmsContentRepository.RUN_BASE);
176 // TODO deal with no run dir available?
177 sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName());
178 }
179 }
180 return sessionRunDir;
181 }
182
183 @Override
184 public boolean equals(Object o) {
185 if (o instanceof CmsContentSession session)
186 return uuid.equals(session.uuid);
187 return false;
188 }
189
190 @Override
191 public String toString() {
192 return "Content Session " + uuid + " (" + CurrentUser.getUsername(subject) + ")";
193 }
194
195 /*
196 * SEARCH
197 */
198 @Override
199 public Stream<Content> search(Consumer<BasicSearch> search) {
200 BasicSearch s = new BasicSearch();
201 search.accept(s);
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
209 String relPath;
210 if (scopePath.startsWith(contentProvider.getKey())) {
211 relPath = scopePath.substring(contentProvider.getKey().length());
212 } else {
213 relPath = null;
214 }
215 SearchPartition searchPartition = new SearchPartition(s, relPath, contentProvider.getValue());
216 searchPartitions.put(contentProvider.getKey(), searchPartition);
217 }
218 }
219 return StreamSupport.stream(new SearchPartitionsSpliterator(searchPartitions), true);
220 }
221
222 class SearchPartition {
223 BasicSearch search;
224 String relPath;
225 ContentProvider contentProvider;
226
227 public SearchPartition(BasicSearch search, String relPath, ContentProvider contentProvider) {
228 super();
229 this.search = search;
230 this.relPath = relPath;
231 this.contentProvider = contentProvider;
232 }
233
234 public BasicSearch getSearch() {
235 return search;
236 }
237
238 public String getRelPath() {
239 return relPath;
240 }
241
242 public ContentProvider getContentProvider() {
243 return contentProvider;
244 }
245
246 }
247
248 class SearchPartitionsSpliterator implements Spliterator<Content> {
249 NavigableMap<String, SearchPartition> searchPartitions;
250
251 Spliterator<Content> currentSpliterator;
252
253 public SearchPartitionsSpliterator(NavigableMap<String, SearchPartition> searchPartitions) {
254 super();
255 this.searchPartitions = searchPartitions;
256 SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
257 currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
258 searchPartition.getSearch(), searchPartition.getRelPath());
259 }
260
261 @Override
262 public boolean tryAdvance(Consumer<? super Content> action) {
263 boolean remaining = currentSpliterator.tryAdvance(action);
264 if (remaining)
265 return true;
266 if (searchPartitions.isEmpty())
267 return false;
268 SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
269 currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
270 searchPartition.getSearch(), searchPartition.getRelPath());
271 return true;
272 }
273
274 @Override
275 public Spliterator<Content> trySplit() {
276 if (searchPartitions.isEmpty()) {
277 return null;
278 } else if (searchPartitions.size() == 1) {
279 NavigableMap<String, SearchPartition> newSearchPartitions = new TreeMap<>(searchPartitions);
280 searchPartitions.clear();
281 return new SearchPartitionsSpliterator(newSearchPartitions);
282 } else {
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());
287 }
288 return new SearchPartitionsSpliterator(newSearchPartitions);
289 }
290 }
291
292 @Override
293 public long estimateSize() {
294 return Long.MAX_VALUE;
295 }
296
297 @Override
298 public int characteristics() {
299 return NONNULL;
300 }
301
302 }
303 }