X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.jcr%2Fsrc%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=3be8be184b25f269d581d09f2bf541980883143d;hb=46cc2039ac20703c484aa994b830a2da113f2c97;hp=e304649e23ac386c718895b532b0b276e52e92ca;hpb=8e5934c1ff5587a5156854839210cc93bb66fc41;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 e304649e2..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,17 +31,20 @@ 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; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; import javax.jcr.Repository; 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; @@ -305,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} */ @@ -691,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 @@ -704,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()) { @@ -728,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(); @@ -744,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) { @@ -1142,17 +1216,62 @@ public class JcrUtils { } /** - * 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()); @@ -1168,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); @@ -1343,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(); } /* @@ -1450,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); @@ -1494,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); @@ -1507,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. * @@ -1562,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("/>"); + } + } + }