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