diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTest.java b/source/java/org/alfresco/repo/avm/AVMServiceTest.java index 1239aef410..b8c44152f0 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceTest.java @@ -42,6 +42,8 @@ import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; import org.alfresco.service.cmr.avm.LayeringDescriptor; import org.alfresco.service.cmr.avm.VersionDescriptor; +import org.alfresco.service.cmr.avmsync.AVMDifference; +import org.alfresco.service.cmr.avmsync.AVMSyncException; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessPermission; @@ -56,6 +58,79 @@ import org.alfresco.service.transaction.TransactionService; */ public class AVMServiceTest extends AVMServiceTestBase { + /** + * Test AVMSyncService update. + */ + public void testUpdate() + { + try + { + setupBasicTree(); + // Try branch to branch update. + fService.createBranch(-1, "main:/a", "main:/", "abranch"); + fService.createFile("main:/abranch", "monkey").close(); + fService.getFileOutputStream("main:/abranch/b/c/foo").close(); + System.out.println(recursiveList("main", -1, true)); + List diffs = new ArrayList(); + diffs.add(new AVMDifference(-1, "main:/abranch/monkey", + -1, "main:/a/monkey", + AVMDifference.NEWER)); + diffs.add(new AVMDifference(-1, "main:/abranch/b/c/foo", + -1, "main:/a/b/c/foo", + AVMDifference.NEWER)); + fSyncService.update(diffs, false, false, false, false); + fService.createSnapshot("main"); + System.out.println(recursiveList("main", -1, true)); + assertEquals(fService.lookup(-1, "main:/abranch/monkey").getId(), + fService.lookup(-1, "main:/a/monkey").getId()); + assertEquals(fService.lookup(-1, "main:/abranch/b/c/foo").getId(), + fService.lookup(-1, "main:/a/b/c/foo").getId()); + // Try updating a deletion. + fService.removeNode("main:/abranch", "monkey"); + System.out.println(recursiveList("main", -1, true)); + diffs.clear(); + diffs.add(new AVMDifference(-1, "main:/abranch/monkey", + -1, "main:/a/monkey", + AVMDifference.NEWER)); + fSyncService.update(diffs, false, false, false, false); + fService.createSnapshot("main"); + System.out.println(recursiveList("main", -1, true)); + assertEquals(fService.lookup(-1, "main:/abranch/monkey", true).getId(), + fService.lookup(-1, "main:/a/monkey", true).getId()); + // Try one that should fail. + fService.createFile("main:/abranch", "monkey").close(); + diffs.clear(); + diffs.add(new AVMDifference(-1, "main:/a/monkey", + -1, "main:/abranch/monkey", + AVMDifference.NEWER)); + try + { + fSyncService.update(diffs, false, false, false, false); + fail(); + } + catch (AVMSyncException se) + { + // Do nothing. + } + // Get synced again by doing an override conflict. + System.out.println(recursiveList("main", -1, true)); + diffs.clear(); + diffs.add(new AVMDifference(-1, "main:/a/monkey", + -1, "main:/abranch/monkey", + AVMDifference.NEWER)); + fSyncService.update(diffs, false, false, true, false); + fService.createSnapshot("main"); + System.out.println(recursiveList("main", -1, true)); + assertEquals(fService.lookup(-1, "main:/a/monkey", true).getId(), + fService.lookup(-1, "main:/abranch/monkey", true).getId()); + } + catch (Exception e) + { + e.printStackTrace(System.err); + fail(); + } + } + /** * Test link AVMService call. */ diff --git a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java index 8efe75c3bb..a96bd1b920 100644 --- a/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java +++ b/source/java/org/alfresco/repo/avm/AVMSyncServiceImpl.java @@ -22,12 +22,12 @@ 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.AVMSyncException; import org.alfresco.service.cmr.avmsync.AVMSyncService; /** @@ -85,17 +85,199 @@ public class AVMSyncServiceImpl implements AVMSyncService * will cause the transaction to roll back. * @param diffList A List of AVMDifference structs. * @param ignoreConflicts If this is true the update will skip those - * AVMDifferences which are in conflict or for which the source is older than + * AVMDifferences which are in conflict with * the destination. + * @param ignoreOlder If this is true the update will skip those + * AVMDifferences which have the source older than the destination. * @param overrideConflicts If this is true the update will override conflicting * AVMDifferences and replace the destination with the conflicting source. * @param overrideOlder If this is true the update will override AVMDifferences * in which the source is older than the destination and overwrite the destination. */ - public void update(List diffList, boolean ignoreConflicts, + public void update(List diffList, boolean ignoreConflicts, boolean ignoreOlder, boolean overrideConflicts, boolean overrideOlder) { - // TODO Implement. + for (AVMDifference diff : diffList) + { + if (!diff.isValid()) + { + throw new AVMSyncException("Malformed AVMDifference."); + } + AVMNodeDescriptor srcDesc = fAVMService.lookup(diff.getSourceVersion(), + diff.getSourcePath(), true); + if (srcDesc == null) + { + throw new AVMSyncException("Source node not found: " + diff.getSourcePath()); + } + String [] dstParts = AVMNodeConverter.SplitBase(diff.getDestinationPath()); + if (dstParts[0] == null || diff.getDestinationVersion() >= 0) + { + throw new AVMSyncException("Invalid destination node: " + diff.getDestinationPath()); + } + AVMNodeDescriptor dstDesc = fAVMService.lookup(-1, diff.getDestinationPath(), true); + int diffCode = AVMDifference.NEWER; + if (dstDesc != null) + { + diffCode = compareOne(srcDesc, dstDesc); + } + switch (diffCode) + { + case AVMDifference.SAME : + { + continue; + } + case AVMDifference.NEWER : + { + if (dstDesc != null) + { + fAVMService.removeNode(dstParts[0], dstParts[1]); + } + fAVMService.link(dstParts[0], dstParts[1], srcDesc); + continue; + } + case AVMDifference.OLDER : + { + if (overrideOlder) + { + fAVMService.removeNode(dstParts[0], dstParts[1]); + fAVMService.link(dstParts[0], dstParts[1], srcDesc); + continue; + } + if (ignoreOlder) + { + continue; + } + throw new AVMSyncException("Older version prevents update."); + } + case AVMDifference.CONFLICT : + { + if (overrideConflicts) + { + fAVMService.removeNode(dstParts[0], dstParts[1]); + fAVMService.link(dstParts[0], dstParts[1], srcDesc); + continue; + } + if (ignoreConflicts) + { + continue; + } + throw new AVMSyncException("Conflict prevents update."); + } + case AVMDifference.DIRECTORY : + { + if (ignoreConflicts) + { + continue; + } + throw new AVMSyncException("Directory conflict prevents update."); + } + default : + { + throw new AVMSyncException("Invalid Differenc Code: Internal Error."); + } + } + } + } + + /** + * The workhorse of comparison and updating. Determine the versioning relationship + * of two nodes. + * @param srcDesc Descriptor for the source node. + * @param dstDesc Descriptor for the destination node. + * @return One of SAME, OLDER, NEWER, CONFLICT, DIRECTORY + */ + private int compareOne(AVMNodeDescriptor srcDesc, AVMNodeDescriptor dstDesc) + { + if (srcDesc.getId() == dstDesc.getId()) + { + return AVMDifference.SAME; + } + // Matched directories that are not identical are operationally in conflict + // but get their own special difference code. + if (srcDesc.isDirectory() && dstDesc.isDirectory()) + { + return AVMDifference.DIRECTORY; + } + // Check for mismatched fundamental types. + if ((srcDesc.isDirectory() && dstDesc.isFile()) || + (srcDesc.isFile() && dstDesc.isDirectory())) + { + return AVMDifference.CONFLICT; + } + if (srcDesc.isDeleted() || dstDesc.isDeleted()) + { + AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc); + if (common == null) + { + return AVMDifference.CONFLICT; + } + if (common.getId() == srcDesc.getId()) + { + return AVMDifference.OLDER; + } + if (common.getId() == dstDesc.getId()) + { + return AVMDifference.NEWER; + } + // Must be a conflict. + return AVMDifference.CONFLICT; + } + // At this point both source and destination are both some kind of file. + if (srcDesc.isLayeredFile()) + { + if (dstDesc.isPlainFile()) + { + if (srcDesc.getIndirection().equals(dstDesc.getPath())) + { + return AVMDifference.SAME; + } + // We know that they are in conflict since they are of different types. + return AVMDifference.CONFLICT; + } + // Destination is a layered file also. + AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc); + if (common == null) + { + return AVMDifference.CONFLICT; + } + if (common.getId() == srcDesc.getId()) + { + return AVMDifference.OLDER; + } + if (common.getId() == dstDesc.getId()) + { + return AVMDifference.NEWER; + } + // Finally we know they are in conflict. + return AVMDifference.CONFLICT; + } + // Source is a plain file. + if (dstDesc.isLayeredFile()) + { + if (dstDesc.getIndirection().equals(srcDesc.getPath())) + { + return AVMDifference.SAME; + } + // Otherwise we know they are in conflict because they are of different type. + return AVMDifference.CONFLICT; + } + // Destination is a plain file. + AVMNodeDescriptor common = fAVMService.getCommonAncestor(srcDesc, dstDesc); + // Conflict case. + if (common == null) + { + return AVMDifference.CONFLICT; + } + if (common.getId() == srcDesc.getId()) + { + return AVMDifference.OLDER; + } + if (common.getId() == dstDesc.getId()) + { + return AVMDifference.NEWER; + } + // The must, finally, be in conflict. + return AVMDifference.CONFLICT; } /** @@ -132,48 +314,48 @@ public class AVMSyncServiceImpl implements AVMSyncService */ private void flatten(AVMNodeDescriptor layer, AVMNodeDescriptor underlying) { - // First case: a layered directory. - if (layer.isLayeredDirectory()) + if (!layer.isLayeredDirectory()) { - // layer and underlying must match. - if (!layer.getIndirection().equals(underlying.getPath())) + return; + } + // layer and underlying must match for flattening to be useful. + if (!layer.getIndirection().equals(underlying.getPath())) + { + return; + } + // 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) { - throw new AVMException("Layer and Underlying do not match."); + continue; } - // The underlying thing must be a directory. - if (!underlying.isDirectory()) + // We've found an identity so flatten it. + if (topNode.getId() == bottomNode.getId()) { - throw new AVMWrongTypeException("Underlying is not a directory: " + underlying); + fAVMService.removeNode(layer.getPath(), name); + fAVMService.uncover(layer.getPath(), name); } - Map layerListing = - fAVMService.getDirectoryListingDirect(-1, layer.getPath(), true); - // If the layer is empty (directly, that is) we're done. - if (layerListing.size() == 0) + else { - 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); - } + // Otherwise recursively flatten the children. + flatten(topNode, bottomNode); } } } diff --git a/source/java/org/alfresco/service/cmr/avmsync/AVMDifference.java b/source/java/org/alfresco/service/cmr/avmsync/AVMDifference.java index ffc1168fe3..2199b7e295 100644 --- a/source/java/org/alfresco/service/cmr/avmsync/AVMDifference.java +++ b/source/java/org/alfresco/service/cmr/avmsync/AVMDifference.java @@ -32,6 +32,8 @@ public class AVMDifference implements Serializable public static final int NEWER = 0; public static final int OLDER = 1; public static final int CONFLICT = 2; + public static final int DIRECTORY = 3; + public static final int SAME = 4; /** * Version number of the source node. @@ -120,4 +122,13 @@ public class AVMDifference implements Serializable { return fDiffCode; } + + /** + * Check for improperly initialized instances. + * @return Whether source and destination are non null. + */ + public boolean isValid() + { + return fSourcePath != null && fDestPath != null; + } } diff --git a/source/java/org/alfresco/service/cmr/avmsync/AVMSyncException.java b/source/java/org/alfresco/service/cmr/avmsync/AVMSyncException.java new file mode 100644 index 0000000000..1d326a2817 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/avmsync/AVMSyncException.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.service.cmr.avmsync; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Exception for tree to tree synchronization problems. + * @author britt + */ +public class AVMSyncException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 1075466935333921588L; + + /** + * @param msgId + */ + public AVMSyncException(String msgId) + { + super(msgId); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + */ + public AVMSyncException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param cause + */ + public AVMSyncException(String msgId, Throwable cause) + { + super(msgId, cause); + // TODO Auto-generated constructor stub + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public AVMSyncException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + // TODO Auto-generated constructor stub + } + +} diff --git a/source/java/org/alfresco/service/cmr/avmsync/AVMSyncService.java b/source/java/org/alfresco/service/cmr/avmsync/AVMSyncService.java index 3e9f7c39d9..0b4757b719 100644 --- a/source/java/org/alfresco/service/cmr/avmsync/AVMSyncService.java +++ b/source/java/org/alfresco/service/cmr/avmsync/AVMSyncService.java @@ -45,14 +45,16 @@ public interface AVMSyncService * will cause the transaction to roll back. * @param diffList A List of AVMDifference structs. * @param ignoreConflicts If this is true the update will skip those - * AVMDifferences which are in conflict or for which the source is older than + * AVMDifferences which are in conflict with * the destination. + * @param ignoreOlder If this is true the update will skip those + * AVMDifferences which have the source older than the destination. * @param overrideConflicts If this is true the update will override conflicting * AVMDifferences and replace the destination with the conflicting source. * @param overrideOlder If this is true the update will override AVMDifferences * in which the source is older than the destination and overwrite the destination. */ - public void update(List diffList, boolean ignoreConflicts, + public void update(List diffList, boolean ignoreConflicts, boolean ignoreOlder, boolean overrideConflicts, boolean overrideOlder); /**