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