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=08f8d55c393727aecb32259c6b6f45df97db931c;hb=7b192bb9e877cada25fc90ff339e6129cd007b38;hp=cf0d2d74aa9bc7adf0b273ef0b0d9b845d6f4b71;hpb=a13bf13c2f10678d17985c09b5a7acfecbf8e3ae;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 cf0d2d74a..08f8d55c3 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,6 +16,9 @@ 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; @@ -52,6 +55,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; @@ -60,6 +64,17 @@ import org.argeo.ArgeoException; public class JcrUtils implements ArgeoJcrConstants { private final static Log log = LogFactory.getLog(JcrUtils.class); + /** + * Not complete yet. See + * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local + * %20Names + */ + public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', + '|', '*', /* + * invalid XML chars : + */ + '<', '>', '&' }; + /** Prevents instantiation */ private JcrUtils() { } @@ -90,13 +105,6 @@ public class JcrUtils implements ArgeoJcrConstants { return node; } - /** Removes forbidden characters from a path, replacing them with '_' */ - public static String removeForbiddenCharacters(String str) { - return str.replace('[', '_').replace(']', '_').replace('/', '_') - .replace('*', '_'); - - } - /** Retrieves the parent path of the provided path */ public static String parentPath(String path) { if (path.equals("/")) @@ -160,19 +168,24 @@ public class JcrUtils implements ArgeoJcrConstants { */ public static String dateAsPath(Calendar cal, Boolean addHour) { StringBuffer buf = new StringBuffer(14); - buf.append('Y').append(cal.get(Calendar.YEAR)); + buf.append('Y'); + buf.append(cal.get(Calendar.YEAR)); buf.append('/'); + int month = cal.get(Calendar.MONTH) + 1; buf.append('M'); if (month < 10) buf.append(0); buf.append(month); buf.append('/'); + int day = cal.get(Calendar.DAY_OF_MONTH); + buf.append('D'); if (day < 10) buf.append(0); - buf.append('D').append(day); + buf.append(day); buf.append('/'); + if (addHour) { int hour = cal.get(Calendar.HOUR_OF_DAY); buf.append('H'); @@ -210,14 +223,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, @@ -589,13 +623,60 @@ public class JcrUtils implements ArgeoJcrConstants { } /** - * Normalize a name so taht it can be stores in contexts not supporting + * Normalizes a name so that it can be stored in contexts not supporting * names with ':' (typically databases). Replaces ':' by '_'. */ public static String normalize(String name) { return name.replace(':', '_'); } + /** + * Replaces characters which are invalid in a JCR name by '_'. Currently not + * exhaustive. + * + * @see JcrUtils#INVALID_NAME_CHARACTERS + */ + public static String replaceInvalidChars(String name) { + return replaceInvalidChars(name, '_'); + } + + /** + * Replaces characters which are invalid in a JCR name. Currently not + * exhaustive. + * + * @see JcrUtils#INVALID_NAME_CHARACTERS + */ + public static String replaceInvalidChars(String name, char replacement) { + boolean modified = false; + char[] arr = name.toCharArray(); + for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + invalid: for (char invalid : INVALID_NAME_CHARACTERS) { + if (c == invalid) { + arr[i] = replacement; + modified = true; + break invalid; + } + } + } + if (modified) + return new String(arr); + else + // do not create new object if unnecessary + return name; + } + + /** + * Removes forbidden characters from a path, replacing them with '_' + * + * @deprecated use {@link #replaceInvalidChars(String)} instead + */ + public static String removeForbiddenCharacters(String str) { + return str.replace('[', '_').replace(']', '_').replace('/', '_') + .replace('*', '_'); + + } + /** Cleanly disposes a {@link Binary} even if it is null. */ public static void closeQuietly(Binary binary) { if (binary == null) @@ -603,6 +684,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 @@ -690,8 +808,13 @@ public class JcrUtils implements ArgeoJcrConstants { /** Logs out the session, not throwing any exception, even if it is null. */ public static void logoutQuietly(Session session) { - if (session != null) - session.logout(); + try { + if (session != null) + if (session.isLive()) + session.logout(); + } catch (Exception e) { + // silent + } } /** Returns the home node of the session user or null if none was found. */ @@ -786,15 +909,35 @@ public class JcrUtils implements ArgeoJcrConstants { if (session.hasPendingChanges()) throw new ArgeoException( "Session has pending changes, save them first"); + String homePath = homeBasePath + '/' + firstCharsToPath(username, 2) + '/' + username; - Node userHome = JcrUtils.mkdirs(session, homePath); - Node userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); - userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - // we need to save the profile before adding the user home type + if (session.itemExists(homePath)) { + try { + throw new ArgeoException( + "Trying to create a user home that already exists"); + } 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 at path:" + + homePath + ". Stack trace : "); + e.printStackTrace(); + } + } + + Node userHome = JcrUtils.mkdirs(session, homePath); + Node userProfile; + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { + 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); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + session.save(); + // we need to save the profile before adding the user home type + } userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); // see // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html @@ -860,4 +1003,64 @@ public class JcrUtils implements ArgeoJcrConstants { throw new ArgeoException("Cannot update last modified", e); } } + + /** + * Returns a String representing the short version (see Node type + * Notation 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); + } + } }