]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.jcr/src/org/argeo/jcr/Jcr.java
Factorise get image path.
[lgpl/argeo-commons.git] / org.argeo.jcr / src / org / argeo / jcr / Jcr.java
1 package org.argeo.jcr;
2
3 import java.math.BigDecimal;
4 import java.time.Instant;
5 import java.util.ArrayList;
6 import java.util.Calendar;
7 import java.util.Collections;
8 import java.util.Date;
9 import java.util.GregorianCalendar;
10 import java.util.Iterator;
11 import java.util.List;
12
13 import javax.jcr.Binary;
14 import javax.jcr.ItemNotFoundException;
15 import javax.jcr.Node;
16 import javax.jcr.NodeIterator;
17 import javax.jcr.Property;
18 import javax.jcr.PropertyType;
19 import javax.jcr.Repository;
20 import javax.jcr.RepositoryException;
21 import javax.jcr.Session;
22 import javax.jcr.Value;
23 import javax.jcr.Workspace;
24 import javax.jcr.nodetype.NodeType;
25 import javax.jcr.security.Privilege;
26 import javax.jcr.version.Version;
27 import javax.jcr.version.VersionHistory;
28 import javax.jcr.version.VersionIterator;
29 import javax.jcr.version.VersionManager;
30
31 /**
32 * Utility class whose purpose is to make using JCR less verbose by
33 * systematically using unchecked exceptions and returning <code>null</code>
34 * when something is not found. This is especially useful when writing user
35 * interfaces (such as with SWT) where listeners and callbacks expect unchecked
36 * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
37 */
38 public class Jcr {
39 /**
40 * The name of a node which will be serialized as XML text, as per section 7.3.1
41 * of the JCR 2.0 specifications.
42 */
43 public final static String JCR_XMLTEXT = "jcr:xmltext";
44 /**
45 * The name of a property which will be serialized as XML text, as per section
46 * 7.3.1 of the JCR 2.0 specifications.
47 */
48 public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
49 /**
50 * <code>jcr:name</code>, when used in another context than
51 * {@link Property#JCR_NAME}, typically to name a node rather than a property.
52 */
53 public final static String JCR_NAME = "jcr:name";
54 /**
55 * <code>jcr:path</code>, when used in another context than
56 * {@link Property#JCR_PATH}, typically to name a node rather than a property.
57 */
58 public final static String JCR_PATH = "jcr:path";
59 /**
60 * <code>jcr:primaryType</code> with prefix instead of namespace (as in
61 * {@link Property#JCR_PRIMARY_TYPE}.
62 */
63 public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
64 /**
65 * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
66 * {@link Property#JCR_MIXIN_TYPES}.
67 */
68 public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
69 /**
70 * <code>jcr:uuid</code> with prefix instead of namespace (as in
71 * {@link Property#JCR_UUID}.
72 */
73 public final static String JCR_UUID = "jcr:uuid";
74 /**
75 * <code>jcr:created</code> with prefix instead of namespace (as in
76 * {@link Property#JCR_CREATED}.
77 */
78 public final static String JCR_CREATED = "jcr:created";
79 /**
80 * <code>jcr:createdBy</code> with prefix instead of namespace (as in
81 * {@link Property#JCR_CREATED_BY}.
82 */
83 public final static String JCR_CREATED_BY = "jcr:createdBy";
84 /**
85 * <code>jcr:lastModified</code> with prefix instead of namespace (as in
86 * {@link Property#JCR_LAST_MODIFIED}.
87 */
88 public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
89 /**
90 * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
91 * {@link Property#JCR_LAST_MODIFIED_BY}.
92 */
93 public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
94
95 /**
96 * @see Node#isNodeType(String)
97 * @throws IllegalStateException caused by {@link RepositoryException}
98 */
99 public static boolean isNodeType(Node node, String nodeTypeName) {
100 try {
101 return node.isNodeType(nodeTypeName);
102 } catch (RepositoryException e) {
103 throw new IllegalStateException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
104 }
105 }
106
107 /**
108 * @see Node#hasNodes()
109 * @throws IllegalStateException caused by {@link RepositoryException}
110 */
111 public static boolean hasNodes(Node node) {
112 try {
113 return node.hasNodes();
114 } catch (RepositoryException e) {
115 throw new IllegalStateException("Cannot get whether " + node + " has children.", e);
116 }
117 }
118
119 /**
120 * @see Node#getParent()
121 * @throws IllegalStateException caused by {@link RepositoryException}
122 */
123 public static Node getParent(Node node) {
124 try {
125 return isRoot(node) ? null : node.getParent();
126 } catch (RepositoryException e) {
127 throw new IllegalStateException("Cannot get parent of " + node, e);
128 }
129 }
130
131 /**
132 * Whether this node is the root node.
133 *
134 * @throws IllegalStateException caused by {@link RepositoryException}
135 */
136 public static boolean isRoot(Node node) {
137 try {
138 return node.getDepth() == 0;
139 } catch (RepositoryException e) {
140 throw new IllegalStateException("Cannot get depth of " + node, e);
141 }
142 }
143
144 /**
145 * @see Node#getPath()
146 * @throws IllegalStateException caused by {@link RepositoryException}
147 */
148 public static String getPath(Node node) {
149 try {
150 return node.getPath();
151 } catch (RepositoryException e) {
152 throw new IllegalStateException("Cannot get path of " + node, e);
153 }
154 }
155
156 /**
157 * @see Node#getSession()
158 * @see Session#getWorkspace()
159 * @see Workspace#getName()
160 */
161 public static String getWorkspaceName(Node node) {
162 return session(node).getWorkspace().getName();
163 }
164
165 /**
166 * @see Node#getIdentifier()
167 * @throws IllegalStateException caused by {@link RepositoryException}
168 */
169 public static String getIdentifier(Node node) {
170 try {
171 return node.getIdentifier();
172 } catch (RepositoryException e) {
173 throw new IllegalStateException("Cannot get identifier of " + node, e);
174 }
175 }
176
177 /**
178 * @see Node#getName()
179 * @throws IllegalStateException caused by {@link RepositoryException}
180 */
181 public static String getName(Node node) {
182 try {
183 return node.getName();
184 } catch (RepositoryException e) {
185 throw new IllegalStateException("Cannot get name of " + node, e);
186 }
187 }
188
189 /**
190 * If node has mixin {@link NodeType#MIX_TITLE}, return
191 * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
192 */
193 public static String getTitle(Node node) {
194 if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
195 return get(node, Property.JCR_TITLE);
196 else
197 return Jcr.getName(node);
198 }
199
200 /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
201 @SuppressWarnings("unchecked")
202 public static Iterable<Node> iterate(NodeIterator nodeIterator) {
203 return new Iterable<Node>() {
204
205 @Override
206 public Iterator<Node> iterator() {
207 return nodeIterator;
208 }
209 };
210 }
211
212 /**
213 * @return the children as an {@link Iterable} for use in for-each llops.
214 * @see Node#getNodes()
215 * @throws IllegalStateException caused by {@link RepositoryException}
216 */
217 public static Iterable<Node> nodes(Node node) {
218 try {
219 return iterate(node.getNodes());
220 } catch (RepositoryException e) {
221 throw new IllegalStateException("Cannot get children of " + node, e);
222 }
223 }
224
225 /**
226 * @return the children as a (possibly empty) {@link List}.
227 * @see Node#getNodes()
228 * @throws IllegalStateException caused by {@link RepositoryException}
229 */
230 public static List<Node> getNodes(Node node) {
231 List<Node> nodes = new ArrayList<>();
232 try {
233 if (node.hasNodes()) {
234 NodeIterator nit = node.getNodes();
235 while (nit.hasNext())
236 nodes.add(nit.nextNode());
237 return nodes;
238 } else
239 return nodes;
240 } catch (RepositoryException e) {
241 throw new IllegalStateException("Cannot get children of " + node, e);
242 }
243 }
244
245 /**
246 * @return the child or <code>null</node> if not found
247 * @see Node#getNode(String)
248 * @throws IllegalStateException caused by {@link RepositoryException}
249 */
250 public static Node getNode(Node node, String child) {
251 try {
252 if (node.hasNode(child))
253 return node.getNode(child);
254 else
255 return null;
256 } catch (RepositoryException e) {
257 throw new IllegalStateException("Cannot get child of " + node, e);
258 }
259 }
260
261 /**
262 * @return the node at this path or <code>null</node> if not found
263 * @see Session#getNode(String)
264 * @throws IllegalStateException caused by {@link RepositoryException}
265 */
266 public static Node getNode(Session session, String path) {
267 try {
268 if (session.nodeExists(path))
269 return session.getNode(path);
270 else
271 return null;
272 } catch (RepositoryException e) {
273 throw new IllegalStateException("Cannot get node " + path, e);
274 }
275 }
276
277 /**
278 * @return the node with htis id or <code>null</node> if not found
279 * @see Session#getNodeByIdentifier(String)
280 * @throws IllegalStateException caused by {@link RepositoryException}
281 */
282 public static Node getNodeById(Session session, String id) {
283 try {
284 return session.getNodeByIdentifier(id);
285 } catch (ItemNotFoundException e) {
286 return null;
287 } catch (RepositoryException e) {
288 throw new IllegalStateException("Cannot get node with id " + id, e);
289 }
290 }
291
292 /**
293 * Set a property to the given value, or remove it if the value is
294 * <code>null</code>.
295 *
296 * @throws IllegalStateException caused by {@link RepositoryException}
297 */
298 public static void set(Node node, String property, Object value) {
299 try {
300 if (!node.hasProperty(property))
301 throw new IllegalArgumentException("No property " + property + " in " + node);
302 Property prop = node.getProperty(property);
303 if (value == null) {
304 prop.remove();
305 return;
306 }
307
308 if (value instanceof String)
309 prop.setValue((String) value);
310 else if (value instanceof Long)
311 prop.setValue((Long) value);
312 else if (value instanceof Double)
313 prop.setValue((Double) value);
314 else if (value instanceof Calendar)
315 prop.setValue((Calendar) value);
316 else if (value instanceof BigDecimal)
317 prop.setValue((BigDecimal) value);
318 else if (value instanceof Boolean)
319 prop.setValue((Boolean) value);
320 else if (value instanceof byte[])
321 JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
322 else if (value instanceof Instant) {
323 Instant instant = (Instant) value;
324 GregorianCalendar calendar = new GregorianCalendar();
325 calendar.setTime(Date.from(instant));
326 prop.setValue(calendar);
327 } else // try with toString()
328 prop.setValue(value.toString());
329 } catch (RepositoryException e) {
330 throw new IllegalStateException("Cannot set property " + property + " of " + node + " to " + value, e);
331 }
332 }
333
334 /**
335 * Get property as {@link String}.
336 *
337 * @return the value of
338 * {@link Node#getProperty(String)}.{@link Property#getString()} or
339 * <code>null</code> if the property does not exist.
340 * @throws IllegalStateException caused by {@link RepositoryException}
341 */
342 public static String get(Node node, String property) {
343 return get(node, property, null);
344 }
345
346 /**
347 * Get property as a {@link String}. If the property is multiple it returns the
348 * first value.
349 *
350 * @return the value of
351 * {@link Node#getProperty(String)}.{@link Property#getString()} or
352 * <code>defaultValue</code> if the property does not exist.
353 * @throws IllegalStateException caused by {@link RepositoryException}
354 */
355 public static String get(Node node, String property, String defaultValue) {
356 try {
357 if (node.hasProperty(property)) {
358 Property p = node.getProperty(property);
359 if (!p.isMultiple())
360 return p.getString();
361 else {
362 Value[] values = p.getValues();
363 if (values.length == 0)
364 return defaultValue;
365 else
366 return values[0].getString();
367 }
368 } else
369 return defaultValue;
370 } catch (RepositoryException e) {
371 throw new IllegalStateException("Cannot retrieve property " + property + " from " + node);
372 }
373 }
374
375 /**
376 * Get property as a {@link Value}.
377 *
378 * @return {@link Node#getProperty(String)} or <code>null</code> if the property
379 * does not exist.
380 * @throws IllegalStateException caused by {@link RepositoryException}
381 */
382 public static Value getValue(Node node, String property) {
383 try {
384 if (node.hasProperty(property))
385 return node.getProperty(property).getValue();
386 else
387 return null;
388 } catch (RepositoryException e) {
389 throw new IllegalStateException("Cannot retrieve property " + property + " from " + node);
390 }
391 }
392
393 /**
394 * Get property doing a best effort to cast it as the target object.
395 *
396 * @return the value of {@link Node#getProperty(String)} or
397 * <code>defaultValue</code> if the property does not exist.
398 * @throws IllegalArgumentException if the value could not be cast
399 * @throws IllegalStateException in case of unexpected
400 * {@link RepositoryException}
401 */
402 @SuppressWarnings("unchecked")
403 public static <T> T getAs(Node node, String property, T defaultValue) {
404 try {
405 // TODO deal with multiple
406 if (node.hasProperty(property)) {
407 Property p = node.getProperty(property);
408 try {
409 switch (p.getType()) {
410 case PropertyType.STRING:
411 return (T) node.getProperty(property).getString();
412 case PropertyType.DOUBLE:
413 return (T) (Double) node.getProperty(property).getDouble();
414 case PropertyType.LONG:
415 return (T) (Long) node.getProperty(property).getLong();
416 case PropertyType.BOOLEAN:
417 return (T) (Boolean) node.getProperty(property).getBoolean();
418 case PropertyType.DATE:
419 return (T) node.getProperty(property).getDate();
420 default:
421 return (T) node.getProperty(property).getString();
422 }
423 } catch (ClassCastException e) {
424 throw new IllegalArgumentException(
425 "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
426 }
427 } else {
428 return defaultValue;
429 }
430 } catch (RepositoryException e) {
431 throw new IllegalStateException("Cannot retrieve property " + property + " from " + node);
432 }
433 }
434
435 /**
436 * Retrieves the {@link Session} related to this node.
437 *
438 * @deprecated Use {@link #getSession(Node)} instead.
439 */
440 @Deprecated
441 public static Session session(Node node) {
442 return getSession(node);
443 }
444
445 /** Retrieves the {@link Session} related to this node. */
446 public static Session getSession(Node node) {
447 try {
448 return node.getSession();
449 } catch (RepositoryException e) {
450 throw new IllegalStateException("Cannot retrieve session related to " + node, e);
451 }
452 }
453
454 /** Retrieves the root node related to this session. */
455 public static Node getRootNode(Session session) {
456 try {
457 return session.getRootNode();
458 } catch (RepositoryException e) {
459 throw new IllegalStateException("Cannot get root node for " + session, e);
460 }
461 }
462
463 /**
464 * Saves the {@link Session} related to this node. Note that all other unrelated
465 * modifications in this session will also be saved.
466 */
467 public static void save(Node node) {
468 try {
469 Session session = node.getSession();
470 // if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
471 // set(node, Property.JCR_LAST_MODIFIED, Instant.now());
472 // set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
473 // }
474 if (session.hasPendingChanges())
475 session.save();
476 } catch (RepositoryException e) {
477 throw new IllegalStateException("Cannot save session related to " + node + " in workspace "
478 + session(node).getWorkspace().getName(), e);
479 }
480 }
481
482 /** Login to a JCR repository. */
483 public static Session login(Repository repository, String workspace) {
484 try {
485 return repository.login(workspace);
486 } catch (RepositoryException e) {
487 throw new IllegalArgumentException("Cannot login to repository", e);
488 }
489 }
490
491 /** Safely and silently logs out a session. */
492 public static void logout(Session session) {
493 try {
494 if (session != null)
495 if (session.isLive())
496 session.logout();
497 } catch (Exception e) {
498 // silent
499 }
500 }
501
502 /** Safely and silently logs out the underlying session. */
503 public static void logout(Node node) {
504 Jcr.logout(session(node));
505 }
506
507 /*
508 * SECURITY
509 */
510 /**
511 * Add a single privilege to a node.
512 *
513 * @see Privilege
514 */
515 public static void addPrivilege(Node node, String principal, String privilege) {
516 try {
517 Session session = node.getSession();
518 JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
519 } catch (RepositoryException e) {
520 throw new IllegalStateException("Cannot add privilege " + privilege + " to " + node, e);
521 }
522 }
523
524 /*
525 * VERSIONING
526 */
527 /** Get checked out status. */
528 public static boolean isCheckedOut(Node node) {
529 try {
530 return node.isCheckedOut();
531 } catch (RepositoryException e) {
532 throw new IllegalStateException("Cannot retrieve checked out status of " + node, e);
533 }
534 }
535
536 /** @see VersionManager#checkpoint(String) */
537 public static void checkpoint(Node node) {
538 try {
539 versionManager(node).checkpoint(node.getPath());
540 } catch (RepositoryException e) {
541 throw new IllegalStateException("Cannot check in " + node, e);
542 }
543 }
544
545 /** @see VersionManager#checkin(String) */
546 public static void checkin(Node node) {
547 try {
548 versionManager(node).checkin(node.getPath());
549 } catch (RepositoryException e) {
550 throw new IllegalStateException("Cannot check in " + node, e);
551 }
552 }
553
554 /** @see VersionManager#checkout(String) */
555 public static void checkout(Node node) {
556 try {
557 versionManager(node).checkout(node.getPath());
558 } catch (RepositoryException e) {
559 throw new IllegalStateException("Cannot check out " + node, e);
560 }
561 }
562
563 /** Get the {@link VersionManager} related to this node. */
564 public static VersionManager versionManager(Node node) {
565 try {
566 return node.getSession().getWorkspace().getVersionManager();
567 } catch (RepositoryException e) {
568 throw new IllegalStateException("Cannot get version manager from " + node, e);
569 }
570 }
571
572 /** Get the {@link VersionHistory} related to this node. */
573 public static VersionHistory getVersionHistory(Node node) {
574 try {
575 return versionManager(node).getVersionHistory(node.getPath());
576 } catch (RepositoryException e) {
577 throw new IllegalStateException("Cannot get version history from " + node, e);
578 }
579 }
580
581 /**
582 * The linear versions of this version history in reverse order and without the
583 * root version.
584 */
585 public static List<Version> getLinearVersions(VersionHistory versionHistory) {
586 try {
587 List<Version> lst = new ArrayList<>();
588 VersionIterator vit = versionHistory.getAllLinearVersions();
589 while (vit.hasNext())
590 lst.add(vit.nextVersion());
591 lst.remove(0);
592 Collections.reverse(lst);
593 return lst;
594 } catch (RepositoryException e) {
595 throw new IllegalStateException("Cannot get linear versions from " + versionHistory, e);
596 }
597 }
598
599 /** The frozen node related to this {@link Version}. */
600 public static Node getFrozenNode(Version version) {
601 try {
602 return version.getFrozenNode();
603 } catch (RepositoryException e) {
604 throw new IllegalStateException("Cannot get frozen node from " + version, e);
605 }
606 }
607
608 /** Get the base {@link Version} related to this node. */
609 public static Version getBaseVersion(Node node) {
610 try {
611 return versionManager(node).getBaseVersion(node.getPath());
612 } catch (RepositoryException e) {
613 throw new IllegalStateException("Cannot get base version from " + node, e);
614 }
615 }
616
617 /*
618 * FILES
619 */
620 /**
621 * Returns the size of this file.
622 *
623 * @see NodeType#NT_FILE
624 */
625 public static long getFileSize(Node fileNode) {
626 try {
627 if (!fileNode.isNodeType(NodeType.NT_FILE))
628 throw new IllegalArgumentException(fileNode + " must be a file.");
629 return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
630 } catch (RepositoryException e) {
631 throw new IllegalStateException("Cannot get file size of " + fileNode, e);
632 }
633 }
634
635 /** Returns the size of this {@link Binary}. */
636 public static long getBinarySize(Binary binaryArg) {
637 try {
638 try (Bin binary = new Bin(binaryArg)) {
639 return binary.getSize();
640 }
641 } catch (RepositoryException e) {
642 throw new IllegalStateException("Cannot get file size of binary " + binaryArg, e);
643 }
644 }
645
646 /** Singleton. */
647 private Jcr() {
648
649 }
650 }