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