]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java
First working remote node
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jcr / src / main / java / org / argeo / jcr / JcrUtils.java
index b03be7396bdce3cd4b17733bd73058d5692d60d7..facd475cafd1e789c1689e7f88e317ff1ec1d749 100644 (file)
 
 package org.argeo.jcr;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.DateFormat;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 import java.util.TreeMap;
 
 import javax.jcr.Binary;
@@ -36,6 +40,7 @@ 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.RepositoryFactory;
@@ -52,6 +57,7 @@ import javax.jcr.query.qom.QueryObjectModelFactory;
 import javax.jcr.query.qom.Selector;
 import javax.jcr.query.qom.StaticOperand;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
@@ -139,6 +145,46 @@ public class JcrUtils implements ArgeoJcrConstants {
                }
        }
 
+       /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
+       public static void urlToAddressProperties(Node node, String url) {
+               try {
+                       URL u = new URL(url);
+                       node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
+                       node.setProperty(Property.JCR_HOST, u.getHost());
+                       node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
+                       node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot set URL " + url
+                                       + " as nt:address properties", e);
+               }
+       }
+
+       /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
+       public static String urlFromAddressProperties(Node node) {
+               try {
+                       URL u = new URL(
+                                       node.getProperty(Property.JCR_PROTOCOL).getString(), node
+                                                       .getProperty(Property.JCR_HOST).getString(),
+                                       (int) node.getProperty(Property.JCR_PORT).getLong(), node
+                                                       .getProperty(Property.JCR_PATH).getString());
+                       return u.toString();
+               } catch (Exception e) {
+                       throw new ArgeoException(
+                                       "Cannot get URL from nt:address properties of " + node, e);
+               }
+       }
+
+       /** Make sure that: starts with '/', do not end with '/', do not have '//' */
+       public static String normalizePath(String path) {
+               List<String> tokens = tokenize(path);
+               StringBuffer buf = new StringBuffer(path.length());
+               for (String token : tokens) {
+                       buf.append('/');
+                       buf.append(token);
+               }
+               return buf.toString();
+       }
+
        /**
         * Creates a path from a FQDN, inverting the order of the component:
         * www.argeo.org => org.argeo.www
@@ -219,14 +265,35 @@ public class JcrUtils implements ArgeoJcrConstants {
                return path.substring(index + 1);
        }
 
+       /**
+        * Routine that get the child with this name, adding id 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);
+       }
+
+       /**
+        * Routine that get the child with this name, adding id 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);
+       }
+
        /** Creates the nodes making path, if they don't exist. */
        public static Node mkdirs(Session session, String path) {
                return mkdirs(session, path, null, null, false);
        }
 
        /**
-        * @deprecated use {@link #mkdirs(Session, String, String, String, Boolean)}
-        *             instead.
+        * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
+        * 
+        * @deprecated
         */
        @Deprecated
        public static Node mkdirs(Session session, String path, String type,
@@ -242,9 +309,33 @@ public class JcrUtils implements ArgeoJcrConstants {
                return mkdirs(session, path, type, null, false);
        }
 
+       /**
+        * Synchronized and save is performed, to avoid race conditions in
+        * initializers leading to duplicate nodes.
+        */
+       public synchronized static Node mkdirsSafe(Session session, String path,
+                       String type) {
+               try {
+                       if (session.hasPendingChanges())
+                               throw new ArgeoException(
+                                               "Session has pending changes, save them first.");
+                       Node node = mkdirs(session, path, type);
+                       session.save();
+                       return node;
+               } catch (RepositoryException e) {
+                       discardQuietly(session);
+                       throw new ArgeoException("Cannot safely make directories", e);
+               }
+       }
+
+       public synchronized static Node mkdirsSafe(Session session, String path) {
+               return mkdirsSafe(session, path, null);
+       }
+
        /**
         * Creates the nodes making path, if they don't exist. This is up to the
-        * caller to save the session.
+        * caller to save the session. Use with caution since it can create
+        * duplicate nodes if used concurrently.
         */
        public static Node mkdirs(Session session, String path, String type,
                        String intermediaryNodeType, Boolean versioning) {
@@ -265,16 +356,16 @@ public class JcrUtils implements ArgeoJcrConstants {
                                return node;
                        }
 
-                       StringTokenizer st = new StringTokenizer(path, "/");
                        StringBuffer current = new StringBuffer("/");
                        Node currentNode = session.getRootNode();
-                       while (st.hasMoreTokens()) {
-                               String part = st.nextToken();
+                       Iterator<String> it = tokenize(path).iterator();
+                       while (it.hasNext()) {
+                               String part = it.next();
                                current.append(part).append('/');
                                if (!session.itemExists(current.toString())) {
-                                       if (!st.hasMoreTokens() && type != null)
+                                       if (!it.hasNext() && type != null)
                                                currentNode = currentNode.addNode(part, type);
-                                       else if (st.hasMoreTokens() && intermediaryNodeType != null)
+                                       else if (it.hasNext() && intermediaryNodeType != null)
                                                currentNode = currentNode.addNode(part,
                                                                intermediaryNodeType);
                                        else
@@ -287,11 +378,45 @@ public class JcrUtils implements ArgeoJcrConstants {
                                        currentNode = (Node) session.getItem(current.toString());
                                }
                        }
-                       // session.save();
                        return currentNode;
                } catch (RepositoryException e) {
+                       discardQuietly(session);
                        throw new ArgeoException("Cannot mkdirs " + path, e);
+               } finally {
+               }
+       }
+
+       /** Convert a path to the list of its tokens */
+       public static List<String> tokenize(String path) {
+               List<String> tokens = new ArrayList<String>();
+               boolean optimized = false;
+               if (!optimized) {
+                       String[] rawTokens = path.split("/");
+                       for (String token : rawTokens) {
+                               if (!token.equals(""))
+                                       tokens.add(token);
+                       }
+               } else {
+                       StringBuffer curr = new StringBuffer();
+                       char[] arr = path.toCharArray();
+                       chars: for (int i = 0; i < arr.length; i++) {
+                               char c = arr[i];
+                               if (c == '/') {
+                                       if (i == 0 || (i == arr.length - 1))
+                                               continue chars;
+                                       if (curr.length() > 0) {
+                                               tokens.add(curr.toString());
+                                               curr = new StringBuffer();
+                                       }
+                               } else
+                                       curr.append(c);
+                       }
+                       if (curr.length() > 0) {
+                               tokens.add(curr.toString());
+                               curr = new StringBuffer();
+                       }
                }
+               return Collections.unmodifiableList(tokens);
        }
 
        /**
@@ -354,15 +479,17 @@ public class JcrUtils implements ArgeoJcrConstants {
                        NodeIterator it = node.getNodes();
                        while (it.hasNext()) {
                                Node childNode = it.nextNode();
-                               debug(childNode);
+                               debug(childNode, log);
                        }
 
                        // Then output the properties
                        PropertyIterator properties = node.getProperties();
                        // log.debug("Property are : ");
 
-                       while (properties.hasNext()) {
+                       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();
@@ -505,16 +632,18 @@ public class JcrUtils implements ArgeoJcrConstants {
                                                        relPath, p.getValue(), null);
                                        diffs.put(relPath, pDiff);
                                } else {
-                                       if (p.isMultiple())
-                                               continue props;
-                                       Value referenceValue = p.getValue();
-                                       Value newValue = observed.getProperty(name).getValue();
-                                       if (!referenceValue.equals(newValue)) {
-                                               String relPath = propertyRelPath(baseRelPath, name);
-                                               PropertyDiff pDiff = new PropertyDiff(
-                                                               PropertyDiff.MODIFIED, relPath, referenceValue,
-                                                               newValue);
-                                               diffs.put(relPath, pDiff);
+                                       if (p.isMultiple()) {
+                                               // FIXME implement multiple
+                                       } else {
+                                               Value referenceValue = p.getValue();
+                                               Value newValue = observed.getProperty(name).getValue();
+                                               if (!referenceValue.equals(newValue)) {
+                                                       String relPath = propertyRelPath(baseRelPath, name);
+                                                       PropertyDiff pDiff = new PropertyDiff(
+                                                                       PropertyDiff.MODIFIED, relPath,
+                                                                       referenceValue, newValue);
+                                                       diffs.put(relPath, pDiff);
+                                               }
                                        }
                                }
                        }
@@ -526,10 +655,14 @@ public class JcrUtils implements ArgeoJcrConstants {
                                if (name.startsWith("jcr:"))
                                        continue props;
                                if (!reference.hasProperty(name)) {
-                                       String relPath = propertyRelPath(baseRelPath, name);
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED,
-                                                       relPath, null, p.getValue());
-                                       diffs.put(relPath, pDiff);
+                                       if (p.isMultiple()) {
+                                               // FIXME implement multiple
+                                       } else {
+                                               String relPath = propertyRelPath(baseRelPath, name);
+                                               PropertyDiff pDiff = new PropertyDiff(
+                                                               PropertyDiff.ADDED, relPath, null, p.getValue());
+                                               diffs.put(relPath, pDiff);
+                                       }
                                }
                        }
                } catch (RepositoryException e) {
@@ -659,6 +792,43 @@ public class JcrUtils implements ArgeoJcrConstants {
                binary.dispose();
        }
 
+       /** Retrieve a {@link Binary} as a byte array */
+       public static byte[] getBinaryAsBytes(Property property) {
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               InputStream in = null;
+               Binary binary = null;
+               try {
+                       binary = property.getBinary();
+                       in = binary.getStream();
+                       IOUtils.copy(in, out);
+                       return out.toByteArray();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot read binary " + property
+                                       + " as bytes", e);
+               } finally {
+                       IOUtils.closeQuietly(out);
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
+       /** Writes a {@link Binary} from a byte array */
+       public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
+               InputStream in = null;
+               Binary binary = null;
+               try {
+                       in = new ByteArrayInputStream(bytes);
+                       binary = node.getSession().getValueFactory().createBinary(in);
+                       node.setProperty(property, binary);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot read binary " + property
+                                       + " as bytes", e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
        /**
         * Creates depth from a string (typically a username) by adding levels based
         * on its first characters: "aBcD",2 => a/aB
@@ -830,8 +1000,8 @@ public class JcrUtils implements ArgeoJcrConstants {
                        Constraint constraint = qomf.comparison(userIdDop,
                                        QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop);
                        Query query = qomf.createQuery(sel, constraint, null, null);
-                       Node userHome = JcrUtils.querySingleNode(query);
-                       return userHome;
+                       Node userProfile = JcrUtils.querySingleNode(query);
+                       return userProfile;
                } catch (RepositoryException e) {
                        throw new ArgeoException(
                                        "Cannot find profile for user " + username, e);
@@ -858,7 +1028,8 @@ public class JcrUtils implements ArgeoJcrConstants {
                                } catch (Exception e) {
                                        // we use this workaround to be sure to get the stack trace
                                        // to identify the sink of the bug.
-                                       log.warn("trying to create an already existing userHome. Stack trace : ");
+                                       log.warn("trying to create an already existing userHome at path:"
+                                                       + homePath + ". Stack trace : ");
                                        e.printStackTrace();
                                }
                        }
@@ -866,13 +1037,17 @@ public class JcrUtils implements ArgeoJcrConstants {
                        Node userHome = JcrUtils.mkdirs(session, homePath);
                        Node userProfile;
                        if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
-                               log.warn("User profile node already exists. We do not add a new one");
-
+                               log.warn("userProfile node already exists for userHome path: "
+                                               + homePath + ". We do not add a new one");
                        } else {
                                userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE);
                                userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
+                               // session.getWorkspace().getVersionManager()
+                               // .checkout(userProfile.getPath());
                                userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
                                session.save();
+                               session.getWorkspace().getVersionManager()
+                                               .checkin(userProfile.getPath());
                                // we need to save the profile before adding the user home type
                        }
                        userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
@@ -930,14 +1105,89 @@ public class JcrUtils implements ArgeoJcrConstants {
         */
        public static void updateLastModified(Node node) {
                try {
-                       if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-                               node.setProperty(Property.JCR_LAST_MODIFIED,
-                                               new GregorianCalendar());
-                               node.setProperty(Property.JCR_LAST_MODIFIED_BY, node
-                                               .getSession().getUserID());
-                       }
+                       if (!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());
                } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot update last modified", e);
+                       throw new ArgeoException("Cannot update last modified on " + node,
+                                       e);
+               }
+       }
+
+       /** Update lastModified recursively until this parent. */
+       public static void updateLastModifiedAndParents(Node node, String untilPath) {
+               try {
+                       if (!node.getPath().startsWith(untilPath))
+                               throw new ArgeoException(node + " is not under " + untilPath);
+                       updateLastModified(node);
+                       if (!node.getPath().equals(untilPath))
+                               updateLastModifiedAndParents(node.getParent(), untilPath);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot update lastModified from " + node
+                                       + " until " + untilPath, e);
+               }
+       }
+
+       /**
+        * Returns a String representing the short version (see <a
+        * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+        * Notation </a> attributes grammar) of the main business attributes of this
+        * property definition
+        * 
+        * @param prop
+        */
+       public static String getPropertyDefinitionAsString(Property prop) {
+               StringBuffer sbuf = new StringBuffer();
+               try {
+                       if (prop.getDefinition().isAutoCreated())
+                               sbuf.append("a");
+                       if (prop.getDefinition().isMandatory())
+                               sbuf.append("m");
+                       if (prop.getDefinition().isProtected())
+                               sbuf.append("p");
+                       if (prop.getDefinition().isMultiple())
+                               sbuf.append("*");
+               } catch (RepositoryException re) {
+                       throw new ArgeoException(
+                                       "unexpected error while getting property definition as String",
+                                       re);
+               }
+               return sbuf.toString();
+       }
+
+       /**
+        * Estimate the sub tree size from current node. Computation is based on the
+        * Jcr {@link Property.getLength()} method. Note : it is not the exact size
+        * used on the disk by the current part of the JCR Tree.
+        */
+
+       public static long getNodeApproxSize(Node node) {
+               long curNodeSize = 0;
+               try {
+                       PropertyIterator pi = node.getProperties();
+                       while (pi.hasNext()) {
+                               Property prop = pi.nextProperty();
+                               if (prop.isMultiple()) {
+                                       int nb = prop.getLengths().length;
+                                       for (int i = 0; i < nb; i++) {
+                                               curNodeSize += (prop.getLengths()[i] > 0 ? prop
+                                                               .getLengths()[i] : 0);
+                                       }
+                               } else
+                                       curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
+                       }
+
+                       NodeIterator ni = node.getNodes();
+                       while (ni.hasNext())
+                               curNodeSize += getNodeApproxSize(ni.nextNode());
+                       return curNodeSize;
+               } catch (RepositoryException re) {
+                       throw new ArgeoException(
+                                       "Unexpected error while recursively determining node size.",
+                                       re);
                }
        }
 }