3 import java
.math
.BigDecimal
;
4 import java
.time
.Instant
;
5 import java
.util
.ArrayList
;
6 import java
.util
.Calendar
;
7 import java
.util
.Collections
;
9 import java
.util
.GregorianCalendar
;
10 import java
.util
.Iterator
;
11 import java
.util
.List
;
13 import javax
.jcr
.Binary
;
14 import javax
.jcr
.ItemNotFoundException
;
15 import javax
.jcr
.Node
;
16 import javax
.jcr
.NodeIterator
;
17 import javax
.jcr
.Property
;
18 import javax
.jcr
.PropertyType
;
19 import javax
.jcr
.Repository
;
20 import javax
.jcr
.RepositoryException
;
21 import javax
.jcr
.Session
;
22 import javax
.jcr
.Value
;
23 import javax
.jcr
.Workspace
;
24 import javax
.jcr
.nodetype
.NodeType
;
25 import javax
.jcr
.security
.Privilege
;
26 import javax
.jcr
.version
.Version
;
27 import javax
.jcr
.version
.VersionHistory
;
28 import javax
.jcr
.version
.VersionIterator
;
29 import javax
.jcr
.version
.VersionManager
;
32 * Utility class whose purpose is to make using JCR less verbose by
33 * systematically using unchecked exceptions and returning <code>null</code>
34 * when something is not found. This is especially useful when writing user
35 * interfaces (such as with SWT) where listeners and callbacks expect unchecked
36 * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
40 * The name of a node which will be serialized as XML text, as per section 7.3.1
41 * of the JCR 2.0 specifications.
43 public final static String JCR_XMLTEXT
= "jcr:xmltext";
45 * The name of a property which will be serialized as XML text, as per section
46 * 7.3.1 of the JCR 2.0 specifications.
48 public final static String JCR_XMLCHARACTERS
= "jcr:xmlcharacters";
50 * <code>jcr:name</code>, when used in another context than
51 * {@link Property#JCR_NAME}, typically to name a node rather than a property.
53 public final static String JCR_NAME
= "jcr:name";
55 * <code>jcr:path</code>, when used in another context than
56 * {@link Property#JCR_PATH}, typically to name a node rather than a property.
58 public final static String JCR_PATH
= "jcr:path";
60 * <code>jcr:primaryType</code> with prefix instead of namespace (as in
61 * {@link Property#JCR_PRIMARY_TYPE}.
63 public final static String JCR_PRIMARY_TYPE
= "jcr:primaryType";
65 * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
66 * {@link Property#JCR_MIXIN_TYPES}.
68 public final static String JCR_MIXIN_TYPES
= "jcr:mixinTypes";
70 * <code>jcr:uuid</code> with prefix instead of namespace (as in
71 * {@link Property#JCR_UUID}.
73 public final static String JCR_UUID
= "jcr:uuid";
75 * <code>jcr:created</code> with prefix instead of namespace (as in
76 * {@link Property#JCR_CREATED}.
78 public final static String JCR_CREATED
= "jcr:created";
80 * <code>jcr:createdBy</code> with prefix instead of namespace (as in
81 * {@link Property#JCR_CREATED_BY}.
83 public final static String JCR_CREATED_BY
= "jcr:createdBy";
85 * <code>jcr:lastModified</code> with prefix instead of namespace (as in
86 * {@link Property#JCR_LAST_MODIFIED}.
88 public final static String JCR_LAST_MODIFIED
= "jcr:lastModified";
90 * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
91 * {@link Property#JCR_LAST_MODIFIED_BY}.
93 public final static String JCR_LAST_MODIFIED_BY
= "jcr:lastModifiedBy";
96 * @see Node#isNodeType(String)
97 * @throws JcrException caused by {@link RepositoryException}
99 public static boolean isNodeType(Node node
, String nodeTypeName
) {
101 return node
.isNodeType(nodeTypeName
);
102 } catch (RepositoryException e
) {
103 throw new JcrException("Cannot get whether " + node
+ " is of type " + nodeTypeName
, e
);
108 * @see Node#hasNodes()
109 * @throws JcrException caused by {@link RepositoryException}
111 public static boolean hasNodes(Node node
) {
113 return node
.hasNodes();
114 } catch (RepositoryException e
) {
115 throw new JcrException("Cannot get whether " + node
+ " has children.", e
);
120 * @see Node#getParent()
121 * @throws JcrException caused by {@link RepositoryException}
123 public static Node
getParent(Node node
) {
125 return isRoot(node
) ?
null : node
.getParent();
126 } catch (RepositoryException e
) {
127 throw new JcrException("Cannot get parent of " + node
, e
);
132 * Whether this node is the root node.
134 * @throws JcrException caused by {@link RepositoryException}
136 public static boolean isRoot(Node node
) {
138 return node
.getDepth() == 0;
139 } catch (RepositoryException e
) {
140 throw new JcrException("Cannot get depth of " + node
, e
);
145 * @see Node#getPath()
146 * @throws JcrException caused by {@link RepositoryException}
148 public static String
getPath(Node node
) {
150 return node
.getPath();
151 } catch (RepositoryException e
) {
152 throw new JcrException("Cannot get path of " + node
, e
);
157 * @see Node#getSession()
158 * @see Session#getWorkspace()
159 * @see Workspace#getName()
161 public static String
getWorkspaceName(Node node
) {
162 return session(node
).getWorkspace().getName();
166 * @see Node#getIdentifier()
167 * @throws JcrException caused by {@link RepositoryException}
169 public static String
getIdentifier(Node node
) {
171 return node
.getIdentifier();
172 } catch (RepositoryException e
) {
173 throw new JcrException("Cannot get identifier of " + node
, e
);
178 * @see Node#getName()
179 * @throws JcrException caused by {@link RepositoryException}
181 public static String
getName(Node node
) {
183 return node
.getName();
184 } catch (RepositoryException e
) {
185 throw new JcrException("Cannot get name of " + node
, e
);
190 * If node has mixin {@link NodeType#MIX_TITLE}, return
191 * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
193 public static String
getTitle(Node node
) {
194 if (Jcr
.isNodeType(node
, NodeType
.MIX_TITLE
))
195 return get(node
, Property
.JCR_TITLE
);
197 return Jcr
.getName(node
);
200 /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
201 @SuppressWarnings("unchecked")
202 public static Iterable
<Node
> iterate(NodeIterator nodeIterator
) {
203 return new Iterable
<Node
>() {
206 public Iterator
<Node
> iterator() {
213 * @return the children as an {@link Iterable} for use in for-each llops.
214 * @see Node#getNodes()
215 * @throws JcrException caused by {@link RepositoryException}
217 public static Iterable
<Node
> nodes(Node node
) {
219 return iterate(node
.getNodes());
220 } catch (RepositoryException e
) {
221 throw new JcrException("Cannot get children of " + node
, e
);
226 * @return the children as a (possibly empty) {@link List}.
227 * @see Node#getNodes()
228 * @throws JcrException caused by {@link RepositoryException}
230 public static List
<Node
> getNodes(Node node
) {
231 List
<Node
> nodes
= new ArrayList
<>();
233 if (node
.hasNodes()) {
234 NodeIterator nit
= node
.getNodes();
235 while (nit
.hasNext())
236 nodes
.add(nit
.nextNode());
240 } catch (RepositoryException e
) {
241 throw new JcrException("Cannot get children of " + node
, e
);
246 * @return the child or <code>null</node> if not found
247 * @see Node#getNode(String)
248 * @throws JcrException caused by {@link RepositoryException}
250 public static Node
getNode(Node node
, String child
) {
252 if (node
.hasNode(child
))
253 return node
.getNode(child
);
256 } catch (RepositoryException e
) {
257 throw new JcrException("Cannot get child of " + node
, e
);
262 * @return the node at this path or <code>null</node> if not found
263 * @see Session#getNode(String)
264 * @throws JcrException caused by {@link RepositoryException}
266 public static Node
getNode(Session session
, String path
) {
268 if (session
.nodeExists(path
))
269 return session
.getNode(path
);
272 } catch (RepositoryException e
) {
273 throw new JcrException("Cannot get node " + path
, e
);
278 * @return the node with htis id or <code>null</node> if not found
279 * @see Session#getNodeByIdentifier(String)
280 * @throws JcrException caused by {@link RepositoryException}
282 public static Node
getNodeById(Session session
, String id
) {
284 return session
.getNodeByIdentifier(id
);
285 } catch (ItemNotFoundException e
) {
287 } catch (RepositoryException e
) {
288 throw new JcrException("Cannot get node with id " + id
, e
);
293 * Set a property to the given value, or remove it if the value is
296 * @throws JcrException caused by {@link RepositoryException}
298 public static void set(Node node
, String property
, Object value
) {
300 if (!node
.hasProperty(property
)) {
302 node
.setProperty(property
, value
.toString());
304 // throw new IllegalArgumentException("No property " + property + " in " +
307 Property prop
= node
.getProperty(property
);
313 if (value
instanceof String
)
314 prop
.setValue((String
) value
);
315 else if (value
instanceof Long
)
316 prop
.setValue((Long
) value
);
317 else if (value
instanceof Integer
)
318 prop
.setValue(((Integer
) value
).longValue());
319 else if (value
instanceof Double
)
320 prop
.setValue((Double
) value
);
321 else if (value
instanceof Float
)
322 prop
.setValue(((Float
) value
).doubleValue());
323 else if (value
instanceof Calendar
)
324 prop
.setValue((Calendar
) value
);
325 else if (value
instanceof BigDecimal
)
326 prop
.setValue((BigDecimal
) value
);
327 else if (value
instanceof Boolean
)
328 prop
.setValue((Boolean
) value
);
329 else if (value
instanceof byte[])
330 JcrUtils
.setBinaryAsBytes(prop
, (byte[]) value
);
331 else if (value
instanceof Instant
) {
332 Instant instant
= (Instant
) value
;
333 GregorianCalendar calendar
= new GregorianCalendar();
334 calendar
.setTime(Date
.from(instant
));
335 prop
.setValue(calendar
);
336 } else // try with toString()
337 prop
.setValue(value
.toString());
338 } catch (RepositoryException e
) {
339 throw new JcrException("Cannot set property " + property
+ " of " + node
+ " to " + value
, e
);
344 * Get property as {@link String}.
346 * @return the value of
347 * {@link Node#getProperty(String)}.{@link Property#getString()} or
348 * <code>null</code> if the property does not exist.
349 * @throws JcrException caused by {@link RepositoryException}
351 public static String
get(Node node
, String property
) {
352 return get(node
, property
, null);
356 * Get property as a {@link String}. If the property is multiple it returns the
359 * @return the value of
360 * {@link Node#getProperty(String)}.{@link Property#getString()} or
361 * <code>defaultValue</code> if the property does not exist.
362 * @throws JcrException caused by {@link RepositoryException}
364 public static String
get(Node node
, String property
, String defaultValue
) {
366 if (node
.hasProperty(property
)) {
367 Property p
= node
.getProperty(property
);
369 return p
.getString();
371 Value
[] values
= p
.getValues();
372 if (values
.length
== 0)
375 return values
[0].getString();
379 } catch (RepositoryException e
) {
380 throw new JcrException("Cannot retrieve property " + property
+ " from " + node
, e
);
385 * Get property as a {@link Value}.
387 * @return {@link Node#getProperty(String)} or <code>null</code> if the property
389 * @throws JcrException caused by {@link RepositoryException}
391 public static Value
getValue(Node node
, String property
) {
393 if (node
.hasProperty(property
))
394 return node
.getProperty(property
).getValue();
397 } catch (RepositoryException e
) {
398 throw new JcrException("Cannot retrieve property " + property
+ " from " + node
, e
);
403 * Get property doing a best effort to cast it as the target object.
405 * @return the value of {@link Node#getProperty(String)} or
406 * <code>defaultValue</code> if the property does not exist.
407 * @throws IllegalArgumentException if the value could not be cast
408 * @throws JcrException in case of unexpected
409 * {@link RepositoryException}
411 @SuppressWarnings("unchecked")
412 public static <T
> T
getAs(Node node
, String property
, T defaultValue
) {
414 // TODO deal with multiple
415 if (node
.hasProperty(property
)) {
416 Property p
= node
.getProperty(property
);
418 switch (p
.getType()) {
419 case PropertyType
.STRING
:
420 return (T
) node
.getProperty(property
).getString();
421 case PropertyType
.DOUBLE
:
422 return (T
) (Double
) node
.getProperty(property
).getDouble();
423 case PropertyType
.LONG
:
424 return (T
) (Long
) node
.getProperty(property
).getLong();
425 case PropertyType
.BOOLEAN
:
426 return (T
) (Boolean
) node
.getProperty(property
).getBoolean();
427 case PropertyType
.DATE
:
428 return (T
) node
.getProperty(property
).getDate();
430 return (T
) node
.getProperty(property
).getString();
432 } catch (ClassCastException e
) {
433 throw new IllegalArgumentException(
434 "Cannot cast property of type " + PropertyType
.nameFromValue(p
.getType()), e
);
439 } catch (RepositoryException e
) {
440 throw new JcrException("Cannot retrieve property " + property
+ " from " + node
, e
);
445 * Retrieves the {@link Session} related to this node.
447 * @deprecated Use {@link #getSession(Node)} instead.
450 public static Session
session(Node node
) {
451 return getSession(node
);
454 /** Retrieves the {@link Session} related to this node. */
455 public static Session
getSession(Node node
) {
457 return node
.getSession();
458 } catch (RepositoryException e
) {
459 throw new JcrException("Cannot retrieve session related to " + node
, e
);
463 /** Retrieves the root node related to this session. */
464 public static Node
getRootNode(Session session
) {
466 return session
.getRootNode();
467 } catch (RepositoryException e
) {
468 throw new JcrException("Cannot get root node for " + session
, e
);
472 /** Whether this item exists. */
473 public static boolean itemExists(Session session
, String path
) {
475 return session
.itemExists(path
);
476 } catch (RepositoryException e
) {
477 throw new JcrException("Cannot check whether " + path
+ " exists", e
);
482 * Saves the {@link Session} related to this node. Note that all other unrelated
483 * modifications in this session will also be saved.
485 public static void save(Node node
) {
487 Session session
= node
.getSession();
488 // if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
489 // set(node, Property.JCR_LAST_MODIFIED, Instant.now());
490 // set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
492 if (session
.hasPendingChanges())
494 } catch (RepositoryException e
) {
495 throw new JcrException("Cannot save session related to " + node
+ " in workspace "
496 + session(node
).getWorkspace().getName(), e
);
500 /** Login to a JCR repository. */
501 public static Session
login(Repository repository
, String workspace
) {
503 return repository
.login(workspace
);
504 } catch (RepositoryException e
) {
505 throw new IllegalArgumentException("Cannot login to repository", e
);
509 /** Safely and silently logs out a session. */
510 public static void logout(Session session
) {
513 if (session
.isLive())
515 } catch (Exception e
) {
520 /** Safely and silently logs out the underlying session. */
521 public static void logout(Node node
) {
522 Jcr
.logout(session(node
));
529 * Add a single privilege to a node.
533 public static void addPrivilege(Node node
, String principal
, String privilege
) {
535 Session session
= node
.getSession();
536 JcrUtils
.addPrivilege(session
, node
.getPath(), principal
, privilege
);
537 } catch (RepositoryException e
) {
538 throw new JcrException("Cannot add privilege " + privilege
+ " to " + node
, e
);
545 /** Get checked out status. */
546 public static boolean isCheckedOut(Node node
) {
548 return node
.isCheckedOut();
549 } catch (RepositoryException e
) {
550 throw new JcrException("Cannot retrieve checked out status of " + node
, e
);
554 /** @see VersionManager#checkpoint(String) */
555 public static void checkpoint(Node node
) {
557 versionManager(node
).checkpoint(node
.getPath());
558 } catch (RepositoryException e
) {
559 throw new JcrException("Cannot check in " + node
, e
);
563 /** @see VersionManager#checkin(String) */
564 public static void checkin(Node node
) {
566 versionManager(node
).checkin(node
.getPath());
567 } catch (RepositoryException e
) {
568 throw new JcrException("Cannot check in " + node
, e
);
572 /** @see VersionManager#checkout(String) */
573 public static void checkout(Node node
) {
575 versionManager(node
).checkout(node
.getPath());
576 } catch (RepositoryException e
) {
577 throw new JcrException("Cannot check out " + node
, e
);
581 /** Get the {@link VersionManager} related to this node. */
582 public static VersionManager
versionManager(Node node
) {
584 return node
.getSession().getWorkspace().getVersionManager();
585 } catch (RepositoryException e
) {
586 throw new JcrException("Cannot get version manager from " + node
, e
);
590 /** Get the {@link VersionHistory} related to this node. */
591 public static VersionHistory
getVersionHistory(Node node
) {
593 return versionManager(node
).getVersionHistory(node
.getPath());
594 } catch (RepositoryException e
) {
595 throw new JcrException("Cannot get version history from " + node
, e
);
600 * The linear versions of this version history in reverse order and without the
603 public static List
<Version
> getLinearVersions(VersionHistory versionHistory
) {
605 List
<Version
> lst
= new ArrayList
<>();
606 VersionIterator vit
= versionHistory
.getAllLinearVersions();
607 while (vit
.hasNext())
608 lst
.add(vit
.nextVersion());
610 Collections
.reverse(lst
);
612 } catch (RepositoryException e
) {
613 throw new JcrException("Cannot get linear versions from " + versionHistory
, e
);
617 /** The frozen node related to this {@link Version}. */
618 public static Node
getFrozenNode(Version version
) {
620 return version
.getFrozenNode();
621 } catch (RepositoryException e
) {
622 throw new JcrException("Cannot get frozen node from " + version
, e
);
626 /** Get the base {@link Version} related to this node. */
627 public static Version
getBaseVersion(Node node
) {
629 return versionManager(node
).getBaseVersion(node
.getPath());
630 } catch (RepositoryException e
) {
631 throw new JcrException("Cannot get base version from " + node
, e
);
639 * Returns the size of this file.
641 * @see NodeType#NT_FILE
643 public static long getFileSize(Node fileNode
) {
645 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
646 throw new IllegalArgumentException(fileNode
+ " must be a file.");
647 return getBinarySize(fileNode
.getNode(Node
.JCR_CONTENT
).getProperty(Property
.JCR_DATA
).getBinary());
648 } catch (RepositoryException e
) {
649 throw new JcrException("Cannot get file size of " + fileNode
, e
);
653 /** Returns the size of this {@link Binary}. */
654 public static long getBinarySize(Binary binaryArg
) {
656 try (Bin binary
= new Bin(binaryArg
)) {
657 return binary
.getSize();
659 } catch (RepositoryException e
) {
660 throw new JcrException("Cannot get file size of binary " + binaryArg
, e
);