[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