public override System.Collections.ObjectModel.ReadOnlyCollection <LinkChangeGroup> GenerateNextLinkDeltaSlice( LinkService linkService, int maxDeltaSliceSize) { try { var linkChangeGroups = new List <LinkChangeGroup>(); if (null == ExtractLinkChangeActionsCallback) { return(linkChangeGroups.AsReadOnly()); } // load main Highwater Mark m_hwmLink.Reload(); DateTime hwmLinkValue = m_hwmLink.Value; // search back 60 seconds to deal with potential WIT race condition if (!hwmLinkValue.Equals(default(DateTime))) { hwmLinkValue = hwmLinkValue.AddSeconds(-60); } string hwmLinkValueStr = hwmLinkValue.ToString(CultureInfo.InvariantCulture); // load Work Items for extracting links string sourceId = m_migrationSource.UniqueId; string storeName = m_migrationSource.WorkItemStore.StoreName; // Get items based on primary Highwater Mark TraceManager.TraceInformation(TfsWITAdapterResources.GettingModifiedItems, sourceId, storeName); IEnumerable <TfsMigrationWorkItem> items = m_migrationSource.WorkItemStore.GetItems(ref hwmLinkValueStr); TraceManager.TraceInformation(TfsWITAdapterResources.ReceivedModifiedItems, sourceId, storeName); // Record the updated HWM value DateTime wiqlExecutionTime = Convert.ToDateTime(hwmLinkValueStr, CultureInfo.InvariantCulture); // store to be used to analyze deleted links WorkItemLinkStore store = new WorkItemLinkStore(new Guid(sourceId)); // extract links DateTime lastWorkITemUpdateTime = DateTime.MinValue; var inMaxDeltaSliceSize = maxDeltaSliceSize; foreach (TfsMigrationWorkItem tfsMigrationWorkItem in items) { if (tfsMigrationWorkItem.WorkItem == null) { continue; } TraceManager.TraceInformation("Generating linking delta for Work Item: {0}", tfsMigrationWorkItem.WorkItem.Id.ToString()); var detectedLinkChangeGroups = new List <LinkChangeGroup>(); ExtractLinkChangeActionsCallback(tfsMigrationWorkItem, detectedLinkChangeGroups, store); if (detectedLinkChangeGroups.Count == 0) { TraceManager.TraceInformation("Number of links: {0}", 0); continue; } Dictionary <string, LinkChangeGroup> perWorkItemConsolidatedLinkChangeGroup = new Dictionary <string, LinkChangeGroup>(); for (int i = 0; i < detectedLinkChangeGroups.Count; ++i) { foreach (LinkChangeAction action in detectedLinkChangeGroups[i].Actions) { if (!perWorkItemConsolidatedLinkChangeGroup.ContainsKey(action.Link.SourceArtifact.Uri)) { var linkChangeGroup = new LinkChangeGroup( action.Link.SourceArtifactId, LinkChangeGroup.LinkChangeGroupStatus.Created, false); perWorkItemConsolidatedLinkChangeGroup.Add(action.Link.SourceArtifact.Uri, linkChangeGroup); } perWorkItemConsolidatedLinkChangeGroup[action.Link.SourceArtifact.Uri].AddChangeAction(action); } } // always make sure that the currently analyzed work item has a link change group to represent it // even though the group can be empty if (!perWorkItemConsolidatedLinkChangeGroup.ContainsKey(tfsMigrationWorkItem.Uri)) { perWorkItemConsolidatedLinkChangeGroup.Add( tfsMigrationWorkItem.Uri, new LinkChangeGroup(TfsWorkItemHandler.IdFromUri(tfsMigrationWorkItem.Uri), LinkChangeGroup.LinkChangeGroupStatus.Created, false)); } foreach (var workItemLinkGroup in perWorkItemConsolidatedLinkChangeGroup) { string workItemIdStr = TfsWorkItemHandler.IdFromUri(workItemLinkGroup.Key); TraceManager.TraceInformation("Detected {0} links for Work Item '{1}'", workItemLinkGroup.Value.Actions.Count, workItemIdStr); if (workItemLinkGroup.Key.Equals(tfsMigrationWorkItem.Uri, StringComparison.OrdinalIgnoreCase)) { // VERY IMPORTANT: use the RelatedArtifactsStore to detect link deletion store.UpdatePerItemLinkChangeGroupsByCheckingRelatedItemRecords( workItemLinkGroup.Key, workItemLinkGroup.Value, this); } else { store.UpdatePerItemLinkChangeGroupsByCheckingRelatedItemRecordsWithoutImplicitDelete( workItemLinkGroup.Key, workItemLinkGroup.Value, this); } if (workItemLinkGroup.Value.Actions.Count > 0) { linkChangeGroups.Add(workItemLinkGroup.Value); } maxDeltaSliceSize -= workItemLinkGroup.Value.Actions.Count; if (maxDeltaSliceSize <= 0) { // size limit reached - persist groups to DB linkService.AddChangeGroups(linkChangeGroups); linkChangeGroups.Clear(); maxDeltaSliceSize = inMaxDeltaSliceSize; } } DateTime lastRevChangedDate = tfsMigrationWorkItem.WorkItem.ChangedDate; if (lastWorkITemUpdateTime.CompareTo(lastRevChangedDate) <= 0) { lastWorkITemUpdateTime = lastRevChangedDate; } } // persist remaining groups to DB linkService.AddChangeGroups(linkChangeGroups); // clean up the returned link change group collection // when the caller (toolkit) receives an empty collection, it understands there is no more // delta to generate for the moment, and proceeds to next phase linkChangeGroups.Clear(); // update primary Highwater Mark //m_hwmLink.Update(newHwmLinkValue); string newHwmValueStr = hwmLinkValueStr; if (lastWorkITemUpdateTime.Equals(DateTime.MinValue)) { // no changes in this sync cycle, record the wiql query execution time m_hwmLink.Update(wiqlExecutionTime); } else { // hwm is recorded in UTC, so does the WIQL query asof time lastWorkITemUpdateTime = lastWorkITemUpdateTime.ToUniversalTime(); if (lastWorkITemUpdateTime.CompareTo(wiqlExecutionTime) <= 0) { // last work item rev time is earlier than wiql query execution time, use it as hwm m_hwmLink.Update(lastWorkITemUpdateTime); newHwmValueStr = lastWorkITemUpdateTime.ToString(); } else { m_hwmLink.Update(wiqlExecutionTime); } } TraceManager.TraceInformation("Persisted WIT linking HWM: {0}", Toolkit.Constants.HwmDeltaLink); TraceManager.TraceInformation(TfsWITAdapterResources.UpdatedHighWatermark, newHwmValueStr); return(linkChangeGroups.AsReadOnly()); } catch (Exception exception) { MigrationConflict genericeConflict = WitGeneralConflictType.CreateConflict(exception); var conflictManager = m_conflictManager.GetService(typeof(ConflictManager)) as ConflictManager; Debug.Assert(null != conflictManager); List <MigrationAction> resolutionActions; ConflictResolutionResult resolveRslt = conflictManager.TryResolveNewConflict(conflictManager.SourceId, genericeConflict, out resolutionActions); Debug.Assert(!resolveRslt.Resolved); return(new List <LinkChangeGroup>().AsReadOnly()); } }
private bool SubmitBatchedAddOrDeleteLinkChanges( List <XmlDocument> updateDocuments, Dictionary <int, LinkChangeAction> updateDocIndexToLinkChangeActionMap, ITranslationService translationService, ConfigurationService configService, ConflictManager conflictManager) { bool succeeded = true; UpdateResult[] results = TfsBatchUpdateHelper.Submit(Core, WorkItemServer, updateDocuments.ToArray()); if (results.Length != updateDocuments.Count) { throw new SynchronizationEngineException("Wrong number of link update results."); } // Collect list of successful LinkChangeActions (for LinkTypes with GetsActionsFromLinkChangeHistory true) to pass to SetServerLinkChangeIds() List <LinkChangeAction> actionsNeedingServerLinkIdSet = new List <LinkChangeAction>(); for (int i = 0; i < results.Length; ++i) { if (results[i] == null) { continue; } UpdateResult rslt = results[i]; if (rslt.Exception != null) { MigrationConflict conflict = null; ConflictResolutionResult resolutionRslt = null; List <MigrationAction> actions; bool createWitGeneralConflict = false; System.Web.Services.Protocols.SoapException soapException = rslt.Exception as System.Web.Services.Protocols.SoapException; if (soapException != null) { int?tfsErrorNumber = GetTfsErrorNumberFromSoapException(soapException); if (tfsErrorNumber.HasValue) { LinkChangeAction linkChangeAction = updateDocIndexToLinkChangeActionMap[i]; switch (tfsErrorNumber) { case TfsConstants.TfsError_AddLink_LinkExists: case TfsConstants.TfsError_DeleteLink_LinkNotFound: // it is ok to eat these exception and skip the action // mark the change action completed so it is not retried later linkChangeAction.Status = LinkChangeAction.LinkChangeActionStatus.Skipped; if (tfsErrorNumber == TfsConstants.TfsError_AddLink_LinkExists) { TraceManager.TraceInformation("Tried to add a link that already exists so skipping it: " + GetLinkChangeActionDescription(linkChangeAction)); } else { TraceManager.TraceInformation("Tried to delete a link that does not exist so skipping it: " + GetLinkChangeActionDescription(linkChangeAction)); } if (soapException.Detail != null) { TraceManager.TraceVerbose("SoapException.Detail.InnerXml for ignored exception: " + soapException.Detail.InnerXml); } break; case TfsConstants.TfsError_AddLink_TooManyParents: if (linkChangeAction.Group.IsForcedSync) { if (DeleteExistingParentLinkToForceSyncAddLink(linkChangeAction, updateDocuments[i])) { break; } } conflict = TFSMulitpleParentLinkConflictType.CreateConflict( updateDocIndexToLinkChangeActionMap[i], rslt.Exception); resolutionRslt = conflictManager.TryResolveNewConflict(conflictManager.SourceId, conflict, out actions); if (!resolutionRslt.Resolved) { updateDocIndexToLinkChangeActionMap[i].IsConflicted = true; succeeded = false; } break; case TfsConstants.TfsError_AddLink_Circular: case TfsConstants.TfsError_AddLink_ChildIsAncestor: ILinkProvider linkProvider = ServiceContainer.GetService(typeof(ILinkProvider)) as ILinkProvider; Debug.Assert(null != linkProvider, "linkProvider is NULL"); LinkChangeAction conflictedAction = updateDocIndexToLinkChangeActionMap[i]; NonCyclicReferenceClosure linkRefClosure = linkProvider.CreateNonCyclicLinkReferenceClosure(conflictedAction.Link.LinkType, conflictedAction.Link.SourceArtifact); conflict = TFSCyclicLinkConflictType.CreateConflict(conflictedAction, rslt.Exception, linkRefClosure); resolutionRslt = conflictManager.TryResolveNewConflict(conflictManager.SourceId, conflict, out actions); if (!resolutionRslt.Resolved) { updateDocIndexToLinkChangeActionMap[i].IsConflicted = true; succeeded = false; } break; case TfsConstants.TfsError_LinkAuthorizationFailedLinkLocked: conflict = TFSModifyLockedWorkItemLinkConflictType.CreateConflict( updateDocIndexToLinkChangeActionMap[i], rslt.Exception); resolutionRslt = conflictManager.TryResolveNewConflict(conflictManager.SourceId, conflict, out actions); if (!resolutionRslt.Resolved) { updateDocIndexToLinkChangeActionMap[i].IsConflicted = true; } // returning "not succeeded" so that the caller keeps this change group in "ReadyForMigration" status succeeded = false; break; case TfsConstants.TfsError_LinkAuthorizationFailed: case TfsConstants.TfsError_LinkAuthorizationFailedNotServiceAccount: conflict = TFSLinkAccessViolationConflictType.CreateConflict( updateDocIndexToLinkChangeActionMap[i], rslt.Exception); resolutionRslt = conflictManager.TryResolveNewConflict(conflictManager.SourceId, conflict, out actions); if (!resolutionRslt.Resolved) { updateDocIndexToLinkChangeActionMap[i].IsConflicted = true; } // returning "not succeeded" so that the caller keeps this change group in "ReadyForMigration" status succeeded = false; break; default: // TFS error doesn't match any that we explicitly handle TraceManager.TraceError("SubmitBatchedAddOrDeleteLinkChanges:TFS error number in SoapException not explictly handled: {0}", tfsErrorNumber); createWitGeneralConflict = true; break; } } else { TraceManager.TraceError("SubmitBatchedAddOrDeleteLinkChanges: Unable to get TFS error number from SoapException: {0}", soapException.ToString()); createWitGeneralConflict = true; } } else // Exception is not SoapException { TraceManager.TraceError("SubmitBatchedAddOrDeleteLinkChanges: Exception returned is not SoapException: {0}", rslt.Exception.ToString()); createWitGeneralConflict = true; } if (createWitGeneralConflict) { conflict = WitGeneralConflictType.CreateConflict(rslt.Exception); resolutionRslt = conflictManager.TryResolveNewConflict(conflictManager.SourceId, conflict, out actions); if (!resolutionRslt.Resolved) { updateDocIndexToLinkChangeActionMap[i].IsConflicted = true; succeeded = false; } } } else // rslt.Exception == null { LinkChangeAction successfulAction = updateDocIndexToLinkChangeActionMap[i]; MarkLinkChangeActionCompleted(successfulAction); TraceManager.TraceVerbose("Successful " + GetLinkChangeActionDescription(successfulAction)); List <LinkChangeAction> updatedActions = new List <LinkChangeAction>(); updatedActions.Add(successfulAction); if (successfulAction.Link.LinkType.GetsActionsFromLinkChangeHistory) { actionsNeedingServerLinkIdSet.Add(successfulAction); } UpdateLinkConversionHistory(configService, translationService, rslt, updatedActions); } } SetServerLinkChangeIds(actionsNeedingServerLinkIdSet); return(succeeded); }