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