]> git.argeo.org Git - gpl/argeo-jcr.git/blob - org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java
Optimisation and caching
[gpl/argeo-jcr.git] / org.argeo.cms.jcr / src / org / argeo / cms / jcr / acr / JcrContentProvider.java
1 package org.argeo.cms.jcr.acr;
2
3 import java.util.Arrays;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.Iterator;
7 import java.util.Map;
8 import java.util.Objects;
9 import java.util.Spliterator;
10 import java.util.function.Consumer;
11 import java.util.function.Function;
12
13 import javax.jcr.Node;
14 import javax.jcr.NodeIterator;
15 import javax.jcr.Repository;
16 import javax.jcr.RepositoryException;
17 import javax.jcr.Session;
18 import javax.jcr.query.Query;
19 import javax.jcr.query.QueryResult;
20 import javax.xml.namespace.NamespaceContext;
21
22 import org.argeo.api.acr.Content;
23 import org.argeo.api.acr.search.BasicSearch;
24 import org.argeo.api.acr.spi.ContentProvider;
25 import org.argeo.api.acr.spi.ProvidedContent;
26 import org.argeo.api.acr.spi.ProvidedSession;
27 import org.argeo.api.cms.CmsConstants;
28 import org.argeo.cms.acr.ContentUtils;
29 import org.argeo.cms.jcr.CmsJcrUtils;
30 import org.argeo.jcr.JcrException;
31 import org.argeo.jcr.JcrUtils;
32
33 /** A JCR workspace accessed as an {@link ContentProvider}. */
34 public class JcrContentProvider implements ContentProvider, NamespaceContext {
35
36 private Repository jcrRepository;
37 private Session adminSession;
38
39 private String mountPath;
40
41 // cache
42 private String jcrWorkspace;
43
44 private Map<ProvidedSession, JcrSessionAdapter> sessionAdapters = Collections.synchronizedMap(new HashMap<>());
45
46 public void start(Map<String, String> properties) {
47 mountPath = properties.get(CmsConstants.ACR_MOUNT_PATH);
48 if ("/".equals(mountPath))
49 throw new IllegalArgumentException("JCR content provider cannot be root /");
50 Objects.requireNonNull(mountPath);
51 jcrWorkspace = ContentUtils.getParentPath(mountPath)[1];
52 adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, jcrWorkspace);
53 }
54
55 public void stop() {
56 if (adminSession.isLive())
57 JcrUtils.logoutQuietly(adminSession);
58 }
59
60 public void setJcrRepository(Repository jcrRepository) {
61 this.jcrRepository = jcrRepository;
62 }
63
64 @Override
65 public String getMountPath() {
66 return mountPath;
67 }
68
69 /*
70 * READ
71 */
72
73 @Override
74 public ProvidedContent get(ProvidedSession contentSession, String relativePath) {
75 return new JcrContent(contentSession, this, jcrWorkspace, toJcrPath(relativePath));
76 }
77
78 @Override
79 public boolean exists(ProvidedSession contentSession, String relativePath) {
80 String jcrPath = ContentUtils.SLASH + relativePath;
81 return new JcrContent(contentSession, this, jcrWorkspace, jcrPath).exists();
82 }
83
84 protected JcrSessionAdapter getJcrSessionAdapter(ProvidedSession contentSession) {
85 JcrSessionAdapter sessionAdapter = sessionAdapters.get(contentSession);
86 if (sessionAdapter == null) {
87 final JcrSessionAdapter newSessionAdapter = new JcrSessionAdapter(jcrRepository, contentSession,
88 contentSession.getSubject());
89 sessionAdapters.put(contentSession, newSessionAdapter);
90 contentSession.onClose().thenAccept((s) -> newSessionAdapter.close());
91 sessionAdapter = newSessionAdapter;
92 }
93 return sessionAdapter;
94 }
95
96 public Session getJcrSession(ProvidedSession contentSession, String jcrWorkspace) {
97 JcrSessionAdapter sessionAdapter = getJcrSessionAdapter(contentSession);
98 Session jcrSession = sessionAdapter.getSession(jcrWorkspace);
99 return jcrSession;
100 }
101
102 public Session getJcrSession(Content content, String jcrWorkspace) {
103 return getJcrSession(((ProvidedContent) content).getSession(), jcrWorkspace);
104 }
105
106 /*
107 * WRITE
108 */
109 Node openForEdit(ProvidedSession contentSession, String jcrWorkspace, String jcrPath) {
110 try {
111 if (contentSession.isEditing()) {
112 JcrSessionAdapter sessionAdapter = getJcrSessionAdapter(contentSession);
113 return sessionAdapter.openForEdit(jcrWorkspace, jcrPath);
114 } else {
115 return getJcrSession(contentSession, jcrWorkspace).getNode(jcrPath);
116 }
117 } catch (RepositoryException e) {
118 throw new JcrException("Cannot open for edit " + jcrPath + " in workspace " + jcrWorkspace, e);
119 }
120 }
121
122 @Override
123 public void persist(ProvidedSession contentSession) {
124 try {
125 JcrSessionAdapter sessionAdapter = getJcrSessionAdapter(contentSession);
126 sessionAdapter.persist();
127 } catch (RepositoryException e) {
128 throw new JcrException("Cannot persist " + contentSession, e);
129 }
130 }
131
132 /*
133 * EDITING
134 */
135
136 @Override
137 public void openForEdit(ProvidedSession session, String relativePath) {
138 openForEdit(session, relativePath, toJcrPath(relativePath));
139 }
140
141 @Override
142 public void freeze(ProvidedSession session, String relativePath) {
143 try {
144 String jcrPath = toJcrPath(relativePath);
145 if (session.isEditing()) {
146 JcrSessionAdapter sessionAdapter = getJcrSessionAdapter(session);
147 sessionAdapter.freeze(jcrWorkspace, jcrPath);
148 }
149 } catch (RepositoryException e) {
150 throw new JcrException("Cannot freeze " + relativePath + " in workspace " + jcrWorkspace, e);
151 }
152 }
153
154 @Override
155 public boolean isOpenForEdit(ProvidedSession session, String relativePath) {
156 try {
157 String jcrPath = toJcrPath(relativePath);
158 JcrSessionAdapter sessionAdapter = getJcrSessionAdapter(session);
159 return sessionAdapter.isOpenForEdit(jcrWorkspace, jcrPath);
160 } catch (RepositoryException e) {
161 throw new JcrException(
162 "Cannot check whether " + relativePath + " is open for edit in workspace " + jcrWorkspace, e);
163 }
164 }
165
166 /*
167 * NAMESPACE CONTEXT
168 */
169 @Override
170 public synchronized String getNamespaceURI(String prefix) {
171 try {
172 return adminSession.getNamespaceURI(prefix);
173 } catch (RepositoryException e) {
174 throw new JcrException(e);
175 }
176 }
177
178 @Override
179 public synchronized String getPrefix(String namespaceURI) {
180 try {
181 return adminSession.getNamespacePrefix(namespaceURI);
182 } catch (RepositoryException e) {
183 throw new JcrException(e);
184 }
185 }
186
187 @Override
188 public synchronized Iterator<String> getPrefixes(String namespaceURI) {
189 try {
190 return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator();
191 } catch (RepositoryException e) {
192 throw new JcrException(e);
193 }
194 }
195
196 /*
197 * SEARCH
198 */
199
200 @Override
201 public Spliterator<Content> search(ProvidedSession session, BasicSearch search, String relPath) {
202 try {
203 Session jcrSession = getJcrSession(session, jcrWorkspace);
204 BasicSearchToQom jcrBasicSearch = new BasicSearchToQom(jcrSession, search, relPath);
205 Query query = jcrBasicSearch.createQuery();
206 QueryResult queryResult = query.execute();
207 return new QueryResultSpliterator(session, queryResult.getNodes());
208 } catch (RepositoryException e) {
209 throw new JcrException(e);
210 }
211 }
212
213 class QueryResultSpliterator implements Spliterator<Content> {
214 private ProvidedSession providedSession;
215 private NodeIterator nodeIterator;
216
217 public QueryResultSpliterator(ProvidedSession providedSession, NodeIterator nodeIterator) {
218 super();
219 this.providedSession = providedSession;
220 this.nodeIterator = nodeIterator;
221 }
222
223 @Override
224 public boolean tryAdvance(Consumer<? super Content> action) {
225 if (!nodeIterator.hasNext())
226 return false;
227 try {
228 Node node = nodeIterator.nextNode();
229 // TODO optimise by reusing the Node
230 JcrContent jcrContent = new JcrContent(providedSession, JcrContentProvider.this, jcrWorkspace,
231 node.getPath());
232 action.accept(jcrContent);
233 return true;
234 } catch (RepositoryException e) {
235 throw new JcrException(e);
236 }
237 }
238
239 @Override
240 public Spliterator<Content> trySplit() {
241 return null;
242 }
243
244 @Override
245 public long estimateSize() {
246 return nodeIterator.getSize();
247 }
248
249 @Override
250 public int characteristics() {
251 return NONNULL | SIZED;
252 }
253
254 }
255
256 /*
257 * UTILITIES
258 */
259 /**
260 * Just adds a '/' so that it becomes an absolute JCR path within the JCR
261 * workspace of this provider.
262 */
263 private String toJcrPath(String relativePath) {
264 return ContentUtils.SLASH + relativePath;
265 }
266
267 /*
268 * TRANSITIONAL, WHILE MIGRATING FROM JCR TO ACR
269 */
270 @Deprecated
271 public synchronized <T> T doInAdminSession(Function<Session, T> toDo) {
272 try {
273 return toDo.apply(adminSession);
274 } finally {
275 try {
276 if (adminSession.hasPendingChanges())
277 adminSession.save();
278 } catch (RepositoryException e) {
279 throw new JcrException("Cannot save admin session", e);
280 }
281 }
282 }
283
284 }