Skip to main content

Note: This post is part of a series and you can find the rest of the parts in the series index.

IAnalysisProvider, has a name that is a bit misleading, or was misleading to me because for a long time I thought it did some analysis of the environment as a pre-step and then real work happened elsewhere. The reality is the IAnalysisProvider is the reader part of your adapter, it’s goal is to get data from your system and into a format and/or location that the platform can work with.

image

IServiceProvider

IAnalysisProvider inherits from IServiceProvider which means you need to implement a method for that, GetService, which just returns this object.

object IServiceProvider.GetService(Type serviceType)
{
    return (IServiceProvider)this;
}

Misc Methods

I am not cover every method you need to implement from IAnalysisProvider, because you seldom need to implement them all. For example in my implementation of DetectConflicts is just does some logging:

void IAnalysisProvider.DetectConflicts(ChangeGroup changeGroup)
{
    TraceManager.TraceInformation("WSSVC:AP:DetectConflicts");
}

InitializeServices

The first method you must care about is InitializeServices, this is the first method which is called by the platform and it does five key things in my scenario:

void IAnalysisProvider.InitializeServices(IServiceContainer serviceContainer)
{
    TraceManager.TraceInformation("WSSVC:AP:Initialize");
    this.analysisServiceContainer = serviceContainer;

    supportedContentTypes = new Collection<ContentType>();
    supportedContentTypes.Add(WellKnownContentType.VersionControlledFile);
    supportedContentTypes.Add(WellKnownContentType.VersionControlledFolder);

    SharePointVCChangeActionHandler handler = new SharePointVCChangeActionHandler(this);
    supportedChangeActions = new Dictionary<Guid, ChangeActionHandler>();
    supportedChangeActions.Add(WellKnownChangeActionId.Add, handler.BasicActionHandler);
    supportedChangeActions.Add(WellKnownChangeActionId.Delete, handler.BasicActionHandler);
    supportedChangeActions.Add(WellKnownChangeActionId.Edit, handler.BasicActionHandler);

    configurationService = (ConfigurationService)analysisServiceContainer.GetService(typeof(ConfigurationService));

    highWaterMarkDelta = new HighWaterMark<DateTime>(Constants.HwmDelta);
    highWaterMarkChangeset = new HighWaterMark<int>("LastChangeSet");
    configurationService.RegisterHighWaterMarkWithSession(highWaterMarkDelta);
    configurationService.RegisterHighWaterMarkWithSession(highWaterMarkChangeset);
    changeGroupService = (ChangeGroupService)analysisServiceContainer.GetService(typeof(ChangeGroupService));
    changeGroupService.RegisterDefaultSourceSerilizer(new SharePointVCMigrationItemSerializer());
}
  • The first part is the setting up of what types of content we support (lines 6 to lines 8). You can see above I only care about files and folders.
  • The second part is the setting up of what actions we support for reading (lines 10 to 14), which in this case is Add, Delete and Edit. Delete is a bit of a lie since we do not actually support it but we say we do.
  • The third part is getting the configuration service which is important since we will use it later (line 16).
  • The forth part is getting the HWM, or high watermark information (lines 18 to 21) which I will explain in a moment.
  • Lastly we register the default item serialiser (line 23) so that the platform knows how to convert the items.

Registration

We setup the content types and actions we support and then we need to register those and the way to do that is with RegisterSupportedChangeActions. What I did was to loop over the actions and then loop over the types finally calling RegisterChangeAction. You could do this differently, for example if you only supported some actions on some types:

void IAnalysisProvider.RegisterSupportedChangeActions(ChangeActionRegistrationService contentActionRegistrationService)
{
    TraceManager.TraceInformation("WSSVC:AP:RegisterSupportedChangeActions");
    this.changeActionRegistrationService = contentActionRegistrationService;
    foreach (KeyValuePair<Guid, ChangeActionHandler> supportedChangeAction in supportedChangeActions)
    {
        foreach (ContentType contentType in ((IAnalysisProvider)this).SupportedContentTypes)
        {
            changeActionRegistrationService.RegisterChangeAction(supportedChangeAction.Key, contentType.ReferenceName, supportedChangeAction.Value);
        }
    }
}

ChangeActionHandler

The ChangeActionHandler class is a separate class which is used in the IAnalysisProvider during registration which provides the minimal functionality for figuring out how to register a new action (i.e. add file, update work item etc…).

public abstract class ChangeActionHandlers
{
    protected ChangeActionHandlers(IAnalysisProvider analysisProvider)
    {
    }

    public virtual void BasicActionHandler(MigrationAction action, ChangeGroup group)
    {
        if (action == null)
        {
            throw new ArgumentNullException("action");
        }

        if (group == null)
        {
            throw new ArgumentNullException("group");
        }

        group.CreateAction(action.Action, 
            action.SourceItem, 
            action.FromPath, 
            action.Path, action.Version,
            action.MergeVersionTo,
            action.ItemTypeReferenceName, 
            action.MigrationActionDescription);
    }
}

High Watermark

High watermarks are a very interesting feature of the platform, and they let you store a value in the database for the usage of identification of change groups. One aspect I liked of it’s construction is its use of generics, meaning you can work with the types that make the most sense, so I have Int and DateTime, and as you associate a name to it.

To get the value from the database you called the Reload method, to set the value and save call Update.

highWaterMarkDelta.Reload();
highWaterMarkDelta.Update(deltaTableStartTime);

Conflict Types

I have mentioned conflict types briefly before and I have said that my VC adapter does not have a conflict type which is not 100% true. It does have one, the GenericConflictType, which is base from the platform. Below is the code snippet from the WIT adapter which does have a custom conflict type. The only difference with the VC adapter is that last line does not exist.

public void RegisterConflictTypes(ConflictManager conflictManager)
{
    TraceManager.TraceInformation("WSSWIT:AP:RegisterConflictTypes");
    this.conflictManagerService = (ConflictManager)analysisServiceContainer.GetService(typeof(ConflictManager));
    this.conflictManagerService.RegisterConflictType(new GenericConflictType());
    this.conflictManagerService.RegisterConflictType(new SharePointWITGeneralConflictType(), SyncOrchestrator.ConflictsSyncOrchOptions.Continue);
}

GenerateDeltaTable

The next method to cover, and the second most important is GenerateDeltaTable which is responsible for actually getting the values from the source system. This is done below in two steps first GetSharePointUpdates and second PromoteDeltaToPending.

void IAnalysisProvider.GenerateDeltaTable()
{
    TraceManager.TraceInformation("WSSVC:AP:GenerateDeltaTable");
    highWaterMarkDelta.Reload();
    TraceManager.TraceInformation("\tWSSVC:AP:Initial HighWaterMark {0} ", highWaterMarkDelta.Value);
    deltaTableStartTime = DateTime.Now;
    TraceManager.TraceInformation("\tWSSVC:AP:CutOff {0} ", deltaTableStartTime);
    GetSharePointUpdates();
    highWaterMarkDelta.Update(deltaTableStartTime);
    TraceManager.TraceInformation("\tWSSVC:AP:Updated HighWaterMark {0} ", highWaterMarkDelta.Value);
    changeGroupService.PromoteDeltaToPending();
}

GetSharePointUpdates

This is a huge method and does the heavily lifting and I will skip covering all the boring details of talking to SharePoint and focus covering what you need to do. First you need to identify what is new, this is done using the HWM and comparing the modified date.

// item has been modified since HWM & before deltra table start time
if (item.Modified.CompareTo(highWaterMarkDelta.Value) > 0 && item.Modified.CompareTo(deltaTableStartTime) < 0) 

You also need to figure out if the file is new or an update. In my VC adapter I created a special system called the ProcessLog. This was to cater with a situation caused by SharePoint and won’t apply to other systems. Once you done all of that you can tell the platform about it by creating an action and saving the action. The following code is for VC:

TraceManager.TraceInformation("\tChangeSet:{0} - {1} ({2})", highWaterMarkChangeset.Value, item.Filename, item.AbsoluteURL);
string itemType = item.ItemType.ToWellKnownContentType().ReferenceName;
ChangeGroup cg = CreateChangeGroup(highWaterMarkChangeset.Value, 0);
cg.CreateAction(actionGuid, item, null, item.AbsoluteURL, item.Version, null, itemType, null);
cg.Save();
highWaterMarkChangeset.Update(highWaterMarkChangeset.Value + 1);

and this is the same logic for WIT:

ChangeGroup changeGroup = CreateChangeGroup(highWaterMarkChangeSet.Value, 0);
changeGroup.CreateAction(actionGuid, task, string.Empty, listName, string.Empty, string.Empty,
    WellKnownContentType.WorkItem.ReferenceName, CreateFieldRevisionDescriptionDoc(task));
changeGroup.Save();
highWaterMarkChangeSet.Update(highWaterMarkChangeSet.Value + 1);

Revision Description Doc

While the VC adapter is fairly easy, the downloading is done in the SharePointItem - the WIT adapter doesn’t download anything. What it needs is a special XML file called a revision description document. You are responsible for the generation of this document as part of the creation of the action (you may have noticed that in the sample above).

This is what actually makes the field mapping possible, if you do not understand field mapping you must read Willy-Peter’s post on it. You can see below how I create my document, which is created per SharePoint list item and how I can support all the columns, including custom ones:

private static XmlDocument CreateFieldRevisionDescriptionDoc(SharePointListItem task)
{
    XElement columns = new XElement("Columns",
            new XElement("Column",
                new XAttribute("DisplayName", "Author"),
                new XAttribute("ReferenceName", "Author"),
                new XAttribute("Type", "String"),
                new XElement("Value", task.AuthorId)),
            new XElement("Column",
                new XAttribute("DisplayName", "DisplayName"),
                new XAttribute("ReferenceName", "DisplayName"),
                new XAttribute("Type", "String"),
                new XElement("Value", task.DisplayName)),
            new XElement("Column",
                new XAttribute("DisplayName", "Id"),
                new XAttribute("ReferenceName", "Id"),
                new XAttribute("Type", "String"),
                new XElement("Value", task.Id.ToString())));

    foreach (KeyValuePair<string, object> column in task.Columns)
    {
        columns.Add(new XElement("Column",
            new XAttribute("DisplayName", column.Key),
                new XAttribute("ReferenceName", column.Key),
                new XAttribute("Type", "String"),
                new XElement("Value", column.Value)));
    }

    XElement descriptionDoc = new XElement("WorkItemChanges",
        new XAttribute("Revision", "0"),
        new XAttribute("WorkItemType", "SharePointItem"),
        new XAttribute("Author", task.AuthorId),
        new XAttribute("ChangeDate", task.ModifiedOn.ToString(CultureInfo.CurrentCulture)),
        new XAttribute("WorkItemID", task.Id.ToString()),
        columns);

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(descriptionDoc.ToString());
    return doc;
}