View Javadoc
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 			if (session.hasPendingChanges())
369 				session.save();
370 		} catch (RepositoryException e) {
371 			throw new IllegalStateException("Cannot save session related to " + node + " in workspace "
372 					+ session(node).getWorkspace().getName(), e);
373 		}
374 	}
375 
376 	/** Login to a JCR repository. */
377 	public static Session login(Repository repository, String workspace) {
378 		try {
379 			return repository.login(workspace);
380 		} catch (RepositoryException e) {
381 			throw new IllegalArgumentException("Cannot login to repository", e);
382 		}
383 	}
384 
385 	/** Safely and silently logs out a session. */
386 	public static void logout(Session session) {
387 		try {
388 			if (session != null)
389 				if (session.isLive())
390 					session.logout();
391 		} catch (Exception e) {
392 			// silent
393 		}
394 	}
395 
396 	/*
397 	 * SECURITY
398 	 */
399 	/**
400 	 * Add a single privilege to a node.
401 	 * 
402 	 * @see Privilege
403 	 */
404 	public static void addPrivilege(Node node, String principal, String privilege) {
405 		try {
406 			Session session = node.getSession();
407 			JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
408 		} catch (RepositoryException e) {
409 			throw new IllegalStateException("Cannot add privilege " + privilege + " to " + node, e);
410 		}
411 	}
412 
413 	/*
414 	 * VERSIONING
415 	 */
416 	/** Get checked out status. */
417 	public static boolean isCheckedOut(Node node) {
418 		try {
419 			return node.isCheckedOut();
420 		} catch (RepositoryException e) {
421 			throw new IllegalStateException("Cannot retrieve checked out status of " + node, e);
422 		}
423 	}
424 
425 	/** @see VersionManager#checkpoint(String) */
426 	public static void checkpoint(Node node) {
427 		try {
428 			versionManager(node).checkpoint(node.getPath());
429 		} catch (RepositoryException e) {
430 			throw new IllegalStateException("Cannot check in " + node, e);
431 		}
432 	}
433 
434 	/** @see VersionManager#checkin(String) */
435 	public static void checkin(Node node) {
436 		try {
437 			versionManager(node).checkin(node.getPath());
438 		} catch (RepositoryException e) {
439 			throw new IllegalStateException("Cannot check in " + node, e);
440 		}
441 	}
442 
443 	/** @see VersionManager#checkout(String) */
444 	public static void checkout(Node node) {
445 		try {
446 			versionManager(node).checkout(node.getPath());
447 		} catch (RepositoryException e) {
448 			throw new IllegalStateException("Cannot check out " + node, e);
449 		}
450 	}
451 
452 	/** Get the {@link VersionManager} related to this node. */
453 	public static VersionManager versionManager(Node node) {
454 		try {
455 			return node.getSession().getWorkspace().getVersionManager();
456 		} catch (RepositoryException e) {
457 			throw new IllegalStateException("Cannot get version manager from " + node, e);
458 		}
459 	}
460 
461 	/** Get the {@link VersionHistory} related to this node. */
462 	public static VersionHistory getVersionHistory(Node node) {
463 		try {
464 			return versionManager(node).getVersionHistory(node.getPath());
465 		} catch (RepositoryException e) {
466 			throw new IllegalStateException("Cannot get version history from " + node, e);
467 		}
468 	}
469 
470 	/**
471 	 * The linear versions of this version history in reverse order and without the
472 	 * root version.
473 	 */
474 	public static List<Version> getLinearVersions(VersionHistory versionHistory) {
475 		try {
476 			List<Version> lst = new ArrayList<>();
477 			VersionIterator vit = versionHistory.getAllLinearVersions();
478 			while (vit.hasNext())
479 				lst.add(vit.nextVersion());
480 			lst.remove(0);
481 			Collections.reverse(lst);
482 			return lst;
483 		} catch (RepositoryException e) {
484 			throw new IllegalStateException("Cannot get linear versions from " + versionHistory, e);
485 		}
486 	}
487 
488 	/** The frozen node related to this {@link Version}. */
489 	public static Node getFrozenNode(Version version) {
490 		try {
491 			return version.getFrozenNode();
492 		} catch (RepositoryException e) {
493 			throw new IllegalStateException("Cannot get frozen node from " + version, e);
494 		}
495 	}
496 
497 	/** Get the base {@link Version} related to this node. */
498 	public static Version getBaseVersion(Node node) {
499 		try {
500 			return versionManager(node).getBaseVersion(node.getPath());
501 		} catch (RepositoryException e) {
502 			throw new IllegalStateException("Cannot get base version from " + node, e);
503 		}
504 	}
505 
506 	/*
507 	 * FILES
508 	 */
509 	/**
510 	 * Returns the size of this file.
511 	 * 
512 	 * @see NodeType#NT_FILE
513 	 */
514 	public static long getFileSize(Node fileNode) {
515 		try {
516 			if (!fileNode.isNodeType(NodeType.NT_FILE))
517 				throw new IllegalArgumentException(fileNode + " must be a file.");
518 			return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
519 		} catch (RepositoryException e) {
520 			throw new IllegalStateException("Cannot get file size of " + fileNode, e);
521 		}
522 	}
523 
524 	/** Returns the size of this {@link Binary}. */
525 	public static long getBinarySize(Binary binaryArg) {
526 		try {
527 			try (Bin binary = new Bin(binaryArg)) {
528 				return binary.getSize();
529 			}
530 		} catch (RepositoryException e) {
531 			throw new IllegalStateException("Cannot get file size of binary " + binaryArg, e);
532 		}
533 	}
534 
535 	/** Singleton. */
536 	private Jcr() {
537 
538 	}
539 }