]> git.argeo.org Git - lgpl/argeo-commons.git/blob - jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
Improve ACR, introduce migration from JCR.
[lgpl/argeo-commons.git] / jcr / org.argeo.cms.jcr / src / org / argeo / cms / jcr / acr / JcrContent.java
1 package org.argeo.cms.jcr.acr;
2
3 import java.io.Closeable;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.PipedInputStream;
7 import java.io.PipedOutputStream;
8 import java.util.ArrayList;
9 import java.util.Calendar;
10 import java.util.HashSet;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.Optional;
14 import java.util.Set;
15 import java.util.TreeSet;
16 import java.util.concurrent.ForkJoinPool;
17
18 import javax.jcr.Node;
19 import javax.jcr.NodeIterator;
20 import javax.jcr.Property;
21 import javax.jcr.PropertyIterator;
22 import javax.jcr.PropertyType;
23 import javax.jcr.RepositoryException;
24 import javax.jcr.Value;
25 import javax.jcr.nodetype.NodeType;
26 import javax.xml.namespace.QName;
27 import javax.xml.transform.Source;
28 import javax.xml.transform.stream.StreamSource;
29
30 import org.argeo.api.acr.Content;
31 import org.argeo.api.acr.NamespaceUtils;
32 import org.argeo.api.acr.spi.ContentProvider;
33 import org.argeo.api.acr.spi.ProvidedSession;
34 import org.argeo.api.cms.CmsConstants;
35 import org.argeo.cms.acr.AbstractContent;
36 import org.argeo.cms.acr.ContentUtils;
37 import org.argeo.jcr.Jcr;
38 import org.argeo.jcr.JcrException;
39 import org.argeo.jcr.JcrUtils;
40
41 /** A JCR {@link Node} accessed as {@link Content}. */
42 public class JcrContent extends AbstractContent {
43 // private Node jcrNode;
44
45 private JcrContentProvider provider;
46
47 private String jcrWorkspace;
48 private String jcrPath;
49
50 protected JcrContent(ProvidedSession session, JcrContentProvider provider, String jcrWorkspace, String jcrPath) {
51 super(session);
52 this.provider = provider;
53 this.jcrWorkspace = jcrWorkspace;
54 this.jcrPath = jcrPath;
55 }
56
57 @Override
58 public QName getName() {
59 String name = Jcr.getName(getJcrNode());
60 if (name.equals("")) {// root
61 String mountPath = provider.getMountPath();
62 name = ContentUtils.getParentPath(mountPath)[1];
63 // name = Jcr.getWorkspaceName(getJcrNode());
64 }
65 return NamespaceUtils.parsePrefixedName(provider, name);
66 }
67
68 @SuppressWarnings("unchecked")
69 @Override
70 public <A> Optional<A> get(QName key, Class<A> clss) {
71 if (isDefaultAttrTypeRequested(clss)) {
72 return Optional.of((A) get(getJcrNode(), key.toString()));
73 }
74 return Optional.of((A) Jcr.get(getJcrNode(), key.toString()));
75 }
76
77 @Override
78 public Iterator<Content> iterator() {
79 try {
80 return new JcrContentIterator(getJcrNode().getNodes());
81 } catch (RepositoryException e) {
82 throw new JcrException("Cannot list children of " + getJcrNode(), e);
83 }
84 }
85
86 @Override
87 protected Iterable<QName> keys() {
88 try {
89 Set<QName> keys = new HashSet<>();
90 for (PropertyIterator propertyIterator = getJcrNode().getProperties(); propertyIterator.hasNext();) {
91 Property property = propertyIterator.nextProperty();
92 // TODO convert standard names
93 // TODO skip technical properties
94 QName name = NamespaceUtils.parsePrefixedName(provider, property.getName());
95 keys.add(name);
96 }
97 return keys;
98 } catch (RepositoryException e) {
99 throw new JcrException("Cannot list properties of " + getJcrNode(), e);
100 }
101 }
102
103 public Node getJcrNode() {
104 try {
105 // TODO caching?
106 return provider.getJcrSession(getSession(), jcrWorkspace).getNode(jcrPath);
107 } catch (RepositoryException e) {
108 throw new JcrException("Cannot retrieve " + jcrPath + " from workspace " + jcrWorkspace, e);
109 }
110 }
111
112 /** Cast to a standard Java object. */
113 static Object get(Node node, String property) {
114 try {
115 Property p = node.getProperty(property);
116 if (p.isMultiple()) {
117 Value[] values = p.getValues();
118 List<Object> lst = new ArrayList<>();
119 for (Value value : values) {
120 lst.add(convertSingleValue(value));
121 }
122 return lst;
123 } else {
124 Value value = node.getProperty(property).getValue();
125 return convertSingleValue(value);
126 }
127 } catch (RepositoryException e) {
128 throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
129 }
130 }
131
132 static Object convertSingleValue(Value value) throws RepositoryException {
133 switch (value.getType()) {
134 case PropertyType.STRING:
135 return value.getString();
136 case PropertyType.DOUBLE:
137 return (Double) value.getDouble();
138 case PropertyType.LONG:
139 return (Long) value.getLong();
140 case PropertyType.BOOLEAN:
141 return (Boolean) value.getBoolean();
142 case PropertyType.DATE:
143 Calendar calendar = value.getDate();
144 return calendar.toInstant();
145 case PropertyType.BINARY:
146 throw new IllegalArgumentException("Binary is not supported as an attribute");
147 default:
148 return value.getString();
149 }
150 }
151
152 class JcrContentIterator implements Iterator<Content> {
153 private final NodeIterator nodeIterator;
154 // we keep track in order to be able to delete it
155 private JcrContent current = null;
156
157 protected JcrContentIterator(NodeIterator nodeIterator) {
158 this.nodeIterator = nodeIterator;
159 }
160
161 @Override
162 public boolean hasNext() {
163 return nodeIterator.hasNext();
164 }
165
166 @Override
167 public Content next() {
168 current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
169 return current;
170 }
171
172 @Override
173 public void remove() {
174 if (current != null) {
175 Jcr.remove(current.getJcrNode());
176 }
177 }
178
179 }
180
181 @Override
182 public String getPath() {
183 try {
184 // Note: it is important to to use the default way (recursing through parents),
185 // since the session may not have access to parent nodes
186 return ContentUtils.ROOT_SLASH + jcrWorkspace + getJcrNode().getPath();
187 } catch (RepositoryException e) {
188 throw new JcrException("Cannot get depth of " + getJcrNode(), e);
189 }
190 }
191
192 @Override
193 public int getDepth() {
194 try {
195 return getJcrNode().getDepth() + 1;
196 } catch (RepositoryException e) {
197 throw new JcrException("Cannot get depth of " + getJcrNode(), e);
198 }
199 }
200
201 @Override
202 public Content getParent() {
203 if (Jcr.isRoot(getJcrNode())) // root
204 return null;
205 return new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
206 }
207
208 @Override
209 public Content add(QName name, QName... classes) {
210 if (classes.length > 0) {
211 QName primaryType = classes[0];
212 Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
213 for (int i = 1; i < classes.length; i++) {
214 try {
215 child.addMixin(classes[i].toString());
216 } catch (RepositoryException e) {
217 throw new JcrException("Cannot add child to " + getJcrNode(), e);
218 }
219 }
220
221 } else {
222 Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
223 }
224 return null;
225 }
226
227 @Override
228 public void remove() {
229 Jcr.remove(getJcrNode());
230 }
231
232 @Override
233 protected void removeAttr(QName key) {
234 Property property = Jcr.getProperty(getJcrNode(), key.toString());
235 if (property != null) {
236 try {
237 property.remove();
238 } catch (RepositoryException e) {
239 throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
240 }
241 }
242
243 }
244
245 boolean exists() {
246 try {
247 return provider.getJcrSession(getSession(), jcrWorkspace).itemExists(jcrPath);
248 } catch (RepositoryException e) {
249 throw new JcrException("Cannot check whether " + jcrPath + " exists", e);
250 }
251 }
252
253 /*
254 * ADAPTERS
255 */
256 @SuppressWarnings("unchecked")
257 public <A> A adapt(Class<A> clss) {
258 if (Source.class.isAssignableFrom(clss)) {
259 // try {
260 PipedInputStream in = new PipedInputStream();
261
262 ForkJoinPool.commonPool().execute(() -> {
263 try (PipedOutputStream out = new PipedOutputStream(in)) {
264 provider.getJcrSession(getSession(), jcrWorkspace).exportDocumentView(jcrPath, out, true, false);
265 out.flush();
266 } catch (IOException | RepositoryException e) {
267 throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
268 }
269
270 });
271 return (A) new StreamSource(in);
272 // } catch (IOException e) {
273 // throw new RuntimeException("Cannot adapt " + JcrContent.this + " to " + clss, e);
274 // }
275 } else
276
277 return super.adapt(clss);
278 }
279
280 @SuppressWarnings("unchecked")
281 @Override
282 public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
283 if (InputStream.class.isAssignableFrom(clss)) {
284 Node node = getJcrNode();
285 if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
286 try {
287 return (C) JcrUtils.getFileAsStream(node);
288 } catch (RepositoryException e) {
289 throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e);
290 }
291 }
292 }
293 return super.open(clss);
294 }
295
296 @Override
297 public ContentProvider getProvider() {
298 return provider;
299 }
300
301 @Override
302 public String getSessionLocalId() {
303 try {
304 return getJcrNode().getIdentifier();
305 } catch (RepositoryException e) {
306 throw new JcrException("Cannot get identifier for " + getJcrNode(), e);
307 }
308 }
309
310 /*
311 * TYPING
312 */
313 @Override
314 public List<QName> getContentClasses() {
315 try {
316 // Node node = getJcrNode();
317 // List<QName> res = new ArrayList<>();
318 // res.add(nodeTypeToQName(node.getPrimaryNodeType()));
319 // for (NodeType mixin : node.getMixinNodeTypes()) {
320 // res.add(nodeTypeToQName(mixin));
321 // }
322 // return res;
323 Node context = getJcrNode();
324
325 List<QName> res = new ArrayList<>();
326 // primary node type
327 NodeType primaryType = context.getPrimaryNodeType();
328 res.add(nodeTypeToQName(primaryType));
329
330 Set<QName> secondaryTypes = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
331 for (NodeType mixinType : context.getMixinNodeTypes()) {
332 secondaryTypes.add(nodeTypeToQName(mixinType));
333 }
334 for (NodeType superType : primaryType.getDeclaredSupertypes()) {
335 secondaryTypes.add(nodeTypeToQName(superType));
336 }
337 // mixins
338 for (NodeType mixinType : context.getMixinNodeTypes()) {
339 for (NodeType superType : mixinType.getDeclaredSupertypes()) {
340 secondaryTypes.add(nodeTypeToQName(superType));
341 }
342 }
343 // // entity type
344 // if (context.isNodeType(EntityType.entity.get())) {
345 // if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
346 // String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
347 // if (byType.containsKey(entityTypeName)) {
348 // types.add(entityTypeName);
349 // }
350 // }
351 // }
352 res.addAll(secondaryTypes);
353 return res;
354 } catch (RepositoryException e) {
355 throw new JcrException("Cannot list node types from " + getJcrNode(), e);
356 }
357 }
358
359 private QName nodeTypeToQName(NodeType nodeType) {
360 String name = nodeType.getName();
361 return QName.valueOf(name);
362 }
363
364 /*
365 * STATIC UTLITIES
366 */
367 public static Content nodeToContent(Node node) {
368 if (node == null)
369 return null;
370 try {
371 ProvidedSession contentSession = (ProvidedSession) node.getSession()
372 .getAttribute(ProvidedSession.class.getName());
373 if (contentSession == null)
374 throw new IllegalArgumentException(
375 "Cannot adapt " + node + " to content, because it was not loaded from a content session");
376 return contentSession.get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + node.getPath());
377 } catch (RepositoryException e) {
378 throw new JcrException("Cannot adapt " + node + " to a content", e);
379 }
380 }
381
382 }