]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
Optimise get() method
[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(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);
79 return content;
80 }
81
82 @Override
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);
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 // TODO deal with depth
210 String relPath;
211 if (scopePath.startsWith(contentProvider.getKey())) {
212 relPath = scopePath.substring(contentProvider.getKey().length());
213 } else {
214 relPath = null;
215 }
216 SearchPartition searchPartition = new SearchPartition(s, relPath, contentProvider.getValue());
217 searchPartitions.put(contentProvider.getKey(), searchPartition);
218 }
219 }
220 return StreamSupport.stream(new SearchPartitionsSpliterator(searchPartitions), true);
221 }
222
223 class SearchPartition {
224 BasicSearch search;
225 String relPath;
226 ContentProvider contentProvider;
227
228 public SearchPartition(BasicSearch search, String relPath, ContentProvider contentProvider) {
229 super();
230 this.search = search;
231 this.relPath = relPath;
232 this.contentProvider = contentProvider;
233 }
234
235 public BasicSearch getSearch() {
236 return search;
237 }
238
239 public String getRelPath() {
240 return relPath;
241 }
242
243 public ContentProvider getContentProvider() {
244 return contentProvider;
245 }
246
247 }
248
249 class SearchPartitionsSpliterator implements Spliterator<Content> {
250 NavigableMap<String, SearchPartition> searchPartitions;
251
252 Spliterator<Content> currentSpliterator;
253
254 public SearchPartitionsSpliterator(NavigableMap<String, SearchPartition> searchPartitions) {
255 super();
256 this.searchPartitions = searchPartitions;
257 SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
258 currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
259 searchPartition.getSearch(), searchPartition.getRelPath());
260 }
261
262 @Override
263 public boolean tryAdvance(Consumer<? super Content> action) {
264 boolean remaining = currentSpliterator.tryAdvance(action);
265 if (remaining)
266 return true;
267 if (searchPartitions.isEmpty())
268 return false;
269 SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
270 currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
271 searchPartition.getSearch(), searchPartition.getRelPath());
272 return true;
273 }
274
275 @Override
276 public Spliterator<Content> trySplit() {
277 if (searchPartitions.isEmpty()) {
278 return null;
279 } else if (searchPartitions.size() == 1) {
280 NavigableMap<String, SearchPartition> newSearchPartitions = new TreeMap<>(searchPartitions);
281 searchPartitions.clear();
282 return new SearchPartitionsSpliterator(newSearchPartitions);
283 } else {
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());
288 }
289 return new SearchPartitionsSpliterator(newSearchPartitions);
290 }
291 }
292
293 @Override
294 public long estimateSize() {
295 return Long.MAX_VALUE;
296 }
297
298 @Override
299 public int characteristics() {
300 return NONNULL;
301 }
302
303 }
304 }