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