Introduce set mime type.
[lgpl/argeo-commons.git] / org.argeo.jcr / src / org / argeo / jcr / JcrUtils.java
index e304649e23ac386c718895b532b0b276e52e92ca..cb9fe6e471797a9289a7ff945c53d4530221ff33 100644 (file)
@@ -6,6 +6,8 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.MessageDigest;
@@ -13,6 +15,7 @@ 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 +28,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;
@@ -36,6 +40,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;
@@ -305,20 +310,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 +722,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 +762,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 +796,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 +807,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 +1212,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,
         * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
         * not automatically updated</a>, 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 +1283,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 +1468,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();
        }
 
        /*
@@ -1494,6 +1621,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 +1635,19 @@ 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?
+       }
+
        /**
         * Computes the checksum of an nt:file.
         *