コード例 #1
0
        public override int Prepare(StageConfiguration configuration)
        {
            mapping = (WorkItemsStageConfiguration)configuration;
            Debug.Assert(mapping != null);

            sourceWIStore = sourceConn.Collection.GetService <WorkItemStore>();
            if (mapping.Mode.HasFlag(WorkItemsStageConfiguration.Modes.BypassWorkItemStoreRules))
            {
                eventSink.BypassingRulesOnDestinationWorkItemStore(destConn);
                // this will turn off validation!
                destWIStore = new WorkItemStore(destConn.Collection, WorkItemStoreFlags.BypassRules);
            }
            else
            {
                destWIStore = destConn.Collection.GetService <WorkItemStore>();
            }//if

            mapping.SetDefaults(sourceConn, sourceWIStore, destConn, destWIStore);

            eventSink.DumpMapping(mapping);

            checker = new WorkItemsStageConfigurationChecker(sourceWIStore, sourceConn.ProjectName, destWIStore, destConn.ProjectName, eventSink);
            checker.AgnosticCheck(mapping);
            if (!checker.Passed)
            {
                // abort
                return(checker.ErrorCount);
            }

            return(0);
        }
コード例 #2
0
        private List <WorkItem> SaveWorkItems(WorkItemsStageConfiguration mapping, WitMappingIndex index, WorkItemStore destWIStore, List <WorkItem> changedWorkItems, bool testOnly)
        {
            var failedWorkItems = new List <WorkItem>();

            if (testOnly)
            {
                eventSink.SavingSkipped();
            }
            else
            {
                var errors = destWIStore.BatchSave(changedWorkItems.ToArray(), SaveFlags.MergeAll);
                failedWorkItems = ExamineSaveErrors(errors, index);
            }//if

            var validWorkItems = changedWorkItems.Except(failedWorkItems);

            // some succeeded: their Ids could be changed, so refresh index
            if (!testOnly)
            {
                UpdateIndex(index, validWorkItems, mapping);
                foreach (var item in validWorkItems)
                {
                    this.ChangeLog.AddEntry(
                        new WorkItemChangeEntry(
                            index.GetSourceIdFromTargetId(item.Id),
                            item.Id,
                            item.IsNew ? WorkItemChangeEntry.Change.New : WorkItemChangeEntry.Change.Update));
                } //for
            }     //if

            return(validWorkItems.ToList());
        }
コード例 #3
0
        private void SaveWorkItems3Passes(WorkItemsStageConfiguration mapping, WitMappingIndex index, bool testOnly, WorkItemStore destWIStore, List <WorkItem> newWorkItems, List <WorkItem> updatedWorkItems, List <WorkItem> validWorkItems)
        {
            eventSink.SaveFirstPassSavingNewWorkItems(newWorkItems);
            //HACK: force all new workitems to the Initial state
            var realStates = new Dictionary <WorkItem, string>();

            newWorkItems.ForEach(w =>
            {
                realStates.Add(w, w.State);
                w.State = GetInitialState(w);
            });
            validWorkItems.AddRange(SaveWorkItems(mapping, index, destWIStore, newWorkItems, testOnly));

            eventSink.SaveSecondPassUpdatingNewWorkItemsState(newWorkItems);
            // and now update the no-more-new WI with the real state
            newWorkItems.ForEach(w =>
            {
                w.State = realStates[w];
            });
            validWorkItems.AddRange(SaveWorkItems(mapping, index, destWIStore, newWorkItems, testOnly));

            eventSink.SaveThirdPassSavingUpdatedWorkItems(updatedWorkItems);
            // existing WI do not need tricks
            validWorkItems.AddRange(SaveWorkItems(mapping, index, destWIStore, updatedWorkItems, testOnly));
        }
コード例 #4
0
 public void FixNulls()
 {
     SourceConnection        = SourceConnection ?? new ConnectionInfo();
     DestinationConnection   = DestinationConnection ?? new ConnectionInfo();
     PipelineStages          = PipelineStages ?? new List <string>();
     AreasAndIterationsStage = AreasAndIterationsStage ?? new AreasAndIterationsStageConfiguration();
     GlobalListsStage        = GlobalListsStage ?? new GlobalListsStageConfiguration();
     WorkItemsStage          = WorkItemsStage ?? new WorkItemsStageConfiguration();
 }
コード例 #5
0
 internal SyncContext(TfsConnection sourceConnection, WorkItemStore sourceWIStore, string sourceProjectName, WorkItemStore destWIStore, string destProjectName, WorkItemsStageConfiguration mapping, WitMappingIndex index, IEngineEvents eventSink)
 {
     this.sourceConnection  = sourceConnection;
     this.sourceWIStore     = sourceWIStore;
     this.sourceProjectName = sourceProjectName;
     this.destWIStore       = destWIStore;
     this.destProjectName   = destProjectName;
     this.mapping           = mapping;
     this.index             = index;
     this.eventSink         = eventSink;
 }
コード例 #6
0
 internal SyncContext(SyncContext rhs)
 {
     this.sourceConnection  = rhs.sourceConnection;
     this.sourceWIStore     = rhs.sourceWIStore;
     this.sourceProjectName = rhs.sourceProjectName;
     this.destWIStore       = rhs.destWIStore;
     this.destProjectName   = rhs.destProjectName;
     this.mapping           = rhs.mapping;
     this.index             = rhs.index;
     this.eventSink         = rhs.eventSink;
 }
コード例 #7
0
 private void SaveLinks(WorkItemsStageConfiguration mapping, WitMappingIndex index, WorkItemStore destWIStore, IEnumerable <WorkItem> changedWorkItems, bool testOnly)
 {
     if (testOnly)
     {
         eventSink.SavingSkipped();
     }
     else
     {
         var errors = destWIStore.BatchSave(changedWorkItems.ToArray(), SaveFlags.MergeAll);
         ExamineSaveErrors(errors, index);
     }//if
 }
コード例 #8
0
        internal object MapState(FieldMap rule, WorkItemMap map, WorkItemsStageConfiguration mapping, object sourceValue)
        {
            var x = map.FindMappedState(sourceValue.ToString());

            if (x != null)
            {
                return(x.Destination);
            }
            else
            {
                eventSink.NoTargetState(map, sourceValue);
                return(string.Empty);
            }
        }
コード例 #9
0
        public void DumpMapping(WorkItemsStageConfiguration mapping)
        {
            var output = new System.IO.StringWriter();

            output.WriteLine();
            output.WriteLine("# mapping dump start #");
            var serializer = new YamlDotNet.Serialization.Serializer(YamlDotNet.Serialization.SerializationOptions.EmitDefaults, new YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention());

            serializer.Serialize(output, mapping);
            output.WriteLine("# mapping dump  end  #");
            output.Flush();

            this.Verbose("Dumping Mapping");
            base.RawOut(VerboseColor, TraceLevel.Verbose, output.ToString());
        }
コード例 #10
0
        public bool Validate()
        {
            if (this.WorkItemsStage == null)
            {
                // mapping file could be empty
                this.WorkItemsStage = new WorkItemsStageConfiguration();
            }

            foreach (var stageName in this.PipelineStages)
            {
                //TODO
            }
            // TODO more and more

            return(true);
        }
コード例 #11
0
        internal void Check(QueryResult sourceResult, WorkItemsStageConfiguration mapping, QueryResult destResult)
        {
            var workItemMappings = mapping.WorkItemMappings.ToList();

            var sourceWorkItems = sourceResult.WorkItems.Values.ToList();
            var destWorkItems   = destResult.WorkItems.Values.ToList();

            var sourceTypeNames       = sourceWorkItems.ConvertAll(wi => wi.Type.Name).Distinct();
            var mappedSourceTypeNames = workItemMappings.ConvertAll(m => m.SourceType);

            sourceTypeNames.Except(mappedSourceTypeNames).ToList().ForEach(
                t => Log("Missing mapping for source type {0}", t)
                );

            var destTypeNames       = destWorkItems.ConvertAll(wi => wi.Type.Name).Distinct();
            var mappedDestTypeNames = workItemMappings.ConvertAll(m => m.DestinationType);

            destTypeNames.Except(mappedDestTypeNames).ToList().ForEach(
                t => Log("Missing mapping for destination type {0}", t));
        }
コード例 #12
0
        public static T Generate <T>()
            where T : PipelineConfiguration, new()
        {
            var self = new T()
            {
                SourceConnection = new ConnectionInfo()
                {
                    CollectionUrl = "http://localhost:8080/tfs/DefaultCollection",
                    ProjectName   = "yourSourceProject",
                    User          = "******",
                    Password      = "******"
                },
                DestinationConnection = new ConnectionInfo()
                {
                    CollectionUrl = "http://localhost:8080/tfs/DefaultCollection",
                    ProjectName   = "yourTargetProject",
                    User          = "******",
                    Password      = "******"
                },
                PipelineStages = new List <string>()
                {
                    "step1", "step2"
                },
                StopPipelineOnFirstError = true,
                TestOnly      = true,
                Logging       = LoggingLevel.Diagnostic,
                ChangeLogFile = "changes.csv",
                LogFile       = "log.txt",
                // let them say
                AreasAndIterationsStage = AreasAndIterationsStageConfiguration.Generate(),
                GlobalListsStage        = GlobalListsStageConfiguration.Generate(),
                WorkItemsStage          = WorkItemsStageConfiguration.Generate()
            };

            return(self);
        }
コード例 #13
0
        internal object MapIterationPath(FieldMap rule, WorkItemMap map, WorkItemsStageConfiguration mapping, object sourceValue)
        {
            var path = sourceValue.ToString();
            // search suitable mapping
            var x = mapping.FindExactMappedIterationPath(path);

            if (x == null)
            {
                x = mapping.GetDefaultIterationPathMapping();
                if (x == null)
                {
                    eventSink.NoWildcardIterationRule(mapping, sourceValue);
                }
                else
                {
                    eventSink.IterationPathNotFoundUsingWildcardRule(mapping, sourceValue);
                }
            }
            if (x != null)
            {
                if (x.DestinationPath == "*")
                {
                    // replace project name
                    path = this.destProjectName + path.Substring(this.sourceProjectName.Length);
                }
                else if (!string.IsNullOrWhiteSpace(x.DestinationPath))
                {
                    path = x.DestinationPath;
                }
                else
                {
                    path = this.destProjectName;
                }
            }
            return(path);
        }
コード例 #14
0
ファイル: FieldCopier.cs プロジェクト: gitter-badger/WitSync
        public FieldCopier(WorkItemsStageConfiguration mapping, MapperFunctions functions, bool useEditableProperty, WorkItemType sourceType, WorkItemMap map, WorkItemType targetType, IEngineEvents engineEvents)
        {
            engineEvents.TraceRule("Interpreting rules for mapping '{0}' workitems to '{1}'", sourceType.Name, targetType.Name);

            foreach (FieldDefinition fromField in sourceType.FieldDefinitions)
            {
                var rule = map.FindFieldRule(fromField.ReferenceName);
                if (rule == null)
                {
                    // if no rule -> skip field
                    engineEvents.NoRuleFor(sourceType, fromField.ReferenceName);
                    continue;
                }
                string targetFieldName
                    = rule.IsWildcard
                    ? fromField.ReferenceName : rule.Destination;
                if (string.IsNullOrWhiteSpace(rule.Destination))
                {
                    engineEvents.TraceRule("Skip {0}", fromField.ReferenceName);
                    continue;
                }
                if (!targetType.FieldDefinitions.Contains(targetFieldName))
                {
                    engineEvents.TraceRule("Skip {0} (Target field {1} does not exist)", fromField.ReferenceName, targetFieldName);
                    continue;
                }

                var toField = targetType.FieldDefinitions[targetFieldName];

                if (!IsAssignable(useEditableProperty, fromField, toField))
                {
                    engineEvents.TraceRule("Skip {0} (Not assignable to {1})", fromField.ReferenceName, targetFieldName);
                    continue;
                }//if

                // make the proper copier function
                Action <Field, Field> copyAction;

                if (rule.IsWildcard)
                {
                    engineEvents.TraceRule("Copy {0} to {1} (Wildcard)", fromField.ReferenceName, targetFieldName);
                    copyAction = (src, dst) => { dst.Value = src.Value; };
                }
                else if (!string.IsNullOrWhiteSpace(rule.Set))
                {
                    engineEvents.TraceRule("Set {0} to value '{1}'", targetFieldName, rule.Set);
                    copyAction = (src, dst) => {
                        engineEvents.Trace("  *** converting '{0}' to {1}", rule.Set, dst.FieldDefinition.FieldType);
                        SetFieldWithConstant(dst, rule.Set);
                    };
                }
                else if (!string.IsNullOrWhiteSpace(rule.SetIfNull))
                {
                    engineEvents.TraceRule("Set {0} to value '{1}' when source is null or empty", targetFieldName, rule.SetIfNull);
                    copyAction = (src, dst) =>
                    {
                        if (src.Value == null || string.IsNullOrEmpty(src.Value.ToString()))
                        {
                            SetFieldWithConstant(dst, rule.SetIfNull);
                        }
                        else
                        {
                            dst.Value = src.Value;
                        }
                    };
                }
                else if (!string.IsNullOrWhiteSpace(rule.Translate))
                {
                    var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
                    // TODO optimize
                    var translatorMethod = functions.GetType().GetMethod(rule.Translate, flags);
                    if (translatorMethod == null)
                    {
                        engineEvents.TranslatorFunctionNotFoundUsingDefault(rule);
                        // default: no translation
                        engineEvents.TraceRule("Copy {0} to {1} (fallback)", fromField.ReferenceName, targetFieldName);
                        copyAction = (src, dst) => { dst.Value = src.Value; };
                    }
                    else
                    {
                        engineEvents.TraceRule("Translate {0} via {1}", targetFieldName, rule.Translate);
                        copyAction = (src, dst) =>
                        {
                            dst.Value = translatorMethod.Invoke(functions, new object[] { rule, map, mapping, src.Value });
                        };
                    }
                }
                else
                {
                    //engineEvents.InvalidRule(rule);
                    engineEvents.TraceRule("Copy {0} to {1} (Explicit)", fromField.ReferenceName, targetFieldName);
                    // crossing fingers
                    copyAction = (src, dst) => { dst.Value = src.Value; };
                }//if

                tasks.Add(fromField, new CopyTask()
                {
                    SourceFieldName = fromField.ReferenceName,
                    CopyAction      = copyAction,
                    TargetFieldName = targetFieldName
                });
            }//for fields

            // now the Set rules!
            foreach (var rule in map.Fields)
            {
                if (string.IsNullOrWhiteSpace(rule.Source))
                {
                    if (!string.IsNullOrWhiteSpace(rule.Set))
                    {
                        engineEvents.TraceRule("Set {0} to value '{1}'", rule.Destination, rule.Set);
                        unboundTasks.Add(new CopyTask()
                        {
                            SourceFieldName = string.Empty,
                            CopyAction      = (src, dst) => { SetFieldWithConstant(dst, rule.Set); },
                            TargetFieldName = rule.Destination
                        });
                    }
                    else if (!string.IsNullOrWhiteSpace(rule.SetIfNull))
                    {
                        engineEvents.TraceRule("Set {0} to value '{1}' when destination is null or empty", rule.Destination, rule.SetIfNull);
                        unboundTasks.Add(new CopyTask()
                        {
                            SourceFieldName = string.Empty,
                            CopyAction      = (src, dst) =>
                            {
                                if (dst.Value == null || string.IsNullOrEmpty(dst.Value.ToString()))
                                {
                                    SetFieldWithConstant(dst, rule.SetIfNull);
                                }
                            },
                            TargetFieldName = rule.Destination
                        });
                    }
                } //if
            }     //for
        }
コード例 #15
0
        public static WorkItemsStageConfiguration Generate()
        {
            var self = new WorkItemsStageConfiguration()
            {
                SourceQuery      = "source query",
                DestinationQuery = "dest query",
                IndexFile        = "index.xml",
                Mode             = Modes.OpenTargetWorkItem | Modes.UseEditableProperty,
                AreaMap          = new AreaMap[] {
                    new AreaMap()
                    {
                        SourcePath = "srcArea1", DestinationPath = "dstArea1"
                    },
                    new AreaMap()
                    {
                        SourcePath = "srcArea2", DestinationPath = "dstArea2"
                    }
                },
                IterationMap = new IterationMap[] {
                    new IterationMap()
                    {
                        SourcePath = "src", DestinationPath = "dst"
                    },
                    new IterationMap()
                    {
                        SourcePath = "*", DestinationPath = ""
                    }
                },
                WorkItemMappings = new WorkItemMap[] {
                    new WorkItemMap()
                    {
                        SourceType  = "srctype", DestinationType = "desttype",
                        Attachments = WorkItemMap.AttachmentMode.Sync,
                        RollbackValidationErrors = true,
                        IDField = new FieldMap()
                        {
                            Source = "srcID", Destination = "dstID"
                        },
                        StateList = new StateList()
                        {
                            States = new StateMap[] {
                                new StateMap()
                                {
                                    Source = "srcstate1", Destination = "deststate1"
                                },
                                new StateMap()
                                {
                                    Source = "srcstate2", Destination = "deststate2"
                                }
                            }
                        },
                        Fields = new FieldMap[] {
                            new FieldMap()
                            {
                                Source = "src1", Destination = "dst1"
                            },
                            new FieldMap()
                            {
                                Source = "src2", Destination = "dst2", Translate = "tranFunc2"
                            },
                            new FieldMap()
                            {
                                Destination = "dst3", Set = "val3"
                            },
                            new FieldMap()
                            {
                                Source = "src4", Destination = "dst4", SetIfNull = "set4"
                            },
                            new FieldMap()
                            {
                                Source = "*", Destination = "*"
                            },
                            new FieldMap()
                            {
                                Source = "*", Destination = ""
                            },
                        }
                    }
                },
                LinkTypeMap = new LinkTypeMap[] {
                    new LinkTypeMap()
                    {
                        SourceType = "srclnk1", DestinationType = "dstlnk1"
                    },
                    new LinkTypeMap()
                    {
                        SourceType = "srclnk2", DestinationType = "dstlnk2"
                    },
                    new LinkTypeMap()
                    {
                        SourceType = "*", DestinationType = "*"
                    }
                }
            };

            return(self);
        }
コード例 #16
0
        private WitMappingIndex BuildIndex(WorkItemStore destWIStore, IEnumerable <WorkItem> existingTargetWorkItems, WorkItemsStageConfiguration mapping)
        {
            var index = new WitMappingIndex();

            if (mapping.HasIndex)
            {
                if (!System.IO.File.Exists(mapping.IndexFile))
                {
                    //HACK on first run the file cannot exists, so create an empty one
                    index = WitMappingIndex.CreateEmpty(mapping.IndexFile);
                }
                else
                {
                    index = WitMappingIndex.Load(mapping.IndexFile, destWIStore);
                }//if
            }
            else
            {
                index.Clear();
                foreach (var targetWI in existingTargetWorkItems)
                {
                    var originatingFieldMap = mapping.FindIdFieldForTargetWorkItemType(targetWI.Type.Name);
                    var v = targetWI.Fields[originatingFieldMap.Destination].Value;
                    // could be that the destination exists, with no origin (e.g. manual intervention)
                    int originatingId = (int)(v ?? 0);
                    index.Add(originatingId, targetWI);
                } //for
            }     //if

            return(index);
        }
コード例 #17
0
 public void IterationPathNotFoundUsingWildcardRule(WorkItemsStageConfiguration mapping, object sourceValue)
 {
     this.UniqueVerbose("    Iteration path '{0}' not found: using wildcard rule.", sourceValue);
 }
コード例 #18
0
 public void NoWildcardIterationRule(WorkItemsStageConfiguration mapping, object sourceValue)
 {
     this.UniqueWarning("    No wildcard Iteration rule.");
 }
コード例 #19
0
 private void UpdateIndex(WitMappingIndex index, IEnumerable <WorkItem> updatedWorkItems, WorkItemsStageConfiguration mapping)
 {
     if (mapping.HasIndex)
     {
         index.Update(updatedWorkItems);
         index.Save(mapping.IndexFile);
     }
     else
     {
         foreach (var dst in updatedWorkItems)
         {
             var originatingFieldMap = mapping.FindIdFieldForTargetWorkItemType(dst.Type.Name);
             int originatingId       = (int)dst.Fields[originatingFieldMap.Destination].Value;
             index.Update(originatingId, dst);
         }//for
     }
 }
コード例 #20
0
        public override int Execute(StageConfiguration configuration)
        {
            mapping = (WorkItemsStageConfiguration)configuration;

            var sourceRunner = new QueryRunner(sourceWIStore, sourceConn.ProjectName);

            eventSink.ExecutingSourceQuery(mapping.SourceQuery, sourceConn);
            var sourceResult = sourceRunner.RunQuery(mapping.SourceQuery);

            if (sourceResult == null)
            {
                eventSink.SourceQueryNotFound(mapping.SourceQuery);
                return(3);
            }

            var destRunner = new QueryRunner(destWIStore, destConn.ProjectName);

            eventSink.ExecutingDestinationQuery(mapping.DestinationQuery, destConn);
            var destResult = destRunner.RunQuery(mapping.DestinationQuery);

            if (destResult == null)
            {
                eventSink.DestinationQueryNotFound(mapping.DestinationQuery);
                return(4);
            }

            // use query data for more thorough checks
            checker.Check(sourceResult, mapping, destResult);
            if (!checker.Passed)
            {
                // abort
                return(checker.ErrorCount);
            }


            // this needs also connection to target, better after query execution, so we have warm caches
            var index = BuildIndex(destWIStore, destResult.WorkItems.Values, mapping);

            var context = new SyncContext(sourceConn, sourceWIStore, sourceConn.ProjectName, destWIStore, destConn.ProjectName, mapping, index, eventSink);

            var workItemMapper = new WorkItemMapper(context);

            // configure options
            workItemMapper.UseEditableProperty       = mapping.Mode.HasFlag(WorkItemsStageConfiguration.Modes.UseEditableProperty);
            workItemMapper.OpenTargetWorkItem        = mapping.Mode.HasFlag(WorkItemsStageConfiguration.Modes.OpenTargetWorkItem);
            workItemMapper.PartialOpenTargetWorkItem = mapping.Mode.HasFlag(WorkItemsStageConfiguration.Modes.PartialOpenTargetWorkItem);

            List <WorkItem> newWorkItems;
            List <WorkItem> updatedWorkItems;

            workItemMapper.MapWorkItems(sourceResult, destResult, out newWorkItems, out updatedWorkItems);

            // from http://social.msdn.microsoft.com/Forums/vstudio/en-US/0cbc378b-09ad-4899-865d-b418aecb8375/work-item-links-error-message-unexplained
            // "It happens when you add a link when you are creating a new work item. If you add the link after the new work item is saved then it works OK."
            eventSink.SavingWorkItems(newWorkItems, updatedWorkItems);
            var validWorkItems = new List <WorkItem>();

            if (mapping.Mode.HasFlag(WorkItemsStageConfiguration.Modes.CreateThenUpdate))
            {
                // uncommon path
                eventSink.UsingThreePassSavingAlgorithm();
                SaveWorkItems3Passes(mapping, index, configuration.TestOnly, destWIStore, newWorkItems, updatedWorkItems, validWorkItems);
                // multi-pass records the same WI object multiple times
                validWorkItems = validWorkItems.DistinctBy(x => x.Id, null).ToList();
            }
            else
            {
                // normal path
                var changedWorkItems = newWorkItems.Concat(updatedWorkItems).ToList();
                var savedWorkItems   = SaveWorkItems(mapping, index, destWIStore, changedWorkItems, configuration.TestOnly);
                validWorkItems.AddRange(savedWorkItems);
            }//if

            workItemMapper.CleanUp();

            var linkMapper   = new LinkMapper(context);
            var changedLinks = linkMapper.MapLinks(sourceResult.WorkItems.Values, validWorkItems);

            eventSink.SavingLinks(changedLinks, validWorkItems);
            SaveLinks(mapping, index, destWIStore, validWorkItems, configuration.TestOnly);

            return(saveErrors);
        }
コード例 #21
0
        internal void AgnosticCheck(WorkItemsStageConfiguration mapping)
        {
            if (mapping.HasIndex && System.IO.File.Exists(mapping.IndexFile))
            {
                //TODO check indexFile is valid
            }//if

            var workItemMappings = mapping.WorkItemMappings.ToList();

            var allSourceTypes = this.sourceWIStore.Projects[this.sourceProjectName].WorkItemTypes;
            var allDestTypes   = this.destWIStore.Projects[this.destProjectName].WorkItemTypes;

            if (mapping.HasIndex)
            {
                // IDField is wrong
                workItemMappings.Where(
                    m => m.IDField != null
                    )
                .ToList()
                .ForEach(t => Log("IDField cannot be used with IndexFile: found on WorkItem type '{0}' .", t.SourceType));
            }
            else
            {
                workItemMappings
                .Where(m => m.IDField == null || string.IsNullOrWhiteSpace(m.IDField.Destination))
                .ToList()
                .ForEach(t => Log("Invalid ID Field Destination on WorkItem type '{0}'."
                                  , t.DestinationType));
                workItemMappings
                .Where(m => m.IDField == null || string.IsNullOrWhiteSpace(m.IDField.Source))
                .ToList()
                .ForEach(t => Log("Invalid ID Field Source on WorkItem type '{0}'."
                                  , t.SourceType));

                // check that all target types have an originating ID field
                workItemMappings
                .Where(m =>
                       m.IDField != null &&
                       !string.IsNullOrWhiteSpace(m.IDField.Destination) &&
                       !allDestTypes[m.DestinationType].FieldDefinitions.Contains(m.IDField.Destination))
                .ToList()
                .ForEach(t => Log("Destination WorkItem type '{0}' has no '{1}' Field to host source ID."
                                  , t.DestinationType, t.IDField.Destination));
                // check that source ID field match
                workItemMappings
                .Where(m =>
                       m.IDField != null &&
                       !string.IsNullOrWhiteSpace(m.IDField.Source) &&
                       !allSourceTypes[m.SourceType].FieldDefinitions.Contains(m.IDField.Source))
                .ToList()
                .ForEach(t => Log("Source WorkItem type '{0}' has no source '{1}' ID Field."
                                  , t.SourceType, t.IDField.Source));
            }

            // check Rules are valid
            foreach (var wiMapping in workItemMappings)
            {
                //TODO if MapState function then States is mandatory!

                foreach (var fieldRule in wiMapping.Fields)
                {
                    bool isSetRule       = !string.IsNullOrWhiteSpace(fieldRule.Set);
                    bool isSetIfNullRule = !string.IsNullOrWhiteSpace(fieldRule.SetIfNull);

                    // check combo, valid combos are: S+D S+D+T D+S D+Sif
                    if (isSetRule || isSetIfNullRule)
                    {
                        // Set rule
                        if (!string.IsNullOrWhiteSpace(fieldRule.Source))
                        {
                            Log("Invalid Set rule for destination field '{1}/{0}'."
                                , fieldRule.Destination, wiMapping.DestinationType);
                        }
                        if (!string.IsNullOrWhiteSpace(fieldRule.Source))
                        {
                            Log("Invalid Set rule for destination field '{1}/{0}'."
                                , fieldRule.Destination, wiMapping.DestinationType);
                        }
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(fieldRule.Translate))
                        {
                            // Copy rule
                            if (string.IsNullOrWhiteSpace(fieldRule.Source))
                            {
                                Log("Invalid Copy rule for destination field '{1}/{0}'."
                                    , fieldRule.Destination, wiMapping.DestinationType);
                            }
                        }
                        else
                        {
                            // Translate rule
                            if (string.IsNullOrWhiteSpace(fieldRule.Source))
                            {
                                Log("Invalid Translate rule for destination field '{1}/{0}'."
                                    , fieldRule.Destination, wiMapping.DestinationType);
                            }
                        }
                    }//if

                    // check on Translator
                    if (!string.IsNullOrWhiteSpace(fieldRule.Translate))
                    {
                        var bindingFlags     = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
                        var translatorMethod = typeof(MapperFunctions).GetMethod(fieldRule.Translate, bindingFlags);
                        if (translatorMethod == null)
                        {
                            Log("Translator {0} does not exists.", fieldRule.Translate);
                        }
                    }//if

                    // check on Set & SetIfNull
                    if (isSetRule || isSetIfNullRule)
                    {
                        // TODO can fail!
                        var destFieldType = this.destWIStore.FieldDefinitions[fieldRule.Destination].FieldType;

                        string setValue = isSetRule ? fieldRule.Set : fieldRule.SetIfNull;

                        bool parseOk = false;
                        switch (destFieldType)
                        {
                        case FieldType.Boolean:
                            bool _bool;
                            parseOk = bool.TryParse(setValue, out _bool);
                            break;

                        case FieldType.DateTime:
                            DateTime _DateTime;
                            parseOk = DateTime.TryParse(setValue, out _DateTime);
                            break;

                        case FieldType.Double:
                            double _double;
                            parseOk = double.TryParse(setValue, out _double);
                            break;

                        case FieldType.Guid:
                            Guid _Guid;
                            parseOk = Guid.TryParse(setValue, out _Guid);
                            break;

                        case FieldType.History:
                            Log("Destination field '{1}/{0}' has {2} type and cannot be set."
                                , fieldRule.Destination, wiMapping.DestinationType, destFieldType);
                            parseOk = false;
                            break;

                        case FieldType.Html:
                            // string-like
                            parseOk = true;
                            break;

                        case FieldType.Integer:
                            int _int;
                            parseOk = int.TryParse(setValue, out _int);
                            break;

                        case FieldType.Internal:
                            Log("Destination field '{1}/{0}' has {2} type and cannot be set."
                                , fieldRule.Destination, wiMapping.DestinationType, destFieldType);
                            parseOk = false;
                            break;

                        case FieldType.PlainText:
                            // string-like
                            parseOk = true;
                            break;

                        case FieldType.String:
                            parseOk = true;
                            break;

                        case FieldType.TreePath:
                            // string-like
                            parseOk = true;
                            break;

                        default:
                            Log("Destination field '{1}/{0}' has unknown type {2}."
                                , fieldRule.Destination, wiMapping.DestinationType, destFieldType);
                            parseOk = false;
                            break;
                        }//switch
                        if (!parseOk)
                        {
                            Log("Cannot set destination field '{1}/{0}' to '{2}': invalid value."
                                , fieldRule.Destination, wiMapping.DestinationType, setValue);
                        } //if
                    }     //if
                }         //for
            }             //for

            var allSourceLinkTypes = this.sourceWIStore.WorkItemLinkTypes;
            var allDestLinkTypes   = this.destWIStore.WorkItemLinkTypes;

            foreach (var linkMap in mapping.LinkTypeMap)
            {
                if (linkMap.IsWildcard)
                {
                    // * -> * === same name
                    // * -> '' === not mapped
                    if (linkMap.DestinationType != "*" &&
                        !string.IsNullOrWhiteSpace(linkMap.DestinationType))
                    {
                        Log("Invalid Link wildcard rule.");
                    }
                }

                if (!linkMap.IsWildcard)
                {
                    if (!allSourceLinkTypes.Any(st => st.ForwardEnd.Name == linkMap.SourceType))
                    {
                        Log("Source link type '{0}' does not exist.", linkMap.SourceType);
                    }
                    if (!allDestLinkTypes.Any(st => st.ForwardEnd.Name == linkMap.DestinationType))
                    {
                        Log("Destination link type '{0}' does not exist.", linkMap.DestinationType);
                    }
                    // TODO check if mapping is sensible
                }//if
            }

            //TODO more checks, e.g. on fields and functions
        }