]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
Merge tag 'v2.3.17' into testing
[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.acr.xml.DomContentProvider;
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 if (provider instanceof DomContentProvider) {
141 ((DomContentProvider) provider).persist(s);
142 }
143 }
144 modifiedProviders.clear();
145 return s;
146 }
147 });
148 return edition.minimalCompletionStage();
149 }
150
151 @Override
152 public boolean isEditing() {
153 return edition != null && !edition.isDone();
154 }
155
156 @Override
157 public synchronized void notifyModification(ProvidedContent content) {
158 ContentProvider contentProvider = content.getProvider();
159 modifiedProviders.add(contentProvider);
160 }
161
162 @Override
163 public UUID getUuid() {
164 return uuid;
165 }
166
167 // @Override
168 public Content getSessionRunDir() {
169 if (sessionRunDir == null) {
170 String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString();
171 if (exists(runDirPath))
172 sessionRunDir = get(runDirPath);
173 else {
174 Content runDir = get(CmsContentRepository.RUN_BASE);
175 // TODO deal with no run dir available?
176 sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName());
177 }
178 }
179 return sessionRunDir;
180 }
181
182 /*
183 * SEARCH
184 */
185 @Override
186 public Stream<Content> search(Consumer<BasicSearch> search) {
187 BasicSearch s = new BasicSearch();
188 search.accept(s);
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
196 String relPath;
197 if (scopePath.startsWith(contentProvider.getKey())) {
198 relPath = scopePath.substring(contentProvider.getKey().length());
199 } else {
200 relPath = null;
201 }
202 SearchPartition searchPartition = new SearchPartition(s, relPath, contentProvider.getValue());
203 searchPartitions.put(contentProvider.getKey(), searchPartition);
204 }
205 }
206 return StreamSupport.stream(new SearchPartitionsSpliterator(searchPartitions), true);
207 }
208
209 class SearchPartition {
210 BasicSearch search;
211 String relPath;
212 ContentProvider contentProvider;
213
214 public SearchPartition(BasicSearch search, String relPath, ContentProvider contentProvider) {
215 super();
216 this.search = search;
217 this.relPath = relPath;
218 this.contentProvider = contentProvider;
219 }
220
221 public BasicSearch getSearch() {
222 return search;
223 }
224
225 public String getRelPath() {
226 return relPath;
227 }
228
229 public ContentProvider getContentProvider() {
230 return contentProvider;
231 }
232
233 }
234
235 class SearchPartitionsSpliterator implements Spliterator<Content> {
236 NavigableMap<String, SearchPartition> searchPartitions;
237
238 Spliterator<Content> currentSpliterator;
239
240 public SearchPartitionsSpliterator(NavigableMap<String, SearchPartition> searchPartitions) {
241 super();
242 this.searchPartitions = searchPartitions;
243 SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
244 currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
245 searchPartition.getSearch(), searchPartition.getRelPath());
246 }
247
248 @Override
249 public boolean tryAdvance(Consumer<? super Content> action) {
250 boolean remaining = currentSpliterator.tryAdvance(action);
251 if (remaining)
252 return true;
253 if (searchPartitions.isEmpty())
254 return false;
255 SearchPartition searchPartition = searchPartitions.pollFirstEntry().getValue();
256 currentSpliterator = searchPartition.getContentProvider().search(CmsContentSession.this,
257 searchPartition.getSearch(), searchPartition.getRelPath());
258 return true;
259 }
260
261 @Override
262 public Spliterator<Content> trySplit() {
263 if (searchPartitions.isEmpty()) {
264 return null;
265 } else if (searchPartitions.size() == 1) {
266 NavigableMap<String, SearchPartition> newSearchPartitions = new TreeMap<>(searchPartitions);
267 searchPartitions.clear();
268 return new SearchPartitionsSpliterator(newSearchPartitions);
269 } else {
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());
274 }
275 return new SearchPartitionsSpliterator(newSearchPartitions);
276 }
277 }
278
279 @Override
280 public long estimateSize() {
281 return Long.MAX_VALUE;
282 }
283
284 @Override
285 public int characteristics() {
286 return NONNULL;
287 }
288
289 }
290 }