]> git.argeo.org Git - gpl/argeo-jcr.git/blob - org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
Merge remote-tracking branch 'origin/unstable' into testing
[gpl/argeo-jcr.git] / 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 PipedOutputStream out = new PipedOutputStream();
261 PipedInputStream in;
262 try {
263 in = new PipedInputStream(out);
264 } catch (IOException e) {
265 throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
266 }
267
268 ForkJoinPool.commonPool().execute(() -> {
269 // try (PipedOutputStream out = new PipedOutputStream(in)) {
270 try {
271 provider.getJcrSession(getSession(), jcrWorkspace).exportDocumentView(jcrPath, out, true, false);
272 out.flush();
273 out.close();
274 } catch (IOException | RepositoryException e) {
275 throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
276 }
277
278 });
279 return (A) new StreamSource(in);
280 // } catch (IOException e) {
281 // throw new RuntimeException("Cannot adapt " + JcrContent.this + " to " + clss, e);
282 // }
283 } else
284
285 return super.adapt(clss);
286 }
287
288 @SuppressWarnings("unchecked")
289 @Override
290 public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
291 if (InputStream.class.isAssignableFrom(clss)) {
292 Node node = getJcrNode();
293 if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
294 try {
295 return (C) JcrUtils.getFileAsStream(node);
296 } catch (RepositoryException e) {
297 throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e);
298 }
299 }
300 }
301 return super.open(clss);
302 }
303
304 @Override
305 public ContentProvider getProvider() {
306 return provider;
307 }
308
309 @Override
310 public String getSessionLocalId() {
311 try {
312 return getJcrNode().getIdentifier();
313 } catch (RepositoryException e) {
314 throw new JcrException("Cannot get identifier for " + getJcrNode(), e);
315 }
316 }
317
318 /*
319 * TYPING
320 */
321 @Override
322 public List<QName> getContentClasses() {
323 try {
324 // Node node = getJcrNode();
325 // List<QName> res = new ArrayList<>();
326 // res.add(nodeTypeToQName(node.getPrimaryNodeType()));
327 // for (NodeType mixin : node.getMixinNodeTypes()) {
328 // res.add(nodeTypeToQName(mixin));
329 // }
330 // return res;
331 Node context = getJcrNode();
332
333 List<QName> res = new ArrayList<>();
334 // primary node type
335 NodeType primaryType = context.getPrimaryNodeType();
336 res.add(nodeTypeToQName(primaryType));
337
338 Set<QName> secondaryTypes = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
339 for (NodeType mixinType : context.getMixinNodeTypes()) {
340 secondaryTypes.add(nodeTypeToQName(mixinType));
341 }
342 for (NodeType superType : primaryType.getDeclaredSupertypes()) {
343 secondaryTypes.add(nodeTypeToQName(superType));
344 }
345 // mixins
346 for (NodeType mixinType : context.getMixinNodeTypes()) {
347 for (NodeType superType : mixinType.getDeclaredSupertypes()) {
348 secondaryTypes.add(nodeTypeToQName(superType));
349 }
350 }
351 // // entity type
352 // if (context.isNodeType(EntityType.entity.get())) {
353 // if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
354 // String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
355 // if (byType.containsKey(entityTypeName)) {
356 // types.add(entityTypeName);
357 // }
358 // }
359 // }
360 res.addAll(secondaryTypes);
361 return res;
362 } catch (RepositoryException e) {
363 throw new JcrException("Cannot list node types from " + getJcrNode(), e);
364 }
365 }
366
367 private QName nodeTypeToQName(NodeType nodeType) {
368 String name = nodeType.getName();
369 return QName.valueOf(name);
370 }
371
372 @Override
373 public int getSiblingIndex() {
374 return Jcr.getIndex(getJcrNode());
375 }
376
377 /*
378 * STATIC UTLITIES
379 */
380 public static Content nodeToContent(Node node) {
381 if (node == null)
382 return null;
383 try {
384 ProvidedSession contentSession = (ProvidedSession) node.getSession()
385 .getAttribute(ProvidedSession.class.getName());
386 if (contentSession == null)
387 throw new IllegalArgumentException(
388 "Cannot adapt " + node + " to content, because it was not loaded from a content session");
389 return contentSession.get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + node.getPath());
390 } catch (RepositoryException e) {
391 throw new JcrException("Cannot adapt " + node + " to a content", e);
392 }
393 }
394
395 }