]> git.argeo.org Git - lgpl/argeo-commons.git/blob - Jcr.java
fb6f610131842bf546196eeaa7eb3302f029586a
[lgpl/argeo-commons.git] / Jcr.java
1 package org.argeo.jcr;
2
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;
8 import java.util.Date;
9 import java.util.GregorianCalendar;
10 import java.util.Iterator;
11 import java.util.List;
12
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;
30
31 /**
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.
37 */
38 public class Jcr {
39 /**
40 * @see Node#isNodeType(String)
41 * @throws IllegalStateException caused by {@link RepositoryException}
42 */
43 public static boolean isNodeType(Node node, String nodeTypeName) {
44 try {
45 return node.isNodeType(nodeTypeName);
46 } catch (RepositoryException e) {
47 throw new IllegalStateException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
48 }
49 }
50
51 /**
52 * @see Node#hasNodes()
53 * @throws IllegalStateException caused by {@link RepositoryException}
54 */
55 public static boolean hasNodes(Node node) {
56 try {
57 return node.hasNodes();
58 } catch (RepositoryException e) {
59 throw new IllegalStateException("Cannot get whether " + node + " has children.", e);
60 }
61 }
62
63 /**
64 * @see Node#getParent()
65 * @throws IllegalStateException caused by {@link RepositoryException}
66 */
67 public static Node getParent(Node node) {
68 try {
69 return isRoot(node) ? null : node.getParent();
70 } catch (RepositoryException e) {
71 throw new IllegalStateException("Cannot get parent of " + node, e);
72 }
73 }
74
75 /**
76 * Whether this node is the root node.
77 *
78 * @throws IllegalStateException caused by {@link RepositoryException}
79 */
80 public static boolean isRoot(Node node) {
81 try {
82 return node.getDepth() == 0;
83 } catch (RepositoryException e) {
84 throw new IllegalStateException("Cannot get depth of " + node, e);
85 }
86 }
87
88 /**
89 * @see Node#getPath()
90 * @throws IllegalStateException caused by {@link RepositoryException}
91 */
92 public static String getPath(Node node) {
93 try {
94 return node.getPath();
95 } catch (RepositoryException e) {
96 throw new IllegalStateException("Cannot get path of " + node, e);
97 }
98 }
99
100 /**
101 * @see Node#getSession()
102 * @see Session#getWorkspace()
103 * @see Workspace#getName()
104 */
105 public static String getWorkspaceName(Node node) {
106 return session(node).getWorkspace().getName();
107 }
108
109 /**
110 * @see Node#getIdentifier()
111 * @throws IllegalStateException caused by {@link RepositoryException}
112 */
113 public static String getIdentifier(Node node) {
114 try {
115 return node.getIdentifier();
116 } catch (RepositoryException e) {
117 throw new IllegalStateException("Cannot get identifier of " + node, e);
118 }
119 }
120
121 /**
122 * @see Node#getName()
123 * @throws IllegalStateException caused by {@link RepositoryException}
124 */
125 public static String getName(Node node) {
126 try {
127 return node.getName();
128 } catch (RepositoryException e) {
129 throw new IllegalStateException("Cannot get name of " + node, e);
130 }
131 }
132
133 /**
134 * If node has mixin {@link NodeType#MIX_TITLE}, return
135 * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
136 */
137 public static String getTitle(Node node) {
138 if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
139 return get(node, Property.JCR_TITLE);
140 else
141 return Jcr.getName(node);
142 }
143
144 /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
145 @SuppressWarnings("unchecked")
146 public static Iterable<Node> iterate(NodeIterator nodeIterator) {
147 return new Iterable<Node>() {
148
149 @Override
150 public Iterator<Node> iterator() {
151 return nodeIterator;
152 }
153 };
154 }
155
156 /**
157 * @return the children as an {@link Iterable} for use in for-each llops.
158 * @see Node#getNodes()
159 * @throws IllegalStateException caused by {@link RepositoryException}
160 */
161 public static Iterable<Node> nodes(Node node) {
162 try {
163 return iterate(node.getNodes());
164 } catch (RepositoryException e) {
165 throw new IllegalStateException("Cannot get children of " + node, e);
166 }
167 }
168
169 /**
170 * @return the children as a (possibly empty) {@link List}.
171 * @see Node#getNodes()
172 * @throws IllegalStateException caused by {@link RepositoryException}
173 */
174 public static List<Node> getNodes(Node node) {
175 List<Node> nodes = new ArrayList<>();
176 try {
177 if (node.hasNodes()) {
178 NodeIterator nit = node.getNodes();
179 while (nit.hasNext())
180 nodes.add(nit.nextNode());
181 return nodes;
182 } else
183 return nodes;
184 } catch (RepositoryException e) {
185 throw new IllegalStateException("Cannot get children of " + node, e);
186 }
187 }
188
189 /**
190 * @return the child or <code>null</node> if not found
191 * @see Node#getNode(String)
192 * @throws IllegalStateException caused by {@link RepositoryException}
193 */
194 public static Node getNode(Node node, String child) {
195 try {
196 if (node.hasNode(child))
197 return node.getNode(child);
198 else
199 return null;
200 } catch (RepositoryException e) {
201 throw new IllegalStateException("Cannot get child of " + node, e);
202 }
203 }
204
205 /**
206 * @return the node at this path or <code>null</node> if not found
207 * @see Session#getNode(String)
208 * @throws IllegalStateException caused by {@link RepositoryException}
209 */
210 public static Node getNode(Session session, String path) {
211 try {
212 if (session.nodeExists(path))
213 return session.getNode(path);
214 else
215 return null;
216 } catch (RepositoryException e) {
217 throw new IllegalStateException("Cannot get node " + path, e);
218 }
219 }
220
221 /**
222 * @return the node with htis id or <code>null</node> if not found
223 * @see Session#getNodeByIdentifier(String)
224 * @throws IllegalStateException caused by {@link RepositoryException}
225 */
226 public static Node getNodeById(Session session, String id) {
227 try {
228 return session.getNodeByIdentifier(id);
229 } catch (ItemNotFoundException e) {
230 return null;
231 } catch (RepositoryException e) {
232 throw new IllegalStateException("Cannot get node with id " + id, e);
233 }
234 }
235
236 /**
237 * Set a property to the given value, or remove it if the value is
238 * <code>null</code>.
239 *
240 * @throws IllegalStateException caused by {@link RepositoryException}
241 */
242 public static void set(Node node, String property, Object value) {
243 try {
244 if (!node.hasProperty(property))
245 throw new IllegalArgumentException("No property " + property + " in " + node);
246 Property prop = node.getProperty(property);
247 if (value == null) {
248 prop.remove();
249 return;
250 }
251
252 if (value instanceof String)
253 prop.setValue((String) value);
254 else if (value instanceof Long)
255 prop.setValue((Long) value);
256 else if (value instanceof Double)
257 prop.setValue((Double) value);
258 else if (value instanceof Calendar)
259 prop.setValue((Calendar) value);
260 else if (value instanceof BigDecimal)
261 prop.setValue((BigDecimal) value);
262 else if (value instanceof Boolean)
263 prop.setValue((Boolean) value);
264 else if (value instanceof byte[])
265 JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
266 else if (value instanceof Instant) {
267 Instant instant = (Instant) value;
268 GregorianCalendar calendar = new GregorianCalendar();
269 calendar.setTime(Date.from(instant));
270 prop.setValue(calendar);
271 } else // try with toString()
272 prop.setValue(value.toString());
273 } catch (RepositoryException e) {
274 throw new IllegalStateException("Cannot set property " + property + " of " + node + " to " + value, e);
275 }
276 }
277
278 /**
279 * Get property as {@link String}.
280 *
281 * @return the value of
282 * {@link Node#getProperty(String)}.{@link Property#getString()} or
283 * <code>null</code> if the property does not exist.
284 * @throws IllegalStateException caused by {@link RepositoryException}
285 */
286 public static String get(Node node, String property) {
287 return get(node, property, null);
288 }
289
290 /**
291 * Get property as a {@link String}.
292 *
293 * @return the value of
294 * {@link Node#getProperty(String)}.{@link Property#getString()} or
295 * <code>defaultValue</code> if the property does not exist.
296 * @throws IllegalStateException caused by {@link RepositoryException}
297 */
298 public static String get(Node node, String property, String defaultValue) {
299 try {
300 if (node.hasProperty(property))
301 return node.getProperty(property).getString();
302 else
303 return defaultValue;
304 } catch (RepositoryException e) {
305 throw new IllegalStateException("Cannot retrieve property " + property + " from " + node);
306 }
307 }
308
309 /**
310 * Get property as a {@link Value}.
311 *
312 * @return {@link Node#getProperty(String)} or <code>null</code> if the property
313 * does not exist.
314 * @throws IllegalStateException caused by {@link RepositoryException}
315 */
316 public static Value getValue(Node node, String property) {
317 try {
318 if (node.hasProperty(property))
319 return node.getProperty(property).getValue();
320 else
321 return null;
322 } catch (RepositoryException e) {
323 throw new IllegalStateException("Cannot retrieve property " + property + " from " + node);
324 }
325 }
326
327 /**
328 * Get property doing a best effort to cast it as the target object.
329 *
330 * @return the value of {@link Node#getProperty(String)} or
331 * <code>defaultValue</code> if the property does not exist.
332 * @throws IllegalArgumentException if the value could not be cast
333 * @throws IllegalStateException in case of unexpected
334 * {@link RepositoryException}
335 */
336 @SuppressWarnings("unchecked")
337 public static <T> T getAs(Node node, String property, T defaultValue) {
338 try {
339 if (node.hasProperty(property)) {
340 Property p = node.getProperty(property);
341 try {
342 switch (p.getType()) {
343 case PropertyType.STRING:
344 return (T) node.getProperty(property).getString();
345 case PropertyType.DOUBLE:
346 return (T) (Double) node.getProperty(property).getDouble();
347 case PropertyType.LONG:
348 return (T) (Long) node.getProperty(property).getLong();
349 case PropertyType.BOOLEAN:
350 return (T) (Boolean) node.getProperty(property).getBoolean();
351 case PropertyType.DATE:
352 return (T) node.getProperty(property).getDate();
353 default:
354 return (T) node.getProperty(property).getString();
355 }
356 } catch (ClassCastException e) {
357 throw new IllegalArgumentException(
358 "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
359 }
360 } else {
361 return defaultValue;
362 }
363 } catch (RepositoryException e) {
364 throw new IllegalStateException("Cannot retrieve property " + property + " from " + node);
365 }
366 }
367
368 /**
369 * Retrieves the {@link Session} related to this node.
370 *
371 * @deprecated Use {@link #getSession(Node)} instead.
372 */
373 @Deprecated
374 public static Session session(Node node) {
375 return getSession(node);
376 }
377
378 /** Retrieves the {@link Session} related to this node. */
379 public static Session getSession(Node node) {
380 try {
381 return node.getSession();
382 } catch (RepositoryException e) {
383 throw new IllegalStateException("Cannot retrieve session related to " + node, e);
384 }
385 }
386
387 /** Retrieves the root node related to this session. */
388 public static Node getRootNode(Session session) {
389 try {
390 return session.getRootNode();
391 } catch (RepositoryException e) {
392 throw new IllegalStateException("Cannot get root node for " + session, e);
393 }
394 }
395
396 /**
397 * Saves the {@link Session} related to this node. Note that all other unrelated
398 * modifications in this session will also be saved.
399 */
400 public static void save(Node node) {
401 try {
402 Session session = node.getSession();
403 // if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
404 // set(node, Property.JCR_LAST_MODIFIED, Instant.now());
405 // set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
406 // }
407 if (session.hasPendingChanges())
408 session.save();
409 } catch (RepositoryException e) {
410 throw new IllegalStateException("Cannot save session related to " + node + " in workspace "
411 + session(node).getWorkspace().getName(), e);
412 }
413 }
414
415 /** Login to a JCR repository. */
416 public static Session login(Repository repository, String workspace) {
417 try {
418 return repository.login(workspace);
419 } catch (RepositoryException e) {
420 throw new IllegalArgumentException("Cannot login to repository", e);
421 }
422 }
423
424 /** Safely and silently logs out a session. */
425 public static void logout(Session session) {
426 try {
427 if (session != null)
428 if (session.isLive())
429 session.logout();
430 } catch (Exception e) {
431 // silent
432 }
433 }
434
435 /** Safely and silently logs out the underlying session. */
436 public static void logout(Node node) {
437 Jcr.logout(session(node));
438 }
439
440 /*
441 * SECURITY
442 */
443 /**
444 * Add a single privilege to a node.
445 *
446 * @see Privilege
447 */
448 public static void addPrivilege(Node node, String principal, String privilege) {
449 try {
450 Session session = node.getSession();
451 JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
452 } catch (RepositoryException e) {
453 throw new IllegalStateException("Cannot add privilege " + privilege + " to " + node, e);
454 }
455 }
456
457 /*
458 * VERSIONING
459 */
460 /** Get checked out status. */
461 public static boolean isCheckedOut(Node node) {
462 try {
463 return node.isCheckedOut();
464 } catch (RepositoryException e) {
465 throw new IllegalStateException("Cannot retrieve checked out status of " + node, e);
466 }
467 }
468
469 /** @see VersionManager#checkpoint(String) */
470 public static void checkpoint(Node node) {
471 try {
472 versionManager(node).checkpoint(node.getPath());
473 } catch (RepositoryException e) {
474 throw new IllegalStateException("Cannot check in " + node, e);
475 }
476 }
477
478 /** @see VersionManager#checkin(String) */
479 public static void checkin(Node node) {
480 try {
481 versionManager(node).checkin(node.getPath());
482 } catch (RepositoryException e) {
483 throw new IllegalStateException("Cannot check in " + node, e);
484 }
485 }
486
487 /** @see VersionManager#checkout(String) */
488 public static void checkout(Node node) {
489 try {
490 versionManager(node).checkout(node.getPath());
491 } catch (RepositoryException e) {
492 throw new IllegalStateException("Cannot check out " + node, e);
493 }
494 }
495
496 /** Get the {@link VersionManager} related to this node. */
497 public static VersionManager versionManager(Node node) {
498 try {
499 return node.getSession().getWorkspace().getVersionManager();
500 } catch (RepositoryException e) {
501 throw new IllegalStateException("Cannot get version manager from " + node, e);
502 }
503 }
504
505 /** Get the {@link VersionHistory} related to this node. */
506 public static VersionHistory getVersionHistory(Node node) {
507 try {
508 return versionManager(node).getVersionHistory(node.getPath());
509 } catch (RepositoryException e) {
510 throw new IllegalStateException("Cannot get version history from " + node, e);
511 }
512 }
513
514 /**
515 * The linear versions of this version history in reverse order and without the
516 * root version.
517 */
518 public static List<Version> getLinearVersions(VersionHistory versionHistory) {
519 try {
520 List<Version> lst = new ArrayList<>();
521 VersionIterator vit = versionHistory.getAllLinearVersions();
522 while (vit.hasNext())
523 lst.add(vit.nextVersion());
524 lst.remove(0);
525 Collections.reverse(lst);
526 return lst;
527 } catch (RepositoryException e) {
528 throw new IllegalStateException("Cannot get linear versions from " + versionHistory, e);
529 }
530 }
531
532 /** The frozen node related to this {@link Version}. */
533 public static Node getFrozenNode(Version version) {
534 try {
535 return version.getFrozenNode();
536 } catch (RepositoryException e) {
537 throw new IllegalStateException("Cannot get frozen node from " + version, e);
538 }
539 }
540
541 /** Get the base {@link Version} related to this node. */
542 public static Version getBaseVersion(Node node) {
543 try {
544 return versionManager(node).getBaseVersion(node.getPath());
545 } catch (RepositoryException e) {
546 throw new IllegalStateException("Cannot get base version from " + node, e);
547 }
548 }
549
550 /*
551 * FILES
552 */
553 /**
554 * Returns the size of this file.
555 *
556 * @see NodeType#NT_FILE
557 */
558 public static long getFileSize(Node fileNode) {
559 try {
560 if (!fileNode.isNodeType(NodeType.NT_FILE))
561 throw new IllegalArgumentException(fileNode + " must be a file.");
562 return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
563 } catch (RepositoryException e) {
564 throw new IllegalStateException("Cannot get file size of " + fileNode, e);
565 }
566 }
567
568 /** Returns the size of this {@link Binary}. */
569 public static long getBinarySize(Binary binaryArg) {
570 try {
571 try (Bin binary = new Bin(binaryArg)) {
572 return binary.getSize();
573 }
574 } catch (RepositoryException e) {
575 throw new IllegalStateException("Cannot get file size of binary " + binaryArg, e);
576 }
577 }
578
579 /** Singleton. */
580 private Jcr() {
581
582 }
583 }