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