3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.ByteArrayOutputStream
;
6 import java
.io
.FileInputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.net
.MalformedURLException
;
11 import java
.security
.MessageDigest
;
12 import java
.security
.NoSuchAlgorithmException
;
13 import java
.security
.Principal
;
14 import java
.text
.DateFormat
;
15 import java
.text
.ParseException
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Calendar
;
18 import java
.util
.Collections
;
19 import java
.util
.Date
;
20 import java
.util
.GregorianCalendar
;
21 import java
.util
.Iterator
;
22 import java
.util
.List
;
24 import java
.util
.TreeMap
;
26 import javax
.jcr
.Binary
;
27 import javax
.jcr
.Credentials
;
28 import javax
.jcr
.NamespaceRegistry
;
29 import javax
.jcr
.NoSuchWorkspaceException
;
30 import javax
.jcr
.Node
;
31 import javax
.jcr
.NodeIterator
;
32 import javax
.jcr
.Property
;
33 import javax
.jcr
.PropertyIterator
;
34 import javax
.jcr
.PropertyType
;
35 import javax
.jcr
.Repository
;
36 import javax
.jcr
.RepositoryException
;
37 import javax
.jcr
.Session
;
38 import javax
.jcr
.Value
;
39 import javax
.jcr
.Workspace
;
40 import javax
.jcr
.nodetype
.NodeType
;
41 import javax
.jcr
.observation
.EventListener
;
42 import javax
.jcr
.query
.Query
;
43 import javax
.jcr
.query
.QueryResult
;
44 import javax
.jcr
.security
.AccessControlEntry
;
45 import javax
.jcr
.security
.AccessControlList
;
46 import javax
.jcr
.security
.AccessControlManager
;
47 import javax
.jcr
.security
.AccessControlPolicy
;
48 import javax
.jcr
.security
.AccessControlPolicyIterator
;
49 import javax
.jcr
.security
.Privilege
;
51 import org
.apache
.commons
.io
.IOUtils
;
52 import org
.apache
.commons
.logging
.Log
;
53 import org
.apache
.commons
.logging
.LogFactory
;
55 /** Utility methods to simplify common JCR operations. */
56 public class JcrUtils
{
58 final private static Log log
= LogFactory
.getLog(JcrUtils
.class);
61 * Not complete yet. See
62 * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
65 public final static char[] INVALID_NAME_CHARACTERS
= { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<',
68 /** Prevents instantiation */
73 * Queries one single node.
75 * @return one single node or null if none was found
76 * @throws JcrException if more than one node was found
78 public static Node
querySingleNode(Query query
) {
79 NodeIterator nodeIterator
;
81 QueryResult queryResult
= query
.execute();
82 nodeIterator
= queryResult
.getNodes();
83 } catch (RepositoryException e
) {
84 throw new JcrException("Cannot execute query " + query
, e
);
87 if (nodeIterator
.hasNext())
88 node
= nodeIterator
.nextNode();
92 if (nodeIterator
.hasNext())
93 throw new IllegalArgumentException("Query returned more than one node.");
97 /** Retrieves the node name from the provided path */
98 public static String
nodeNameFromPath(String path
) {
101 if (path
.charAt(0) != '/')
102 throw new IllegalArgumentException("Path " + path
+ " must start with a '/'");
104 if (pathT
.charAt(pathT
.length() - 1) == '/')
105 pathT
= pathT
.substring(0, pathT
.length() - 2);
107 int index
= pathT
.lastIndexOf('/');
108 return pathT
.substring(index
+ 1);
111 /** Retrieves the parent path of the provided path */
112 public static String
parentPath(String path
) {
113 if (path
.equals("/"))
114 throw new IllegalArgumentException("Root path '/' has no parent path");
115 if (path
.charAt(0) != '/')
116 throw new IllegalArgumentException("Path " + path
+ " must start with a '/'");
118 if (pathT
.charAt(pathT
.length() - 1) == '/')
119 pathT
= pathT
.substring(0, pathT
.length() - 2);
121 int index
= pathT
.lastIndexOf('/');
122 return pathT
.substring(0, index
);
125 /** The provided data as a path ('/' at the end, not the beginning) */
126 public static String
dateAsPath(Calendar cal
) {
127 return dateAsPath(cal
, false);
131 * Creates a deep path based on a URL:
132 * http://subdomain.example.com/to/content?args becomes
133 * com/example/subdomain/to/content
135 public static String
urlAsPath(String url
) {
137 URL u
= new URL(url
);
138 StringBuffer path
= new StringBuffer(url
.length());
140 path
.append(hostAsPath(u
.getHost()));
141 // we don't put port since it may not always be there and may change
142 path
.append(u
.getPath());
143 return path
.toString();
144 } catch (MalformedURLException e
) {
145 throw new IllegalArgumentException("Cannot generate URL path for " + url
, e
);
149 /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
150 public static void urlToAddressProperties(Node node
, String url
) {
152 URL u
= new URL(url
);
153 node
.setProperty(Property
.JCR_PROTOCOL
, u
.getProtocol());
154 node
.setProperty(Property
.JCR_HOST
, u
.getHost());
155 node
.setProperty(Property
.JCR_PORT
, Integer
.toString(u
.getPort()));
156 node
.setProperty(Property
.JCR_PATH
, normalizePath(u
.getPath()));
157 } catch (RepositoryException e
) {
158 throw new JcrException("Cannot set URL " + url
+ " as nt:address properties", e
);
159 } catch (MalformedURLException e
) {
160 throw new IllegalArgumentException("Cannot set URL " + url
+ " as nt:address properties", e
);
164 /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
165 public static String
urlFromAddressProperties(Node node
) {
167 URL u
= new URL(node
.getProperty(Property
.JCR_PROTOCOL
).getString(),
168 node
.getProperty(Property
.JCR_HOST
).getString(),
169 (int) node
.getProperty(Property
.JCR_PORT
).getLong(),
170 node
.getProperty(Property
.JCR_PATH
).getString());
172 } catch (RepositoryException e
) {
173 throw new JcrException("Cannot get URL from nt:address properties of " + node
, e
);
174 } catch (MalformedURLException e
) {
175 throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node
, e
);
184 * Make sure that: starts with '/', do not end with '/', do not have '//'
186 public static String
normalizePath(String path
) {
187 List
<String
> tokens
= tokenize(path
);
188 StringBuffer buf
= new StringBuffer(path
.length());
189 for (String token
: tokens
) {
193 return buf
.toString();
197 * Creates a path from a FQDN, inverting the order of the component:
198 * www.argeo.org becomes org.argeo.www
200 public static String
hostAsPath(String host
) {
201 StringBuffer path
= new StringBuffer(host
.length());
202 String
[] hostTokens
= host
.split("\\.");
203 for (int i
= hostTokens
.length
- 1; i
>= 0; i
--) {
204 path
.append(hostTokens
[i
]);
208 return path
.toString();
212 * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes
213 * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
215 public static String
uuidAsPath(String uuid
) {
216 StringBuffer path
= new StringBuffer(uuid
.length());
217 String
[] tokens
= uuid
.split("-");
218 for (int i
= 0; i
< tokens
.length
; i
++) {
219 path
.append(tokens
[i
]);
223 return path
.toString();
227 * The provided data as a path ('/' at the end, not the beginning)
229 * @param cal the date
230 * @param addHour whether to add hour as well
232 public static String
dateAsPath(Calendar cal
, Boolean addHour
) {
233 StringBuffer buf
= new StringBuffer(14);
235 buf
.append(cal
.get(Calendar
.YEAR
));
238 int month
= cal
.get(Calendar
.MONTH
) + 1;
245 int day
= cal
.get(Calendar
.DAY_OF_MONTH
);
253 int hour
= cal
.get(Calendar
.HOUR_OF_DAY
);
260 return buf
.toString();
264 /** Converts in one call a string into a gregorian calendar. */
265 public static Calendar
parseCalendar(DateFormat dateFormat
, String value
) {
267 Date date
= dateFormat
.parse(value
);
268 Calendar calendar
= new GregorianCalendar();
269 calendar
.setTime(date
);
271 } catch (ParseException e
) {
272 throw new IllegalArgumentException("Cannot parse " + value
+ " with date format " + dateFormat
, e
);
277 /** The last element of a path. */
278 public static String
lastPathElement(String path
) {
279 if (path
.charAt(path
.length() - 1) == '/')
280 throw new IllegalArgumentException("Path " + path
+ " cannot end with '/'");
281 int index
= path
.lastIndexOf('/');
284 return path
.substring(index
+ 1);
288 * Call {@link Node#getName()} without exceptions (useful in super
291 public static String
getNameQuietly(Node node
) {
293 return node
.getName();
294 } catch (RepositoryException e
) {
295 throw new JcrException("Cannot get name from " + node
, e
);
300 * Call {@link Node#getProperty(String)} without exceptions (useful in super
303 public static String
getStringPropertyQuietly(Node node
, String propertyName
) {
305 return node
.getProperty(propertyName
).getString();
306 } catch (RepositoryException e
) {
307 throw new JcrException("Cannot get name from " + node
, e
);
312 * Routine that get the child with this name, adding id it does not already
315 public static Node
getOrAdd(Node parent
, String childName
, String childPrimaryNodeType
) throws RepositoryException
{
316 return parent
.hasNode(childName
) ? parent
.getNode(childName
) : parent
.addNode(childName
, childPrimaryNodeType
);
320 * Routine that get the child with this name, adding id it does not already
323 public static Node
getOrAdd(Node parent
, String childName
) throws RepositoryException
{
324 return parent
.hasNode(childName
) ? parent
.getNode(childName
) : parent
.addNode(childName
);
327 /** Convert a {@link NodeIterator} to a list of {@link Node} */
328 public static List
<Node
> nodeIteratorToList(NodeIterator nodeIterator
) {
329 List
<Node
> nodes
= new ArrayList
<Node
>();
330 while (nodeIterator
.hasNext()) {
331 nodes
.add(nodeIterator
.nextNode());
341 * Concisely get the string value of a property or null if this node doesn't
344 public static String
get(Node node
, String propertyName
) {
346 if (!node
.hasProperty(propertyName
))
348 return node
.getProperty(propertyName
).getString();
349 } catch (RepositoryException e
) {
350 throw new JcrException("Cannot get property " + propertyName
+ " of " + node
, e
);
354 /** Concisely get the path of the given node. */
355 public static String
getPath(Node node
) {
357 return node
.getPath();
358 } catch (RepositoryException e
) {
359 throw new JcrException("Cannot get path of " + node
, e
);
363 /** Concisely get the boolean value of a property */
364 public static Boolean
check(Node node
, String propertyName
) {
366 return node
.getProperty(propertyName
).getBoolean();
367 } catch (RepositoryException e
) {
368 throw new JcrException("Cannot get property " + propertyName
+ " of " + node
, e
);
372 /** Concisely get the bytes array value of a property */
373 public static byte[] getBytes(Node node
, String propertyName
) {
375 return getBinaryAsBytes(node
.getProperty(propertyName
));
376 } catch (RepositoryException e
) {
377 throw new JcrException("Cannot get property " + propertyName
+ " of " + node
, e
);
386 * Create sub nodes relative to a parent node
388 public static Node
mkdirs(Node parentNode
, String relativePath
) {
389 return mkdirs(parentNode
, relativePath
, null, null);
393 * Create sub nodes relative to a parent node
395 * @param nodeType the type of the leaf node
397 public static Node
mkdirs(Node parentNode
, String relativePath
, String nodeType
) {
398 return mkdirs(parentNode
, relativePath
, nodeType
, null);
402 * Create sub nodes relative to a parent node
404 * @param nodeType the type of the leaf node
406 public static Node
mkdirs(Node parentNode
, String relativePath
, String nodeType
, String intermediaryNodeType
) {
407 List
<String
> tokens
= tokenize(relativePath
);
408 Node currParent
= parentNode
;
410 for (int i
= 0; i
< tokens
.size(); i
++) {
411 String name
= tokens
.get(i
);
412 if (currParent
.hasNode(name
)) {
413 currParent
= currParent
.getNode(name
);
415 if (i
!= (tokens
.size() - 1)) {// intermediary
416 currParent
= currParent
.addNode(name
, intermediaryNodeType
);
418 currParent
= currParent
.addNode(name
, nodeType
);
423 } catch (RepositoryException e
) {
424 throw new JcrException("Cannot mkdirs relative path " + relativePath
+ " from " + parentNode
, e
);
429 * Synchronized and save is performed, to avoid race conditions in initializers
430 * leading to duplicate nodes.
432 public synchronized static Node
mkdirsSafe(Session session
, String path
, String type
) {
434 if (session
.hasPendingChanges())
435 throw new IllegalStateException("Session has pending changes, save them first.");
436 Node node
= mkdirs(session
, path
, type
);
439 } catch (RepositoryException e
) {
440 discardQuietly(session
);
441 throw new JcrException("Cannot safely make directories", e
);
445 public synchronized static Node
mkdirsSafe(Session session
, String path
) {
446 return mkdirsSafe(session
, path
, null);
449 /** Creates the nodes making path, if they don't exist. */
450 public static Node
mkdirs(Session session
, String path
) {
451 return mkdirs(session
, path
, null, null, false);
455 * @param type the type of the leaf node
457 public static Node
mkdirs(Session session
, String path
, String type
) {
458 return mkdirs(session
, path
, type
, null, false);
462 * Creates the nodes making path, if they don't exist. This is up to the caller
463 * to save the session. Use with caution since it can create duplicate nodes if
464 * used concurrently. Requires read access to the root node of the workspace.
466 public static Node
mkdirs(Session session
, String path
, String type
, String intermediaryNodeType
,
467 Boolean versioning
) {
469 if (path
.equals("/"))
470 return session
.getRootNode();
472 if (session
.itemExists(path
)) {
473 Node node
= session
.getNode(path
);
475 if (type
!= null && !node
.isNodeType(type
) && !node
.getPath().equals("/"))
476 throw new IllegalArgumentException("Node " + node
+ " exists but is of type "
477 + node
.getPrimaryNodeType().getName() + " not of type " + type
);
478 // TODO: check versioning
482 // StringBuffer current = new StringBuffer("/");
483 // Node currentNode = session.getRootNode();
485 Node currentNode
= findClosestExistingParent(session
, path
);
486 String closestExistingParentPath
= currentNode
.getPath();
487 StringBuffer current
= new StringBuffer(closestExistingParentPath
);
488 if (!closestExistingParentPath
.endsWith("/"))
490 Iterator
<String
> it
= tokenize(path
.substring(closestExistingParentPath
.length())).iterator();
491 while (it
.hasNext()) {
492 String part
= it
.next();
493 current
.append(part
).append('/');
494 if (!session
.itemExists(current
.toString())) {
495 if (!it
.hasNext() && type
!= null)
496 currentNode
= currentNode
.addNode(part
, type
);
497 else if (it
.hasNext() && intermediaryNodeType
!= null)
498 currentNode
= currentNode
.addNode(part
, intermediaryNodeType
);
500 currentNode
= currentNode
.addNode(part
);
502 currentNode
.addMixin(NodeType
.MIX_VERSIONABLE
);
503 if (log
.isTraceEnabled())
504 log
.debug("Added folder " + part
+ " as " + current
);
506 currentNode
= (Node
) session
.getItem(current
.toString());
510 } catch (RepositoryException e
) {
511 discardQuietly(session
);
512 throw new JcrException("Cannot mkdirs " + path
, e
);
517 private static Node
findClosestExistingParent(Session session
, String path
) throws RepositoryException
{
518 int idx
= path
.lastIndexOf('/');
520 return session
.getRootNode();
521 String parentPath
= path
.substring(0, idx
);
522 if (session
.itemExists(parentPath
))
523 return session
.getNode(parentPath
);
525 return findClosestExistingParent(session
, parentPath
);
528 /** Convert a path to the list of its tokens */
529 public static List
<String
> tokenize(String path
) {
530 List
<String
> tokens
= new ArrayList
<String
>();
531 boolean optimized
= false;
533 String
[] rawTokens
= path
.split("/");
534 for (String token
: rawTokens
) {
535 if (!token
.equals(""))
539 StringBuffer curr
= new StringBuffer();
540 char[] arr
= path
.toCharArray();
541 chars
: for (int i
= 0; i
< arr
.length
; i
++) {
544 if (i
== 0 || (i
== arr
.length
- 1))
546 if (curr
.length() > 0) {
547 tokens
.add(curr
.toString());
548 curr
= new StringBuffer();
553 if (curr
.length() > 0) {
554 tokens
.add(curr
.toString());
555 curr
= new StringBuffer();
558 return Collections
.unmodifiableList(tokens
);
562 // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
567 // public static Node mkdirs(Session session, String path, String type,
568 // Boolean versioning) {
569 // return mkdirs(session, path, type, type, false);
573 * Safe and repository implementation independent registration of a namespace.
575 public static void registerNamespaceSafely(Session session
, String prefix
, String uri
) {
577 registerNamespaceSafely(session
.getWorkspace().getNamespaceRegistry(), prefix
, uri
);
578 } catch (RepositoryException e
) {
579 throw new JcrException("Cannot find namespace registry", e
);
584 * Safe and repository implementation independent registration of a namespace.
586 public static void registerNamespaceSafely(NamespaceRegistry nr
, String prefix
, String uri
) {
588 String
[] prefixes
= nr
.getPrefixes();
589 for (String pref
: prefixes
)
590 if (pref
.equals(prefix
)) {
591 String registeredUri
= nr
.getURI(pref
);
592 if (!registeredUri
.equals(uri
))
593 throw new IllegalArgumentException("Prefix " + pref
+ " already registered for URI "
594 + registeredUri
+ " which is different from provided URI " + uri
);
598 nr
.registerNamespace(prefix
, uri
);
599 } catch (RepositoryException e
) {
600 throw new JcrException("Cannot register namespace " + uri
+ " under prefix " + prefix
, e
);
604 /** Recursively outputs the contents of the given node. */
605 public static void debug(Node node
) {
609 /** Recursively outputs the contents of the given node. */
610 public static void debug(Node node
, Log log
) {
612 // First output the node path
613 log
.debug(node
.getPath());
614 // Skip the virtual (and large!) jcr:system subtree
615 if (node
.getName().equals("jcr:system")) {
619 // Then the children nodes (recursive)
620 NodeIterator it
= node
.getNodes();
621 while (it
.hasNext()) {
622 Node childNode
= it
.nextNode();
623 debug(childNode
, log
);
626 // Then output the properties
627 PropertyIterator properties
= node
.getProperties();
628 // log.debug("Property are : ");
630 properties
: while (properties
.hasNext()) {
631 Property property
= properties
.nextProperty();
632 if (property
.getType() == PropertyType
.BINARY
)
633 continue properties
;// skip
634 if (property
.getDefinition().isMultiple()) {
635 // A multi-valued property, print all values
636 Value
[] values
= property
.getValues();
637 for (int i
= 0; i
< values
.length
; i
++) {
638 log
.debug(property
.getPath() + "=" + values
[i
].getString());
641 // A single-valued property
642 log
.debug(property
.getPath() + "=" + property
.getString());
645 } catch (Exception e
) {
646 log
.error("Could not debug " + node
, e
);
651 /** Logs the effective access control policies */
652 public static void logEffectiveAccessPolicies(Node node
) {
654 logEffectiveAccessPolicies(node
.getSession(), node
.getPath());
655 } catch (RepositoryException e
) {
656 log
.error("Cannot log effective access policies of " + node
, e
);
660 /** Logs the effective access control policies */
661 public static void logEffectiveAccessPolicies(Session session
, String path
) {
662 if (!log
.isDebugEnabled())
666 AccessControlPolicy
[] effectivePolicies
= session
.getAccessControlManager().getEffectivePolicies(path
);
667 if (effectivePolicies
.length
> 0) {
668 for (AccessControlPolicy policy
: effectivePolicies
) {
669 if (policy
instanceof AccessControlList
) {
670 AccessControlList acl
= (AccessControlList
) policy
;
671 log
.debug("Access control list for " + path
+ "\n" + accessControlListSummary(acl
));
675 log
.debug("No effective access control policy for " + path
);
677 } catch (RepositoryException e
) {
678 log
.error("Cannot log effective access policies of " + path
, e
);
682 /** Returns a human-readable summary of this access control list. */
683 public static String
accessControlListSummary(AccessControlList acl
) {
684 StringBuffer buf
= new StringBuffer("");
686 for (AccessControlEntry ace
: acl
.getAccessControlEntries()) {
687 buf
.append('\t').append(ace
.getPrincipal().getName()).append('\n');
688 for (Privilege priv
: ace
.getPrivileges())
689 buf
.append("\t\t").append(priv
.getName()).append('\n');
691 return buf
.toString();
692 } catch (RepositoryException e
) {
693 throw new JcrException("Cannot write summary of " + acl
, e
);
698 * Copies recursively the content of a node to another one. Do NOT copy the
699 * property values of {@link NodeType#MIX_CREATED} and
700 * {@link NodeType#MIX_LAST_MODIFIED}, but update the
701 * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY}
702 * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED}
705 public static void copy(Node fromNode
, Node toNode
) {
707 if (toNode
.getDefinition().isProtected())
710 // process properties
711 PropertyIterator pit
= fromNode
.getProperties();
712 properties
: while (pit
.hasNext()) {
713 Property fromProperty
= pit
.nextProperty();
714 String propertyName
= fromProperty
.getName();
715 if (toNode
.hasProperty(propertyName
) && toNode
.getProperty(propertyName
).getDefinition().isProtected())
718 if (fromProperty
.getDefinition().isProtected())
721 if (propertyName
.equals("jcr:created") || propertyName
.equals("jcr:createdBy")
722 || propertyName
.equals("jcr:lastModified") || propertyName
.equals("jcr:lastModifiedBy"))
725 if (fromProperty
.isMultiple()) {
726 toNode
.setProperty(propertyName
, fromProperty
.getValues());
728 toNode
.setProperty(propertyName
, fromProperty
.getValue());
732 // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
733 // they existed, before adding the mixins
734 updateLastModified(toNode
);
737 for (NodeType mixinType
: fromNode
.getMixinNodeTypes()) {
738 toNode
.addMixin(mixinType
.getName());
741 // process children nodes
742 NodeIterator nit
= fromNode
.getNodes();
743 while (nit
.hasNext()) {
744 Node fromChild
= nit
.nextNode();
745 Integer index
= fromChild
.getIndex();
746 String nodeRelPath
= fromChild
.getName() + "[" + index
+ "]";
748 if (toNode
.hasNode(nodeRelPath
))
749 toChild
= toNode
.getNode(nodeRelPath
);
751 toChild
= toNode
.addNode(fromChild
.getName(), fromChild
.getPrimaryNodeType().getName());
752 copy(fromChild
, toChild
);
754 } catch (RepositoryException e
) {
755 throw new JcrException("Cannot copy " + fromNode
+ " to " + toNode
, e
);
760 * Check whether all first-level properties (except jcr:* properties) are equal.
761 * Skip jcr:* properties
763 public static Boolean
allPropertiesEquals(Node reference
, Node observed
, Boolean onlyCommonProperties
) {
765 PropertyIterator pit
= reference
.getProperties();
766 props
: while (pit
.hasNext()) {
767 Property propReference
= pit
.nextProperty();
768 String propName
= propReference
.getName();
769 if (propName
.startsWith("jcr:"))
772 if (!observed
.hasProperty(propName
))
773 if (onlyCommonProperties
)
777 // TODO: deal with multiple property values?
778 if (!observed
.getProperty(propName
).getValue().equals(propReference
.getValue()))
782 } catch (RepositoryException e
) {
783 throw new JcrException("Cannot check all properties equals of " + reference
+ " and " + observed
, e
);
787 public static Map
<String
, PropertyDiff
> diffProperties(Node reference
, Node observed
) {
788 Map
<String
, PropertyDiff
> diffs
= new TreeMap
<String
, PropertyDiff
>();
789 diffPropertiesLevel(diffs
, null, reference
, observed
);
794 * Compare the properties of two nodes. Recursivity to child nodes is not yet
795 * supported. Skip jcr:* properties.
797 static void diffPropertiesLevel(Map
<String
, PropertyDiff
> diffs
, String baseRelPath
, Node reference
,
800 // check removed and modified
801 PropertyIterator pit
= reference
.getProperties();
802 props
: while (pit
.hasNext()) {
803 Property p
= pit
.nextProperty();
804 String name
= p
.getName();
805 if (name
.startsWith("jcr:"))
808 if (!observed
.hasProperty(name
)) {
809 String relPath
= propertyRelPath(baseRelPath
, name
);
810 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.REMOVED
, relPath
, p
.getValue(), null);
811 diffs
.put(relPath
, pDiff
);
813 if (p
.isMultiple()) {
814 // FIXME implement multiple
816 Value referenceValue
= p
.getValue();
817 Value newValue
= observed
.getProperty(name
).getValue();
818 if (!referenceValue
.equals(newValue
)) {
819 String relPath
= propertyRelPath(baseRelPath
, name
);
820 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.MODIFIED
, relPath
, referenceValue
,
822 diffs
.put(relPath
, pDiff
);
828 pit
= observed
.getProperties();
829 props
: while (pit
.hasNext()) {
830 Property p
= pit
.nextProperty();
831 String name
= p
.getName();
832 if (name
.startsWith("jcr:"))
834 if (!reference
.hasProperty(name
)) {
835 if (p
.isMultiple()) {
836 // FIXME implement multiple
838 String relPath
= propertyRelPath(baseRelPath
, name
);
839 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.ADDED
, relPath
, null, p
.getValue());
840 diffs
.put(relPath
, pDiff
);
844 } catch (RepositoryException e
) {
845 throw new JcrException("Cannot diff " + reference
+ " and " + observed
, e
);
850 * Compare only a restricted list of properties of two nodes. No recursivity.
853 public static Map
<String
, PropertyDiff
> diffProperties(Node reference
, Node observed
, List
<String
> properties
) {
854 Map
<String
, PropertyDiff
> diffs
= new TreeMap
<String
, PropertyDiff
>();
856 Iterator
<String
> pit
= properties
.iterator();
858 props
: while (pit
.hasNext()) {
859 String name
= pit
.next();
860 if (!reference
.hasProperty(name
)) {
861 if (!observed
.hasProperty(name
))
863 Value val
= observed
.getProperty(name
).getValue();
865 // empty String but not null
866 if ("".equals(val
.getString()))
868 } catch (Exception e
) {
869 // not parseable as String, silent
871 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.ADDED
, name
, null, val
);
872 diffs
.put(name
, pDiff
);
873 } else if (!observed
.hasProperty(name
)) {
874 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.REMOVED
, name
,
875 reference
.getProperty(name
).getValue(), null);
876 diffs
.put(name
, pDiff
);
878 Value referenceValue
= reference
.getProperty(name
).getValue();
879 Value newValue
= observed
.getProperty(name
).getValue();
880 if (!referenceValue
.equals(newValue
)) {
881 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.MODIFIED
, name
, referenceValue
, newValue
);
882 diffs
.put(name
, pDiff
);
886 } catch (RepositoryException e
) {
887 throw new JcrException("Cannot diff " + reference
+ " and " + observed
, e
);
892 /** Builds a property relPath to be used in the diff. */
893 private static String
propertyRelPath(String baseRelPath
, String propertyName
) {
894 if (baseRelPath
== null)
897 return baseRelPath
+ '/' + propertyName
;
901 * Normalizes a name so that it can be stored in contexts not supporting names
902 * with ':' (typically databases). Replaces ':' by '_'.
904 public static String
normalize(String name
) {
905 return name
.replace(':', '_');
909 * Replaces characters which are invalid in a JCR name by '_'. Currently not
912 * @see JcrUtils#INVALID_NAME_CHARACTERS
914 public static String
replaceInvalidChars(String name
) {
915 return replaceInvalidChars(name
, '_');
919 * Replaces characters which are invalid in a JCR name. Currently not
922 * @see JcrUtils#INVALID_NAME_CHARACTERS
924 public static String
replaceInvalidChars(String name
, char replacement
) {
925 boolean modified
= false;
926 char[] arr
= name
.toCharArray();
927 for (int i
= 0; i
< arr
.length
; i
++) {
929 invalid
: for (char invalid
: INVALID_NAME_CHARACTERS
) {
931 arr
[i
] = replacement
;
938 return new String(arr
);
940 // do not create new object if unnecessary
945 // * Removes forbidden characters from a path, replacing them with '_'
947 // * @deprecated use {@link #replaceInvalidChars(String)} instead
949 // public static String removeForbiddenCharacters(String str) {
950 // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*',
955 /** Cleanly disposes a {@link Binary} even if it is null. */
956 public static void closeQuietly(Binary binary
) {
962 /** Retrieve a {@link Binary} as a byte array */
963 public static byte[] getBinaryAsBytes(Property property
) {
964 try (ByteArrayOutputStream out
= new ByteArrayOutputStream();
965 Bin binary
= new Bin(property
);
966 InputStream in
= binary
.getStream()) {
967 IOUtils
.copy(in
, out
);
968 return out
.toByteArray();
969 } catch (RepositoryException e
) {
970 throw new JcrException("Cannot read binary " + property
+ " as bytes", e
);
971 } catch (IOException e
) {
972 throw new RuntimeException("Cannot read binary " + property
+ " as bytes", e
);
976 /** Writes a {@link Binary} from a byte array */
977 public static void setBinaryAsBytes(Node node
, String property
, byte[] bytes
) {
978 Binary binary
= null;
979 try (InputStream in
= new ByteArrayInputStream(bytes
)) {
980 binary
= node
.getSession().getValueFactory().createBinary(in
);
981 node
.setProperty(property
, binary
);
982 } catch (RepositoryException e
) {
983 throw new JcrException("Cannot set binary " + property
+ " as bytes", e
);
984 } catch (IOException e
) {
985 throw new RuntimeException("Cannot set binary " + property
+ " as bytes", e
);
987 closeQuietly(binary
);
991 /** Writes a {@link Binary} from a byte array */
992 public static void setBinaryAsBytes(Property prop
, byte[] bytes
) {
993 Binary binary
= null;
994 try (InputStream in
= new ByteArrayInputStream(bytes
)) {
995 binary
= prop
.getSession().getValueFactory().createBinary(in
);
996 prop
.setValue(binary
);
997 } catch (RepositoryException e
) {
998 throw new JcrException("Cannot set binary " + prop
+ " as bytes", e
);
999 } catch (IOException e
) {
1000 throw new RuntimeException("Cannot set binary " + prop
+ " as bytes", e
);
1002 closeQuietly(binary
);
1007 * Creates depth from a string (typically a username) by adding levels based on
1008 * its first characters: "aBcD",2 becomes a/aB
1010 public static String
firstCharsToPath(String str
, Integer nbrOfChars
) {
1011 if (str
.length() < nbrOfChars
)
1012 throw new IllegalArgumentException("String " + str
+ " length must be greater or equal than " + nbrOfChars
);
1013 StringBuffer path
= new StringBuffer("");
1014 StringBuffer curr
= new StringBuffer("");
1015 for (int i
= 0; i
< nbrOfChars
; i
++) {
1016 curr
.append(str
.charAt(i
));
1018 if (i
< nbrOfChars
- 1)
1021 return path
.toString();
1025 * Discards the current changes in the session attached to this node. To be used
1026 * typically in a catch block.
1028 * @see #discardQuietly(Session)
1030 public static void discardUnderlyingSessionQuietly(Node node
) {
1032 discardQuietly(node
.getSession());
1033 } catch (RepositoryException e
) {
1034 log
.warn("Cannot quietly discard session of node " + node
+ ": " + e
.getMessage());
1039 * Discards the current changes in a session by calling
1040 * {@link Session#refresh(boolean)} with <code>false</code>, only logging
1041 * potential errors when doing so. To be used typically in a catch block.
1043 public static void discardQuietly(Session session
) {
1045 if (session
!= null)
1046 session
.refresh(false);
1047 } catch (RepositoryException e
) {
1048 log
.warn("Cannot quietly discard session " + session
+ ": " + e
.getMessage());
1053 * Login to a workspace with implicit credentials, creates the workspace with
1054 * these credentials if it does not already exist.
1056 public static Session
loginOrCreateWorkspace(Repository repository
, String workspaceName
)
1057 throws RepositoryException
{
1058 return loginOrCreateWorkspace(repository
, workspaceName
, null);
1062 * Login to a workspace with implicit credentials, creates the workspace with
1063 * these credentials if it does not already exist.
1065 public static Session
loginOrCreateWorkspace(Repository repository
, String workspaceName
, Credentials credentials
)
1066 throws RepositoryException
{
1067 Session workspaceSession
= null;
1068 Session defaultSession
= null;
1071 workspaceSession
= repository
.login(credentials
, workspaceName
);
1072 } catch (NoSuchWorkspaceException e
) {
1073 // try to create workspace
1074 defaultSession
= repository
.login(credentials
);
1075 defaultSession
.getWorkspace().createWorkspace(workspaceName
);
1076 workspaceSession
= repository
.login(credentials
, workspaceName
);
1078 return workspaceSession
;
1080 logoutQuietly(defaultSession
);
1085 * Logs out the session, not throwing any exception, even if it is null.
1086 * {@link Jcr#logout(Session)} should rather be used.
1088 public static void logoutQuietly(Session session
) {
1089 Jcr
.logout(session
);
1091 // if (session != null)
1092 // if (session.isLive())
1093 // session.logout();
1094 // } catch (Exception e) {
1100 * Convenient method to add a listener. uuids passed as null, deep=true,
1101 * local=true, only one node type
1103 public static void addListener(Session session
, EventListener listener
, int eventTypes
, String basePath
,
1106 session
.getWorkspace().getObservationManager().addEventListener(listener
, eventTypes
, basePath
, true, null,
1107 nodeType
== null ?
null : new String
[] { nodeType
}, true);
1108 } catch (RepositoryException e
) {
1109 throw new JcrException("Cannot add JCR listener " + listener
+ " to session " + session
, e
);
1113 /** Removes a listener without throwing exception */
1114 public static void removeListenerQuietly(Session session
, EventListener listener
) {
1115 if (session
== null || !session
.isLive())
1118 session
.getWorkspace().getObservationManager().removeEventListener(listener
);
1119 } catch (RepositoryException e
) {
1125 * Quietly unregisters an {@link EventListener} from the udnerlying workspace of
1128 public static void unregisterQuietly(Node node
, EventListener eventListener
) {
1130 unregisterQuietly(node
.getSession().getWorkspace(), eventListener
);
1131 } catch (RepositoryException e
) {
1133 if (log
.isTraceEnabled())
1134 log
.trace("Could not unregister event listener " + eventListener
);
1138 /** Quietly unregisters an {@link EventListener} from this workspace */
1139 public static void unregisterQuietly(Workspace workspace
, EventListener eventListener
) {
1140 if (eventListener
== null)
1143 workspace
.getObservationManager().removeEventListener(eventListener
);
1144 } catch (RepositoryException e
) {
1146 if (log
.isTraceEnabled())
1147 log
.trace("Could not unregister event listener " + eventListener
);
1152 * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it updates
1153 * the {@link Property#JCR_LAST_MODIFIED} property with the current time and the
1154 * {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying session
1155 * user id. In Jackrabbit 2.x,
1156 * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
1157 * not automatically updated</a>, hence the need for manual update. The session
1160 public static void updateLastModified(Node node
) {
1162 if (!node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
))
1163 node
.addMixin(NodeType
.MIX_LAST_MODIFIED
);
1164 node
.setProperty(Property
.JCR_LAST_MODIFIED
, new GregorianCalendar());
1165 node
.setProperty(Property
.JCR_LAST_MODIFIED_BY
, node
.getSession().getUserID());
1166 } catch (RepositoryException e
) {
1167 throw new JcrException("Cannot update last modified on " + node
, e
);
1172 * Update lastModified recursively until this parent.
1174 * @param node the node
1175 * @param untilPath the base path, null is equivalent to "/"
1177 public static void updateLastModifiedAndParents(Node node
, String untilPath
) {
1179 if (untilPath
!= null && !node
.getPath().startsWith(untilPath
))
1180 throw new IllegalArgumentException(node
+ " is not under " + untilPath
);
1181 updateLastModified(node
);
1182 if (untilPath
== null) {
1183 if (!node
.getPath().equals("/"))
1184 updateLastModifiedAndParents(node
.getParent(), untilPath
);
1186 if (!node
.getPath().equals(untilPath
))
1187 updateLastModifiedAndParents(node
.getParent(), untilPath
);
1189 } catch (RepositoryException e
) {
1190 throw new JcrException("Cannot update lastModified from " + node
+ " until " + untilPath
, e
);
1195 * Returns a String representing the short version (see
1196 * <a href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
1197 * Notation </a> attributes grammar) of the main business attributes of this
1198 * property definition
1202 public static String
getPropertyDefinitionAsString(Property prop
) {
1203 StringBuffer sbuf
= new StringBuffer();
1205 if (prop
.getDefinition().isAutoCreated())
1207 if (prop
.getDefinition().isMandatory())
1209 if (prop
.getDefinition().isProtected())
1211 if (prop
.getDefinition().isMultiple())
1213 } catch (RepositoryException re
) {
1214 throw new JcrException("unexpected error while getting property definition as String", re
);
1216 return sbuf
.toString();
1220 * Estimate the sub tree size from current node. Computation is based on the Jcr
1221 * {@link Property#getLength()} method. Note : it is not the exact size used on
1222 * the disk by the current part of the JCR Tree.
1225 public static long getNodeApproxSize(Node node
) {
1226 long curNodeSize
= 0;
1228 PropertyIterator pi
= node
.getProperties();
1229 while (pi
.hasNext()) {
1230 Property prop
= pi
.nextProperty();
1231 if (prop
.isMultiple()) {
1232 int nb
= prop
.getLengths().length
;
1233 for (int i
= 0; i
< nb
; i
++) {
1234 curNodeSize
+= (prop
.getLengths()[i
] > 0 ? prop
.getLengths()[i
] : 0);
1237 curNodeSize
+= (prop
.getLength() > 0 ? prop
.getLength() : 0);
1240 NodeIterator ni
= node
.getNodes();
1241 while (ni
.hasNext())
1242 curNodeSize
+= getNodeApproxSize(ni
.nextNode());
1244 } catch (RepositoryException re
) {
1245 throw new JcrException("Unexpected error while recursively determining node size.", re
);
1254 * Convenience method for adding a single privilege to a principal (user or
1255 * role), typically jcr:all
1257 public synchronized static void addPrivilege(Session session
, String path
, String principal
, String privilege
)
1258 throws RepositoryException
{
1259 List
<Privilege
> privileges
= new ArrayList
<Privilege
>();
1260 privileges
.add(session
.getAccessControlManager().privilegeFromName(privilege
));
1261 addPrivileges(session
, path
, new SimplePrincipal(principal
), privileges
);
1265 * Add privileges on a path to a {@link Principal}. The path must already exist.
1266 * Session is saved. Synchronized to prevent concurrent modifications of the
1269 public synchronized static Boolean
addPrivileges(Session session
, String path
, Principal principal
,
1270 List
<Privilege
> privs
) throws RepositoryException
{
1271 // make sure the session is in line with the persisted state
1272 session
.refresh(false);
1273 AccessControlManager acm
= session
.getAccessControlManager();
1274 AccessControlList acl
= getAccessControlList(acm
, path
);
1276 accessControlEntries
: for (AccessControlEntry ace
: acl
.getAccessControlEntries()) {
1277 Principal currentPrincipal
= ace
.getPrincipal();
1278 if (currentPrincipal
.getName().equals(principal
.getName())) {
1279 Privilege
[] currentPrivileges
= ace
.getPrivileges();
1280 if (currentPrivileges
.length
!= privs
.size())
1281 break accessControlEntries
;
1282 for (int i
= 0; i
< currentPrivileges
.length
; i
++) {
1283 Privilege currP
= currentPrivileges
[i
];
1284 Privilege p
= privs
.get(i
);
1285 if (!currP
.getName().equals(p
.getName())) {
1286 break accessControlEntries
;
1293 Privilege
[] privileges
= privs
.toArray(new Privilege
[privs
.size()]);
1294 acl
.addAccessControlEntry(principal
, privileges
);
1295 acm
.setPolicy(path
, acl
);
1296 if (log
.isDebugEnabled()) {
1297 StringBuffer privBuf
= new StringBuffer();
1298 for (Privilege priv
: privs
)
1299 privBuf
.append(priv
.getName());
1300 log
.debug("Added privileges " + privBuf
+ " to " + principal
.getName() + " on " + path
+ " in '"
1301 + session
.getWorkspace().getName() + "'");
1303 session
.refresh(true);
1309 * Gets the first available access control list for this path, throws exception
1312 public synchronized static AccessControlList
getAccessControlList(AccessControlManager acm
, String path
)
1313 throws RepositoryException
{
1314 // search for an access control list
1315 AccessControlList acl
= null;
1316 AccessControlPolicyIterator policyIterator
= acm
.getApplicablePolicies(path
);
1317 applicablePolicies
: if (policyIterator
.hasNext()) {
1318 while (policyIterator
.hasNext()) {
1319 AccessControlPolicy acp
= policyIterator
.nextAccessControlPolicy();
1320 if (acp
instanceof AccessControlList
) {
1321 acl
= ((AccessControlList
) acp
);
1322 break applicablePolicies
;
1326 AccessControlPolicy
[] existingPolicies
= acm
.getPolicies(path
);
1327 existingPolicies
: for (AccessControlPolicy acp
: existingPolicies
) {
1328 if (acp
instanceof AccessControlList
) {
1329 acl
= ((AccessControlList
) acp
);
1330 break existingPolicies
;
1337 throw new IllegalArgumentException("ACL not found at " + path
);
1340 /** Clear authorizations for a user at this path */
1341 public synchronized static void clearAccessControList(Session session
, String path
, String username
)
1342 throws RepositoryException
{
1343 AccessControlManager acm
= session
.getAccessControlManager();
1344 AccessControlList acl
= getAccessControlList(acm
, path
);
1345 for (AccessControlEntry ace
: acl
.getAccessControlEntries()) {
1346 if (ace
.getPrincipal().getName().equals(username
)) {
1347 acl
.removeAccessControlEntry(ace
);
1350 // the new access control list must be applied otherwise this call:
1351 // acl.removeAccessControlEntry(ace); has no effect
1352 acm
.setPolicy(path
, acl
);
1359 * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
1361 public static Node
mkfolders(Session session
, String path
) {
1362 return mkdirs(session
, path
, NodeType
.NT_FOLDER
, NodeType
.NT_FOLDER
, false);
1366 * Copy only nt:folder and nt:file, without their additional types and
1369 * @param recursive if true copies folders as well, otherwise only first level
1371 * @return how many files were copied
1373 public static Long
copyFiles(Node fromNode
, Node toNode
, Boolean recursive
, JcrMonitor monitor
, boolean onlyAdd
) {
1376 // Binary binary = null;
1377 // InputStream in = null;
1379 NodeIterator fromChildren
= fromNode
.getNodes();
1380 children
: while (fromChildren
.hasNext()) {
1381 if (monitor
!= null && monitor
.isCanceled())
1382 throw new IllegalStateException("Copy cancelled before it was completed");
1384 Node fromChild
= fromChildren
.nextNode();
1385 String fileName
= fromChild
.getName();
1386 if (fromChild
.isNodeType(NodeType
.NT_FILE
)) {
1387 if (onlyAdd
&& toNode
.hasNode(fileName
)) {
1388 monitor
.subTask("Skip existing " + fileName
);
1392 if (monitor
!= null)
1393 monitor
.subTask("Copy " + fileName
);
1394 try (Bin binary
= new Bin(fromChild
.getNode(Node
.JCR_CONTENT
).getProperty(Property
.JCR_DATA
));
1395 InputStream in
= binary
.getStream();) {
1396 copyStreamAsFile(toNode
, fileName
, in
);
1397 } catch (IOException e
) {
1398 throw new RuntimeException("Cannot copy " + fileName
+ " to " + toNode
, e
);
1402 toNode
.getSession().save();
1405 if (log
.isDebugEnabled())
1406 log
.debug("Copied file " + fromChild
.getPath());
1407 if (monitor
!= null)
1409 } else if (fromChild
.isNodeType(NodeType
.NT_FOLDER
) && recursive
) {
1411 if (toNode
.hasNode(fileName
)) {
1412 toChildFolder
= toNode
.getNode(fileName
);
1413 if (!toChildFolder
.isNodeType(NodeType
.NT_FOLDER
))
1414 throw new IllegalArgumentException(toChildFolder
+ " is not of type nt:folder");
1416 toChildFolder
= toNode
.addNode(fileName
, NodeType
.NT_FOLDER
);
1419 toNode
.getSession().save();
1421 count
= count
+ copyFiles(fromChild
, toChildFolder
, recursive
, monitor
, onlyAdd
);
1425 } catch (RepositoryException e
) {
1426 throw new JcrException("Cannot copy files between " + fromNode
+ " and " + toNode
, e
);
1428 // in case there was an exception
1429 // IOUtils.closeQuietly(in);
1430 // closeQuietly(binary);
1435 * Iteratively count all file nodes in subtree, inefficient but can be useful
1436 * when query are poorly supported, such as in remoting.
1438 public static Long
countFiles(Node node
) {
1439 Long localCount
= 0l;
1441 for (NodeIterator nit
= node
.getNodes(); nit
.hasNext();) {
1442 Node child
= nit
.nextNode();
1443 if (child
.isNodeType(NodeType
.NT_FOLDER
))
1444 localCount
= localCount
+ countFiles(child
);
1445 else if (child
.isNodeType(NodeType
.NT_FILE
))
1446 localCount
= localCount
+ 1;
1448 } catch (RepositoryException e
) {
1449 throw new JcrException("Cannot count all children of " + node
, e
);
1455 * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is
1458 * @return the created file node
1460 public static Node
copyFile(Node folderNode
, File file
) {
1461 try (InputStream in
= new FileInputStream(file
)) {
1462 return copyStreamAsFile(folderNode
, file
.getName(), in
);
1463 } catch (IOException e
) {
1464 throw new RuntimeException("Cannot copy file " + file
+ " under " + folderNode
, e
);
1468 /** Copy bytes as an nt:file */
1469 public static Node
copyBytesAsFile(Node folderNode
, String fileName
, byte[] bytes
) {
1470 // InputStream in = null;
1471 try (InputStream in
= new ByteArrayInputStream(bytes
)) {
1472 // in = new ByteArrayInputStream(bytes);
1473 return copyStreamAsFile(folderNode
, fileName
, in
);
1474 } catch (IOException e
) {
1475 throw new RuntimeException("Cannot copy file " + fileName
+ " under " + folderNode
, e
);
1477 // IOUtils.closeQuietly(in);
1482 * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is
1485 * @return the created file node
1487 public static Node
copyStreamAsFile(Node folderNode
, String fileName
, InputStream in
) {
1488 Binary binary
= null;
1492 if (folderNode
.hasNode(fileName
)) {
1493 fileNode
= folderNode
.getNode(fileName
);
1494 if (!fileNode
.isNodeType(NodeType
.NT_FILE
))
1495 throw new IllegalArgumentException(fileNode
+ " is not of type nt:file");
1496 // we assume that the content node is already there
1497 contentNode
= fileNode
.getNode(Node
.JCR_CONTENT
);
1499 fileNode
= folderNode
.addNode(fileName
, NodeType
.NT_FILE
);
1500 contentNode
= fileNode
.addNode(Node
.JCR_CONTENT
, NodeType
.NT_UNSTRUCTURED
);
1502 binary
= contentNode
.getSession().getValueFactory().createBinary(in
);
1503 contentNode
.setProperty(Property
.JCR_DATA
, binary
);
1505 } catch (RepositoryException e
) {
1506 throw new JcrException("Cannot create file node " + fileName
+ " under " + folderNode
, e
);
1508 closeQuietly(binary
);
1512 /** Read an an nt:file as an {@link InputStream}. */
1513 public static InputStream
getFileAsStream(Node fileNode
) throws RepositoryException
{
1514 return fileNode
.getNode(Node
.JCR_CONTENT
).getProperty(Property
.JCR_DATA
).getBinary().getStream();
1518 * Computes the checksum of an nt:file.
1520 * @deprecated use separate digest utilities
1523 public static String
checksumFile(Node fileNode
, String algorithm
) {
1524 try (InputStream in
= fileNode
.getNode(Node
.JCR_CONTENT
).getProperty(Property
.JCR_DATA
).getBinary()
1526 return digest(algorithm
, in
);
1527 } catch (IOException e
) {
1528 throw new RuntimeException("Cannot checksum file " + fileNode
+ " with algorithm " + algorithm
, e
);
1529 } catch (RepositoryException e
) {
1530 throw new JcrException("Cannot checksum file " + fileNode
+ " with algorithm " + algorithm
, e
);
1535 private static String
digest(String algorithm
, InputStream in
) {
1536 final Integer byteBufferCapacity
= 100 * 1024;// 100 KB
1538 MessageDigest digest
= MessageDigest
.getInstance(algorithm
);
1539 byte[] buffer
= new byte[byteBufferCapacity
];
1541 while ((read
= in
.read(buffer
)) > 0) {
1542 digest
.update(buffer
, 0, read
);
1545 byte[] checksum
= digest
.digest();
1546 String res
= encodeHexString(checksum
);
1548 } catch (IOException e
) {
1549 throw new RuntimeException("Cannot digest with algorithm " + algorithm
, e
);
1550 } catch (NoSuchAlgorithmException e
) {
1551 throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm
, e
);
1557 * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
1558 * -a-hex-string-in-java
1561 private static String
encodeHexString(byte[] bytes
) {
1562 final char[] hexArray
= "0123456789abcdef".toCharArray();
1563 char[] hexChars
= new char[bytes
.length
* 2];
1564 for (int j
= 0; j
< bytes
.length
; j
++) {
1565 int v
= bytes
[j
] & 0xFF;
1566 hexChars
[j
* 2] = hexArray
[v
>>> 4];
1567 hexChars
[j
* 2 + 1] = hexArray
[v
& 0x0F];
1569 return new String(hexChars
);