Summary

Class:Xrm.Oss.XTL.Templating.XTLProcessor
Assembly:Xrm.Oss.XTL.Templating
File(s):D:\Entwicklung\Xrm-Templating-Language\src\plugin\Xrm.Oss.XTL.Templating\XTLProcessor.cs
Covered lines:176
Uncovered lines:9
Coverable lines:185
Total lines:291
Line coverage:95.1% (176 of 185)
Covered branches:41
Total branches:48
Branch coverage:85.4% (41 of 48)

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
.ctor()10100%100%2
.ctor(...)10100%100%2
Execute(...)22100%100%6
TriggerUpdateConditionally(...)516100%100%30
HandleCustomAction(...)812892.31%86.67%72
SerializeResult(...)30100%100%12
HandleNonCustomAction(...)61691.3%77.78%42
RetrieveTemplate(...)4890%85.71%20
ValidateConfig(...)4875%71.43%20
CheckExecutionCriteria(...)34100%100%12
GenerateDataSource(...)70100%100%56

File(s)

D:\Entwicklung\Xrm-Templating-Language\src\plugin\Xrm.Oss.XTL.Templating\XTLProcessor.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Net;
 6using System.Runtime.Serialization.Json;
 7using System.Text;
 8using System.Text.RegularExpressions;
 9using System.Threading.Tasks;
 10using Microsoft.Xrm.Sdk;
 11using Microsoft.Xrm.Sdk.Query;
 12using Xrm.Oss.XTL.Interpreter;
 13
 14namespace Xrm.Oss.XTL.Templating
 15{
 16    public class XTLProcessor : IPlugin
 17    {
 18        private ProcessorConfig _config;
 19        private OrganizationConfig _organizationConfig;
 20
 3021        public XTLProcessor(): this("", "") { }
 22
 2623        public XTLProcessor (string unsecure, string secure)
 2624        {
 2625            _config = ProcessorConfig.Parse(unsecure);
 2626            _organizationConfig = OrganizationConfig.Parse(secure);
 2627        }
 28
 29        public void Execute(IServiceProvider serviceProvider)
 2630        {
 2631            var context = serviceProvider.GetService(typeof(IPluginExecutionContext)) as IPluginExecutionContext;
 2632            var crmTracing = serviceProvider.GetService(typeof(ITracingService)) as ITracingService;
 2633            var tracing = new PersistentTracingService(crmTracing);
 2634            var serviceFactory = serviceProvider.GetService(typeof(IOrganizationServiceFactory)) as IOrganizationService
 2635            var service = serviceFactory.CreateOrganizationService(null);
 36
 2637            if (context.InputParameters.ContainsKey("jsonInput"))
 1038            {
 1039                HandleCustomAction(context, tracing, service);
 840            }
 41            else
 1642            {
 1643                HandleNonCustomAction(context, tracing, service);
 1544            }
 2345        }
 46
 47        private void TriggerUpdateConditionally(string newValue, Entity target, ProcessorConfig config, IOrganizationSer
 2148        {
 2149            if (!config.TriggerUpdate)
 1250            {
 1251                return;
 52            }
 53
 954            if (string.IsNullOrEmpty(config.TargetField))
 255            {
 256                throw new InvalidPluginExecutionException("Target field is required when setting the 'triggerUpdate' fla
 57            }
 58
 759            if (config.ForceUpdate || !string.Equals(target.GetAttributeValue<string>(config.TargetField), newValue))
 560            {
 561                var updateObject = new Entity
 562                {
 563                    LogicalName = target.LogicalName,
 564                    Id = target.Id
 565                };
 66
 567                updateObject[config.TargetField] = newValue;
 68
 569                service.Update(updateObject);
 570            }
 1971        }
 72
 73        private void HandleCustomAction(IPluginExecutionContext context, PersistentTracingService tracing, IOrganization
 1074        {
 1075            var config = ProcessorConfig.Parse(context.InputParameters["jsonInput"] as string);
 76
 1077            if (config.Target == null && config.TargetEntity == null)
 178            {
 179                throw new InvalidPluginExecutionException("Target property inside JSON parameters is needed for custom a
 80            }
 81
 82            ColumnSet columnSet;
 83
 984            if (config.TargetColumns != null)
 185            {
 186                columnSet = new ColumnSet(config.TargetColumns);
 187            }
 88            else
 889            {
 890                columnSet = new ColumnSet(true);
 891            }
 92
 93            try
 994            {
 995                var dataSource = config.TargetEntity != null ? config.TargetEntity : service.Retrieve(config.Target.Logi
 96
 997                if (!CheckExecutionCriteria(config, dataSource, service, tracing))
 198                {
 199                    tracing.Trace("Execution criteria not met, aborting");
 100
 1101                    var abortResult = new ProcessingResult
 1102                    {
 1103                        Success = true,
 1104                        Result = config.Template,
 1105                        TraceLog = tracing.TraceLog
 1106                    };
 1107                    context.OutputParameters["jsonOutput"] = SerializeResult(abortResult);
 108
 1109                    return;
 110                }
 111
 8112                var templateText = RetrieveTemplate(config.Template, config.TemplateField, dataSource, service, tracing)
 113
 8114                if (string.IsNullOrEmpty(templateText))
 0115                {
 0116                    tracing.Trace("Template is empty, aborting");
 0117                    return;
 118                }
 119
 8120                var output = TokenMatcher.ProcessTokens(templateText, dataSource, new OrganizationConfig { OrganizationU
 121
 8122                var result = new ProcessingResult
 8123                {
 8124                    Success = true,
 8125                    Result = output,
 8126                    TraceLog = tracing.TraceLog
 8127                };
 8128                context.OutputParameters["jsonOutput"] = SerializeResult(result);
 129
 8130                TriggerUpdateConditionally(output, dataSource, config, service);
 6131            }
 2132            catch (Exception ex)
 2133            {
 2134                var result = new ProcessingResult
 2135                {
 2136                    Success = false,
 2137                    Error = ex.Message,
 2138                    TraceLog = tracing.TraceLog
 2139                };
 2140                context.OutputParameters["jsonOutput"] = SerializeResult(result);
 141
 2142                if (config.ThrowOnCustomActionError)
 1143                {
 1144                    throw;
 145                }
 1146            }
 8147        }
 148
 149        private string SerializeResult(ProcessingResult result)
 11150        {
 11151            var serializer = new DataContractJsonSerializer(typeof(ProcessingResult));
 152
 11153            using (var memoryStream = new MemoryStream())
 11154            {
 11155                serializer.WriteObject(memoryStream, result);
 156
 11157                memoryStream.Position = 0;
 158
 11159                using (var streamReader = new StreamReader(memoryStream))
 11160                {
 11161                    return streamReader.ReadToEnd();
 162                }
 163            }
 11164        }
 165
 166        private void HandleNonCustomAction(IPluginExecutionContext context, ITracingService tracing, IOrganizationServic
 16167        {
 16168            var target = context.InputParameters.ContainsKey("Target") ? context.InputParameters["Target"] as Entity : n
 169
 16170            if (target == null)
 0171            {
 0172                return;
 173            }
 174
 16175            var targetField = _config.TargetField;
 16176            var template = _config.Template;
 16177            var templateField = _config.TemplateField;
 178
 16179            var dataSource = GenerateDataSource(context, target);
 180
 16181            if (!CheckExecutionCriteria(_config, dataSource, service, tracing))
 1182            {
 1183                tracing.Trace("Execution criteria not met, aborting");
 1184                return;
 185            }
 186
 15187            ValidateConfig(targetField, template, templateField);
 14188            var templateText = RetrieveTemplate(template, templateField, dataSource, service, tracing);
 189
 14190            if (string.IsNullOrEmpty(templateText))
 1191            {
 1192                tracing.Trace("Template is empty, aborting");
 1193                return;
 194            }
 195
 13196            var output = TokenMatcher.ProcessTokens(templateText, dataSource, _organizationConfig, service, tracing);
 197
 13198            target[targetField] = output;
 13199            TriggerUpdateConditionally(output, dataSource, _config, service);
 15200        }
 201
 202        private static string RetrieveTemplate(string template, string templateField, Entity dataSource, IOrganizationSe
 22203        {
 204            string templateText;
 205
 22206            if (!string.IsNullOrEmpty(template))
 8207            {
 8208                templateText = template;
 8209            }
 14210            else if (!string.IsNullOrEmpty(templateField))
 14211            {
 14212                if (new Regex("^[a-zA-Z_0-9]*$").IsMatch(templateField))
 13213                {
 13214                    templateText = dataSource.GetAttributeValue<string>(templateField);
 13215                }
 216                else
 1217                {
 1218                    templateText = new XTLInterpreter(templateField, dataSource, null, service, tracing).Produce();
 1219                }
 14220            }
 221            else
 0222            {
 0223                throw new InvalidDataException("You must either pass a template text or define a template field");
 224            }
 225
 226
 227            // Templates inside e-mails will be HTML encoded
 22228            templateText = WebUtility.HtmlDecode(templateText);
 22229            return templateText;
 22230        }
 231
 232        private void ValidateConfig(string targetField, string template, string templateField)
 15233        {
 15234            if (string.IsNullOrEmpty(targetField))
 0235            {
 0236                throw new InvalidPluginExecutionException("Target field was null, please adapt the unsecure config!");
 237            }
 238
 15239            if (string.IsNullOrEmpty(template) && string.IsNullOrEmpty(templateField))
 1240            {
 1241                throw new InvalidPluginExecutionException("Both template and template field were null, please set one of
 242            }
 14243        }
 244
 245        private bool CheckExecutionCriteria(ProcessorConfig config, Entity dataSource, IOrganizationService service, ITr
 25246        {
 25247            if (!string.IsNullOrEmpty(config.ExecutionCriteria))
 3248            {
 3249                var criteriaInterpreter = new XTLInterpreter(config.ExecutionCriteria, dataSource, _organizationConfig, 
 3250                var result = criteriaInterpreter.Produce();
 251
 3252                var criteriaMatched = false;
 3253                bool.TryParse(result, out criteriaMatched);
 254
 3255                if (!criteriaMatched)
 2256                {
 2257                    return false;
 258                }
 259
 1260                return true;
 261            }
 262
 22263            return true;
 25264        }
 265
 266        private Entity GenerateDataSource(IPluginExecutionContext context, Entity target)
 16267        {
 268            // "Merge" pre entity images with targets for having all attribute values
 16269            var dataSource = new Entity
 16270            {
 16271                LogicalName = target.LogicalName,
 16272                Id = target.Id
 16273            };
 274
 50275            foreach (var image in context.PreEntityImages.Values)
 1276            {
 5277                foreach (var property in image.Attributes)
 1278                {
 1279                    dataSource[property.Key] = property.Value;
 1280                }
 1281            }
 282
 218283            foreach (var property in target.Attributes)
 85284            {
 85285                dataSource[property.Key] = property.Value;
 85286            }
 287
 16288            return dataSource;
 16289        }
 290    }
 291}