]> git.argeo.org Git - gpl/argeo-jcr.git/blob - Jcr.java
2ff49e7ce92c988902a54d613acd6e39ef39ea08
[gpl/argeo-jcr.git] / Jcr.java
1 package org.argeo.jcr;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.math.BigDecimal;
7 import java.text.MessageFormat;
8 import java.time.Instant;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Calendar;
12 import java.util.Collections;
13 import java.util.Date;
14 import java.util.GregorianCalendar;
15 import java.util.Iterator;
16 import java.util.List;
17
18 import javax.jcr.Binary;
19 import javax.jcr.ItemNotFoundException;
20 import javax.jcr.Node;
21 import javax.jcr.NodeIterator;
22 import javax.jcr.Property;
23 import javax.jcr.PropertyType;
24 import javax.jcr.Repository;
25 import javax.jcr.RepositoryException;
26 import javax.jcr.Session;
27 import javax.jcr.Value;
28 import javax.jcr.Workspace;
29 import javax.jcr.nodetype.NodeType;
30 import javax.jcr.query.Query;
31 import javax.jcr.query.QueryManager;
32 import javax.jcr.query.Row;
33 import javax.jcr.security.Privilege;
34 import javax.jcr.version.Version;
35 import javax.jcr.version.VersionHistory;
36 import javax.jcr.version.VersionIterator;
37 import javax.jcr.version.VersionManager;
38
39 import org.apache.commons.io.IOUtils;
40
41 /**
42 * Utility class whose purpose is to make using JCR less verbose by
43 * systematically using unchecked exceptions and returning <code>null</code>
44 * when something is not found. This is especially useful when writing user
45 * interfaces (such as with SWT) where listeners and callbacks expect unchecked
46 * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
47 */
48 public class Jcr {
49 /**
50 * The name of a node which will be serialized as XML text, as per section 7.3.1
51 * of the JCR 2.0 specifications.
52 */
53 public final static String JCR_XMLTEXT = "jcr:xmltext";
54 /**
55 * The name of a property which will be serialized as XML text, as per section
56 * 7.3.1 of the JCR 2.0 specifications.
57 */
58 public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
59 /**
60 * <code>jcr:name</code>, when used in another context than
61 * {@link Property#JCR_NAME}, typically to name a node rather than a property.
62 */
63 public final static String JCR_NAME = "jcr:name";
64 /**
65 * <code>jcr:path</code>, when used in another context than
66 * {@link Property#JCR_PATH}, typically to name a node rather than a property.
67 */
68 public final static String JCR_PATH = "jcr:path";
69 /**
70 * <code>jcr:primaryType</code> with prefix instead of namespace (as in
71 * {@link Property#JCR_PRIMARY_TYPE}.
72 */
73 public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
74 /**
75 * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
76 * {@link Property#JCR_MIXIN_TYPES}.
77 */
78 public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
79 /**
80 * <code>jcr:uuid</code> with prefix instead of namespace (as in
81 * {@link Property#JCR_UUID}.
82 */
83 public final static String JCR_UUID = "jcr:uuid";
84 /**
85 * <code>jcr:created</code> with prefix instead of namespace (as in
86 * {@link Property#JCR_CREATED}.
87 */
88 public final static String JCR_CREATED = "jcr:created";
89 /**
90 * <code>jcr:createdBy</code> with prefix instead of namespace (as in
91 * {@link Property#JCR_CREATED_BY}.
92 */
93 public final static String JCR_CREATED_BY = "jcr:createdBy";
94 /**
95 * <code>jcr:lastModified</code> with prefix instead of namespace (as in
96 * {@link Property#JCR_LAST_MODIFIED}.
97 */
98 public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
99 /**
100 * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
101 * {@link Property#JCR_LAST_MODIFIED_BY}.
102 */
103 public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
104
105 /**
106 * @see Node#isNodeType(String)
107 * @throws JcrException caused by {@link RepositoryException}
108 */
109 public static boolean isNodeType(Node node, String nodeTypeName) {
110 try {
111 return node.isNodeType(nodeTypeName);
112 } catch (RepositoryException e) {
113 throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
114 }
115 }
116
117 /**
118 * @see Node#hasNodes()
119 * @throws JcrException caused by {@link RepositoryException}
120 */
121 public static boolean hasNodes(Node node) {
122 try {
123 return node.hasNodes();
124 } catch (RepositoryException e) {
125 throw new JcrException("Cannot get whether " + node + " has children.", e);
126 }
127 }
128
129 /**
130 * @see Node#getParent()
131 * @throws JcrException caused by {@link RepositoryException}
132 */
133 public static Node getParent(Node node) {
134 try {
135 return isRoot(node) ? null : node.getParent();
136 } catch (RepositoryException e) {
137 throw new JcrException("Cannot get parent of " + node, e);
138 }
139 }
140
141 /**
142 * @see Node#getParent()
143 * @throws JcrException caused by {@link RepositoryException}
144 */
145 public static String getParentPath(Node node) {
146 return getPath(getParent(node));
147 }
148
149 /**
150 * Whether this node is the root node.
151 *
152 * @throws JcrException caused by {@link RepositoryException}
153 */
154 public static boolean isRoot(Node node) {
155 try {
156 return node.getDepth() == 0;
157 } catch (RepositoryException e) {
158 throw new JcrException("Cannot get depth of " + node, e);
159 }
160 }
161
162 /**
163 * @see Node#getPath()
164 * @throws JcrException caused by {@link RepositoryException}
165 */
166 public static String getPath(Node node) {
167 try {
168 return node.getPath();
169 } catch (RepositoryException e) {
170 throw new JcrException("Cannot get path of " + node, e);
171 }
172 }
173
174 /**
175 * @see Node#getSession()
176 * @see Session#getWorkspace()
177 * @see Workspace#getName()
178 */
179 public static String getWorkspaceName(Node node) {
180 return session(node).getWorkspace().getName();
181 }
182
183 /**
184 * @see Node#getIdentifier()
185 * @throws JcrException caused by {@link RepositoryException}
186 */
187 public static String getIdentifier(Node node) {
188 try {
189 return node.getIdentifier();
190 } catch (RepositoryException e) {
191 throw new JcrException("Cannot get identifier of " + node, e);
192 }
193 }
194
195 /**
196 * @see Node#getName()
197 * @throws JcrException caused by {@link RepositoryException}
198 */
199 public static String getName(Node node) {
200 try {
201 return node.getName();
202 } catch (RepositoryException e) {
203 throw new JcrException("Cannot get name of " + node, e);
204 }
205 }
206
207 /**
208 * Returns the node name with its current index (useful for re-ordering).
209 *
210 * @see Node#getName()
211 * @see Node#getIndex()
212 * @throws JcrException caused by {@link RepositoryException}
213 */
214 public static String getIndexedName(Node node) {
215 try {
216 return node.getName() + "[" + node.getIndex() + "]";
217 } catch (RepositoryException e) {
218 throw new JcrException("Cannot get name of " + node, e);
219 }
220 }
221
222 /**
223 * @see Node#getProperty(String)
224 * @throws JcrException caused by {@link RepositoryException}
225 */
226 public static Property getProperty(Node node, String property) {
227 try {
228 if (node.hasProperty(property))
229 return node.getProperty(property);
230 else
231 return null;
232 } catch (RepositoryException e) {
233 throw new JcrException("Cannot get property " + property + " of " + node, e);
234 }
235 }
236
237 /**
238 * @see Node#getIndex()
239 * @throws JcrException caused by {@link RepositoryException}
240 */
241 public static int getIndex(Node node) {
242 try {
243 return node.getIndex();
244 } catch (RepositoryException e) {
245 throw new JcrException("Cannot get index of " + node, e);
246 }
247 }
248
249 /**
250 * If node has mixin {@link NodeType#MIX_TITLE}, return
251 * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
252 */
253 public static String getTitle(Node node) {
254 if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
255 return get(node, Property.JCR_TITLE);
256 else
257 return Jcr.getName(node);
258 }
259
260 /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
261 @SuppressWarnings("unchecked")
262 public static Iterable<Node> iterate(NodeIterator nodeIterator) {
263 return new Iterable<Node>() {
264
265 @Override
266 public Iterator<Node> iterator() {
267 return nodeIterator;
268 }
269 };
270 }
271
272 /**
273 * @return the children as an {@link Iterable} for use in for-each llops.
274 * @see Node#getNodes()
275 * @throws JcrException caused by {@link RepositoryException}
276 */
277 public static Iterable<Node> nodes(Node node) {
278 try {
279 return iterate(node.getNodes());
280 } catch (RepositoryException e) {
281 throw new JcrException("Cannot get children of " + node, e);
282 }
283 }
284
285 /**
286 * @return the children as a (possibly empty) {@link List}.
287 * @see Node#getNodes()
288 * @throws JcrException caused by {@link RepositoryException}
289 */
290 public static List<Node> getNodes(Node node) {
291 List<Node> nodes = new ArrayList<>();
292 try {
293 if (node.hasNodes()) {
294 NodeIterator nit = node.getNodes();
295 while (nit.hasNext())
296 nodes.add(nit.nextNode());
297 return nodes;
298 } else
299 return nodes;
300 } catch (RepositoryException e) {
301 throw new JcrException("Cannot get children of " + node, e);
302 }
303 }
304
305 /**
306 * @return the child or <code>null</node> if not found
307 * @see Node#getNode(String)
308 * @throws JcrException caused by {@link RepositoryException}
309 */
310 public static Node getNode(Node node, String child) {
311 try {
312 if (node.hasNode(child))
313 return node.getNode(child);
314 else
315 return null;
316 } catch (RepositoryException e) {
317 throw new JcrException("Cannot get child of " + node, e);
318 }
319 }
320
321 /**
322 * @return the node at this path or <code>null</node> if not found
323 * @see Session#getNode(String)
324 * @throws JcrException caused by {@link RepositoryException}
325 */
326 public static Node getNode(Session session, String path) {
327 try {
328 if (session.nodeExists(path))
329 return session.getNode(path);
330 else
331 return null;
332 } catch (RepositoryException e) {
333 throw new JcrException("Cannot get node " + path, e);
334 }
335 }
336
337 /**
338 * Add a node to this parent, setting its primary type and its mixins.
339 *
340 * @param parent the parent node
341 * @param name the name of the node, if <code>null</code>, the primary
342 * type will be used (typically for XML structures)
343 * @param primaryType the primary type, if <code>null</code>
344 * {@link NodeType#NT_UNSTRUCTURED} will be used.
345 * @param mixins the mixins
346 * @return the created node
347 * @see Node#addNode(String, String)
348 * @see Node#addMixin(String)
349 */
350 public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
351 if (name == null && primaryType == null)
352 throw new IllegalArgumentException("Both node name and primary type cannot be null");
353 try {
354 Node newNode = parent.addNode(name == null ? primaryType : name,
355 primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
356 for (String mixin : mixins) {
357 newNode.addMixin(mixin);
358 }
359 return newNode;
360 } catch (RepositoryException e) {
361 throw new JcrException("Cannot add node " + name + " to " + parent, e);
362 }
363 }
364
365 /**
366 * Add an {@link NodeType#NT_BASE} node to this parent.
367 *
368 * @param parent the parent node
369 * @param name the name of the node, cannot be <code>null</code>
370 * @return the created node
371 *
372 * @see Node#addNode(String)
373 */
374 public static Node addNode(Node parent, String name) {
375 if (name == null)
376 throw new IllegalArgumentException("Node name cannot be null");
377 try {
378 Node newNode = parent.addNode(name);
379 return newNode;
380 } catch (RepositoryException e) {
381 throw new JcrException("Cannot add node " + name + " to " + parent, e);
382 }
383 }
384
385 /**
386 * Add mixins to a node.
387 *
388 * @param node the node
389 * @param mixins the mixins
390 * @see Node#addMixin(String)
391 */
392 public static void addMixin(Node node, String... mixins) {
393 try {
394 for (String mixin : mixins) {
395 node.addMixin(mixin);
396 }
397 } catch (RepositoryException e) {
398 throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
399 }
400 }
401
402 /**
403 * Removes this node.
404 *
405 * @see Node#remove()
406 */
407 public static void remove(Node node) {
408 try {
409 node.remove();
410 } catch (RepositoryException e) {
411 throw new JcrException("Cannot remove node " + node, e);
412 }
413 }
414
415 /**
416 * @return the node with htis id or <code>null</node> if not found
417 * @see Session#getNodeByIdentifier(String)
418 * @throws JcrException caused by {@link RepositoryException}
419 */
420 public static Node getNodeById(Session session, String id) {
421 try {
422 return session.getNodeByIdentifier(id);
423 } catch (ItemNotFoundException e) {
424 return null;
425 } catch (RepositoryException e) {
426 throw new JcrException("Cannot get node with id " + id, e);
427 }
428 }
429
430 /**
431 * Set a property to the given value, or remove it if the value is
432 * <code>null</code>.
433 *
434 * @throws JcrException caused by {@link RepositoryException}
435 */
436 public static void set(Node node, String property, Object value) {
437 try {
438 if (!node.hasProperty(property)) {
439 if (value != null) {
440 if (value instanceof List) {// multiple
441 List<?> lst = (List<?>) value;
442 String[] values = new String[lst.size()];
443 for (int i = 0; i < lst.size(); i++) {
444 values[i] = lst.get(i).toString();
445 }
446 node.setProperty(property, values);
447 } else {
448 node.setProperty(property, value.toString());
449 }
450 }
451 return;
452 }
453 Property prop = node.getProperty(property);
454 if (value == null) {
455 prop.remove();
456 return;
457 }
458
459 // multiple
460 if (value instanceof List) {
461 List<?> lst = (List<?>) value;
462 String[] values = new String[lst.size()];
463 // TODO better cast?
464 for (int i = 0; i < lst.size(); i++) {
465 values[i] = lst.get(i).toString();
466 }
467 if (!prop.isMultiple())
468 prop.remove();
469 node.setProperty(property, values);
470 return;
471 }
472
473 // single
474 if (prop.isMultiple()) {
475 prop.remove();
476 node.setProperty(property, value.toString());
477 return;
478 }
479
480 if (value instanceof String)
481 prop.setValue((String) value);
482 else if (value instanceof Long)
483 prop.setValue((Long) value);
484 else if (value instanceof Integer)
485 prop.setValue(((Integer) value).longValue());
486 else if (value instanceof Double)
487 prop.setValue((Double) value);
488 else if (value instanceof Float)
489 prop.setValue(((Float) value).doubleValue());
490 else if (value instanceof Calendar)
491 prop.setValue((Calendar) value);
492 else if (value instanceof BigDecimal)
493 prop.setValue((BigDecimal) value);
494 else if (value instanceof Boolean)
495 prop.setValue((Boolean) value);
496 else if (value instanceof byte[])
497 JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
498 else if (value instanceof Instant) {
499 Instant instant = (Instant) value;
500 GregorianCalendar calendar = new GregorianCalendar();
501 calendar.setTime(Date.from(instant));
502 prop.setValue(calendar);
503 } else // try with toString()
504 prop.setValue(value.toString());
505 } catch (RepositoryException e) {
506 throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
507 }
508 }
509
510 /**
511 * Get property as {@link String}.
512 *
513 * @return the value of
514 * {@link Node#getProperty(String)}.{@link Property#getString()} or
515 * <code>null</code> if the property does not exist.
516 * @throws JcrException caused by {@link RepositoryException}
517 */
518 public static String get(Node node, String property) {
519 return get(node, property, null);
520 }
521
522 /**
523 * Get property as a {@link String}. If the property is multiple it returns the
524 * first value.
525 *
526 * @return the value of
527 * {@link Node#getProperty(String)}.{@link Property#getString()} or
528 * <code>defaultValue</code> if the property does not exist.
529 * @throws JcrException caused by {@link RepositoryException}
530 */
531 public static String get(Node node, String property, String defaultValue) {
532 try {
533 if (node.hasProperty(property)) {
534 Property p = node.getProperty(property);
535 if (!p.isMultiple())
536 return p.getString();
537 else {
538 Value[] values = p.getValues();
539 if (values.length == 0)
540 return defaultValue;
541 else
542 return values[0].getString();
543 }
544 } else
545 return defaultValue;
546 } catch (RepositoryException e) {
547 throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
548 }
549 }
550
551 /**
552 * Get property as a {@link Value}.
553 *
554 * @return {@link Node#getProperty(String)} or <code>null</code> if the property
555 * does not exist.
556 * @throws JcrException caused by {@link RepositoryException}
557 */
558 public static Value getValue(Node node, String property) {
559 try {
560 if (node.hasProperty(property))
561 return node.getProperty(property).getValue();
562 else
563 return null;
564 } catch (RepositoryException e) {
565 throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
566 }
567 }
568
569 /**
570 * Get property doing a best effort to cast it as the target object.
571 *
572 * @return the value of {@link Node#getProperty(String)} or
573 * <code>defaultValue</code> if the property does not exist.
574 * @throws IllegalArgumentException if the value could not be cast
575 * @throws JcrException in case of unexpected
576 * {@link RepositoryException}
577 */
578 @SuppressWarnings("unchecked")
579 public static <T> T getAs(Node node, String property, T defaultValue) {
580 try {
581 // TODO deal with multiple
582 if (node.hasProperty(property)) {
583 Property p = node.getProperty(property);
584 try {
585 if (p.isMultiple()) {
586 throw new UnsupportedOperationException("Multiple values properties are not supported");
587 }
588 Value value = p.getValue();
589 return (T) get(value);
590 } catch (ClassCastException e) {
591 throw new IllegalArgumentException(
592 "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
593 }
594 } else {
595 return defaultValue;
596 }
597 } catch (RepositoryException e) {
598 throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
599 }
600 }
601
602 public static <T> T getAs(Node node, String property, Class<T> clss) {
603 if (String.class.isAssignableFrom(clss)) {
604 return (T) get(node, property);
605 } else if (Long.class.isAssignableFrom(clss)) {
606 return (T) get(node, property);
607 } else {
608 throw new IllegalArgumentException("Unsupported format " + clss);
609 }
610 }
611
612 /**
613 * Retrieve a {@link PropertyType#DATE} property as an {@link Instant}.
614 *
615 * @return the property value, or <code>null</code> if not found.
616 */
617 public static Instant getAsInstant(Node node, String property) {
618 try {
619 if (!node.hasProperty(property))
620 return null;
621 Calendar calendar = node.getProperty(property).getDate();
622 return calendar.getTime().toInstant();
623 } catch (RepositoryException e) {
624 throw new JcrException("Cannot get property " + property + " of " + node + " as an instant.", e);
625 }
626
627 }
628
629 /**
630 * Get a multiple property as a list, doing a best effort to cast it as the
631 * target list.
632 *
633 * @return the value of {@link Node#getProperty(String)}.
634 * @throws IllegalArgumentException if the value could not be cast
635 * @throws JcrException in case of unexpected
636 * {@link RepositoryException}
637 */
638 public static <T> List<T> getMultiple(Node node, String property) {
639 try {
640 if (node.hasProperty(property)) {
641 Property p = node.getProperty(property);
642 return getMultiple(p);
643 } else {
644 return null;
645 }
646 } catch (RepositoryException e) {
647 throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
648 }
649 }
650
651 /**
652 * Get a multiple property as a list, doing a best effort to cast it as the
653 * target list.
654 */
655 @SuppressWarnings("unchecked")
656 public static <T> List<T> getMultiple(Property p) {
657 try {
658 List<T> res = new ArrayList<>();
659 if (!p.isMultiple()) {
660 res.add((T) get(p.getValue()));
661 return res;
662 }
663 Value[] values = p.getValues();
664 for (Value value : values) {
665 res.add((T) get(value));
666 }
667 return res;
668 } catch (ClassCastException | RepositoryException e) {
669 throw new IllegalArgumentException("Cannot get property " + p, e);
670 }
671 }
672
673 /** Cast a {@link Value} to a standard Java object. */
674 public static Object get(Value value) {
675 Binary binary = null;
676 try {
677 switch (value.getType()) {
678 case PropertyType.STRING:
679 return value.getString();
680 case PropertyType.DOUBLE:
681 return (Double) value.getDouble();
682 case PropertyType.LONG:
683 return (Long) value.getLong();
684 case PropertyType.BOOLEAN:
685 return (Boolean) value.getBoolean();
686 case PropertyType.DATE:
687 return value.getDate();
688 case PropertyType.BINARY:
689 binary = value.getBinary();
690 byte[] arr = null;
691 try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
692 IOUtils.copy(in, out);
693 arr = out.toByteArray();
694 } catch (IOException e) {
695 throw new RuntimeException("Cannot read binary from " + value, e);
696 }
697 return arr;
698 default:
699 return value.getString();
700 }
701 } catch (RepositoryException e) {
702 throw new JcrException("Cannot cast value from " + value, e);
703 } finally {
704 if (binary != null)
705 binary.dispose();
706 }
707 }
708
709 /**
710 * Retrieves the {@link Session} related to this node.
711 *
712 * @deprecated Use {@link #getSession(Node)} instead.
713 */
714 @Deprecated
715 public static Session session(Node node) {
716 return getSession(node);
717 }
718
719 /** Retrieves the {@link Session} related to this node. */
720 public static Session getSession(Node node) {
721 try {
722 return node.getSession();
723 } catch (RepositoryException e) {
724 throw new JcrException("Cannot retrieve session related to " + node, e);
725 }
726 }
727
728 /** Retrieves the root node related to this session. */
729 public static Node getRootNode(Session session) {
730 try {
731 return session.getRootNode();
732 } catch (RepositoryException e) {
733 throw new JcrException("Cannot get root node for " + session, e);
734 }
735 }
736
737 /** Whether this item exists. */
738 public static boolean itemExists(Session session, String path) {
739 try {
740 return session.itemExists(path);
741 } catch (RepositoryException e) {
742 throw new JcrException("Cannot check whether " + path + " exists", e);
743 }
744 }
745
746 /**
747 * Saves the {@link Session} related to this node. Note that all other unrelated
748 * modifications in this session will also be saved.
749 */
750 public static void save(Node node) {
751 try {
752 Session session = node.getSession();
753 // if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
754 // set(node, Property.JCR_LAST_MODIFIED, Instant.now());
755 // set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
756 // }
757 if (session.hasPendingChanges())
758 session.save();
759 } catch (RepositoryException e) {
760 throw new JcrException("Cannot save session related to " + node + " in workspace "
761 + session(node).getWorkspace().getName(), e);
762 }
763 }
764
765 /** Login to a JCR repository. */
766 public static Session login(Repository repository, String workspace) {
767 try {
768 return repository.login(workspace);
769 } catch (RepositoryException e) {
770 throw new IllegalArgumentException("Cannot login to repository", e);
771 }
772 }
773
774 /** Safely and silently logs out a session. */
775 public static void logout(Session session) {
776 try {
777 if (session != null)
778 if (session.isLive())
779 session.logout();
780 } catch (Exception e) {
781 // silent
782 }
783 }
784
785 /** Safely and silently logs out the underlying session. */
786 public static void logout(Node node) {
787 Jcr.logout(session(node));
788 }
789
790 /*
791 * SECURITY
792 */
793 /**
794 * Add a single privilege to a node.
795 *
796 * @see Privilege
797 */
798 public static void addPrivilege(Node node, String principal, String privilege) {
799 try {
800 Session session = node.getSession();
801 JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
802 } catch (RepositoryException e) {
803 throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
804 }
805 }
806
807 /*
808 * VERSIONING
809 */
810 /** Get checked out status. */
811 public static boolean isCheckedOut(Node node) {
812 try {
813 return node.isCheckedOut();
814 } catch (RepositoryException e) {
815 throw new JcrException("Cannot retrieve checked out status of " + node, e);
816 }
817 }
818
819 /** @see VersionManager#checkpoint(String) */
820 public static void checkpoint(Node node) {
821 try {
822 versionManager(node).checkpoint(node.getPath());
823 } catch (RepositoryException e) {
824 throw new JcrException("Cannot check in " + node, e);
825 }
826 }
827
828 /** @see VersionManager#checkin(String) */
829 public static void checkin(Node node) {
830 try {
831 versionManager(node).checkin(node.getPath());
832 } catch (RepositoryException e) {
833 throw new JcrException("Cannot check in " + node, e);
834 }
835 }
836
837 /** @see VersionManager#checkout(String) */
838 public static void checkout(Node node) {
839 try {
840 versionManager(node).checkout(node.getPath());
841 } catch (RepositoryException e) {
842 throw new JcrException("Cannot check out " + node, e);
843 }
844 }
845
846 /** Get the {@link VersionManager} related to this node. */
847 public static VersionManager versionManager(Node node) {
848 try {
849 return node.getSession().getWorkspace().getVersionManager();
850 } catch (RepositoryException e) {
851 throw new JcrException("Cannot get version manager from " + node, e);
852 }
853 }
854
855 /** Get the {@link VersionHistory} related to this node. */
856 public static VersionHistory getVersionHistory(Node node) {
857 try {
858 return versionManager(node).getVersionHistory(node.getPath());
859 } catch (RepositoryException e) {
860 throw new JcrException("Cannot get version history from " + node, e);
861 }
862 }
863
864 /**
865 * The linear versions of this version history in reverse order and without the
866 * root version.
867 */
868 public static List<Version> getLinearVersions(VersionHistory versionHistory) {
869 try {
870 List<Version> lst = new ArrayList<>();
871 VersionIterator vit = versionHistory.getAllLinearVersions();
872 while (vit.hasNext())
873 lst.add(vit.nextVersion());
874 lst.remove(0);
875 Collections.reverse(lst);
876 return lst;
877 } catch (RepositoryException e) {
878 throw new JcrException("Cannot get linear versions from " + versionHistory, e);
879 }
880 }
881
882 /** The frozen node related to this {@link Version}. */
883 public static Node getFrozenNode(Version version) {
884 try {
885 return version.getFrozenNode();
886 } catch (RepositoryException e) {
887 throw new JcrException("Cannot get frozen node from " + version, e);
888 }
889 }
890
891 /** Get the base {@link Version} related to this node. */
892 public static Version getBaseVersion(Node node) {
893 try {
894 return versionManager(node).getBaseVersion(node.getPath());
895 } catch (RepositoryException e) {
896 throw new JcrException("Cannot get base version from " + node, e);
897 }
898 }
899
900 /*
901 * FILES
902 */
903 /**
904 * Returns the size of this file.
905 *
906 * @see NodeType#NT_FILE
907 */
908 public static long getFileSize(Node fileNode) {
909 try {
910 if (!fileNode.isNodeType(NodeType.NT_FILE))
911 throw new IllegalArgumentException(fileNode + " must be a file.");
912 return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
913 } catch (RepositoryException e) {
914 throw new JcrException("Cannot get file size of " + fileNode, e);
915 }
916 }
917
918 /** Returns the size of this {@link Binary}. */
919 public static long getBinarySize(Binary binaryArg) {
920 try {
921 try (Bin binary = new Bin(binaryArg)) {
922 return binary.getSize();
923 }
924 } catch (RepositoryException e) {
925 throw new JcrException("Cannot get file size of binary " + binaryArg, e);
926 }
927 }
928
929 // QUERY
930 /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
931 public static Query createQuery(QueryManager qm, String sql, Object... args) {
932 // fix single quotes
933 sql = sql.replaceAll("'", "''");
934 String query = MessageFormat.format(sql, args);
935 try {
936 return qm.createQuery(query, Query.JCR_SQL2);
937 } catch (RepositoryException e) {
938 throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
939 }
940 }
941
942 /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
943 public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
944 Query query = createQuery(qm, sql, args);
945 try {
946 return query.execute().getNodes();
947 } catch (RepositoryException e) {
948 throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
949 }
950 }
951
952 /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
953 public static NodeIterator executeQuery(Session session, String sql, Object... args) {
954 QueryManager queryManager;
955 try {
956 queryManager = session.getWorkspace().getQueryManager();
957 } catch (RepositoryException e) {
958 throw new JcrException("Cannot get query manager from session " + session, e);
959 }
960 return executeQuery(queryManager, sql, args);
961 }
962
963 /**
964 * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
965 * single node at most.
966 *
967 * @return the node or <code>null</code> if not found.
968 */
969 public static Node getNode(QueryManager qm, String sql, Object... args) {
970 NodeIterator nit = executeQuery(qm, sql, args);
971 if (nit.hasNext()) {
972 Node node = nit.nextNode();
973 if (nit.hasNext())
974 throw new IllegalStateException(
975 "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
976 return node;
977 } else {
978 return null;
979 }
980 }
981
982 /**
983 * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
984 * single node at most.
985 *
986 * @return the node or <code>null</code> if not found.
987 */
988 public static Node getNode(Session session, String sql, Object... args) {
989 QueryManager queryManager;
990 try {
991 queryManager = session.getWorkspace().getQueryManager();
992 } catch (RepositoryException e) {
993 throw new JcrException("Cannot get query manager from session " + session, e);
994 }
995 return getNode(queryManager, sql, args);
996 }
997
998 public static Node getRowNode(Row row, String selectorName) {
999 try {
1000 return row.getNode(selectorName);
1001 } catch (RepositoryException e) {
1002 throw new JcrException("Cannot get node " + selectorName + " from row", e);
1003 }
1004 }
1005
1006 /** Singleton. */
1007 private Jcr() {
1008
1009 }
1010 }