You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by al...@apache.org on 2012/08/28 16:56:16 UTC

svn commit: r1378156 - in /jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak: plugins/index/ plugins/lucene/ spi/query/

Author: alexparvulescu
Date: Tue Aug 28 14:56:15 2012
New Revision: 1378156

URL: http://svn.apache.org/viewvc?rev=1378156&view=rev
Log:
OAK-269 Query: IndexManager to manage existing indexes

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexFactory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexFactory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexDefinitionImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexFactory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManager.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManagerImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexUtils.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexFactory.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexFactory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/PropertyIndexFactory.java Tue Aug 28 14:56:15 2012
@@ -16,16 +16,28 @@
  */
 package org.apache.jackrabbit.oak.plugins.index;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.oak.spi.query.Index;
 import org.apache.jackrabbit.oak.spi.query.IndexDefinition;
 import org.apache.jackrabbit.oak.spi.query.IndexFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class PropertyIndexFactory implements IndexFactory {
 
     public static final String TYPE_PROPERTY = "property";
     public static final String TYPE_PREFIX = "prefix";
 
+    private static final Logger LOG = LoggerFactory
+            .getLogger(PropertyIndexFactory.class);
+
+    private final ConcurrentHashMap<IndexDefinition, Index> indexes = new ConcurrentHashMap<IndexDefinition, Index>();
+
     private Indexer indexer;
 
     @Override
@@ -38,8 +50,7 @@ public class PropertyIndexFactory implem
         return new String[] { TYPE_PREFIX, TYPE_PROPERTY };
     }
 
-    @Override
-    public Index createIndex(IndexDefinition indexDefinition) {
+    private Index createIndex(IndexDefinition indexDefinition) {
         if (TYPE_PREFIX.equals(indexDefinition.getType())) {
             String prefix = indexDefinition.getProperties().get("prefix");
             if (prefix != null) {
@@ -57,4 +68,34 @@ public class PropertyIndexFactory implem
         return null;
     }
 
+    @Override
+    public Index getIndex(IndexDefinition indexDefinition) {
+        Index index = indexes.get(indexDefinition);
+        if (index == null) {
+            index = createIndex(indexDefinition);
+            indexes.put(indexDefinition, index);
+        }
+        return index;
+    }
+
+    @Override
+    public void close() throws IOException {
+        Iterator<IndexDefinition> iterator = indexes.keySet().iterator();
+        while (iterator.hasNext()) {
+            IndexDefinition id = iterator.next();
+            try {
+                indexes.get(id).close();
+            } catch (IOException e) {
+                LOG.error("Error closing index {}.", id.getName(), e);
+            }
+            iterator.remove();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "PropertyIndexFactory [getTypes()="
+                + Arrays.toString(getTypes()) + "]";
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexFactory.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexFactory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexFactory.java Tue Aug 28 14:56:15 2012
@@ -16,15 +16,27 @@
  */
 package org.apache.jackrabbit.oak.plugins.lucene;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.oak.spi.query.Index;
 import org.apache.jackrabbit.oak.spi.query.IndexDefinition;
 import org.apache.jackrabbit.oak.spi.query.IndexFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class LuceneIndexFactory implements IndexFactory {
 
+    private static final Logger LOG = LoggerFactory
+            .getLogger(LuceneIndexFactory.class);
+
     public static final String TYPE = "lucene";
 
+    private final ConcurrentHashMap<IndexDefinition, Index> indexes = new ConcurrentHashMap<IndexDefinition, Index>();
+
     @Override
     public void init(MicroKernel mk) {
         // not needed
@@ -36,8 +48,32 @@ public class LuceneIndexFactory implemen
     }
 
     @Override
-    public Index createIndex(IndexDefinition indexDefinition) {
-        return new LuceneEditor(indexDefinition);
+    public void close() throws IOException {
+        Iterator<IndexDefinition> iterator = indexes.keySet().iterator();
+        while (iterator.hasNext()) {
+            IndexDefinition id = iterator.next();
+            try {
+                indexes.get(id).close();
+            } catch (IOException e) {
+                LOG.error("Error closing index {}.", id.getName(), e);
+            }
+            iterator.remove();
+        }
+    }
+
+    @Override
+    public Index getIndex(IndexDefinition indexDefinition) {
+        Index index = indexes.get(indexDefinition);
+        if (index == null) {
+            index = new LuceneEditor(indexDefinition);
+            indexes.put(indexDefinition, index);
+        }
+        return index;
     }
 
+    @Override
+    public String toString() {
+        return "LuceneIndexFactory [getTypes()=" + Arrays.toString(getTypes())
+                + "]";
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexDefinitionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexDefinitionImpl.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexDefinitionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexDefinitionImpl.java Tue Aug 28 14:56:15 2012
@@ -66,11 +66,21 @@ public class IndexDefinitionImpl impleme
     }
 
     @Override
+    public String toString() {
+        return "IndexDefinitionImpl [name=" + name + ", type=" + type
+                + ", path=" + path + ", unique=" + unique + ", properties="
+                + properties + "]";
+    }
+
+    @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((path == null) ? 0 : path.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result
+                + ((properties == null) ? 0 : properties.hashCode());
         result = prime * result + ((type == null) ? 0 : type.hashCode());
+        result = prime * result + (unique ? 1231 : 1237);
         return result;
     }
 
@@ -83,17 +93,23 @@ public class IndexDefinitionImpl impleme
         if (getClass() != obj.getClass())
             return false;
         IndexDefinitionImpl other = (IndexDefinitionImpl) obj;
-        if (path == null) {
-            if (other.path != null)
+        if (name == null) {
+            if (other.name != null)
                 return false;
-        } else if (!path.equals(other.path))
+        } else if (!name.equals(other.name))
+            return false;
+        if (properties == null) {
+            if (other.properties != null)
+                return false;
+        } else if (!properties.equals(other.properties))
             return false;
         if (type == null) {
             if (other.type != null)
                 return false;
         } else if (!type.equals(other.type))
             return false;
+        if (unique != other.unique)
+            return false;
         return true;
     }
-
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexFactory.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexFactory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexFactory.java Tue Aug 28 14:56:15 2012
@@ -16,12 +16,14 @@
  */
 package org.apache.jackrabbit.oak.spi.query;
 
+import java.io.Closeable;
+
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.mk.api.MicroKernel;
 
-public interface IndexFactory {
+public interface IndexFactory extends Closeable {
 
     /**
      * initializes the provided factory
@@ -39,6 +41,6 @@ public interface IndexFactory {
      * @return
      */
     @CheckForNull
-    Index createIndex(IndexDefinition indexDefinition);
+    Index getIndex(IndexDefinition indexDefinition);
 
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManager.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManager.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManager.java Tue Aug 28 14:56:15 2012
@@ -17,10 +17,14 @@
 package org.apache.jackrabbit.oak.spi.query;
 
 import java.io.Closeable;
-import java.util.Set;
+import java.util.List;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
+import org.apache.jackrabbit.oak.spi.commit.CommitEditor;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
 /**
  * <p>
  * Index Manager keeps track of all the available indexes.
@@ -32,28 +36,31 @@ import javax.annotation.Nonnull;
  * </p>
  * 
  * <p>
- * TODO It *should* define an API for managing indexes (CRUD ops)
- * </p>
- * 
- * <p>
  * TODO Document simple node properties to create an index type
  * </p>
  * </p>
  */
-public interface IndexManager extends Closeable {
+public interface IndexManager extends CommitEditor, Closeable {
+
+    void registerIndexFactory(IndexFactory... factory);
+
+    void unregisterIndexFactory(IndexFactory factory);
 
     /**
-     * Creates an index by passing the {@link IndexDefinition} to the registered
-     * {@link IndexFactory}(es)
-     * 
-     * @param indexDefinition
+     * @return the index with the given name
      */
-    void registerIndex(IndexDefinition... indexDefinition);
+    @CheckForNull
+    Index getIndex(String name, NodeState nodeState);
 
-    void registerIndexFactory(IndexFactory factory);
-
-    void init();
+    /**
+     * @return the index with the given definition
+     */
+    @CheckForNull
+    public Index getIndex(IndexDefinition definition);
 
+    /**
+     * @return the existing index definitions
+     */
     @Nonnull
-    Set<IndexDefinition> getIndexes();
+    List<IndexDefinition> getIndexDefinitions(NodeState nodeState);
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManagerImpl.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManagerImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexManagerImpl.java Tue Aug 28 14:56:15 2012
@@ -18,122 +18,155 @@ package org.apache.jackrabbit.oak.spi.qu
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.jackrabbit.mk.api.MicroKernel;
-import org.apache.jackrabbit.oak.api.ContentSession;
-import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.spi.commit.CommitEditor;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.ImmutableSet;
-
-public class IndexManagerImpl implements IndexManager {
-
-    // TODO implement an observation listener so that the {@link
-    // IndexManagerImpl} automatically creates new indexes based on new nodes
-    // added under {@link #indexConfigPath}
+public class IndexManagerImpl implements IndexManager, CommitEditor {
 
     private static final Logger LOG = LoggerFactory
             .getLogger(IndexManagerImpl.class);
 
     private final String indexConfigPath;
 
-    private final ContentSession session;
-
     private final MicroKernel mk;
 
-    private final Map<String, IndexFactory> indexFactories = new ConcurrentHashMap<String, IndexFactory>();
+    private final Map<IndexFactory, String[]> indexFactories = new ConcurrentHashMap<IndexFactory, String[]>();
 
-    private final Map<IndexDefinition, Index> indexes = new ConcurrentHashMap<IndexDefinition, Index>();
-
-    public IndexManagerImpl(String indexConfigPath, ContentSession session,
-            MicroKernel mk) {
+    public IndexManagerImpl(String indexConfigPath, MicroKernel mk,
+            IndexFactory... factories) {
         this.indexConfigPath = indexConfigPath;
-        this.session = session;
         this.mk = mk;
+        internalRegisterIndexFactory(factories);
     }
 
     @Override
-    public void registerIndexFactory(IndexFactory factory) {
-        factory.init(mk);
-        for (String type : factory.getTypes()) {
-            if (indexFactories.remove(type) != null) {
+    public void registerIndexFactory(IndexFactory... factories) {
+        internalRegisterIndexFactory(factories);
+    }
+
+    @Override
+    public void unregisterIndexFactory(IndexFactory factory) {
+        indexFactories.remove(factory);
+    }
+
+    private void internalRegisterIndexFactory(IndexFactory... factories) {
+        if (factories == null) {
+            return;
+        }
+        for (IndexFactory factory : factories) {
+            if (indexFactories.remove(factory) != null) {
                 // TODO is override allowed?
             }
-            indexFactories.put(type, factory);
+            factory.init(mk);
+            indexFactories.put(factory, factory.getTypes());
+            LOG.debug("Registered index factory {}.", factory);
         }
     }
 
-    @Override
-    public void init() {
-        //
-        // TODO hardwire default property indexes first ?
-        // registerIndexFactory(type, factory);
-        Tree definitions = session.getCurrentRoot().getTree(indexConfigPath);
+    /**
+     * Builds a list of the existing index definitions from the repository
+     * 
+     */
+    private static List<IndexDefinition> buildIndexDefinitions(boolean log,
+            NodeState nodeState, String indexConfigPath) {
+
+        NodeState definitions = IndexUtils.getNode(nodeState, indexConfigPath);
         if (definitions == null) {
-            return;
+            return Collections.emptyList();
         }
 
         List<IndexDefinition> defs = new ArrayList<IndexDefinition>();
-        for (Tree c : definitions.getChildren()) {
-            IndexDefinition def = IndexUtils.getDefs(indexConfigPath, c);
+        for (ChildNodeEntry c : definitions.getChildNodeEntries()) {
+            IndexDefinition def = IndexUtils.getDefinition(indexConfigPath, c);
             if (def == null) {
-                LOG.warn("Skipping illegal index definition name {} @ {}",
-                        c.getName(), indexConfigPath);
-                continue;
-            }
-            if (indexes.get(def.getName()) != null) {
-                LOG.warn("Skipping existing index definition name {} @ {}",
-                        c.getName(), indexConfigPath);
+                if (log) {
+                    LOG.warn("Skipping illegal index definition name {} @ {}",
+                            c.getName(), indexConfigPath);
+                }
                 continue;
             }
             defs.add(def);
         }
-        registerIndex(defs.toArray(new IndexDefinition[defs.size()]));
+        return defs;
     }
 
     @Override
-    public void registerIndex(IndexDefinition... indexDefinition) {
-        for (IndexDefinition def : indexDefinition) {
-            if (def == null) {
-                continue;
-            }
-            IndexFactory f = indexFactories.get(def.getType());
-            if (f == null) {
-                LOG.warn(
-                        "Skipping unknown index definition type {}, name {} @ {}",
-                        new String[] { def.getType(), indexConfigPath,
-                                def.getName() });
-                continue;
+    public Index getIndex(String name, NodeState nodeState) {
+        if (name == null) {
+            return null;
+        }
+        IndexDefinition id = null;
+        for (IndexDefinition def : getIndexDefinitions(nodeState)) {
+            if (name.equals(def.getName())) {
+                id = def;
+                break;
             }
-            Index index = f.createIndex(def);
-            if (index != null) {
-                indexes.put(def, index);
+        }
+        return getIndex(id);
+    }
+
+    @Override
+    public Index getIndex(IndexDefinition def) {
+        if (def == null) {
+            return null;
+        }
+        Iterator<IndexFactory> iterator = indexFactories.keySet().iterator();
+        while (iterator.hasNext()) {
+            IndexFactory factory = iterator.next();
+            for (String type : factory.getTypes()) {
+                if (type != null && type.equals(def.getType())) {
+                    return factory.getIndex(def);
+                }
             }
         }
+        LOG.debug("Index definition {} doesn't have a known factory.", def);
+        return null;
     }
 
     @Override
-    public Set<IndexDefinition> getIndexes() {
-        return ImmutableSet.copyOf(indexes.keySet());
+    public List<IndexDefinition> getIndexDefinitions(NodeState nodeState) {
+        return buildIndexDefinitions(true, nodeState, indexConfigPath);
     }
 
     @Override
     public synchronized void close() throws IOException {
-        Iterator<IndexDefinition> iterator = indexes.keySet().iterator();
+        Iterator<IndexFactory> iterator = indexFactories.keySet().iterator();
         while (iterator.hasNext()) {
-            IndexDefinition id = iterator.next();
+            IndexFactory factory = iterator.next();
             try {
-                indexes.get(id).close();
+                factory.close();
             } catch (IOException e) {
-                LOG.error("error closing index {}", id.getName(), e);
+                LOG.error("error closing index factory {}", factory, e);
             }
             iterator.remove();
         }
     }
+
+    @Override
+    public NodeState editCommit(NodeStore store, NodeState before,
+            NodeState after) throws CommitFailedException {
+
+        NodeState newState = after;
+        for (IndexDefinition def : buildIndexDefinitions(true, after,
+                indexConfigPath)) {
+            Index index = getIndex(def);
+            if (index == null) {
+                continue;
+            }
+            newState = index.editCommit(store, before, newState);
+        }
+        return newState;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexUtils.java?rev=1378156&r1=1378155&r2=1378156&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexUtils.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/IndexUtils.java Tue Aug 28 14:56:15 2012
@@ -18,12 +18,14 @@ package org.apache.jackrabbit.oak.spi.qu
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 public class IndexUtils {
 
@@ -32,24 +34,28 @@ public class IndexUtils {
      */
     public static final String DEFAULT_INDEX_HOME = "/oak-index";
 
-    public static IndexDefinition getDefs(String path, Tree tree) {
-        String name = tree.getName();
-        PropertyState typeProp = tree
-                .getProperty(IndexDefinition.TYPE_PROPERTY_NAME);
+    /**
+     * Builds an {@link IndexDefinition} out of a {@link ChildNodeEntry}
+     * 
+     */
+    public static IndexDefinition getDefinition(String path, ChildNodeEntry def) {
+        String name = def.getName();
+        PropertyState typeProp = def.getNodeState().getProperty(
+                IndexDefinition.TYPE_PROPERTY_NAME);
         if (typeProp == null || typeProp.isArray()) {
             return null;
         }
         String type = typeProp.getValue().getString();
 
         boolean unique = false;
-        PropertyState uniqueProp = tree
-                .getProperty(IndexDefinition.UNIQUE_PROPERTY_NAME);
+        PropertyState uniqueProp = def.getNodeState().getProperty(
+                IndexDefinition.UNIQUE_PROPERTY_NAME);
         if (uniqueProp != null && !uniqueProp.isArray()) {
             unique = uniqueProp.getValue().getBoolean();
         }
 
         Map<String, String> props = new HashMap<String, String>();
-        for (PropertyState ps : tree.getProperties()) {
+        for (PropertyState ps : def.getNodeState().getProperties()) {
             if (ps != null && !ps.isArray()) {
                 String v = ps.getValue().getString();
                 props.put(ps.getName(), v);
@@ -75,8 +81,25 @@ public class IndexUtils {
         if (append != null && append.trim().length() != 0) {
             paths.add(append);
         }
-
         return paths.toArray(new String[paths.size()]);
     }
 
+    /**
+     * @return the 'destination' node if the path exists, null if otherwise
+     */
+    public static NodeState getNode(NodeState nodeState, String destination) {
+        NodeState retval = nodeState;
+        Iterator<String> pathIterator = PathUtils.elements(destination)
+                .iterator();
+        while (pathIterator.hasNext()) {
+            String path = pathIterator.next();
+            if (retval.hasChildNode(path)) {
+                retval = retval.getChildNode(path);
+            } else {
+                return null;
+            }
+        }
+        return retval;
+    }
+
 }