X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;ds=sidebyside;f=org.argeo.jcr%2Fsrc%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=3be8be184b25f269d581d09f2bf541980883143d;hb=46cc2039ac20703c484aa994b830a2da113f2c97;hp=857bd60b01044ac802584dd0697315a266b5e05c;hpb=215480a865603e0090c43114541441ac1586b379;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java index 857bd60b0..3be8be184 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java @@ -6,13 +6,19 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.text.DateFormat; import java.text.ParseException; +import java.time.Instant; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -25,6 +31,7 @@ import java.util.TreeMap; import javax.jcr.Binary; import javax.jcr.Credentials; +import javax.jcr.ImportUUIDBehavior; import javax.jcr.NamespaceRegistry; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; @@ -37,6 +44,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.Workspace; +import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeType; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; @@ -49,13 +57,11 @@ import javax.jcr.security.AccessControlPolicyIterator; import javax.jcr.security.Privilege; import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** Utility methods to simplify common JCR operations. */ public class JcrUtils { - final private static Log log = LogFactory.getLog(JcrUtils.class); +// final private static Log log = LogFactory.getLog(JcrUtils.class); /** * Not complete yet. See @@ -308,20 +314,46 @@ public class JcrUtils { } } +// /** +// * Routine that get the child with this name, adding it if it does not already +// * exist +// */ +// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException { +// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType); +// } + /** - * Routine that get the child with this name, adding id it does not already + * Routine that get the child with this name, adding it if it does not already * exist */ - public static Node getOrAdd(Node parent, String childName, String childPrimaryNodeType) throws RepositoryException { - return parent.hasNode(childName) ? parent.getNode(childName) : parent.addNode(childName, childPrimaryNodeType); + public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes) + throws RepositoryException { + Node node; + if (parent.hasNode(name)) { + node = parent.getNode(name); + if (primaryNodeType != null && !node.isNodeType(primaryNodeType)) + throw new IllegalArgumentException("Node " + node + " exists but is of primary node type " + + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType); + for (String mixin : mixinNodeTypes) { + if (!node.isNodeType(mixin)) + node.addMixin(mixin); + } + return node; + } else { + node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name); + for (String mixin : mixinNodeTypes) { + node.addMixin(mixin); + } + return node; + } } /** - * Routine that get the child with this name, adding id it does not already + * Routine that get the child with this name, adding it if it does not already * exist */ - public static Node getOrAdd(Node parent, String childName) throws RepositoryException { - return parent.hasNode(childName) ? parent.getNode(childName) : parent.addNode(childName); + public static Node getOrAdd(Node parent, String name) throws RepositoryException { + return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name); } /** Convert a {@link NodeIterator} to a list of {@link Node} */ @@ -500,8 +532,8 @@ public class JcrUtils { currentNode = currentNode.addNode(part); if (versioning) currentNode.addMixin(NodeType.MIX_VERSIONABLE); - if (log.isTraceEnabled()) - log.debug("Added folder " + part + " as " + current); +// if (log.isTraceEnabled()) +// log.debug("Added folder " + part + " as " + current); } else { currentNode = (Node) session.getItem(current.toString()); } @@ -601,83 +633,83 @@ public class JcrUtils { } } - /** Recursively outputs the contents of the given node. */ - public static void debug(Node node) { - debug(node, log); - } - - /** Recursively outputs the contents of the given node. */ - public static void debug(Node node, Log log) { - try { - // First output the node path - log.debug(node.getPath()); - // Skip the virtual (and large!) jcr:system subtree - if (node.getName().equals("jcr:system")) { - return; - } - - // Then the children nodes (recursive) - NodeIterator it = node.getNodes(); - while (it.hasNext()) { - Node childNode = it.nextNode(); - debug(childNode, log); - } - - // Then output the properties - PropertyIterator properties = node.getProperties(); - // log.debug("Property are : "); - - properties: while (properties.hasNext()) { - Property property = properties.nextProperty(); - if (property.getType() == PropertyType.BINARY) - continue properties;// skip - if (property.getDefinition().isMultiple()) { - // A multi-valued property, print all values - Value[] values = property.getValues(); - for (int i = 0; i < values.length; i++) { - log.debug(property.getPath() + "=" + values[i].getString()); - } - } else { - // A single-valued property - log.debug(property.getPath() + "=" + property.getString()); - } - } - } catch (Exception e) { - log.error("Could not debug " + node, e); - } - - } - - /** Logs the effective access control policies */ - public static void logEffectiveAccessPolicies(Node node) { - try { - logEffectiveAccessPolicies(node.getSession(), node.getPath()); - } catch (RepositoryException e) { - log.error("Cannot log effective access policies of " + node, e); - } - } - - /** Logs the effective access control policies */ - public static void logEffectiveAccessPolicies(Session session, String path) { - if (!log.isDebugEnabled()) - return; +// /** Recursively outputs the contents of the given node. */ +// public static void debug(Node node) { +// debug(node, log); +// } +// +// /** Recursively outputs the contents of the given node. */ +// public static void debug(Node node, Log log) { +// try { +// // First output the node path +// log.debug(node.getPath()); +// // Skip the virtual (and large!) jcr:system subtree +// if (node.getName().equals("jcr:system")) { +// return; +// } +// +// // Then the children nodes (recursive) +// NodeIterator it = node.getNodes(); +// while (it.hasNext()) { +// Node childNode = it.nextNode(); +// debug(childNode, log); +// } +// +// // Then output the properties +// PropertyIterator properties = node.getProperties(); +// // log.debug("Property are : "); +// +// properties: while (properties.hasNext()) { +// Property property = properties.nextProperty(); +// if (property.getType() == PropertyType.BINARY) +// continue properties;// skip +// if (property.getDefinition().isMultiple()) { +// // A multi-valued property, print all values +// Value[] values = property.getValues(); +// for (int i = 0; i < values.length; i++) { +// log.debug(property.getPath() + "=" + values[i].getString()); +// } +// } else { +// // A single-valued property +// log.debug(property.getPath() + "=" + property.getString()); +// } +// } +// } catch (Exception e) { +// log.error("Could not debug " + node, e); +// } +// +// } - try { - AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path); - if (effectivePolicies.length > 0) { - for (AccessControlPolicy policy : effectivePolicies) { - if (policy instanceof AccessControlList) { - AccessControlList acl = (AccessControlList) policy; - log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl)); - } - } - } else { - log.debug("No effective access control policy for " + path); - } - } catch (RepositoryException e) { - log.error("Cannot log effective access policies of " + path, e); - } - } +// /** Logs the effective access control policies */ +// public static void logEffectiveAccessPolicies(Node node) { +// try { +// logEffectiveAccessPolicies(node.getSession(), node.getPath()); +// } catch (RepositoryException e) { +// log.error("Cannot log effective access policies of " + node, e); +// } +// } +// +// /** Logs the effective access control policies */ +// public static void logEffectiveAccessPolicies(Session session, String path) { +// if (!log.isDebugEnabled()) +// return; +// +// try { +// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path); +// if (effectivePolicies.length > 0) { +// for (AccessControlPolicy policy : effectivePolicies) { +// if (policy instanceof AccessControlList) { +// AccessControlList acl = (AccessControlList) policy; +// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl)); +// } +// } +// } else { +// log.debug("No effective access control policy for " + path); +// } +// } catch (RepositoryException e) { +// log.error("Cannot log effective access policies of " + path, e); +// } +// } /** Returns a human-readable summary of this access control list. */ public static String accessControlListSummary(AccessControlList acl) { @@ -694,6 +726,33 @@ public class JcrUtils { } } + /** Copy the whole workspace via a system view XML. */ + public static void copyWorkspaceXml(Session fromSession, Session toSession) { + Workspace fromWorkspace = fromSession.getWorkspace(); + Workspace toWorkspace = toSession.getWorkspace(); + String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML."; + + try (PipedInputStream in = new PipedInputStream(1024 * 1024);) { + new Thread(() -> { + try (PipedOutputStream out = new PipedOutputStream(in)) { + fromSession.exportSystemView("/", out, false, false); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(errorMsg, e); + } catch (RepositoryException e) { + throw new JcrException(errorMsg, e); + } + }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start(); + + toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); + toSession.save(); + } catch (IOException e) { + throw new RuntimeException(errorMsg, e); + } catch (RepositoryException e) { + throw new JcrException(errorMsg, e); + } + } + /** * Copies recursively the content of a node to another one. Do NOT copy the * property values of {@link NodeType#MIX_CREATED} and @@ -707,6 +766,16 @@ public class JcrUtils { if (toNode.getDefinition().isProtected()) return; + // add mixins + for (NodeType mixinType : fromNode.getMixinNodeTypes()) { + try { + toNode.addMixin(mixinType.getName()); + } catch (NoSuchNodeTypeException e) { + // ignore unknown mixins + // TODO log it + } + } + // process properties PropertyIterator pit = fromNode.getProperties(); properties: while (pit.hasNext()) { @@ -731,12 +800,7 @@ public class JcrUtils { // update jcr:lastModified and jcr:lastModifiedBy in toNode in case // they existed, before adding the mixins - updateLastModified(toNode); - - // add mixins - for (NodeType mixinType : fromNode.getMixinNodeTypes()) { - toNode.addMixin(mixinType.getName()); - } + updateLastModified(toNode, true); // process children nodes NodeIterator nit = fromNode.getNodes(); @@ -747,8 +811,15 @@ public class JcrUtils { Node toChild; if (toNode.hasNode(nodeRelPath)) toChild = toNode.getNode(nodeRelPath); - else - toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName()); + else { + try { + toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName()); + } catch (NoSuchNodeTypeException e) { + // ignore unknown primary types + // TODO log it + return; + } + } copy(fromChild, toChild); } } catch (RepositoryException e) { @@ -1031,7 +1102,7 @@ public class JcrUtils { try { discardQuietly(node.getSession()); } catch (RepositoryException e) { - log.warn("Cannot quietly discard session of node " + node + ": " + e.getMessage()); + // silent } } @@ -1045,7 +1116,7 @@ public class JcrUtils { if (session != null) session.refresh(false); } catch (RepositoryException e) { - log.warn("Cannot quietly discard session " + session + ": " + e.getMessage()); + // silent } } @@ -1130,8 +1201,6 @@ public class JcrUtils { unregisterQuietly(node.getSession().getWorkspace(), eventListener); } catch (RepositoryException e) { // silent - if (log.isTraceEnabled()) - log.trace("Could not unregister event listener " + eventListener); } } @@ -1143,23 +1212,66 @@ public class JcrUtils { workspace.getObservationManager().removeEventListener(eventListener); } catch (RepositoryException e) { // silent - if (log.isTraceEnabled()) - log.trace("Could not unregister event listener " + eventListener); } } /** - * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it updates - * the {@link Property#JCR_LAST_MODIFIED} property with the current time and the - * {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying session - * user id. In Jackrabbit 2.x, + * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards) + * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}. + */ + public static Instant getModified(Node node) { + Calendar calendar = null; + try { + if (node.hasProperty(Property.JCR_LAST_MODIFIED)) + calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate(); + else if (node.hasProperty(Property.JCR_CREATED)) + calendar = node.getProperty(Property.JCR_CREATED).getDate(); + else + throw new IllegalArgumentException("No modification time found in " + node); + return calendar.toInstant(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get modification time for " + node, e); + } + + } + + /** + * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set. + */ + public static Instant getCreated(Node node) { + Calendar calendar = null; + try { + if (node.hasProperty(Property.JCR_CREATED)) + calendar = node.getProperty(Property.JCR_CREATED).getDate(); + else + throw new IllegalArgumentException("No created time found in " + node); + return calendar.toInstant(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get created time for " + node, e); + } + + } + + /** + * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time + * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying + * session user id. + */ + public static void updateLastModified(Node node) { + updateLastModified(node, false); + } + + /** + * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time + * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying + * session user id. In Jackrabbit 2.x, * these properties are * not automatically updated, hence the need for manual update. The session * is not saved. */ - public static void updateLastModified(Node node) { + public static void updateLastModified(Node node, boolean addMixin) { try { - if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) + if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) node.addMixin(NodeType.MIX_LAST_MODIFIED); node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar()); node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID()); @@ -1175,16 +1287,26 @@ public class JcrUtils { * @param untilPath the base path, null is equivalent to "/" */ public static void updateLastModifiedAndParents(Node node, String untilPath) { + updateLastModifiedAndParents(node, untilPath, true); + } + + /** + * Update lastModified recursively until this parent. + * + * @param node the node + * @param untilPath the base path, null is equivalent to "/" + */ + public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) { try { if (untilPath != null && !node.getPath().startsWith(untilPath)) throw new IllegalArgumentException(node + " is not under " + untilPath); - updateLastModified(node); + updateLastModified(node, addMixin); if (untilPath == null) { if (!node.getPath().equals("/")) - updateLastModifiedAndParents(node.getParent(), untilPath); + updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); } else { if (!node.getPath().equals(untilPath)) - updateLastModifiedAndParents(node.getParent(), untilPath); + updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); } } catch (RepositoryException e) { throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e); @@ -1293,13 +1415,13 @@ public class JcrUtils { Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); acl.addAccessControlEntry(principal, privileges); acm.setPolicy(path, acl); - if (log.isDebugEnabled()) { - StringBuffer privBuf = new StringBuffer(); - for (Privilege priv : privs) - privBuf.append(priv.getName()); - log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" - + session.getWorkspace().getName() + "'"); - } +// if (log.isDebugEnabled()) { +// StringBuffer privBuf = new StringBuffer(); +// for (Privilege priv : privs) +// privBuf.append(priv.getName()); +// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" +// + session.getWorkspace().getName() + "'"); +// } session.refresh(true); session.save(); return true; @@ -1350,6 +1472,8 @@ public class JcrUtils { // the new access control list must be applied otherwise this call: // acl.removeAccessControlEntry(ace); has no effect acm.setPolicy(path, acl); + session.refresh(true); + session.save(); } /* @@ -1402,8 +1526,8 @@ public class JcrUtils { toNode.getSession().save(); count++; - if (log.isDebugEnabled()) - log.debug("Copied file " + fromChild.getPath()); +// if (log.isDebugEnabled()) +// log.debug("Copied file " + fromChild.getPath()); if (monitor != null) monitor.worked(1); } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) { @@ -1457,6 +1581,7 @@ public class JcrUtils { * * @return the created file node */ + @Deprecated public static Node copyFile(Node folderNode, File file) { try (InputStream in = new FileInputStream(file)) { return copyStreamAsFile(folderNode, file.getName(), in); @@ -1501,6 +1626,7 @@ public class JcrUtils { } binary = contentNode.getSession().getValueFactory().createBinary(in); contentNode.setProperty(Property.JCR_DATA, binary); + updateLastModified(contentNode); return fileNode; } catch (RepositoryException e) { throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e); @@ -1514,6 +1640,41 @@ public class JcrUtils { return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream(); } + /** + * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this + * file node. + */ + public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException { + Node contentNode = fileNode.getNode(Node.JCR_CONTENT); + if (mimeType != null) + contentNode.setProperty(Property.JCR_MIMETYPE, mimeType); + if (encoding != null) + contentNode.setProperty(Property.JCR_ENCODING, encoding); + // TODO remove properties if args are null? + } + + public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) { + try { + Files.createDirectories(targetDir); + for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) { + Node node = nit.nextNode(); + if (node.isNodeType(NodeType.NT_FILE)) { + Path filePath = targetDir.resolve(node.getName()); + try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) { + IOUtils.copy(in, out); + } + } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) { + Path dirPath = targetDir.resolve(node.getName()); + copyFilesToFs(node, dirPath, true); + } + } + } catch (RepositoryException e) { + throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e); + } catch (IOException e) { + throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e); + } + } + /** * Computes the checksum of an nt:file. * @@ -1569,4 +1730,49 @@ public class JcrUtils { return new String(hexChars); } + /** Export a subtree as a compact XML without namespaces. */ + public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException { + sb.append('<'); + String nodeName = node.getName(); + int colIndex = nodeName.indexOf(':'); + if (colIndex > 0) { + nodeName = nodeName.substring(colIndex + 1); + } + sb.append(nodeName); + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property p = pit.nextProperty(); + // skip multiple properties + if (p.isMultiple()) + continue properties; + String propertyName = p.getName(); + int pcolIndex = propertyName.indexOf(':'); + // skip properties with namespaces + if (pcolIndex > 0) + continue properties; + // skip binaries + if (p.getType() == PropertyType.BINARY) { + continue properties; + // TODO retrieve identifier? + } + sb.append(' '); + sb.append(propertyName); + sb.append('='); + sb.append('\"').append(p.getString()).append('\"'); + } + + if (node.hasNodes()) { + sb.append('>'); + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + toSimpleXml(children.nextNode(), sb); + } + sb.append("'); + } else { + sb.append("/>"); + } + } + }