diff --git a/source/java/org/alfresco/repo/avm/AVMRepository.java b/source/java/org/alfresco/repo/avm/AVMRepository.java index ae3c55452b..e5c57aefd5 100644 --- a/source/java/org/alfresco/repo/avm/AVMRepository.java +++ b/source/java/org/alfresco/repo/avm/AVMRepository.java @@ -1217,4 +1217,18 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); return store.getACL(version, pathParts[1]); } + + /** + * Link a node into a directory, directly. + * @param parentPath The path to the parent. + * @param name The name to give the node. + * @param toLink The node to link. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink) + { + fLookupCount.set(1); + String [] pathParts = SplitPath(parentPath); + AVMStore store = getAVMStoreByName(pathParts[0]); + store.link(pathParts[1], name, toLink); + } } diff --git a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java index 00a7dd600c..925e6c5d5f 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceImpl.java @@ -1033,4 +1033,19 @@ public class AVMServiceImpl implements AVMService } return fAVMRepository.hasAspect(version, path, aspectName); } + + /** + * This inserts a node into a parent directly. + * @param parentPath The path to the parent directory. + * @param name The name to give the node. + * @param toLink A descriptor for the node to insert. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink) + { + if (parentPath == null || name == null || toLink == null) + { + throw new AVMBadArgumentException("Illegal Null Argument."); + } + fAVMRepository.link(parentPath, name, toLink); + } } diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTest.java b/source/java/org/alfresco/repo/avm/AVMServiceTest.java index ea1a1fa5d9..1239aef410 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceTest.java @@ -56,6 +56,76 @@ import org.alfresco.service.transaction.TransactionService; */ public class AVMServiceTest extends AVMServiceTestBase { + /** + * Test link AVMService call. + */ + public void testLink() + { + try + { + setupBasicTree(); + // Just try linking /a/b/c/foo into /a/b + fService.link("main:/a/b", "foo", fService.lookup(-1, "main:/a/b/c/foo")); + assertEquals(fService.lookup(-1, "main:/a/b/c/foo").getId(), + fService.lookup(-1, "main:/a/b/foo").getId()); + // Try linking /a/b/c/bar to /a/b/foo. It should fail. + System.out.println(recursiveList("main", -1, true)); + try + { + fService.link("main:/a/b", "foo", fService.lookup(-1, "main:/a/b/c/bar")); + fail(); + } + catch (AVMExistsException e) + { + // Do nothing. It's OK. + } + // Delete /a/b/foo, and link /a/b/c/foo into /a/b. This checks that + // a deleted node is no impediment. + fService.removeNode("main:/a/b", "foo"); + fService.link("main:/a/b", "foo", fService.lookup(-1, "main:/a/b/c/foo")); + assertEquals(fService.lookup(-1, "main:/a/b/c/foo").getId(), + fService.lookup(-1, "main:/a/b/foo").getId()); + // Delete /a/b/foo again in prep for layer tests. + fService.removeNode("main:/a/b", "foo"); + System.out.println(recursiveList("main", -1, true)); + fService.createSnapshot("main"); + // Create a layer do a link from /layer/b/c/bar to /layer/b + fService.createLayeredDirectory("main:/a", "main:/", "layer"); + fService.link("main:/layer/b", "bar", fService.lookup(-1, "main:/layer/b/c/bar")); + assertEquals(fService.lookup(-1, "main:/layer/b/c/bar").getId(), + fService.lookup(-1, "main:/layer/b/bar").getId()); + System.out.println(recursiveList("main", -1, true)); + // Now link /layer/b/c/foo into /layer/b. + fService.link("main:/layer/b", "foo", fService.lookup(-1, "main:/layer/b/c/foo")); + assertEquals(fService.lookup(-1, "main:/layer/b/c/foo").getId(), + fService.lookup(-1, "main:/layer/b/foo").getId()); + // Make sure that the underlying layer is not mucked up. + assertTrue(fService.lookup(-1, "main:/a/b/foo", true).isDeleted()); + System.out.println(recursiveList("main", -1, true)); + // Try to link /layer/b/c/bar to /layer/b/c. It should fail. + try + { + fService.link("main:/layer/b", "bar", fService.lookup(-1, "main:/layer/b/c/bar")); + fail(); + } + catch (AVMExistsException e) + { + // Do nothing. + } + // Delete /layer/b/bar and redo. It should work. + fService.removeNode("main:/layer/b", "bar"); + fService.link("main:/layer/b", "bar", fService.lookup(-1, "main:/layer/b/c/bar")); + assertEquals(fService.lookup(-1, "main:/layer/b/c/bar").getId(), + fService.lookup(-1, "main:/layer/b/bar").getId()); + System.out.println(recursiveList("main", -1, true)); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + /** * Test goofy paths. */ diff --git a/source/java/org/alfresco/repo/avm/AVMStore.java b/source/java/org/alfresco/repo/avm/AVMStore.java index e4aa705072..2c658bb22a 100644 --- a/source/java/org/alfresco/repo/avm/AVMStore.java +++ b/source/java/org/alfresco/repo/avm/AVMStore.java @@ -404,4 +404,12 @@ public interface AVMStore * @return The ACL. */ public DbAccessControlList getACL(int version, String path); + + /** + * Link a node intro a directory, directly. + * @param parentPath The path to the directory. + * @param name The name to give the node. + * @param toLink The node to link. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/AVMStoreImpl.java b/source/java/org/alfresco/repo/avm/AVMStoreImpl.java index 3bf5531a34..581e1aa15a 100644 --- a/source/java/org/alfresco/repo/avm/AVMStoreImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMStoreImpl.java @@ -1135,4 +1135,17 @@ public class AVMStoreImpl implements AVMStore, Serializable Lookup lPath = lookup(version, path, false, false); return lPath.getCurrentNode().getAcl(); } + + /** + * Link a node intro a directory, directly. + * @param parentPath The path to the directory. + * @param name The name to give the parent. + * @param toLink The node to link. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink) + { + Lookup lPath = lookupDirectory(-1, parentPath, true); + DirectoryNode dir = (DirectoryNode)lPath.getCurrentNode(); + dir.link(lPath, name, toLink.getId()); + } } diff --git a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java index 1924627c20..8efe75c3bb 100644 --- a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java @@ -17,11 +17,16 @@ package org.alfresco.repo.avm; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import org.alfresco.service.cmr.avm.AVMBadArgumentException; +import org.alfresco.service.cmr.avm.AVMException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMNotFoundException; import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.AVMWrongTypeException; import org.alfresco.service.cmr.avmsync.AVMDifference; import org.alfresco.service.cmr.avmsync.AVMSyncService; @@ -70,7 +75,7 @@ public class AVMSyncServiceImpl implements AVMSyncService int dstVersion, String dstPath) { // TODO Implement. - return null; + return new ArrayList(); } /** @@ -103,7 +108,74 @@ public class AVMSyncServiceImpl implements AVMSyncService */ public void flatten(String layerPath, String underlyingPath) { - // TODO Implement. + if (layerPath == null || underlyingPath == null) + { + throw new AVMBadArgumentException("Illegal null path."); + } + AVMNodeDescriptor layerNode = fAVMService.lookup(-1, layerPath); + if (layerNode == null) + { + throw new AVMNotFoundException("Not found: " + layerPath); + } + AVMNodeDescriptor underlyingNode = fAVMService.lookup(-1, underlyingPath); + if (underlyingNode == null) + { + throw new AVMNotFoundException("Not found: " + underlyingPath); + } + flatten(layerNode, underlyingNode); + } + + /** + * This is the implementation of flatten. + * @param layer The on top node. + * @param underlying The underlying node. + */ + private void flatten(AVMNodeDescriptor layer, AVMNodeDescriptor underlying) + { + // First case: a layered directory. + if (layer.isLayeredDirectory()) + { + // layer and underlying must match. + if (!layer.getIndirection().equals(underlying.getPath())) + { + throw new AVMException("Layer and Underlying do not match."); + } + // The underlying thing must be a directory. + if (!underlying.isDirectory()) + { + throw new AVMWrongTypeException("Underlying is not a directory: " + underlying); + } + Map layerListing = + fAVMService.getDirectoryListingDirect(-1, layer.getPath(), true); + // If the layer is empty (directly, that is) we're done. + if (layerListing.size() == 0) + { + return; + } + // Grab the listing + Map underListing = + fAVMService.getDirectoryListing(-1, underlying.getPath(), true); + for (String name : layerListing.keySet()) + { + AVMNodeDescriptor topNode = layerListing.get(name); + AVMNodeDescriptor bottomNode = underListing.get(name); + if (bottomNode == null) + { + continue; + } + // We've found an identity so flatten it. + if (topNode.getId() == bottomNode.getId()) + { + fAVMService.removeNode(layer.getPath(), name); + fAVMService.uncover(layer.getPath(), name); + } + else + { + // Otherwise recursively flatten the children. + flatten(topNode, bottomNode); + } + } + } } /** diff --git a/source/java/org/alfresco/repo/avm/DirectoryNode.java b/source/java/org/alfresco/repo/avm/DirectoryNode.java index f95c57395e..79b1b0daba 100644 --- a/source/java/org/alfresco/repo/avm/DirectoryNode.java +++ b/source/java/org/alfresco/repo/avm/DirectoryNode.java @@ -114,4 +114,12 @@ public interface DirectoryNode extends AVMNode * @param isRoot */ public void setIsRoot(boolean isRoot); + + /** + * Link a node with the given id into this directory. + * @param lPath The Lookup for this node. + * @param name The name to give the node. + * @param id The id of the node to insert. + */ + public void link(Lookup lPath, String name, long id); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java index 22e8a59490..400578cddc 100644 --- a/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java +++ b/source/java/org/alfresco/repo/avm/LayeredDirectoryNodeImpl.java @@ -27,7 +27,9 @@ import java.util.TreeMap; import org.alfresco.service.cmr.avm.AVMBadArgumentException; import org.alfresco.service.cmr.avm.AVMCycleException; import org.alfresco.service.cmr.avm.AVMException; +import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMNotFoundException; /** * A layered directory node. A layered directory node points at @@ -616,6 +618,10 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec public void uncover(Lookup lPath, String name) { ChildEntry entry = AVMContext.fgInstance.fChildEntryDAO.getByNameParent(name, this); + if (entry.getChild().getType() != AVMNodeType.DELETED_NODE) + { + throw new AVMException("One can only uncover deleted nodes."); + } if (entry != null) { AVMContext.fgInstance.fChildEntryDAO.delete(entry); @@ -759,4 +765,39 @@ class LayeredDirectoryNodeImpl extends DirectoryNodeImpl implements LayeredDirec { fOpacity = opacity; } + + /** + * Link a node with the given id into this directory. + * @param lPath The Lookup for this. + * @param name The name to give the node. + * @param id The id of the node to insert. + */ + public void link(Lookup lPath, String name, long id) + { + AVMNode node = AVMContext.fgInstance.fAVMNodeDAO.getByID(id); + if (node == null) + { + throw new AVMNotFoundException("Not Found: " + id); + } + // Look for an existing child of that name. + AVMNode existing = lookupChild(lPath, name, -1, false, true); + if (existing != null) + { + if (existing.getType() != AVMNodeType.DELETED_NODE) + { + // If the existing child is not a DELETED_NODE it's an error. + throw new AVMExistsException(name + " exists."); + } + // Only if the existing DELETED_NODE child exists directly in this + // directory do we delete it. + if (directlyContains(existing)) + { + ChildEntry entry = AVMContext.fgInstance.fChildEntryDAO.getByNameParent(name, this); + AVMContext.fgInstance.fChildEntryDAO.delete(entry); + } + } + // Make the new ChildEntry and save. + ChildEntry newChild = new ChildEntryImpl(name, this, node); + AVMContext.fgInstance.fChildEntryDAO.save(newChild); + } } diff --git a/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java b/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java index 4b2c0a3efa..32b8fedad0 100644 --- a/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java +++ b/source/java/org/alfresco/repo/avm/PlainDirectoryNodeImpl.java @@ -25,7 +25,9 @@ import java.util.SortedMap; import java.util.TreeMap; import org.alfresco.service.cmr.avm.AVMBadArgumentException; +import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMNotFoundException; /** * A plain directory. No monkey tricks except for possiblyCopy. @@ -400,5 +402,36 @@ class PlainDirectoryNodeImpl extends DirectoryNodeImpl implements PlainDirectory false, -1); } + + /** + * Link a node with the given id into this directory. + * @param lPath The Lookup for this directory. + * @param name The name to give the node. + * @param id The id of the node to insert. + */ + public void link(Lookup lPath, String name, long id) + { + // Assure that the incoming node exists. + AVMNode node = AVMContext.fgInstance.fAVMNodeDAO.getByID(id); + if (node == null) + { + throw new AVMNotFoundException("Node not found: " + id); + } + // Check for an existing child by the given name. + ChildEntry child = AVMContext.fgInstance.fChildEntryDAO.getByNameParent(name, this); + if (child != null) + { + if (child.getChild().getType() != AVMNodeType.DELETED_NODE) + { + // It's an error if there is a non DELETED_NODE child. + throw new AVMExistsException(name + " exists."); + } + // Get rid of the DELETED_NODE child. + AVMContext.fgInstance.fChildEntryDAO.delete(child); + } + // Make the new entry and save. + ChildEntry newChild = new ChildEntryImpl(name, this, node); + AVMContext.fgInstance.fChildEntryDAO.save(newChild); + } } diff --git a/source/java/org/alfresco/service/cmr/avm/AVMService.java b/source/java/org/alfresco/service/cmr/avm/AVMService.java index ec47402d5e..c50b78d3f9 100644 --- a/source/java/org/alfresco/service/cmr/avm/AVMService.java +++ b/source/java/org/alfresco/service/cmr/avm/AVMService.java @@ -668,4 +668,12 @@ public interface AVMService * @return Whether the given node has the given aspect. */ public boolean hasAspect(int version, String path, QName aspectName); + + /** + * This inserts a node into a parent directly. + * @param parentPath The path to the parent directory. + * @param name The name to give the node. + * @param toLink A descriptor for the node to insert. + */ + public void link(String parentPath, String name, AVMNodeDescriptor toLink); }