You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2014/06/25 17:06:10 UTC

svn commit: r1605439 - in /sling/trunk/tooling/ide: api/src/org/apache/sling/ide/serialization/ eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ impl-vlt/src/org/apache/sling/ide/impl/vlt/serialization/

Author: rombert
Date: Wed Jun 25 15:06:09 2014
New Revision: 1605439

URL: http://svn.apache.org/r1605439
Log:
SLING-3697 - Incorrect handling of deeply nested aggregates

- clarified the SerializationData API and contract
- generalize the logic for finding leaf aggregates and constructing
their path to take into account multiple intermediate leaf aggregates
- use the os paths to look for leaf aggregates, instead of repository
paths

Modified:
    sling/trunk/tooling/ide/api/src/org/apache/sling/ide/serialization/SerializationData.java
    sling/trunk/tooling/ide/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java
    sling/trunk/tooling/ide/impl-vlt/src/org/apache/sling/ide/impl/vlt/serialization/VltSerializationDataBuilder.java

Modified: sling/trunk/tooling/ide/api/src/org/apache/sling/ide/serialization/SerializationData.java
URL: http://svn.apache.org/viewvc/sling/trunk/tooling/ide/api/src/org/apache/sling/ide/serialization/SerializationData.java?rev=1605439&r1=1605438&r2=1605439&view=diff
==============================================================================
--- sling/trunk/tooling/ide/api/src/org/apache/sling/ide/serialization/SerializationData.java (original)
+++ sling/trunk/tooling/ide/api/src/org/apache/sling/ide/serialization/SerializationData.java Wed Jun 25 15:06:09 2014
@@ -19,32 +19,42 @@ package org.apache.sling.ide.serializati
 public class SerializationData {
 
     private final byte[] contents;
-    private final String nameHint;
+    private final String fileName;
     private final SerializationKind serializationKind;
-    private final String fileOrFolderNameHint;
+    private final String folderPath;
 
-    public static SerializationData empty(String fileOrFolderNameHint, SerializationKind serializationKind) {
-        return new SerializationData(fileOrFolderNameHint, null, null, serializationKind);
+    public static SerializationData empty(String folderPath, SerializationKind serializationKind) {
+        return new SerializationData(folderPath, null, null, serializationKind);
     }
 
-    public SerializationData(String fileOrFolderNameHint, String nameHint, byte[] contents,
+    public SerializationData(String folderPath, String fileName, byte[] contents,
             SerializationKind serializationKind) {
-        this.fileOrFolderNameHint = fileOrFolderNameHint;
+        this.folderPath = folderPath;
         this.contents = contents;
-        this.nameHint = nameHint;
+        this.fileName = fileName;
         this.serializationKind = serializationKind;
     }
 
-    public String getFileOrFolderNameHint() {
-        return fileOrFolderNameHint;
+    /**
+     * @return the path where the serialization data file should be stores, in OS format
+     */
+    public String getFolderPath() {
+        return folderPath;
     }
 
+    /**
+     * @return the contents of the serialization data file
+     */
     public byte[] getContents() {
         return contents;
     }
 
-    public String getNameHint() {
-        return nameHint;
+    /**
+     * 
+     * @return the name of the serialization data file
+     */
+    public String getFileName() {
+        return fileName;
     }
 
     public SerializationKind getSerializationKind() {
@@ -58,7 +68,7 @@ public class SerializationData {
 
     @Override
     public String toString() {
-        return "[SerializationData# fileOrFolderNameHint: " + fileOrFolderNameHint + ", nameHint: " + nameHint
-                + ", serializationKind: " + serializationKind + ", contents?" + (hasContents()) + "]";
+        return "[SerializationData# folderPath: " + folderPath + ", fileName: " + fileName
+                + ", serializationKind: " + serializationKind + ", hasContents: " + (hasContents()) + "]";
     }
 }
\ No newline at end of file

Modified: sling/trunk/tooling/ide/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java
URL: http://svn.apache.org/viewvc/sling/trunk/tooling/ide/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java?rev=1605439&r1=1605438&r2=1605439&view=diff
==============================================================================
--- sling/trunk/tooling/ide/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java (original)
+++ sling/trunk/tooling/ide/eclipse-ui/src/org/apache/sling/ide/eclipse/ui/internal/ImportRepositoryContentAction.java Wed Jun 25 15:06:09 2014
@@ -196,31 +196,27 @@ public class ImportRepositoryContentActi
 
         ResourceProxy resource = executeCommand(repository.newListChildrenNodeCommand(path));
         
-        // TODO we should know all node types for which to create files and folders
         SerializationData serializationData = builder.buildSerializationData(contentSyncRoot, resource);
         logger.trace("For resource at path {0} got serialization data {1}", resource.getPath(), serializationData);
 
         final List<ResourceProxy> resourceChildren = new LinkedList<ResourceProxy>(resource.getChildren());
 		if (serializationData != null) {
 
-            IPath fileOrFolderPath = contentSyncRootDir.getProjectRelativePath().append(
-                    serializationData.getFileOrFolderNameHint());
+            IPath serializationFolderPath = contentSyncRootDir.getProjectRelativePath().append(
+                    serializationData.getFolderPath());
 	
 	        switch (serializationData.getSerializationKind()) {
 	            case FILE: {
 	                byte[] contents = executeCommand(repository.newGetNodeCommand(path));
-	                importFile(project, fileOrFolderPath, contents);
+                    createFile(project, getPathForPlainFileNode(resource, serializationFolderPath), contents);
 	
 	                if (serializationData.hasContents()) {
-	                    // TODO - should we abstract out .dir serialization?
-	                    IPath directoryPath = fileOrFolderPath.addFileExtension("dir");
-	                    createFolder(project, directoryPath);
-	                    createFile(project, directoryPath.append(serializationData.getNameHint()),
+                        createFolder(project, serializationFolderPath);
+                        createFile(project, serializationFolderPath.append(serializationData.getFileName()),
 	                            serializationData.getContents());
 	                    
-	                    // filter out the child of type Repository.NT_RESOURCE
-	                    for (Iterator<ResourceProxy> it = resourceChildren.iterator(); it
-								.hasNext();) {
+                        // special processing for nt:resource nodes
+                        for (Iterator<ResourceProxy> it = resourceChildren.iterator(); it.hasNext();) {
 	                    	ResourceProxy child = it.next();
 	                        if (Repository.NT_RESOURCE.equals(child.getProperties().get(Repository.JCR_PRIMARY_TYPE))) {
 
@@ -233,12 +229,10 @@ public class ImportRepositoryContentActi
                                                 .size());
 
                                 if (reloadedChildResource.getChildren().size() != 0) {
-                                    // 1. create holder directory ; needs platform name format
 
                                     String pathName = Text.getName(reloadedChildResource.getPath());
                                     pathName = serializationManager.getOsPath(pathName);
-
-                                    createFolder(project, directoryPath.append(pathName));
+                                    createFolder(project, serializationFolderPath.append(pathName));
 
                                     // 2. recursively handle all resources
                                     for (ResourceProxy grandChild : reloadedChildResource.getChildren()) {
@@ -256,12 +250,12 @@ public class ImportRepositoryContentActi
 	            case FOLDER:
 	            case METADATA_PARTIAL: {
 
-                    IFolder folder = createFolder(project, fileOrFolderPath);
+                    IFolder folder = createFolder(project, serializationFolderPath);
 
                     parseIgnoreFiles(folder, path);
 
 	                if (serializationData.hasContents()) {
-	                    createFile(project, fileOrFolderPath.append(serializationData.getNameHint()),
+                        createFile(project, serializationFolderPath.append(serializationData.getFileName()),
 	                            serializationData.getContents());
 	                }
 	                break;
@@ -269,7 +263,8 @@ public class ImportRepositoryContentActi
 	
 	            case METADATA_FULL: {
 	                if (serializationData.hasContents()) {
-	                    createFile(project, fileOrFolderPath, serializationData.getContents());
+                        createFile(project, serializationFolderPath.append(serializationData.getFileName()),
+                                serializationData.getContents());
 	                }
 	                break;
 	            }
@@ -280,6 +275,8 @@ public class ImportRepositoryContentActi
 	        if (serializationData.getSerializationKind() == SerializationKind.METADATA_FULL) {
 	            return;
 	        }
+        } else {
+            logger.warn("No serialization data found for " + resource.getPath());
         }
 		
         ProgressUtils.advance(monitor, 1);
@@ -302,6 +299,23 @@ public class ImportRepositoryContentActi
         }
     }
 
+    /**
+     * Returns the path for serializing the nt:resource data of a nt:file node
+     * 
+     * <p>
+     * The path will be one level above the <tt>serializationFolderPath</tt>, and the name will be the last path segment
+     * of the resource.
+     * </p>
+     * 
+     * @param resource The resource
+     * @param serializationFolderPath the folder where the serialization data should be stored
+     * @return the path for the plain file node
+     */
+    private IPath getPathForPlainFileNode(ResourceProxy resource, IPath serializationFolderPath) {
+
+        return serializationFolderPath.removeLastSegments(1).append(Text.getName(resource.getPath()));
+    }
+
     private void parseIgnoreFiles(IFolder folder, String path) throws IOException, CoreException {
         // TODO - the parsing should be extracted
         IResource vltIgnore = folder.findMember(".vltignore");
@@ -328,12 +342,6 @@ public class ImportRepositoryContentActi
         return result.get();
     }
 
-    private void importFile(IProject project, IPath destinationPath, byte[] content)
-            throws CoreException {
-
-        createFile(project, destinationPath, content);
-    }
-
     private IFolder createFolder(IProject project, IPath destinationPath) throws CoreException {
 
         IFolder destinationFolder = project.getFolder(destinationPath);
@@ -380,7 +388,7 @@ public class ImportRepositoryContentActi
     	}
     	createParents(container.getParent());
     	IFolder parentFolder = (IFolder)container;
-    	parentFolder.create(true, true, null);
+        parentFolder.create(true, true, null);
     }
 
 }
\ No newline at end of file

Modified: sling/trunk/tooling/ide/impl-vlt/src/org/apache/sling/ide/impl/vlt/serialization/VltSerializationDataBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/tooling/ide/impl-vlt/src/org/apache/sling/ide/impl/vlt/serialization/VltSerializationDataBuilder.java?rev=1605439&r1=1605438&r2=1605439&view=diff
==============================================================================
--- sling/trunk/tooling/ide/impl-vlt/src/org/apache/sling/ide/impl/vlt/serialization/VltSerializationDataBuilder.java (original)
+++ sling/trunk/tooling/ide/impl-vlt/src/org/apache/sling/ide/impl/vlt/serialization/VltSerializationDataBuilder.java Wed Jun 25 15:06:09 2014
@@ -20,14 +20,18 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.ListIterator;
 
 import javax.jcr.Credentials;
 import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
 import javax.jcr.nodetype.NodeType;
 
 import org.apache.jackrabbit.vault.fs.api.Aggregate;
@@ -104,99 +108,37 @@ public class VltSerializationDataBuilder
     @Override
     public SerializationData buildSerializationData(File contentSyncRoot, ResourceProxy resource) throws SerializationException {
 
-        // TODO - there is a small mismatch here since we're doing remote calls to the repository
-        // but taking a resourceProxy - not sure if we'll run into problems down the road or not
-
         try {
 
-            AggregateWrapper wrapper = findAggregate(resource);
+            List<Aggregate> chain = findAggregateChain(resource);
 
-            if (wrapper == null || wrapper.aggregate == null) {
-            	//TODO: there are valid cases apparently when aggregate is null and yet there
-            	// are children which must be honored.. so we can't throw an exception here
-            	// but we should review why this aggregate is null here and if that's valid.
-            	System.err.println("No aggregate found for path " + resource.getPath());
+            if (chain == null) {
             	return null;
             }
 
-            String fileOrFolderPathHint;
-            if (wrapper.parent == null) {
-                fileOrFolderPathHint = PlatformNameFormat.getPlatformPath(wrapper.aggregate.getPath());
-            } else {
-                fileOrFolderPathHint = PlatformNameFormat.getPlatformPath(wrapper.parent.getPath()) + ".dir"
-                        + File.separatorChar + PlatformNameFormat.getPlatformPath(wrapper.aggregate.getRelPath());
-            }
+            Aggregate aggregate = chain.get(chain.size() - 1);
 
-            String nameHint = PlatformNameFormat.getPlatformName(wrapper.aggregate.getName());
+            String fileOrFolderPathHint = calculateFileOrFolderPathHint(chain);
 
-            NodeType[] mixinNodeTypes = wrapper.aggregate.getNode().getMixinNodeTypes();
-            List<String> mixinNodeTypeNames = new ArrayList<String>(mixinNodeTypes.length);
-            for (NodeType nodeType : mixinNodeTypes)
-                mixinNodeTypeNames.add(nodeType.getName());
+            String nameHint = PlatformNameFormat.getPlatformName(aggregate.getName());
 
-            SerializationKind serializationKind = skm.getSerializationKind(wrapper.aggregate.getNode()
-                    .getPrimaryNodeType()
-                    .getName(), mixinNodeTypeNames);
+            SerializationKind serializationKind = getSerializationKind(aggregate);
 
             if (resource.getPath().equals("/") || serializationKind == SerializationKind.METADATA_PARTIAL
                     || serializationKind == SerializationKind.FILE || serializationKind == SerializationKind.FOLDER) {
                 nameHint = Constants.DOT_CONTENT_XML;
             } else if (serializationKind == SerializationKind.METADATA_FULL) {
                 nameHint += ".xml";
-                fileOrFolderPathHint += ".xml";
             }
 
             Activator.getDefault().getPluginLogger()
                     .trace("Got location {0} for path {1}", fileOrFolderPathHint, resource.getPath());
 
-            Aggregator aggregator = fs.getAggregateManager().getAggregator(wrapper.aggregate.getNode(), null);
-            if (aggregator instanceof FileAggregator) {
-                // TODO - copy-pasted from FileAggregator, and really does not belong here...
-                Node content = wrapper.aggregate.getNode();
-                if (content.isNodeType(JcrConstants.NT_FILE)) {
-                    content = content.getNode(JcrConstants.JCR_CONTENT);
-                }
-                String mimeType = null;
-                if (content.hasProperty(JcrConstants.JCR_MIMETYPE)) {
-                    try {
-                        mimeType = content.getProperty(JcrConstants.JCR_MIMETYPE).getString();
-                    } catch (RepositoryException e) {
-                        // ignore
-                    }
-                }
-                if (mimeType == null) {
-                    // guess mime type from name
-                    mimeType = MimeTypes.getMimeType(wrapper.aggregate.getNode().getName(),
-                            MimeTypes.APPLICATION_OCTET_STREAM);
-                }
-
-                boolean needsDir = !MimeTypes.matches(wrapper.aggregate.getNode().getName(), mimeType,
-                        MimeTypes.APPLICATION_OCTET_STREAM);
-
-                if (!needsDir) {
-                    if (content.hasProperty(JcrConstants.JCR_MIXINTYPES)) {
-                        for (Value v : content.getProperty(JcrConstants.JCR_MIXINTYPES).getValues()) {
-                            if (!v.getString().equals(JcrConstants.MIX_LOCKABLE)) {
-                                needsDir = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                if (!needsDir) {
-                    return SerializationData.empty(fileOrFolderPathHint, serializationKind);
-                }
-            } else if (aggregator instanceof GenericAggregator) {
-                // TODO - copy-pasted from GenericAggregator
-                if (wrapper.aggregate.getNode().getPrimaryNodeType().getName().equals("nt:folder")
-                        && wrapper.aggregate.getNode().getMixinNodeTypes().length == 0) {
-                    return SerializationData.empty(fileOrFolderPathHint, serializationKind);
-                }
+            if (!needsDir(aggregate)) {
+                return SerializationData.empty(fileOrFolderPathHint, serializationKind);
             }
 
-
-            DocViewSerializer s = new DocViewSerializer(wrapper.aggregate);
+            DocViewSerializer s = new DocViewSerializer(aggregate);
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             s.writeContent(out);
             
@@ -211,7 +153,120 @@ public class VltSerializationDataBuilder
         }
     }
 
-    private AggregateWrapper findAggregate(ResourceProxy resource) throws IOException, RepositoryException {
+    private SerializationKind getSerializationKind(Aggregate aggregate) throws RepositoryException {
+
+        NodeType[] mixinNodeTypes = aggregate.getNode().getMixinNodeTypes();
+        List<String> mixinNodeTypeNames = new ArrayList<String>(mixinNodeTypes.length);
+        for (NodeType nodeType : mixinNodeTypes)
+            mixinNodeTypeNames.add(nodeType.getName());
+
+        return skm.getSerializationKind(aggregate.getNode()
+                .getPrimaryNodeType()
+                .getName(), mixinNodeTypeNames);
+    }
+
+    private boolean needsDir(Aggregate aggregate) throws RepositoryException, PathNotFoundException,
+            ValueFormatException {
+
+        Aggregator aggregator = fs.getAggregateManager().getAggregator(aggregate.getNode(), null);
+        boolean needsDir = true;
+        if (aggregator instanceof FileAggregator) {
+            needsDir = false;
+            // TODO - copy-pasted from FileAggregator, and really does not belong here...
+            Node content = aggregate.getNode();
+            if (content.isNodeType(JcrConstants.NT_FILE)) {
+                content = content.getNode(JcrConstants.JCR_CONTENT);
+            }
+            String mimeType = null;
+            if (content.hasProperty(JcrConstants.JCR_MIMETYPE)) {
+                try {
+                    mimeType = content.getProperty(JcrConstants.JCR_MIMETYPE).getString();
+                } catch (RepositoryException e) {
+                    // ignore
+                }
+            }
+            if (mimeType == null) {
+                // guess mime type from name
+                mimeType = MimeTypes.getMimeType(aggregate.getNode().getName(),
+                        MimeTypes.APPLICATION_OCTET_STREAM);
+            }
+
+            needsDir = !MimeTypes.matches(aggregate.getNode().getName(), mimeType,
+                    MimeTypes.APPLICATION_OCTET_STREAM);
+
+            if (!needsDir) {
+                if (content.hasProperty(JcrConstants.JCR_MIXINTYPES)) {
+                    for (Value v : content.getProperty(JcrConstants.JCR_MIXINTYPES).getValues()) {
+                        if (!v.getString().equals(JcrConstants.MIX_LOCKABLE)) {
+                            needsDir = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
+         // TODO - copy-pasted from GenericAggregator
+        } else if (aggregator instanceof GenericAggregator) {
+            if (isPlainNtFolder(aggregate)) {
+                needsDir = false;
+            }
+        }
+        return needsDir;
+    }
+
+    private String calculateFileOrFolderPathHint(List<Aggregate> chain) throws RepositoryException {
+
+        ListIterator<Aggregate> aggs = chain.listIterator();
+        StringBuilder out = new StringBuilder();
+        while (aggs.hasNext()) {
+            Aggregate cur = aggs.next();
+            if (aggs.previousIndex() == 0) {
+                out.append(PlatformNameFormat.getPlatformPath(cur.getPath()));
+            } else {
+                out.append("/");
+                out.append(PlatformNameFormat.getPlatformPath(cur.getRelPath()));
+            }
+
+            if (needsDir(cur)) {
+                SerializationKind serializationKind = getSerializationKind(cur);
+
+                if (serializationKind == SerializationKind.FILE) {
+                    out.append(".dir");
+                }
+
+                if (!aggs.hasNext() && serializationKind == SerializationKind.METADATA_FULL) {
+                    out.delete(out.lastIndexOf("/"), out.length());
+                }
+            }
+        }
+
+        return out.toString();
+    }
+
+    private boolean isPlainNtFolder(Aggregate agg) throws RepositoryException {
+
+        return agg.getNode().getPrimaryNodeType().getName().equals("nt:folder")
+                && agg.getNode().getMixinNodeTypes().length == 0;
+    }
+
+    /**
+     * Returns the aggregates for a specific resource
+     * 
+     * <p>
+     * In the simplest case, a single element is returned in the chain, signalling that the aggregate is a top-level
+     * one.
+     * </p>
+     * 
+     * <p>
+     * For leaf aggregates, the list contains the top-most aggregates first and ends up with the leaf-most ones.
+     * </p>
+     * 
+     * @param resource the resource to find the aggregate chain for
+     * @return a list of aggregates
+     * @throws IOException
+     * @throws RepositoryException
+     */
+    private List<Aggregate> findAggregateChain(ResourceProxy resource) throws IOException, RepositoryException {
 
         VaultFile vaultFile = fs.getFile(resource.getPath());
         String platformPath = resource.getPath();
@@ -228,49 +283,76 @@ public class VltSerializationDataBuilder
             }
 
             if (vaultFile == null) {
-                // TODO proper logging ; discover if this is expected or not and fail hard if it's not
                 // this file might be a leaf aggregate of a vaultfile higher in the resource path ; so look for a
                 // parent higher
 
                 String parentPath = Text.getRelativeParent(resource.getPath(), 1);
                 while (!parentPath.equals("/")) {
-                    VaultFile parentFile = fs.getFile(parentPath);
-                    if (parentFile != null && parentFile.getAggregate() != null
-                            && parentFile.getAggregate().getLeaves() != null) {
-                        for (Aggregate leaf : parentFile.getAggregate().getLeaves()) {
-                            if (leaf.getPath().equals(resource.getPath())) {
+                    VaultFile parentFile = fs.getFile(PlatformNameFormat.getPlatformPath(parentPath));
 
-                                return new AggregateWrapper(leaf, parentFile.getAggregate());
-                            }
+                    if (parentFile != null) {
+                        Aggregate parentAggregate = parentFile.getAggregate();
+                        ArrayList<Aggregate> parents = new ArrayList<Aggregate>();
+                        parents.add(parentAggregate);
+                        List<Aggregate> chain = lookForAggregateInLeaves(resource, parentAggregate, parents);
+                        if (chain != null) {
+                            return chain;
                         }
                     }
 
                     parentPath = Text.getRelativeParent(parentPath, 1);
                 }
 
-                System.err.println("No vaultFile at path " + resource.getPath());
                 return null;
             }
         }
 
-        return new AggregateWrapper(vaultFile.getAggregate(), null);
+        return Collections.singletonList(vaultFile.getAggregate());
     }
 
-    public void setLocator(VaultFsLocator locator) {
-
-        this.fsLocator = locator;
-    }
+    /**
+     * Recursively looks for an aggregate matching the <tt>resource</tt>'s path starting at the <tt>parentAggregate</tt>
+     * 
+     * <p>
+     * The returned chain will contain at least one aggregate, in case the resource is contained in a stand-alone (?)
+     * aggregate, or multiple aggregates in case the matching aggregate is a leaf one.
+     * </p>
+     * 
+     * @param resource the resource
+     * @param parentAggregate the known parent aggregate which potentially matches this resource
+     * @param chain the chain used to record all intermediate aggregates
+     * @return the final aggregate chain
+     * 
+     * @throws RepositoryException
+     */
+    private List<Aggregate> lookForAggregateInLeaves(ResourceProxy resource, Aggregate parentAggregate,
+            List<Aggregate> chain) throws RepositoryException {
 
-    static class AggregateWrapper {
+        if (parentAggregate == null) {
+            return null;
+        }
 
-        private AggregateWrapper(Aggregate aggregate, Aggregate parent) {
+        List<? extends Aggregate> leaves = parentAggregate.getLeaves();
+        if (leaves == null) {
+            return null;
+        }
 
-            this.aggregate = aggregate;
-            this.parent = parent;
+        for (Aggregate leaf : leaves) {
+            if (leaf.getPath().equals(resource.getPath())) {
+                chain.add(leaf);
+                return chain;
+            } else if (Text.isDescendant(leaf.getPath(), resource.getPath())) {
+                chain.add(leaf);
+                return lookForAggregateInLeaves(resource, leaf, chain);
+            }
         }
 
-        public Aggregate aggregate;
-        public Aggregate parent;
+        return null;
+    }
+
+    public void setLocator(VaultFsLocator locator) {
+
+        this.fsLocator = locator;
     }
 
 }