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