]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.jcr/src/org/argeo/jcr/Jcr.java
Better deal with multiple user directories.
[lgpl/argeo-commons.git] / org.argeo.jcr / src / org / argeo / jcr / 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 * Returns the node name with its current index (useful for re-ordering).
208 *
209 * @see Node#getName()
210 * @see Node#getIndex()
211 * @throws JcrException caused by {@link RepositoryException}
212 */
213 public static String getIndexedName(Node node) {
214 try {
215 return node.getName() + "[" + node.getIndex() + "]";
216 } catch (RepositoryException e) {
217 throw new JcrException("Cannot get name of " + node, e);
218 }
219 }
220
221 /**
222 * @see Node#getProperty(String)
223 * @throws JcrException caused by {@link RepositoryException}
224 */
225 public static Property getProperty(Node node, String property) {
226 try {
227 if (node.hasProperty(property))
228 return node.getProperty(property);
229 else
230 return null;
231 } catch (RepositoryException e) {
232 throw new JcrException("Cannot get property " + property + " of " + node, e);
233 }
234 }
235
236 /**
237 * @see Node#getIndex()
238 * @throws JcrException caused by {@link RepositoryException}
239 */
240 public static int getIndex(Node node) {
241 try {
242 return node.getIndex();
243 } catch (RepositoryException e) {
244 throw new JcrException("Cannot get index of " + node, e);
245 }
246 }
247
248 /**
249 * If node has mixin {@link NodeType#MIX_TITLE}, return
250 * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
251 */
252 public static String getTitle(Node node) {
253 if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
254 return get(node, Property.JCR_TITLE);
255 else
256 return Jcr.getName(node);
257 }
258
259 /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
260 @SuppressWarnings("unchecked")
261 public static Iterable<Node> iterate(NodeIterator nodeIterator) {
262 return new Iterable<Node>() {
263
264 @Override
265 public Iterator<Node> iterator() {
266 return nodeIterator;
267 }
268 };
269 }
270
271 /**
272 * @return the children as an {@link Iterable} for use in for-each llops.
273 * @see Node#getNodes()
274 * @throws JcrException caused by {@link RepositoryException}
275 */
276 public static Iterable<Node> nodes(Node node) {
277 try {
278 return iterate(node.getNodes());
279 } catch (RepositoryException e) {
280 throw new JcrException("Cannot get children of " + node, e);
281 }
282 }
283
284 /**
285 * @return the children as a (possibly empty) {@link List}.
286 * @see Node#getNodes()
287 * @throws JcrException caused by {@link RepositoryException}
288 */
289 public static List<Node> getNodes(Node node) {
290 List<Node> nodes = new ArrayList<>();
291 try {
292 if (node.hasNodes()) {
293 NodeIterator nit = node.getNodes();
294 while (nit.hasNext())
295 nodes.add(nit.nextNode());
296 return nodes;
297 } else
298 return nodes;
299 } catch (RepositoryException e) {
300 throw new JcrException("Cannot get children of " + node, e);
301 }
302 }
303
304 /**
305 * @return the child or <code>null</node> if not found
306 * @see Node#getNode(String)
307 * @throws JcrException caused by {@link RepositoryException}
308 */
309 public static Node getNode(Node node, String child) {
310 try {
311 if (node.hasNode(child))
312 return node.getNode(child);
313 else
314 return null;
315 } catch (RepositoryException e) {
316 throw new JcrException("Cannot get child of " + node, e);
317 }
318 }
319
320 /**
321 * @return the node at this path or <code>null</node> if not found
322 * @see Session#getNode(String)
323 * @throws JcrException caused by {@link RepositoryException}
324 */
325 public static Node getNode(Session session, String path) {
326 try {
327 if (session.nodeExists(path))
328 return session.getNode(path);
329 else
330 return null;
331 } catch (RepositoryException e) {
332 throw new JcrException("Cannot get node " + path, e);
333 }
334 }
335
336 /**
337 * Add a node to this parent, setting its primary type and its mixins.
338 *
339 * @param parent the parent node
340 * @param name the name of the node, if <code>null</code>, the primary
341 * type will be used (typically for XML structures)
342 * @param primaryType the primary type, if <code>null</code>
343 * {@link NodeType#NT_UNSTRUCTURED} will be used.
344 * @param mixins the mixins
345 * @return the created node
346 * @see Node#addNode(String, String)
347 * @see Node#addMixin(String)
348 */
349 public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
350 if (name == null && primaryType == null)
351 throw new IllegalArgumentException("Both node name and primary type cannot be null");
352 try {
353 Node newNode = parent.addNode(name == null ? primaryType : name,
354 primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
355 for (String mixin : mixins) {
356 newNode.addMixin(mixin);
357 }
358 return newNode;
359 } catch (RepositoryException e) {
360 throw new JcrException("Cannot add node " + name + " to " + parent, e);
361 }
362 }
363
364 /**
365 * Add an {@link NodeType#NT_BASE} node to this parent.
366 *
367 * @param parent the parent node
368 * @param name the name of the node, cannot be <code>null</code>
369 * @return the created node
370 *
371 * @see Node#addNode(String)
372 */
373 public static Node addNode(Node parent, String name) {
374 if (name == null)
375 throw new IllegalArgumentException("Node name cannot be null");
376 try {
377 Node newNode = parent.addNode(name);
378 return newNode;
379 } catch (RepositoryException e) {
380 throw new JcrException("Cannot add node " + name + " to " + parent, e);
381 }
382 }
383
384 /**
385 * Add mixins to a node.
386 *
387 * @param node the node
388 * @param mixins the mixins
389 * @return the created node
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 /**
603 * Get a multiple property as a list, doing a best effort to cast it as the
604 * target list.
605 *
606 * @return the value of {@link Node#getProperty(String)}.
607 * @throws IllegalArgumentException if the value could not be cast
608 * @throws JcrException in case of unexpected
609 * {@link RepositoryException}
610 */
611 public static <T> List<T> getMultiple(Node node, String property) {
612 try {
613 if (node.hasProperty(property)) {
614 Property p = node.getProperty(property);
615 return getMultiple(p);
616 } else {
617 return null;
618 }
619 } catch (RepositoryException e) {
620 throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
621 }
622 }
623
624 /**
625 * Get a multiple property as a list, doing a best effort to cast it as the
626 * target list.
627 */
628 @SuppressWarnings("unchecked")
629 public static <T> List<T> getMultiple(Property p) {
630 try {
631 List<T> res = new ArrayList<>();
632 if (!p.isMultiple()) {
633 res.add((T) get(p.getValue()));
634 return res;
635 }
636 Value[] values = p.getValues();
637 for (Value value : values) {
638 res.add((T) get(value));
639 }
640 return res;
641 } catch (ClassCastException | RepositoryException e) {
642 throw new IllegalArgumentException("Cannot get property " + p, e);
643 }
644 }
645
646 /** Cast a {@link Value} to a standard Java object. */
647 public static Object get(Value value) {
648 Binary binary = null;
649 try {
650 switch (value.getType()) {
651 case PropertyType.STRING:
652 return value.getString();
653 case PropertyType.DOUBLE:
654 return (Double) value.getDouble();
655 case PropertyType.LONG:
656 return (Long) value.getLong();
657 case PropertyType.BOOLEAN:
658 return (Boolean) value.getBoolean();
659 case PropertyType.DATE:
660 return value.getDate();
661 case PropertyType.BINARY:
662 binary = value.getBinary();
663 byte[] arr = null;
664 try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
665 IOUtils.copy(in, out);
666 arr = out.toByteArray();
667 } catch (IOException e) {
668 throw new RuntimeException("Cannot read binary from " + value, e);
669 }
670 return arr;
671 default:
672 return value.getString();
673 }
674 } catch (RepositoryException e) {
675 throw new JcrException("Cannot cast value from " + value, e);
676 } finally {
677 if (binary != null)
678 binary.dispose();
679 }
680 }
681
682 /**
683 * Retrieves the {@link Session} related to this node.
684 *
685 * @deprecated Use {@link #getSession(Node)} instead.
686 */
687 @Deprecated
688 public static Session session(Node node) {
689 return getSession(node);
690 }
691
692 /** Retrieves the {@link Session} related to this node. */
693 public static Session getSession(Node node) {
694 try {
695 return node.getSession();
696 } catch (RepositoryException e) {
697 throw new JcrException("Cannot retrieve session related to " + node, e);
698 }
699 }
700
701 /** Retrieves the root node related to this session. */
702 public static Node getRootNode(Session session) {
703 try {
704 return session.getRootNode();
705 } catch (RepositoryException e) {
706 throw new JcrException("Cannot get root node for " + session, e);
707 }
708 }
709
710 /** Whether this item exists. */
711 public static boolean itemExists(Session session, String path) {
712 try {
713 return session.itemExists(path);
714 } catch (RepositoryException e) {
715 throw new JcrException("Cannot check whether " + path + " exists", e);
716 }
717 }
718
719 /**
720 * Saves the {@link Session} related to this node. Note that all other unrelated
721 * modifications in this session will also be saved.
722 */
723 public static void save(Node node) {
724 try {
725 Session session = node.getSession();
726 // if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
727 // set(node, Property.JCR_LAST_MODIFIED, Instant.now());
728 // set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
729 // }
730 if (session.hasPendingChanges())
731 session.save();
732 } catch (RepositoryException e) {
733 throw new JcrException("Cannot save session related to " + node + " in workspace "
734 + session(node).getWorkspace().getName(), e);
735 }
736 }
737
738 /** Login to a JCR repository. */
739 public static Session login(Repository repository, String workspace) {
740 try {
741 return repository.login(workspace);
742 } catch (RepositoryException e) {
743 throw new IllegalArgumentException("Cannot login to repository", e);
744 }
745 }
746
747 /** Safely and silently logs out a session. */
748 public static void logout(Session session) {
749 try {
750 if (session != null)
751 if (session.isLive())
752 session.logout();
753 } catch (Exception e) {
754 // silent
755 }
756 }
757
758 /** Safely and silently logs out the underlying session. */
759 public static void logout(Node node) {
760 Jcr.logout(session(node));
761 }
762
763 /*
764 * SECURITY
765 */
766 /**
767 * Add a single privilege to a node.
768 *
769 * @see Privilege
770 */
771 public static void addPrivilege(Node node, String principal, String privilege) {
772 try {
773 Session session = node.getSession();
774 JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
775 } catch (RepositoryException e) {
776 throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
777 }
778 }
779
780 /*
781 * VERSIONING
782 */
783 /** Get checked out status. */
784 public static boolean isCheckedOut(Node node) {
785 try {
786 return node.isCheckedOut();
787 } catch (RepositoryException e) {
788 throw new JcrException("Cannot retrieve checked out status of " + node, e);
789 }
790 }
791
792 /** @see VersionManager#checkpoint(String) */
793 public static void checkpoint(Node node) {
794 try {
795 versionManager(node).checkpoint(node.getPath());
796 } catch (RepositoryException e) {
797 throw new JcrException("Cannot check in " + node, e);
798 }
799 }
800
801 /** @see VersionManager#checkin(String) */
802 public static void checkin(Node node) {
803 try {
804 versionManager(node).checkin(node.getPath());
805 } catch (RepositoryException e) {
806 throw new JcrException("Cannot check in " + node, e);
807 }
808 }
809
810 /** @see VersionManager#checkout(String) */
811 public static void checkout(Node node) {
812 try {
813 versionManager(node).checkout(node.getPath());
814 } catch (RepositoryException e) {
815 throw new JcrException("Cannot check out " + node, e);
816 }
817 }
818
819 /** Get the {@link VersionManager} related to this node. */
820 public static VersionManager versionManager(Node node) {
821 try {
822 return node.getSession().getWorkspace().getVersionManager();
823 } catch (RepositoryException e) {
824 throw new JcrException("Cannot get version manager from " + node, e);
825 }
826 }
827
828 /** Get the {@link VersionHistory} related to this node. */
829 public static VersionHistory getVersionHistory(Node node) {
830 try {
831 return versionManager(node).getVersionHistory(node.getPath());
832 } catch (RepositoryException e) {
833 throw new JcrException("Cannot get version history from " + node, e);
834 }
835 }
836
837 /**
838 * The linear versions of this version history in reverse order and without the
839 * root version.
840 */
841 public static List<Version> getLinearVersions(VersionHistory versionHistory) {
842 try {
843 List<Version> lst = new ArrayList<>();
844 VersionIterator vit = versionHistory.getAllLinearVersions();
845 while (vit.hasNext())
846 lst.add(vit.nextVersion());
847 lst.remove(0);
848 Collections.reverse(lst);
849 return lst;
850 } catch (RepositoryException e) {
851 throw new JcrException("Cannot get linear versions from " + versionHistory, e);
852 }
853 }
854
855 /** The frozen node related to this {@link Version}. */
856 public static Node getFrozenNode(Version version) {
857 try {
858 return version.getFrozenNode();
859 } catch (RepositoryException e) {
860 throw new JcrException("Cannot get frozen node from " + version, e);
861 }
862 }
863
864 /** Get the base {@link Version} related to this node. */
865 public static Version getBaseVersion(Node node) {
866 try {
867 return versionManager(node).getBaseVersion(node.getPath());
868 } catch (RepositoryException e) {
869 throw new JcrException("Cannot get base version from " + node, e);
870 }
871 }
872
873 /*
874 * FILES
875 */
876 /**
877 * Returns the size of this file.
878 *
879 * @see NodeType#NT_FILE
880 */
881 public static long getFileSize(Node fileNode) {
882 try {
883 if (!fileNode.isNodeType(NodeType.NT_FILE))
884 throw new IllegalArgumentException(fileNode + " must be a file.");
885 return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
886 } catch (RepositoryException e) {
887 throw new JcrException("Cannot get file size of " + fileNode, e);
888 }
889 }
890
891 /** Returns the size of this {@link Binary}. */
892 public static long getBinarySize(Binary binaryArg) {
893 try {
894 try (Bin binary = new Bin(binaryArg)) {
895 return binary.getSize();
896 }
897 } catch (RepositoryException e) {
898 throw new JcrException("Cannot get file size of binary " + binaryArg, e);
899 }
900 }
901
902 // QUERY
903 /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
904 public static Query createQuery(QueryManager qm, String sql, Object... args) {
905 String query = MessageFormat.format(sql, args);
906 try {
907 return qm.createQuery(query, Query.JCR_SQL2);
908 } catch (RepositoryException e) {
909 throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
910 }
911 }
912
913 /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
914 public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
915 Query query = createQuery(qm, sql, args);
916 try {
917 return query.execute().getNodes();
918 } catch (RepositoryException e) {
919 throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
920 }
921 }
922
923 /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
924 public static NodeIterator executeQuery(Session session, String sql, Object... args) {
925 QueryManager queryManager;
926 try {
927 queryManager = session.getWorkspace().getQueryManager();
928 } catch (RepositoryException e) {
929 throw new JcrException("Cannot get query manager from session " + session, e);
930 }
931 return executeQuery(queryManager, sql, args);
932 }
933
934 /**
935 * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
936 * single node at most.
937 *
938 * @return the node or <code>null</code> if not found.
939 */
940 public static Node getNode(QueryManager qm, String sql, Object... args) {
941 NodeIterator nit = executeQuery(qm, sql, args);
942 if (nit.hasNext()) {
943 Node node = nit.nextNode();
944 if (nit.hasNext())
945 throw new IllegalStateException(
946 "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
947 return node;
948 } else {
949 return null;
950 }
951 }
952
953 /**
954 * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
955 * single node at most.
956 *
957 * @return the node or <code>null</code> if not found.
958 */
959 public static Node getNode(Session session, String sql, Object... args) {
960 QueryManager queryManager;
961 try {
962 queryManager = session.getWorkspace().getQueryManager();
963 } catch (RepositoryException e) {
964 throw new JcrException("Cannot get query manager from session " + session, e);
965 }
966 return getNode(queryManager, sql, args);
967 }
968
969 /** Singleton. */
970 private Jcr() {
971
972 }
973 }