X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jcr%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=facd475cafd1e789c1689e7f88e317ff1ec1d749;hb=8b8ee149b20e2578a55e17413fa5f7399ff7ba14;hp=f62196cafe3663de56e1e2ce544832c02ad40fce;hpb=a0c5ef0d3f8c263780c919787f1a62a124b552a8;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index f62196caf..facd475ca 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -16,18 +16,22 @@ 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 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 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,13 +378,47 @@ 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 tokenize(String path) { + List tokens = new ArrayList(); + 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); + } + /** * Safe and repository implementation independent registration of a * namespace. @@ -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); @@ -872,8 +1042,12 @@ public class JcrUtils implements ArgeoJcrConstants { } 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); @@ -931,14 +1105,29 @@ 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 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 last modified", e); + throw new ArgeoException("Cannot update lastModified from " + node + + " until " + untilPath, e); } } @@ -968,4 +1157,37 @@ public class JcrUtils implements ArgeoJcrConstants { } 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); + } + } }