/// <summary> /// Performs a commit /// </summary> /// <param name="workspaceId">Workspace ID</param> /// <param name="changeSet">Changes to commit</param> /// <returns>Changes which were commited</returns> public CommitResult <Guid> Commit(Guid workspaceId, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { bool isSnapshotIsolation = workspaceStateProvider.WorkspaceIsolationLevel(workspaceId) == IsolationLevel.Snapshot; if (isSnapshotIsolation) { workspaceExclusiveLockProvider.EnterLockExclusive(); } try { lock (commitSync) { if (!workspaceStateProvider.IsWorkspaceExpired(workspaceId)) { var result = commitDataService.AcceptCommit(changeSet); workspaceStateProvider.UpdateWorspace(workspaceId, result.ResultSnapshotId); subscriptionManagerService.InvokeEvents(workspaceId, result); return(result); } else { throw new TimeoutException("Workspace timeout has elapsed"); } } } finally { if (isSnapshotIsolation) { workspaceExclusiveLockProvider.ExitLockExclusive(); } } }
public RecursiveResolutionParameters(Hashtable subTree, IsolatedNodeProvider destinationProvider, IsolatedNodeProvider sourceProvider, IsolatedChangeSet <Guid, object, EdgeData> changeSet, Dictionary <Guid, Guid> intermediateChanges, Hashtable visitedNodes) { this.SubTree = subTree; this.DestinationProvider = destinationProvider; this.SourceProvider = sourceProvider; this.ChangeSet = changeSet; this.IntermediateChanges = intermediateChanges; this.VisitedNodes = visitedNodes; }
/// <summary> /// Returns node from the nodes list. If node does not exist in the nodes list node from the changeSet is returned. /// </summary> /// <param name="nodeId"></param> /// <param name="changeSet"></param> /// <returns></returns> private Node <Guid, object, EdgeData> GetNode(Guid nodeId, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { var childNode = nodes.GetNode(nodeId, NodeAccess.ReadWrite); if (childNode != null) { return(childNode); } return(changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite)); }
/// <summary> /// Accepts incoming isolated commit /// </summary> /// <param name="isolatedChangeSet">Isolated changeset</param> /// <returns>Appended changes</returns> public CommitResult <Guid> AcceptCommit(IsolatedChangeSet <Guid, object, EdgeData> isolatedChangeSet) { lock (commitSync) { var latestSnapshot = snapshotsService.GetLatestSnapshotId(); if (latestSnapshot.Equals(isolatedChangeSet.SourceSnapshotId)) { AppendableChangeSet <Guid, object, EdgeData> appendableChangeSet = CreateAppendableChangeSet(isolatedChangeSet.SourceSnapshotId, Guid.NewGuid(), isolatedChangeSet); // Commit is directly on the last snapshot CommitDirect(appendableChangeSet); return(new CommitResult <Guid>(appendableChangeSet.DestinationSnapshotId, appendableChangeSet.Mapping)); } else { // There are snapshots in between #if DEBUG Debug.WriteLine("Source snapshot = " + isolatedChangeSet.SourceSnapshotId); #endif // Calculate changes between last snapshot and source snapshot var intermediateChanges = ChangesBetween(isolatedChangeSet.SourceSnapshotId, latestSnapshot); // Isolate portion of the last snapshot which is relevant for the change set var subTree = IsolateSubTree(latestSnapshot, isolatedChangeSet, intermediateChanges); // Create a brand new changeset which is compatible with last snapshot // Do this by recursive walk through the last snapshot and perform the "compatible" operations done in the incomming changeset var mergedChangeSet = CreateMergedChangeSet(latestSnapshot, subTree, isolatedChangeSet, intermediateChanges); // When complete perform the CommitDirect of the new changeset CommitDirect(mergedChangeSet); // Start with created change mapping var mapping = mergedChangeSet.Mapping; // Merge intermediate changes items to it foreach (var item in intermediateChanges) { AddChangeItem(mapping, item.Key, item.Value); } return(new CommitResult <Guid>(mergedChangeSet.DestinationSnapshotId, mapping)); } } }
/// <summary> /// Creates map of node ids in the last snapshot which are affected by changes in the change set. /// </summary> /// <param name="snapshotId">Last snapshot</param> /// <param name="changeSet">Change set which is created</param> /// <param name="intermediateChanges">Intermediate changes</param> /// <returns>Table of IDs for nodes which are modified in the last snapshot</returns> private Hashtable IsolateSubTree(Guid snapshotId, IsolatedChangeSet <Guid, object, EdgeData> changeSet, Dictionary <Guid, Guid> intermediateChanges) { Hashtable result = new Hashtable(); Collection <Guid> changes = new Collection <Guid>(); foreach (Guid item in changeSet.Nodes.EnumerateNodes()) { // Test if item is new or modified if (changeSet.NodeStates[item] == NodeState.Modified) { Guid nodeId = Guid.Empty; Guid oldNodeId = Guid.Empty; // Try finding the latest id of the changed item if (intermediateChanges.TryGetValue(item, out nodeId)) { oldNodeId = item; } else { // It was not changed in the meantime, so the original ID should be still in use nodeId = item; } // Remember new node ID with old node ID as value result.Add(nodeId, oldNodeId); // Changes changes.Add(nodeId); } } foreach (var item in changes) { AddSubTreeParentsRecursive(snapshotId, item, result); } return(result); }
public string Commit(string workspaceId, object changeSet) { Guid guidWorkspaceId = Guid.Parse(workspaceId); //transform changeSet into IsolatedChangeSet object IsolatedChangeSet <Guid, object, EdgeData> serverChangeSet = ChangeSetParser.Parse(changeSet); //commit changes CommitResult <Guid> commitResult = ServerContextSingleton.Instance.ServerContext.Commit(guidWorkspaceId, serverChangeSet); String resultSnapshotId = commitResult.ResultSnapshotId.ToString(); Dictionary <string, string> mapping = new Dictionary <string, string>(); foreach (KeyValuePair <Guid, Guid> mapObject in commitResult.Mapping) { mapping.Add(mapObject.Key.ToString(), mapObject.Value.ToString()); } CommitResult <String> commitResultString = new CommitResult <string>(resultSnapshotId, mapping); Dictionary <String, CommitResult <String> > rez = new Dictionary <string, CommitResult <string> >(); rez.Add(RESULT, commitResultString); return(rez.ToJSON()); }
/// <summary> /// Adds node to delta with recursive pass through node parents /// </summary> /// <param name="delta">Provider which will accept new nodes</param> /// <param name="nodeId">Node ID to add</param> /// <param name="nodeMapping">Mapping between (old ID)->(new ID)</param> private void AddNodeToAppendableTreeRecursive(DirectNodeProviderUnsafe<Guid, object, EdgeData> delta, Guid nodeId, Dictionary<Guid, Guid> nodeMapping, Dictionary<Guid, NodeState> nodeStates, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { // Skip already added nodes if (!nodeMapping.ContainsKey(nodeId)) { // Generate new ID Guid newId = Guid.NewGuid(); // Check node state NodeState nodeState = NodeState.None; changeSet.NodeStates.TryGetValue(nodeId, out nodeState); switch (nodeState) { case NodeState.None: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Get undelrying node var node = nodes.GetNode(nodeId, NodeAccess.Read); var newNode = CloneNode(node); // Set node to commited newNode.Commited = true; // Create edge to previous node newNode.Previous = nodeId; // New node is created which is copied from underlying provider delta.SetNode(newId, newNode); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { if (enumerator != null) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } } // Update parent nodes UpdateParentNodes(newId, newNode, nodeState, delta, changeSet); } break; case NodeState.Created: { // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // There is no previous node.Previous = Guid.Empty; // Store to delta delta.SetNode(nodeId, node); // This change is defined as creation nodeStates.Add(nodeId, NodeState.Created); // Update parent nodes UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; case NodeState.Modified: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // Set the previous node.Previous = nodeId; // Store to delta delta.SetNode(newId, node); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } // Update parent nodes UpdateParentNodes(newId, node, nodeState, delta, changeSet); } break; case NodeState.Removed: { // Update parent nodes var node = GetNode(nodeId, changeSet); UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; default: throw new ArgumentException(nodeState.ToString()); } } }
/// <summary> /// Accepts incoming isolated commit /// </summary> /// <param name="isolatedChangeSet">Isolated changeset</param> /// <returns>Appended changes</returns> public CommitResult<Guid> AcceptCommit(IsolatedChangeSet<Guid, object, EdgeData> isolatedChangeSet) { lock (commitSync) { var latestSnapshot = snapshotsService.GetLatestSnapshotId(); if (latestSnapshot.Equals(isolatedChangeSet.SourceSnapshotId)) { AppendableChangeSet<Guid, object, EdgeData> appendableChangeSet = CreateAppendableChangeSet(isolatedChangeSet.SourceSnapshotId, Guid.NewGuid(), isolatedChangeSet); // Commit is directly on the last snapshot CommitDirect(appendableChangeSet); return new CommitResult<Guid>(appendableChangeSet.DestinationSnapshotId, appendableChangeSet.Mapping); } else { // There are snapshots in between #if DEBUG Debug.WriteLine("Source snapshot = " + isolatedChangeSet.SourceSnapshotId); #endif // Calculate changes between last snapshot and source snapshot var intermediateChanges = ChangesBetween(isolatedChangeSet.SourceSnapshotId, latestSnapshot); // Isolate portion of the last snapshot which is relevant for the change set var subTree = IsolateSubTree(latestSnapshot, isolatedChangeSet, intermediateChanges); // Create a brand new changeset which is compatible with last snapshot // Do this by recursive walk through the last snapshot and perform the "compatible" operations done in the incomming changeset var mergedChangeSet = CreateMergedChangeSet(latestSnapshot, subTree, isolatedChangeSet, intermediateChanges); // When complete perform the CommitDirect of the new changeset CommitDirect(mergedChangeSet); // Start with created change mapping var mapping = mergedChangeSet.Mapping; // Merge intermediate changes items to it foreach(var item in intermediateChanges) { AddChangeItem(mapping, item.Key, item.Value); } return new CommitResult<Guid>(mergedChangeSet.DestinationSnapshotId, mapping); } } }
public CommitResult <Guid> Commit(Guid workspaceId, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { throw new NotImplementedException(); }
/// <summary> /// Creates map of node ids in the last snapshot which are affected by changes in the change set. /// </summary> /// <param name="snapshotId">Last snapshot</param> /// <param name="changeSet">Change set which is created</param> /// <param name="intermediateChanges">Intermediate changes</param> /// <returns>Table of IDs for nodes which are modified in the last snapshot</returns> private Hashtable IsolateSubTree(Guid snapshotId, IsolatedChangeSet<Guid, object, EdgeData> changeSet, Dictionary<Guid, Guid> intermediateChanges) { Hashtable result = new Hashtable(); Collection<Guid> changes = new Collection<Guid>(); foreach (Guid item in changeSet.Nodes.EnumerateNodes()) { // Test if item is new or modified if (changeSet.NodeStates[item] == NodeState.Modified) { Guid nodeId = Guid.Empty; Guid oldNodeId = Guid.Empty; // Try finding the latest id of the changed item if (intermediateChanges.TryGetValue(item, out nodeId)) { oldNodeId = item; } else { // It was not changed in the meantime, so the original ID should be still in use nodeId = item; } // Remember new node ID with old node ID as value result.Add(nodeId, oldNodeId); // Changes changes.Add(nodeId); } } foreach (var item in changes) { AddSubTreeParentsRecursive(snapshotId, item, result); } return result; }
private AppendableChangeSet<Guid, object, EdgeData> CreateMergedChangeSet(Guid latestSnapshot, Hashtable subTree, IsolatedChangeSet<Guid, object, EdgeData> changeSet, Dictionary<Guid, Guid> intermediateChanges) { #if DEBUG Debug.WriteLine("Merging over intermediate changes"); #endif // We create node provider which will host changes to the last snapshot made by the merge process var isolatedNodes = new DirectNodeProviderUnsafe<Guid,object, EdgeData>(new MemoryStorageUnsafe<Guid, object>(), false); // Perform merging on the current thread IsolatedNodeProvider destinationProvider = new IsolatedNodeProvider(nodes, isolatedNodes, Thread.CurrentThread); IsolatedNodeProvider sourceProvider = new IsolatedNodeProvider(nodes, changeSet.Nodes, Thread.CurrentThread); // Make changes from incoming changes within a subtree MergeRecursive(latestSnapshot, new RecursiveResolutionParameters(subTree, destinationProvider, sourceProvider, changeSet, intermediateChanges, new Hashtable())); // We create an appendable change set from changes made to last snapshot, defining a new snapshot return CreateAppendableChangeSet(latestSnapshot, Guid.NewGuid(), destinationProvider.GetChanges(latestSnapshot)); }
/// <summary> /// For the given nod, goes through all the child nodes and adds that node to the parent node list of the child nodes. /// </summary> /// <param name="newId"></param> /// <param name="parentNode"></param> /// <param name="nodeState"></param> /// <param name="delta">Node provider which contains nodes which will be saved at the end of the commit process</param> private void UpdateParentNodes(Guid newId, Node <Guid, object, EdgeData> parentNode, NodeState nodeState, DirectNodeProviderUnsafe <Guid, object, EdgeData> delta, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { foreach (Edge <Guid, EdgeData> edge in parentNode.Edges.Values) { if ((edge.Data as EdgeData).Semantic == EdgeType.Property && (edge.Data.Flags & EdgeFlags.StoreParentNodes) == EdgeFlags.StoreParentNodes) { var childNode = GetNode(edge.ToNodeId, changeSet); if (childNode != null) { switch (childNode.NodeType) { case NodeType.Object: UpdateParentNode(newId, nodeState, delta, edge, childNode, parentNode); break; case NodeType.Collection: case NodeType.Dictionary: foreach (Edge <Guid, EdgeData> collectionEdge in childNode.Edges.Values) { if (collectionEdge.Data.Semantic.Equals(EdgeType.ListItem)) { UpdateParentNode(newId, nodeState, delta, collectionEdge, GetNode(collectionEdge.ToNodeId, changeSet), parentNode); } } break; default: throw new NotImplementedException("NodeType=" + childNode.NodeType); } } } } }
/// <summary> /// Adds node to delta with recursive pass through node parents /// </summary> /// <param name="delta">Provider which will accept new nodes</param> /// <param name="nodeId">Node ID to add</param> /// <param name="nodeMapping">Mapping between (old ID)->(new ID)</param> private void AddNodeToAppendableTreeRecursive(DirectNodeProviderUnsafe <Guid, object, EdgeData> delta, Guid nodeId, Dictionary <Guid, Guid> nodeMapping, Dictionary <Guid, NodeState> nodeStates, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { // Skip already added nodes if (!nodeMapping.ContainsKey(nodeId)) { // Generate new ID Guid newId = Guid.NewGuid(); // Check node state NodeState nodeState = NodeState.None; changeSet.NodeStates.TryGetValue(nodeId, out nodeState); switch (nodeState) { case NodeState.None: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Get undelrying node var node = nodes.GetNode(nodeId, NodeAccess.Read); var newNode = CloneNode(node); // Set node to commited newNode.Commited = true; // Create edge to previous node newNode.Previous = nodeId; // New node is created which is copied from underlying provider delta.SetNode(newId, newNode); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { if (enumerator != null) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } } // Update parent nodes UpdateParentNodes(newId, newNode, nodeState, delta, changeSet); } break; case NodeState.Created: { // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // There is no previous node.Previous = Guid.Empty; // Store to delta delta.SetNode(nodeId, node); // This change is defined as creation nodeStates.Add(nodeId, NodeState.Created); // Update parent nodes UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; case NodeState.Modified: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // Set the previous node.Previous = nodeId; // Store to delta delta.SetNode(newId, node); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } // Update parent nodes UpdateParentNodes(newId, node, nodeState, delta, changeSet); } break; case NodeState.Removed: { // Update parent nodes var node = GetNode(nodeId, changeSet); UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; default: throw new ArgumentException(nodeState.ToString()); } } }
/// <summary> /// Creates new delta tree describing the new snapshot /// </summary> /// <param name="baseSnapshotId">Base snapshot of the change set</param> /// <param name="newSnapshotId">New snapshot of the change set</param> /// <returns>Delta tree nodes</returns> private INodeProvider <Guid, object, EdgeData> CreateAppendableChangeSetTree(Guid baseSnapshotId, Guid newSnapshotId, IKeyValueStorage <Guid, object> storage, Dictionary <Guid, Guid> nodeMapping, Dictionary <Guid, NodeState> nodeStates, IsolatedChangeSet <Guid, object, EdgeData> changeSet, Hashtable reusedNodes) { DirectNodeProviderUnsafe <Guid, object, EdgeData> delta = new DirectNodeProviderUnsafe <Guid, object, EdgeData>(storage, storage is IForceUpdateStorage); // Create snapshot in delta nodeMapping.Add(baseSnapshotId, newSnapshotId); var snapshotNode = new Node <Guid, object, EdgeData>(NodeType.Snapshot, null); snapshotNode.Previous = baseSnapshotId; // Link to root object snapshotNode.AddEdge(new Edge <Guid, EdgeData>(snapshotsService.GetRootObjectId(baseSnapshotId), new EdgeData(EdgeType.RootObject, null))); // Set node to provider delta.SetNode(newSnapshotId, snapshotNode); // Add all changes and parent nodes foreach (Guid nodeId in changeSet.Nodes.EnumerateNodes()) { AddNodeToAppendableTreeRecursive(delta, nodeId, nodeMapping, nodeStates, changeSet); } // Prepare list of unreferenced node IDs Dictionary <Guid, Collection <Guid> > references = new Dictionary <Guid, Collection <Guid> >(); // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { references.Add(nodeId, new Collection <Guid>()); } // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { var node = delta.GetNode(nodeId, NodeAccess.ReadWrite); foreach (var edge in node.Edges.Values) { // Permanent edges should not be touched if (!Utils.IsPermanentEdge(edge)) { // Reroute based on node mapping if (nodeMapping.ContainsKey(edge.ToNodeId)) { edge.ToNodeId = nodeMapping[edge.ToNodeId]; } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } // Change edge data if it points to changed node if (edge.Data.Data != null && edge.Data.Data is Guid) { if (nodeMapping.ContainsKey((Guid)edge.Data.Data)) { edge.Data = new EdgeData(edge.Data.Semantic, nodeMapping[(Guid)edge.Data.Data]); } } } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } if (references.ContainsKey(edge.ToNodeId)) { references[edge.ToNodeId].Add(nodeId); } } } // Remove remaining unreferenced nodes bool removed = false; do { removed = false; foreach (Guid key in delta.EnumerateNodes()) { // There are no references to the key if ((key != newSnapshotId) && (references[key].Count == 0)) { #if DEBUG Debug.WriteLine("Removed unreferenced key " + key); #endif delta.Remove(key); reusedNodes.Remove(key); nodeStates.Remove(key); foreach (Guid otherKey in references.Keys) { references[otherKey].Remove(key); } foreach (var mappingKey in nodeMapping.Keys) { if (nodeMapping[mappingKey] == key) { nodeMapping.Remove(mappingKey); break; } } removed = true; break; } } }while (removed); // Isolated provider nodes have been corrupted, perform clear changeSet.Nodes.Clear(); changeSet.NodeStates.Clear(); return(delta); }
/// <summary> /// Creates a changeset from isolated changes /// </summary> /// <param name="baseSnapshotId">Base snapshot of the change set</param> /// <returns>Change set object</returns> private AppendableChangeSet <Guid, object, EdgeData> CreateAppendableChangeSet(Guid baseSnapshotId, Guid newSnapshotId, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { Dictionary <Guid, Guid> nodeMapping = new Dictionary <Guid, Guid>(); // Mapping old->new node id Dictionary <Guid, NodeState> nodeStates = new Dictionary <Guid, NodeState>(); Hashtable reusedNodes = new Hashtable(); var tree = CreateAppendableChangeSetTree(baseSnapshotId, newSnapshotId, new MemoryStorageUnsafe <Guid, object>(), nodeMapping, nodeStates, changeSet, reusedNodes); return(new AppendableChangeSet <Guid, object, EdgeData>(baseSnapshotId, newSnapshotId, tree, nodeMapping, nodeStates, reusedNodes)); }
/// <summary> /// Creates a changeset from isolated changes /// </summary> /// <param name="baseSnapshotId">Base snapshot of the change set</param> /// <returns>Change set object</returns> private AppendableChangeSet<Guid, object, EdgeData> CreateAppendableChangeSet(Guid baseSnapshotId, Guid newSnapshotId, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { Dictionary<Guid, Guid> nodeMapping = new Dictionary<Guid, Guid>(); // Mapping old->new node id Dictionary<Guid, NodeState> nodeStates = new Dictionary<Guid, NodeState>(); Hashtable reusedNodes = new Hashtable(); var tree = CreateAppendableChangeSetTree(baseSnapshotId, newSnapshotId, new MemoryStorageUnsafe<Guid, object>(), nodeMapping, nodeStates, changeSet, reusedNodes); return new AppendableChangeSet<Guid, object, EdgeData>(baseSnapshotId, newSnapshotId, tree, nodeMapping, nodeStates, reusedNodes); }
/// <summary> /// Creates new delta tree describing the new snapshot /// </summary> /// <param name="baseSnapshotId">Base snapshot of the change set</param> /// <param name="newSnapshotId">New snapshot of the change set</param> /// <returns>Delta tree nodes</returns> private INodeProvider<Guid, object, EdgeData> CreateAppendableChangeSetTree(Guid baseSnapshotId, Guid newSnapshotId, IKeyValueStorage<Guid, object> storage, Dictionary<Guid, Guid> nodeMapping, Dictionary<Guid, NodeState> nodeStates, IsolatedChangeSet<Guid, object, EdgeData> changeSet, Hashtable reusedNodes) { DirectNodeProviderUnsafe<Guid, object, EdgeData> delta = new DirectNodeProviderUnsafe<Guid, object, EdgeData>(storage, storage is IForceUpdateStorage); // Create snapshot in delta nodeMapping.Add(baseSnapshotId, newSnapshotId); var snapshotNode = new Node<Guid, object, EdgeData>(NodeType.Snapshot, null); snapshotNode.Previous = baseSnapshotId; // Link to root object snapshotNode.AddEdge(new Edge<Guid, EdgeData>(snapshotsService.GetRootObjectId(baseSnapshotId), new EdgeData(EdgeType.RootObject, null))); // Set node to provider delta.SetNode(newSnapshotId, snapshotNode); // Add all changes and parent nodes foreach (Guid nodeId in changeSet.Nodes.EnumerateNodes()) { AddNodeToAppendableTreeRecursive(delta, nodeId, nodeMapping, nodeStates, changeSet); } // Prepare list of unreferenced node IDs Dictionary<Guid, Collection<Guid>> references = new Dictionary<Guid, Collection<Guid>>(); // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { references.Add(nodeId, new Collection<Guid>()); } // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { var node = delta.GetNode(nodeId, NodeAccess.ReadWrite); foreach (var edge in node.Edges.Values) { // Permanent edges should not be touched if (!Utils.IsPermanentEdge(edge)) { // Reroute based on node mapping if (nodeMapping.ContainsKey(edge.ToNodeId)) { edge.ToNodeId = nodeMapping[edge.ToNodeId]; } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } // Change edge data if it points to changed node if (edge.Data.Data != null && edge.Data.Data is Guid) { if (nodeMapping.ContainsKey((Guid)edge.Data.Data)) { edge.Data = new EdgeData(edge.Data.Semantic, nodeMapping[(Guid)edge.Data.Data]); } } } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } if (references.ContainsKey(edge.ToNodeId)) { references[edge.ToNodeId].Add(nodeId); } } } // Remove remaining unreferenced nodes bool removed = false; do { removed = false; foreach (Guid key in delta.EnumerateNodes()) { // There are no references to the key if ((key!=newSnapshotId) && (references[key].Count == 0)) { #if DEBUG Debug.WriteLine("Removed unreferenced key " + key); #endif delta.Remove(key); reusedNodes.Remove(key); nodeStates.Remove(key); foreach (Guid otherKey in references.Keys) { references[otherKey].Remove(key); } foreach (var mappingKey in nodeMapping.Keys) { if (nodeMapping[mappingKey] == key) { nodeMapping.Remove(mappingKey); break; } } removed = true; break; } } } while (removed); // Isolated provider nodes have been corrupted, perform clear changeSet.Nodes.Clear(); changeSet.NodeStates.Clear(); return delta; }
/// <summary> /// Performs a commit /// </summary> /// <param name="workspaceId">Workspace ID</param> /// <param name="changeSet">Changes to commit</param> /// <returns>Changes which were commited</returns> public CommitResult<Guid> Commit(Guid workspaceId, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { bool isSnapshotIsolation = workspaceStateProvider.WorkspaceIsolationLevel(workspaceId) == IsolationLevel.Snapshot; if (isSnapshotIsolation) { workspaceExclusiveLockProvider.EnterLockExclusive(); } try { lock (commitSync) { if (!workspaceStateProvider.IsWorkspaceExpired(workspaceId)) { var result = commitDataService.AcceptCommit(changeSet); workspaceStateProvider.UpdateWorspace(workspaceId, result.ResultSnapshotId); subscriptionManagerService.InvokeEvents(workspaceId, result); return result; } else { throw new TimeoutException("Workspace timeout has elapsed"); } } } finally { if (isSnapshotIsolation) { workspaceExclusiveLockProvider.ExitLockExclusive(); } } }
/// <summary> /// Returns node from the nodes list. If node does not exist in the nodes list node from the changeSet is returned. /// </summary> /// <param name="nodeId"></param> /// <param name="changeSet"></param> /// <returns></returns> private Node<Guid, object, EdgeData> GetNode(Guid nodeId, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { var childNode = nodes.GetNode(nodeId, NodeAccess.ReadWrite); if (childNode != null) { return childNode; } return changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); }
/// <summary> /// Method is used for getting data from object to IsolatedChangeSet. /// Object containing dictionaries that contain data. If object is not /// formated in right way, this method is throwing exception. /// </summary> /// <param name="objectForParsing">object that is containg data that is commited from client</param> /// <returns>IsolatedChangeSet that is created from objectForParsing</returns> public static IsolatedChangeSet <Guid, object, EdgeData> Parse(Object objectForParsing) { Guid sourceSnapshotId = Guid.Empty; if (objectForParsing == null) { throw new Exception("Object is not valid!"); } Dictionary <Guid, NodeState> nodeState = null; DirectNodeProviderSafe <Guid, object, EdgeData> nodes = null; if (objectForParsing is Dictionary <String, Object> ) { Dictionary <String, Object> dictFromObject = (Dictionary <String, Object>)objectForParsing; //finding sourceSnapshotId property from dictionary if (dictFromObject.ContainsKey(SOURCE_SNAPSHOT_ID)) { Object objectSourceId = dictFromObject[SOURCE_SNAPSHOT_ID]; if (objectSourceId is Dictionary <String, Object> ) { Dictionary <String, Object> dSourceId = objectSourceId as Dictionary <String, Object>; if (dSourceId != null) { Object sSourceId = dSourceId[VALUE]; //checking if object is string. If it is then this is guid if (sSourceId is String) { sourceSnapshotId = new Guid(sSourceId as String); } else { //sSourceId is not string so this is not right type and object that is being //parsed is not formated as he should. throw new Exception("Field SourceSnapshotId is not in right format!"); } } else { throw new Exception("Field SourceSnapshotId is not in right format!"); } } } else { //case when SourceSnapshotId is missing in objectForParsing throw new Exception("There is not field SourceSnapshotId!"); } nodes = ParseNodes(dictFromObject); nodeState = ParseNodeStates(dictFromObject); } else { throw new Exception("Object is not valid!"); } //in case some data is missing here we will throw exception if (sourceSnapshotId == null || nodes == null || nodeState == null) { throw new ArgumentNullException(); } // creating IsolatedChangeSet object with parsed data IsolatedChangeSet <Guid, object, EdgeData> result = new IsolatedChangeSet <Guid, object, EdgeData>(sourceSnapshotId, nodes, nodeState); return(result); }
/// <summary> /// For the given nod, goes through all the child nodes and adds that node to the parent node list of the child nodes. /// </summary> /// <param name="newId"></param> /// <param name="parentNode"></param> /// <param name="nodeState"></param> /// <param name="delta">Node provider which contains nodes which will be saved at the end of the commit process</param> private void UpdateParentNodes(Guid newId, Node<Guid, object, EdgeData> parentNode, NodeState nodeState, DirectNodeProviderUnsafe<Guid, object, EdgeData> delta, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { foreach (Edge<Guid, EdgeData> edge in parentNode.Edges.Values) { if ((edge.Data as EdgeData).Semantic == EdgeType.Property && (edge.Data.Flags & EdgeFlags.StoreParentNodes) == EdgeFlags.StoreParentNodes) { var childNode = GetNode(edge.ToNodeId, changeSet); if (childNode != null) { switch (childNode.NodeType) { case NodeType.Object: UpdateParentNode(newId, nodeState, delta, edge, childNode, parentNode); break; case NodeType.Collection: case NodeType.Dictionary: foreach (Edge<Guid, EdgeData> collectionEdge in childNode.Edges.Values) { if (collectionEdge.Data.Semantic.Equals(EdgeType.ListItem)) { UpdateParentNode(newId, nodeState, delta, collectionEdge, GetNode(collectionEdge.ToNodeId, changeSet), parentNode); } } break; default: throw new NotImplementedException("NodeType=" + childNode.NodeType); } } } } }
public CommitResult<Guid> Commit(Guid workspaceId, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { throw new NotImplementedException(); }
/// <summary> /// Method is used for getting data from object to IsolatedChangeSet. /// Object containing dictionaries that contain data. If object is not /// formated in right way, this method is throwing exception. /// </summary> /// <param name="objectForParsing">object that is containg data that is commited from client</param> /// <returns>IsolatedChangeSet that is created from objectForParsing</returns> public static IsolatedChangeSet<Guid, object, EdgeData> Parse(Object objectForParsing) { Guid sourceSnapshotId = Guid.Empty; if (objectForParsing == null) throw new Exception("Object is not valid!"); Dictionary<Guid, NodeState> nodeState = null; DirectNodeProviderSafe<Guid, object, EdgeData> nodes = null; if (objectForParsing is Dictionary<String, Object>) { Dictionary<String, Object> dictFromObject = (Dictionary<String, Object>)objectForParsing; //finding sourceSnapshotId property from dictionary if (dictFromObject.ContainsKey(SOURCE_SNAPSHOT_ID)) { Object objectSourceId = dictFromObject[SOURCE_SNAPSHOT_ID]; if (objectSourceId is Dictionary<String, Object>) { Dictionary<String, Object> dSourceId = objectSourceId as Dictionary<String, Object>; if (dSourceId != null) { Object sSourceId = dSourceId[VALUE]; //checking if object is string. If it is then this is guid if (sSourceId is String) { sourceSnapshotId = new Guid(sSourceId as String); } else { //sSourceId is not string so this is not right type and object that is being //parsed is not formated as he should. throw new Exception("Field SourceSnapshotId is not in right format!"); } } else { throw new Exception("Field SourceSnapshotId is not in right format!"); } } } else { //case when SourceSnapshotId is missing in objectForParsing throw new Exception("There is not field SourceSnapshotId!"); } nodes = ParseNodes(dictFromObject); nodeState = ParseNodeStates(dictFromObject); } else throw new Exception("Object is not valid!"); //in case some data is missing here we will throw exception if (sourceSnapshotId == null || nodes == null || nodeState == null) { throw new ArgumentNullException(); } // creating IsolatedChangeSet object with parsed data IsolatedChangeSet<Guid, object, EdgeData> result = new IsolatedChangeSet<Guid, object, EdgeData>(sourceSnapshotId, nodes, nodeState); return result; }
private AppendableChangeSet <Guid, object, EdgeData> CreateMergedChangeSet(Guid latestSnapshot, Hashtable subTree, IsolatedChangeSet <Guid, object, EdgeData> changeSet, Dictionary <Guid, Guid> intermediateChanges) { #if DEBUG Debug.WriteLine("Merging over intermediate changes"); #endif // We create node provider which will host changes to the last snapshot made by the merge process var isolatedNodes = new DirectNodeProviderUnsafe <Guid, object, EdgeData>(new MemoryStorageUnsafe <Guid, object>(), false); // Perform merging on the current thread IsolatedNodeProvider destinationProvider = new IsolatedNodeProvider(nodes, isolatedNodes, Thread.CurrentThread); IsolatedNodeProvider sourceProvider = new IsolatedNodeProvider(nodes, changeSet.Nodes, Thread.CurrentThread); // Make changes from incoming changes within a subtree MergeRecursive(latestSnapshot, new RecursiveResolutionParameters(subTree, destinationProvider, sourceProvider, changeSet, intermediateChanges, new Hashtable())); // We create an appendable change set from changes made to last snapshot, defining a new snapshot return(CreateAppendableChangeSet(latestSnapshot, Guid.NewGuid(), destinationProvider.GetChanges(latestSnapshot))); }