[exo-jcr-commits] exo-jcr SVN: r4226 - in jcr/trunk/exo.jcr.component.ext/src: main/java/org/exoplatform/services/jcr/ext/distribution and 4 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Mon Apr 11 13:40:26 EDT 2011


Author: nfilotto
Date: 2011-04-11 13:40:26 -0400 (Mon, 11 Apr 2011)
New Revision: 4226

Added:
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionManager.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionMode.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionType.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/AbstractDataDistributionType.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByHash.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByName.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByPath.java
   jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionManagerImpl.java
   jcr/trunk/exo.jcr.component.ext/src/test/java/org/exoplatform/services/jcr/ext/distribution/
   jcr/trunk/exo.jcr.component.ext/src/test/java/org/exoplatform/services/jcr/ext/distribution/TestDataDistributionManager.java
Modified:
   jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-configuration.xml
   jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-jcr-ext-config.xml
Log:
EXOJCR-1206: Create a data distribution service to help the applications to better distribute their child nodes
First Implementation

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionManager.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionManager.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionManager.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution;
+
+
+/**
+ * This service is used to distribute smartly the data over the JCR. It can help you to have the best
+ * possible performances in read and write accesses, especially if you have a lot of nodes of the
+ * same type to store, instead of storing them yourself under the same parent node (which can 
+ * affect the performances if you have a lot of nodes to store), we simply delegate data access and 
+ * storage to the {@link DataDistributionType} corresponding to the expected mode that will store the
+ *  data for you in an optimized and reliable way.
+ * 
+ * See below an example of how it can be used
+ * <pre>
+ * // Get the data distribution corresponding to the readable mode
+ * DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.READABLE);
+ * // Get or create the node corresponding to "john.smith"
+ * Node node = type.getOrCreateDataNode(parentNode, "john.smith");
+ * </pre>
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public interface DataDistributionManager
+{
+   /**
+    * Retrieves the data distribution type corresponding to given mode.
+    * @param mode the mode of distribution to use
+    * @return the expected mode if it exists <code>null</code> otherwise.
+    */
+   DataDistributionType getDataDistributionType(DataDistributionMode mode);
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionMode.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionMode.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionMode.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution;
+
+/**
+ * The existing data distribution modes are the following:
+ * <ul>
+ *    <li>Readable mode that is used to store the data in an understandable way for a human
+ *    being, corresponding to <code>DataDistributionMode.READABLE</code>.</li>
+ *    <li>Optimized mode that is used when we don't need to be able to understand
+ *    how it is stored, we only want to have the best possible performances, 
+ *    corresponding to <code>DataDistributionMode.OPTIMIZED</code>.</li>
+ *    <li>None mode that is used when we just want to store the node
+ *    as it is without using any distribution algorithm. This is mostly
+ *    interesting as helper to create a hierarchy of nodes in a reliable way
+ *    when we already know that we have only few nodes to store, 
+ *    corresponding to <code>DataDistributionMode.NONE</code>.</li>
+ * </ul>
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public enum DataDistributionMode 
+{
+   READABLE, OPTIMIZED, NONE
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionType.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionType.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/DataDistributionType.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+
+/**
+ * This interface describes a type of distribution.
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public interface DataDistributionType
+{
+   /**
+    * Retrieves the node from the JCR under the given root node and corresponding to the given
+    * data id.
+    * @param rootNode the root node under which the data to find is stored
+    * @param dataId the id of the data to find
+    * @return the Node corresponding to the data to find
+    * @throws PathNotFoundException if the data cannot be find
+    * @throws RepositoryException if an error occurred while trying to get the expected data
+    */
+   Node getDataNode(Node rootNode, String dataId) throws PathNotFoundException, RepositoryException;
+   
+   /**
+    * Tries to get the node from the JCR and if it cannot be found, it will create it automatically.
+    * @param rootNode the root node under which the data to find is stored
+    * @param dataId the id of the data to find/create
+    * @return the Node corresponding to the data to find
+    * @throws RepositoryException if an error occurred while trying to get or create the expected data
+    */
+   Node getOrCreateDataNode(Node rootNode, String dataId) throws RepositoryException;
+   
+   /**
+    * Tries to get the node from the JCR and if it cannot be found, it will create it automatically.
+    * If the node has to be created, the node will be created with the given node type.
+    * @param rootNode the root node under which the data to find is stored
+    * @param dataId the id of the data to find/create
+    * @param nodeType the node type to use in case we need to create the node
+    * @return the Node corresponding to the data to find
+    * @throws RepositoryException if an error occurred while trying to get or create the expected data
+    */
+   Node getOrCreateDataNode(Node rootNode, String dataId, String nodeType) throws RepositoryException;
+   
+   /**
+    * Tries to get the node from the JCR and if it cannot be found, it will create it automatically.
+    * If the node has to be created, the node will be created with the given node type and given
+    * mixin types.
+    * @param rootNode the root node under which the data to find is stored
+    * @param dataId the id of the data to find/create
+    * @param nodeType the node type to use in case we need to create the node
+    * @param mixinTypes the mixin types to use in case we need to create the node
+    * @return the Node corresponding to the data to find
+    * @throws RepositoryException if an error occurred while trying to get or create the expected data
+    */
+   Node getOrCreateDataNode(Node rootNode, String dataId, String nodeType, List<String> mixinTypes) throws RepositoryException;
+   
+   /**
+    * Tries to get the node from the JCR and if it cannot be found, it will create it automatically.
+    * If the node has to be created, the node will be created with the given node type, given
+    * mixin types and given permissions.
+    * @param rootNode the root node under which the data to find is stored
+    * @param dataId the id of the data to find/create
+    * @param nodeType the node type to use in case we need to create the node
+    * @param mixinTypes the mixin types to use in case we need to create the node
+    * @param permissions the permissions to use in case we need to create the node
+    * @return the Node corresponding to the data to find
+    * @throws RepositoryException if an error occurred while trying to get or create the expected data
+    */
+   Node getOrCreateDataNode(Node rootNode, String dataId, String nodeType, List<String> mixinTypes, Map<String, String[]> permissions) throws RepositoryException;
+   
+   /**
+    * Remove the node from the JCR if it exists
+    * @param rootNode the root node under which the data to remove is stored
+    * @param dataId the id of the data to remove
+    * @throws RepositoryException if an error occurred while trying to remove the expected data
+    */
+   void removeDataNode(Node rootNode, String dataId) throws RepositoryException;
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/AbstractDataDistributionType.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/AbstractDataDistributionType.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/AbstractDataDistributionType.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution.impl;
+
+import org.exoplatform.services.jcr.core.ExtendedNode;
+import org.exoplatform.services.jcr.ext.distribution.DataDistributionType;
+import org.exoplatform.services.jcr.impl.core.RepositoryImpl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+
+/**
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public abstract class AbstractDataDistributionType implements DataDistributionType
+{
+   /**
+    * The default node type to use when we create a new node
+    */
+   private static final String DEFAULT_NODE_TYPE = "nt:unstructured".intern();
+
+   /**
+    * The map defining all the locks available
+    */
+   private final ConcurrentMap<String, Lock> locks = new ConcurrentHashMap<String, Lock>(64, 0.75f, 64);
+
+   /**
+    * {@inheritDoc}
+    */
+   public Node getDataNode(Node rootNode, String dataId) throws PathNotFoundException, RepositoryException
+   {
+      return rootNode.getNode(getRelativePath(dataId));
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Node getOrCreateDataNode(Node rootNode, String dataId) throws RepositoryException
+   {
+      return getOrCreateDataNode(rootNode, dataId, null);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Node getOrCreateDataNode(Node rootNode, String dataId, String nodeType) throws RepositoryException
+   {
+      return getOrCreateDataNode(rootNode, dataId, nodeType, null);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Node getOrCreateDataNode(Node rootNode, String dataId, String nodeType, List<String> mixinTypes)
+      throws RepositoryException
+   {
+      return getOrCreateDataNode(rootNode, dataId, nodeType, mixinTypes, null);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Node getOrCreateDataNode(Node rootNode, String dataId, String nodeType, List<String> mixinTypes,
+      Map<String, String[]> permissions) throws RepositoryException
+   {
+      try
+      {
+         return getDataNode(rootNode, dataId);
+      }
+      catch (PathNotFoundException e)
+      {
+         // ignore me
+      }
+      // The node could not be found so we need to create it
+      Node node = rootNode;
+      List<String> ancestors = getAncestors(dataId);
+      for (int i = 0, length = ancestors.size(); i < length; i++)
+      {
+         String nodeName = ancestors.get(i);
+         try
+         {
+            node = node.getNode(nodeName);
+            continue;
+         }
+         catch (PathNotFoundException e)
+         {
+            // ignore me
+         }
+         // The node doesn't exist we need to create it
+         node = createNode(node, nodeName, nodeType, mixinTypes, permissions, i == length - 1);
+      }
+      return node;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void removeDataNode(Node rootNode, String dataId) throws RepositoryException
+   {      
+      Lock lock = getLock(rootNode, getRelativePath(dataId));
+      lock.lock();
+      try
+      {
+         Node node = getDataNode(rootNode, dataId);
+         Node parentNode = node.getParent();
+         node.remove();
+         parentNode.save();
+      }
+      catch (PathNotFoundException e)
+      {
+         // ignore me
+      }
+      finally 
+      {
+         lock.unlock();
+      }     
+   }
+   
+   /**
+    * Creates the node of the given node type with the given node name directly under 
+    * the given parent node, using the given mixin types and permissions
+    * @param parentNode the parent node
+    * @param nodeName the name of the node to create
+    * @param nodeType the node type to use
+    * @param mixinTypes the list of mixin types to use
+    * @param permissions the map of permissions to use
+    * @param isLeaf indicates whether or not the current node to create is the leaf node
+    * @return the created node
+    * @throws RepositoryException if any exception occurs while creating the node
+    */
+   private Node createNode(final Node parentNode, final String nodeName, final String nodeType,
+      final List<String> mixinTypes, final Map<String, String[]> permissions, final boolean isLeaf)
+      throws RepositoryException
+   {
+      Lock lock = getLock(parentNode, nodeName);
+      lock.lock();
+      try
+      {
+         try
+         {
+            // We ensure that the node has not been created since the last time we checked
+            return parentNode.getNode(nodeName);
+         }
+         catch (PathNotFoundException e)
+         {
+            // ignore me
+         }
+         
+         boolean useParameters = !useParametersOnLeafOnly() || (useParametersOnLeafOnly() && isLeaf);
+         Node node;
+         if (nodeType == null || nodeType.isEmpty() || !useParameters)
+         {
+            node = parentNode.addNode(nodeName, DEFAULT_NODE_TYPE);
+         }
+         else
+         {
+            node = parentNode.addNode(nodeName, nodeType);
+         }
+         if (useParameters)
+         {
+            if (permissions != null && !permissions.isEmpty())
+            {
+               if (node.canAddMixin("exo:privilegeable"))
+               {
+                  node.addMixin("exo:privilegeable");
+               }
+               ((ExtendedNode)node).setPermissions(permissions);
+            }
+            if (mixinTypes != null)
+            {
+               for (int i = 0, length = mixinTypes.size(); i < length; i++)
+               {
+                  String mixin = mixinTypes.get(i);
+                  if (node.canAddMixin(mixin))
+                  {
+                     node.addMixin(mixin);
+                  }
+               }
+            }
+         }
+         parentNode.save();
+         return node;         
+      }
+      finally 
+      {
+         lock.unlock();
+      }
+   }
+
+   /**
+    * Creates the full path of the given node name
+    * @param parentNode the parent node from which we extract the absolute path
+    * @param relativePath the relative path of the node for which we want the full path
+    * @return the full path of the node for which we want the path
+    * @throws RepositoryException if any exception occurs while creating the path
+    */
+   private String createFullPath(Node parentNode, String relativePath) throws RepositoryException
+   {
+      StringBuilder buffer = new StringBuilder(256);
+      buffer.append(((RepositoryImpl)parentNode.getSession().getRepository()).getName());
+      buffer.append('/');
+      buffer.append(parentNode.getSession().getWorkspace().getName());
+      String rootPath = parentNode.getPath();
+      buffer.append(rootPath);
+      if (!rootPath.endsWith("/"))
+      {
+         buffer.append('/');
+      }
+      buffer.append(relativePath);
+      return buffer.toString();
+   }
+
+   /**
+    * Get the lock corresponding to the given node name
+    * @param parentNode the parent node of the node to lock
+    * @param relativePath the relative path of the node to lock
+    * @return the lock corresponding to the given node name
+    * @throws RepositoryException if any exception occurs while getting the lock
+    */
+   private Lock getLock(Node parentNode, String relativePath) throws RepositoryException
+   {
+      String fullPath = createFullPath(parentNode, relativePath);
+      Lock lock = locks.get(fullPath);
+      if (lock != null)
+      {
+         return lock;
+      }
+      lock = new InternalLock(fullPath);
+      Lock prevLock = locks.putIfAbsent(fullPath, lock);
+      if (prevLock != null)
+      {
+         lock = prevLock;
+      }
+      return lock;
+
+   }
+   
+   /**
+    * Gives the relative path corresponding to the given id of the data to find/create
+    * @param dataId the id of the data to find/create
+    * @return the relative path of the data to find/create
+    */
+   protected String getRelativePath(String dataId)
+   {
+      StringBuilder buffer = new StringBuilder(256);
+      List<String> ancestors = getAncestors(dataId);
+      for (int i = 0, length = ancestors.size(); i < length; i++)
+      {
+         buffer.append(ancestors.get(i));
+         if (i != length - 1)
+         {
+            buffer.append('/');
+         }
+      }
+      return buffer.toString();
+   }
+
+   /**
+    * Gives the list of all the name of the ancestors
+    * @param dataId the id of the data to find/create
+    * @return the list of the ancestor names
+    */
+   protected abstract List<String> getAncestors(String dataId);
+
+   /**
+    * Indicates whether or not the node type, the mixin types and the permissions have to
+    * be used on leaf node only.
+    * @return <code>true</code> if only the leaf node has to be created with the parameters
+    * <code>false</code> otherwise.
+    */
+   protected abstract boolean useParametersOnLeafOnly();
+   
+   
+   /**
+    * This kind of locks can self unregister from the map of locks
+    */
+   private class InternalLock extends ReentrantLock {
+      
+      /**
+       * Serial Version UID
+       */
+      private static final long serialVersionUID = -3362387346368015145L;
+      
+      /**
+       * The id corresponding to the lock in the map
+       */
+      private final String fullPath;
+
+      /**
+       * The default constructor
+       * @param fullId the id corresponding to the lock in the map
+       */
+      public InternalLock(String fullPath) {
+         super();
+         this.fullPath = fullPath;
+      }
+
+      @Override
+      public void unlock() {
+         if (!hasQueuedThreads()) {
+            // No thread is currently waiting for this lock
+            // The lock will then be removed
+            locks.remove(fullPath, this);
+         }
+         super.unlock();
+      }
+   }   
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByHash.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByHash.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByHash.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution.impl;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * It will generate from the data id an hash code thanks to an hashing function
+ * then with this hash code it will be able to generate a hierarchy of sub-nodes 
+ * with <code>n</code> levels of depth for example with <code>n = 4</code> and
+ * MD5 as hashing function:
+ * For "john.smith" with MD5, the hash code in base 32 is 12spjkm4fhsrl151pva3f7mf1r, 
+ * so the path would be "1/2/s/john.smith"
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public class DataDistributionByHash extends AbstractDataDistributionType
+{
+   /**
+    * The level of depth used by the algorithm
+    */
+   private int depth = 4;
+
+   /**
+    * The name of the hash algorithm to use
+    */
+   private String hashAlgorithm = "MD5";
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected List<String> getAncestors(String dataId)
+   {
+      List<String> result = new ArrayList<String>(depth);
+      String hash = hash(dataId);
+      int length = hash.length();
+      for (int i = 0; i < depth - 1 && i < length; i++)
+      {
+         result.add(hash.substring(i, i + 1));
+      }
+      result.add(dataId);
+      return result;
+   }
+
+   /**
+    * Gives the hash code of the given data id
+    * @param dataId The id of the data to hash
+    * @return The hash code of the value of the given data id in base 32
+    */
+   private String hash(String dataId)
+   {
+      try
+      {
+         MessageDigest digest = MessageDigest.getInstance(hashAlgorithm);
+         digest.update(dataId.getBytes("UTF-8"));
+         return new BigInteger(1, digest.digest()).toString(32);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("Could not generate the hash code of '" + dataId + "' with the algorithm '"
+            + hashAlgorithm + "'", e);
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected boolean useParametersOnLeafOnly()
+   {
+      return true;
+   }
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByName.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByName.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByName.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * This data distribution will distribute the data in a understandable way for a human being.
+ * The expected data id is for example a login of a user.
+ * It will generate a hierarchy of sub-nodes with <code>n</code> levels of depth for example
+ *  with <code>n = 4</code>:
+ * <ul>
+ *    <li>Usename: john.smith (size >= 4) it will generate a path of type "j___/jo___/joh___/john.smith"</li>
+ *    <li>Usename: bob (size < 4) it will generate a path of type "b___/bo___/bob"</li>
+ * </ul>
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public class DataDistributionByName extends AbstractDataDistributionType
+{
+   
+   /**
+    * The level of depth used by the algorithm
+    */
+   private int depth = 4;
+   
+   /**
+    * The suffix used by the algorithm to indicate that they are sub nodes inside
+    */
+   private String suffix = "___";
+   
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected List<String> getAncestors(String dataId)
+   {
+      List<String> result = new ArrayList<String>(depth);
+      int length = dataId.length();
+      for (int i = 0; i < depth - 1 && i < length - 1; i++)
+      {
+         StringBuilder buffer = new StringBuilder();
+         buffer.append(dataId, 0, i + 1);
+         buffer.append(suffix);
+         result.add(buffer.toString());
+      }
+      result.add(dataId);
+      return result;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected boolean useParametersOnLeafOnly()
+   {
+      return true;
+   }
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByPath.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByPath.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionByPath.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution.impl;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This distribution doesn't do anything specific, it only stores the content as
+ * requested, which means that the data id expected is simply a relative path. It will
+ * mainly help to create a tree of nodes in a reliable way.
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public class DataDistributionByPath extends AbstractDataDistributionType
+{
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected List<String> getAncestors(String relativePath)
+   {
+      if (relativePath.startsWith("/"))
+         relativePath = relativePath.substring(1);
+      return Arrays.asList(relativePath.split("/"));
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected boolean useParametersOnLeafOnly()
+   {
+      return false;
+   }
+}

Added: jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionManagerImpl.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionManagerImpl.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/main/java/org/exoplatform/services/jcr/ext/distribution/impl/DataDistributionManagerImpl.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution.impl;
+
+import org.exoplatform.container.xml.InitParams;
+import org.exoplatform.container.xml.ObjectParameter;
+import org.exoplatform.services.jcr.ext.distribution.DataDistributionManager;
+import org.exoplatform.services.jcr.ext.distribution.DataDistributionMode;
+import org.exoplatform.services.jcr.ext.distribution.DataDistributionType;
+
+/**
+ * The default implementation of a {@link DataDistributionManager}.
+ * It will use a {@link DataDistributionByName} when the readable mode is expected
+ * and a {@link DataDistributionByHash} when the non readable mode is expected
+ * 
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public class DataDistributionManagerImpl implements DataDistributionManager
+{
+
+   /**
+    * The {@link DataDistributionType} used in case of "readable" mode
+    */
+   private DataDistributionType readable = new DataDistributionByName();
+   
+   /**
+    * The {@link DataDistributionType} used in case of "optimized" mode
+    */
+   private DataDistributionType optimized = new DataDistributionByHash();
+   
+   /**
+    * The {@link DataDistributionType} used in case of "none" mode
+    */
+   private DataDistributionType none = new DataDistributionByPath();
+   
+   public DataDistributionManagerImpl()
+   {
+      this(null);
+   }
+   
+   public DataDistributionManagerImpl(InitParams params)
+   {
+      if (params != null)
+      {
+         ObjectParameter op = params.getObjectParam("readable");
+         if (op != null && op.getObject() instanceof DataDistributionType)
+         {
+            this.readable = (DataDistributionType)op.getObject();
+         }
+         op = params.getObjectParam("optimized");
+         if (op != null && op.getObject() instanceof DataDistributionType)
+         {
+            this.optimized = (DataDistributionType)op.getObject();
+         }
+         op = params.getObjectParam("none");
+         if (op != null && op.getObject() instanceof DataDistributionType)
+         {
+            this.none = (DataDistributionType)op.getObject();
+         }
+      }
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public DataDistributionType getDataDistributionType(DataDistributionMode mode)
+   {
+      if (mode == DataDistributionMode.READABLE)
+      {
+         return readable;
+      }
+      else if (mode == DataDistributionMode.OPTIMIZED)
+      {
+         return optimized;
+      }
+      else if (mode == DataDistributionMode.NONE)
+      {
+         return none;
+      }
+      return null;
+   }
+}

Added: jcr/trunk/exo.jcr.component.ext/src/test/java/org/exoplatform/services/jcr/ext/distribution/TestDataDistributionManager.java
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/test/java/org/exoplatform/services/jcr/ext/distribution/TestDataDistributionManager.java	                        (rev 0)
+++ jcr/trunk/exo.jcr.component.ext/src/test/java/org/exoplatform/services/jcr/ext/distribution/TestDataDistributionManager.java	2011-04-11 17:40:26 UTC (rev 4226)
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2011 eXo Platform SAS.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.exoplatform.services.jcr.ext.distribution;
+
+import org.exoplatform.services.jcr.access.PermissionType;
+import org.exoplatform.services.jcr.core.ExtendedNode;
+import org.exoplatform.services.jcr.ext.BaseStandaloneTest;
+import org.exoplatform.services.jcr.impl.core.SessionImpl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Session;
+
+/**
+ * @author <a href="mailto:nicolas.filotto at exoplatform.com">Nicolas Filotto</a>
+ * @version $Id$
+ *
+ */
+public class TestDataDistributionManager extends BaseStandaloneTest
+{
+   private DataDistributionManager manager;
+
+   private Node parentNode;
+
+   /**
+    * @see org.exoplatform.services.jcr.ext.BaseStandaloneTest#setUp()
+    */
+   @Override
+   public void setUp() throws Exception
+   {
+      super.setUp();
+      manager = (DataDistributionManager)container.getComponentInstanceOfType(DataDistributionManager.class);
+      parentNode = root.addNode("TestDataDistributionManager");
+      session.save();
+   }
+
+   /**
+    * @see org.exoplatform.services.jcr.ext.BaseStandaloneTest#tearDown()
+    */
+   @Override
+   protected void tearDown() throws Exception
+   {
+      manager = null;
+      if (parentNode != null)
+      {
+         parentNode.remove();
+         session.save();
+         parentNode = null;
+      }
+      super.tearDown();
+   }
+
+   public void testDataDistributionModeNone() throws Exception
+   {
+      DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.NONE);
+      String dataId = "/a/a/a/a/";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      Node node = type.getOrCreateDataNode(parentNode, dataId);
+      assertTrue(node.isSame(type.getOrCreateDataNode(parentNode, dataId)));
+      assertEquals(1, node.getParent().getNodes().getSize());
+      Node node2 = type.getDataNode(parentNode, dataId);
+      assertTrue(node.isSame(node2));
+      Node node3 = parentNode.getNode("a/a/a/a");
+      assertTrue(node.isSame(node3));
+      Node node4 = type.getOrCreateDataNode(parentNode, "a/a/a/b", "nt:folder");
+      assertFalse(node.isSame(node4));
+      assertTrue(node.getParent().isSame(node4.getParent()));
+      assertTrue(node4.isNodeType("nt:folder"));
+      assertTrue(node4.canAddMixin("mix:referenceable"));
+      assertTrue(node4.canAddMixin("exo:privilegeable"));
+      
+      dataId =  "b/a/a/a";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      
+      Node node5 =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"));
+      assertFalse(node.isSame(node5));
+      assertFalse(node.getParent().isSame(node5.getParent()));
+      assertTrue(node5.isNodeType("nt:folder"));
+      assertFalse(node5.canAddMixin("mix:referenceable"));
+      assertTrue(node5.canAddMixin("exo:privilegeable"));
+      assertTrue(node5.getParent().isNodeType("nt:folder"));
+      assertFalse(node5.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node5.getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node5.getParent().getParent().isNodeType("nt:folder"));
+      assertFalse(node5.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node5.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node5.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertFalse(node5.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node5.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      Map<String, String[]> permissions = Collections.singletonMap("root", PermissionType.ALL);
+      type.removeDataNode(parentNode, dataId);
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      
+      dataId = "c/a/a/a";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }     
+      Node node6 =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertFalse(node.isSame(node6));
+      assertFalse(node.getParent().isSame(node6.getParent()));
+      assertTrue(node6.isNodeType("nt:folder"));
+      assertFalse(node6.canAddMixin("mix:referenceable"));
+      assertFalse(node6.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node6).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node6).getACL().getPermissions("root"));
+      assertTrue(node6.getParent().isNodeType("nt:folder"));
+      assertFalse(node6.getParent().canAddMixin("mix:referenceable"));
+      assertFalse(node6.getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node6.getParent()).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node6.getParent()).getACL().getPermissions("root"));
+      assertTrue(node6.getParent().getParent().isNodeType("nt:folder"));
+      assertFalse(node6.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertFalse(node6.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node6.getParent().getParent()).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node6.getParent().getParent()).getACL().getPermissions("root"));
+      assertTrue(node6.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertFalse(node6.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertFalse(node6.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node6.getParent().getParent().getParent()).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node6.getParent().getParent().getParent()).getACL().getPermissions("root"));
+      type.removeDataNode(parentNode, dataId);
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }      
+   }
+
+   public void testDataDistributionModeReadable() throws Exception
+   {
+      DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.READABLE);
+      String dataId = "john.smith";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      Map<String, String[]> permissions = Collections.singletonMap("root", PermissionType.ALL);
+      Node node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("j___/jo___/joh___/john.smith"));
+
+      dataId = "bob";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("b___/bo___/bob"));
+
+      dataId = "john___";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("j___/jo___/joh___/john___"));
+      
+      dataId = "joh___";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("j___/jo___/joh___/joh___")); 
+      
+      dataId = "jo___";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("j___/jo___/jo____/jo___"));      
+      
+      dataId = "j___";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("j___/j____/j_____/j___"));
+      type.removeDataNode(parentNode, dataId);
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }      
+   }
+
+   public void testDataDistributionModeOptimized() throws Exception
+   {
+      DataDistributionType type = manager.getDataDistributionType(DataDistributionMode.OPTIMIZED);
+      String dataId = "john.smith";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      Map<String, String[]> permissions = Collections.singletonMap("root", PermissionType.ALL);
+      Node node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("1/2/s/john.smith"));
+
+      dataId = "mary";
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }
+      node =
+         type.getOrCreateDataNode(parentNode, dataId, "nt:folder", Collections.singletonList("mix:referenceable"),
+            permissions);
+      assertTrue(node.isNodeType("nt:folder"));
+      assertFalse(node.canAddMixin("mix:referenceable"));
+      assertFalse(node.canAddMixin("exo:privilegeable"));
+      assertTrue(((ExtendedNode)node).getACL().hasPermissions());
+      assertNotNull(((ExtendedNode)node).getACL().getPermissions("root"));
+      assertFalse(node.getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertFalse(node.getParent().getParent().getParent().isNodeType("nt:folder"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("mix:referenceable"));
+      assertTrue(node.getParent().getParent().getParent().canAddMixin("exo:privilegeable"));
+      assertTrue(node.getPath().endsWith("5/o/s/mary"));
+      type.removeDataNode(parentNode, dataId);
+      try
+      {
+         type.getDataNode(parentNode, dataId);
+         fail("a PathNotFoundException is expected");
+      }
+      catch (PathNotFoundException e)
+      {
+         // expected behavior
+      }      
+   }
+   
+//   public void testMultiThreadingNone() throws Exception
+//   {
+//      testMultiThreading(manager.getDataDistributionType(DataDistributionMode.NONE), "key/");
+//   }
+//   
+//   public void testMultiThreadingReadable() throws Exception
+//   {
+//      testMultiThreading(manager.getDataDistributionType(DataDistributionMode.READABLE), "key-");
+//   }
+//   
+//   public void testMultiThreadingOptimized() throws Exception
+//   {
+//      testMultiThreading(manager.getDataDistributionType(DataDistributionMode.OPTIMIZED), "");
+//   }
+   
+   public void testMultiThreading(final DataDistributionType type, final String dataIdPrefix) throws Exception
+   {
+      long time = System.currentTimeMillis();
+      final int totalElement = 50;
+      final int totalTimes = 3;
+      int reader = 20;
+      int writer = 10;
+      int remover = 5;
+      final CountDownLatch startSignalWriter = new CountDownLatch(1);
+      final CountDownLatch startSignalOthers = new CountDownLatch(1);
+      final CountDownLatch doneSignal = new CountDownLatch(reader + writer + remover);
+      final List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>());
+      for (int i = 0; i < writer; i++)
+      {
+         final int index = i;
+         Thread thread = new Thread()
+         {
+            public void run()
+            {
+               Session session = null;
+               try
+               {
+                  startSignalWriter.await();
+                  for (int j = 0; j < totalTimes; j++)
+                  {
+                     session = (SessionImpl)repository.login(credentials, WS_NAME);
+                     Node node = (Node)session.getItem(parentNode.getPath());
+                     for (int i = 0; i < totalElement; i++)
+                     {
+                        type.getOrCreateDataNode(node, dataIdPrefix + i);
+                     }
+                     if (index == 0 && j == 0)
+                     {
+                        // The cache is full, we can launch the others
+                        startSignalOthers.countDown();
+                     }
+                     session.logout();
+                     sleep(50);
+                  }
+               }
+               catch (Exception e)
+               {
+                  errors.add(e);
+                  startSignalOthers.countDown();
+               }
+               finally
+               {
+                  doneSignal.countDown();
+                  if (session != null)
+                  {
+                     session.logout();
+                     session = null;
+                  }
+               }
+            }
+         };
+         thread.start();
+      }
+      startSignalWriter.countDown();
+      for (int i = 0; i < reader; i++)
+      {
+         Thread thread = new Thread()
+         {
+            public void run()
+            {
+               Session session = null;
+               try
+               {
+                  startSignalOthers.await();
+                  for (int j = 0; j < totalTimes; j++)
+                  {
+                     session = (SessionImpl)repository.login(credentials, WS_NAME);
+                     Node node = (Node)session.getItem(parentNode.getPath());                  
+                     for (int i = 0; i < totalElement; i++)
+                     {
+                        try
+                        {
+                           type.getDataNode(node, dataIdPrefix + i);
+                        }
+                        catch (PathNotFoundException e)
+                        {
+                           // ignore me
+                        }
+                     }
+                     session.logout();
+                     sleep(50);
+                  }
+               }
+               catch (Exception e)
+               {
+                  errors.add(e);
+               }
+               finally
+               {
+                  doneSignal.countDown();
+                  if (session != null)
+                  {
+                     session.logout();
+                     session = null;
+                  }                  
+               }
+            }
+         };
+         thread.start();
+      }
+      for (int i = 0; i < remover; i++)
+      {
+         Thread thread = new Thread()
+         {
+            public void run()
+            {
+               Session session = null;
+               try
+               {
+                  startSignalOthers.await();
+                  for (int j = 0; j < totalTimes; j++)
+                  {
+                     session = (SessionImpl)repository.login(credentials, WS_NAME);
+                     Node node = (Node)session.getItem(parentNode.getPath());                     
+                     for (int i = 0; i < totalElement; i++)
+                     {
+                        type.removeDataNode(node, dataIdPrefix + i);
+                     }
+                     session.logout();
+                     sleep(50);
+                  }
+               }
+               catch (Exception e)
+               {
+                  errors.add(e);
+               }
+               finally
+               {
+                  doneSignal.countDown();
+                  if (session != null)
+                  {
+                     session.logout();
+                     session = null;
+                  }                  
+               }
+            }
+         };
+         thread.start();
+      }
+      doneSignal.await();
+      for (int i = 0; i < totalElement; i++)
+      {
+         type.removeDataNode(parentNode, dataIdPrefix + i);
+      }      
+      if (!errors.isEmpty())
+      {
+         for (Exception e : errors)
+         {
+            e.printStackTrace();
+         }
+         throw errors.get(0);
+      }
+      System.out.println("Total Time for '" + type.getClass().getSimpleName() + "' = " + (System.currentTimeMillis() - time));
+   }
+}

Modified: jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-configuration.xml
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-configuration.xml	2011-04-11 10:54:50 UTC (rev 4225)
+++ jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-configuration.xml	2011-04-11 17:40:26 UTC (rev 4226)
@@ -423,6 +423,11 @@
       <type>org.exoplatform.services.jcr.ext.repository.creation.RepositoryCreationServiceImpl</type>
    </component>
 
+   <component>
+      <key>org.exoplatform.services.jcr.ext.distribution.DataDistributionManager</key>
+      <type>org.exoplatform.services.jcr.ext.distribution.impl.DataDistributionManagerImpl</type>     
+   </component>
+
    <!-- component>
       <key>org.exoplatform.services.organization.OrganizationService</key>
       <type>org.exoplatform.services.organization.hibernate.OrganizationServiceImpl</type>
@@ -992,7 +997,146 @@
          </component-plugin>
       </component-plugins>
    </component>
-
+   <component>
+      <key>org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator</key>
+      <type>org.exoplatform.services.jcr.ext.hierarchy.impl.NodeHierarchyCreatorImpl</type>
+   </component>
+	<component>
+		<type>org.exoplatform.services.jcr.ext.hierarchy.impl.NewUserListener</type>
+		<init-params>
+			<object-param>
+				<name>configuration</name>
+				<description>description</description>
+				<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig">
+					<field name="repository">
+						<string>db1</string>
+					</field>
+					<field name="workspaces">
+						<collection type="java.util.ArrayList">
+							<value>
+								<string>ws1</string>
+							</value>
+						</collection>
+					</field>
+					<field name="jcrPaths">
+						<collection type="java.util.ArrayList">
+							<value>
+								<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$JcrPath">
+									<field name="alias">
+										<string>userApplicationData</string>
+									</field>
+									<field name="path">
+										<string>ApplicationData</string>
+									</field>
+									<field name="nodeType">
+										<string>nt:folder</string>
+									</field>
+									<field name="mixinTypes">
+										<collection type="java.util.ArrayList">
+											<value>
+												<string>mix:referenceable</string>
+											</value>
+										</collection>
+									</field>									
+									<field name="permissions">
+										<collection type="java.util.ArrayList">
+											<value>
+												<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$Permission">
+													<field name="identity">
+														<string>*:/platform/administrators</string>
+													</field>
+													<field name="read">
+														<string>true</string>
+													</field>
+													<field name="addNode">
+														<string>true</string>
+													</field>
+													<field name="setProperty">
+														<string>true</string>
+													</field>
+													<field name="remove">
+														<string>true</string>
+													</field>
+												</object>
+											</value>
+										</collection>
+									</field>
+								</object>
+							</value>
+						</collection>
+					</field>
+				</object>
+			</object-param>
+		</init-params>
+	</component>
+	<component>
+		<type>org.exoplatform.services.jcr.ext.hierarchy.impl.NewGroupListener</type>
+		<init-params>
+			<object-param>
+				<name>configuration></name>
+				<description>description</description>
+				<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig">
+					<field name="repository">
+						<string>db1</string>
+					</field>
+					<field name="workspaces">
+						<collection type="java.util.ArrayList">
+							<value>
+								<string>ws1</string>
+							</value>
+						</collection>
+					</field>
+					<field name="jcrPaths">
+						<collection type="java.util.ArrayList">
+							<value>
+								<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$JcrPath">
+									<field name="alias">
+										<string>groupApplicationData</string>
+									</field>
+									<field name="path">
+										<string>ApplicationData</string>
+									</field>
+									<field name="nodeType">
+										<string>nt:folder</string>
+									</field>
+									<field name="mixinTypes">
+										<collection type="java.util.ArrayList">
+											<value>
+												<string>mix:referenceable</string>
+											</value>
+										</collection>
+									</field>									
+									<field name="permissions">
+										<collection type="java.util.ArrayList">
+											<value>
+												<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$Permission">
+													<field name="identity">
+														<string>*:/platform/administrators</string>
+													</field>
+													<field name="read">
+														<string>true</string>
+													</field>
+													<field name="addNode">
+														<string>true</string>
+													</field>
+													<field name="setProperty">
+														<string>true</string>
+													</field>
+													<field name="remove">
+														<string>true</string>
+													</field>
+												</object>
+											</value>
+										</collection>
+									</field>
+								</object>
+							</value>
+						</collection>
+					</field>
+				</object>
+			</object-param>
+		</init-params>
+	</component>       
    <external-component-plugins>
       <target-component>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</target-component>
       <component-plugin>
@@ -2095,5 +2239,140 @@
          </init-params>
       </component-plugin>
    </external-component-plugins>
-
+	<external-component-plugins>
+		<target-component>org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator</target-component>
+		<component-plugin>
+			<name>addPaths</name>
+			<set-method>addPlugin</set-method>
+			<type>org.exoplatform.services.jcr.ext.hierarchy.impl.AddPathPlugin</type>
+			<init-params>
+				<object-param>
+					<name>cms.configuration</name>
+					<description>configuration for the cms path</description>
+					<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig">
+						<field name="repository">
+							<string>db1</string>
+						</field>
+						<field name="workspaces">
+							<collection type="java.util.ArrayList">
+								<value>
+									<string>ws1</string>
+								</value>
+							</collection>
+						</field>
+						<field name="jcrPaths">
+							<collection type="java.util.ArrayList">
+								<value>
+									<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$JcrPath">
+										<field name="alias">
+											<string>eXoApplications</string>
+										</field>
+										<field name="path">
+											<string>/exo:applications</string>
+										</field>
+										<field name="mixinTypes">
+											<collection type="java.util.ArrayList">
+												<value>
+													<string>mix:referenceable</string>
+												</value>
+											</collection>
+										</field>
+									</object>
+								</value>
+								<value>
+									<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$JcrPath">
+										<field name="alias">
+											<string>eXoServices</string>
+										</field>
+										<field name="path">
+											<string>/exo:services</string>
+										</field>
+										<field name="nodeType">
+											<string>nt:folder</string>
+										</field>
+									</object>
+								</value>
+								<value>
+									<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$JcrPath">
+										<field name="alias">
+											<string>usersPath</string>
+										</field>
+										<field name="path">
+											<string>/Users</string>
+										</field>
+										<field name="permissions">
+											<collection type="java.util.ArrayList">
+												<value>
+													<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$Permission">
+														<field name="identity">
+															<string>*:/platform/administrators</string>
+														</field>
+														<field name="read">
+															<string>true</string>
+														</field>
+														<field name="addNode">
+															<string>true</string>
+														</field>
+														<field name="setProperty">
+															<string>true</string>
+														</field>
+														<field name="remove">
+															<string>true</string>
+														</field>
+													</object>
+												</value>
+											</collection>
+										</field>										
+									</object>
+								</value>
+								<value>
+									<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$JcrPath">
+										<field name="alias">
+											<string>groupsPath</string>
+										</field>
+										<field name="path">
+											<string>/Groups/Path/Home</string>
+										</field>
+										<field name="nodeType">
+											<string>nt:unstructured</string>
+										</field>
+										<field name="mixinTypes">
+											<collection type="java.util.ArrayList">
+												<value>
+													<string>mix:referenceable</string>
+												</value>
+											</collection>
+										</field>
+										<field name="permissions">
+											<collection type="java.util.ArrayList">
+												<value>
+													<object type="org.exoplatform.services.jcr.ext.hierarchy.impl.HierarchyConfig$Permission">
+														<field name="identity">
+															<string>*:/platform/administrators</string>
+														</field>
+														<field name="read">
+															<string>true</string>
+														</field>
+														<field name="addNode">
+															<string>true</string>
+														</field>
+														<field name="setProperty">
+															<string>true</string>
+														</field>
+														<field name="remove">
+															<string>true</string>
+														</field>
+													</object>
+												</value>
+											</collection>
+										</field>
+									</object>
+								</value>
+							</collection>
+						</field>
+					</object>
+				</object-param>
+			</init-params>
+		</component-plugin>
+	</external-component-plugins> 
 </configuration>

Modified: jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-jcr-ext-config.xml
===================================================================
--- jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-jcr-ext-config.xml	2011-04-11 10:54:50 UTC (rev 4225)
+++ jcr/trunk/exo.jcr.component.ext/src/test/resources/conf/standalone/test-jcr-ext-config.xml	2011-04-11 17:40:26 UTC (rev 4226)
@@ -20,7 +20,7 @@
 -->
 <repository-service default-repository="db1">
    <repositories>
-      <repository name="db1" system-workspace="ws" default-workspace="ws">
+      <repository name="db1" system-workspace="ws" default-workspace="ws1">
          <security-domain>exo-domain</security-domain>
          <access-control>optional</access-control>
          <authentication-policy>org.exoplatform.services.jcr.impl.core.access.JAASAuthenticator</authentication-policy>



More information about the exo-jcr-commits mailing list