diff --git a/src/XrmMockup365/Core.cs b/src/XrmMockup365/Core.cs
index bc369704..d221cad5 100644
--- a/src/XrmMockup365/Core.cs
+++ b/src/XrmMockup365/Core.cs
@@ -32,7 +32,7 @@ namespace DG.Tools.XrmMockup
///
/// Class for handling all requests to the database
///
- internal class Core : IXrmMockupExtension
+ internal class Core : IXrmMockupExtension, ICoreOperations
{
public TimeSpan TimeOffset { get; private set; }
public MockupServiceProviderAndFactory ServiceFactory { get; private set; }
@@ -54,6 +54,7 @@ internal class Core : IXrmMockupExtension
internal OrganizationDetail OrganizationDetail { get; private set; }
internal EntityReference BaseCurrency { get; private set; }
+ private RequestExecutionPipeline pipeline;
private PluginManager pluginManager;
private CustomApiManager customApiManager;
private WorkflowManager workflowManager;
@@ -144,14 +145,14 @@ private void InitializeCore(CoreInitializationData initData)
}
var pluginLogger = loggerFactory.CreateLogger(typeof(PluginManager).FullName);
- pluginManager = new PluginManager(initData.Settings.BasePluginTypes, initData.Metadata.EntityMetadata, allPlugins, pluginLogger);
+ pluginManager = new PluginManager(this, initData.Settings.BasePluginTypes, initData.Metadata.EntityMetadata, allPlugins, pluginLogger);
var workflowLogger = loggerFactory.CreateLogger(typeof(WorkflowManager).FullName);
- workflowManager = new WorkflowManager(initData.Settings.CodeActivityInstanceTypes, initData.Settings.IncludeAllWorkflows,
+ workflowManager = new WorkflowManager(this, initData.Settings.CodeActivityInstanceTypes, initData.Settings.IncludeAllWorkflows,
initData.Workflows, initData.Metadata.EntityMetadata, workflowLogger);
var apiLogger = loggerFactory.CreateLogger(typeof(CustomApiManager).FullName);
- customApiManager = new CustomApiManager(initData.Settings.BaseCustomApiTypes, apiLogger);
+ customApiManager = new CustomApiManager(this, initData.Settings.BaseCustomApiTypes, apiLogger);
var typesMissingRegistration = pluginManager.missingRegistrations
.Intersect(customApiManager.missingRegistration)
@@ -189,6 +190,8 @@ private void InitializeCore(CoreInitializationData initData)
security.InitializeSecurityRoles(db);
OrganizationDetail = initData.Settings.OrganizationDetail;
+ pipeline = new RequestExecutionPipeline(this, pluginManager, workflowManager);
+
FormulaFieldEvaluator = new FormulaFieldEvaluator(ServiceFactory);
}
@@ -483,7 +486,7 @@ private Entity GetEntity(string entityType)
return null;
}
- internal DbRow GetDbRow(EntityReference entityReference)
+ public DbRow GetDbRow(EntityReference entityReference)
{
return db.GetDbRow(entityReference);
}
@@ -750,287 +753,55 @@ internal void Initialize(params Entity[] entities)
///
/// Execute the request and trigger plugins if needed
///
- ///
- ///
- ///
public OrganizationResponse Execute(OrganizationRequest request, EntityReference userRef)
{
return Execute(request, userRef, null);
}
- internal OrganizationResponse Execute(OrganizationRequest request, EntityReference userRef,
+ public OrganizationResponse Execute(OrganizationRequest request, EntityReference userRef,
PluginContext parentPluginContext)
{
- // Setup
- HandleInternalPreOperations(request, userRef);
-
- var primaryRef = Mappings.GetPrimaryEntityReferenceFromRequest(request);
-
- // Create the plugin context
- var pluginContext = new PluginContext()
- {
- UserId = userRef.Id,
- InitiatingUserId = userRef.Id,
- MessageName = RequestNameToMessageName(request.RequestName),
- Depth = 1,
- ExtensionDepth = 1,
- OrganizationName = OrganizationName,
- OrganizationId = OrganizationId,
- PrimaryEntityName = primaryRef?.LogicalName,
- // IPluginExecutionContext2-7 defaults
- IsPortalsClientCall = false,
- IsApplicationUser = false,
- AuthenticatedUserId = userRef.Id,
- };
- if (primaryRef != null)
- {
- var refEntity = db.GetEntityOrNull(primaryRef);
- pluginContext.PrimaryEntityId = refEntity == null ? Guid.Empty : refEntity.Id;
- }
-
- foreach (var prop in request.Parameters)
- {
- pluginContext.InputParameters[prop.Key] = prop.Value;
- }
-
- if (parentPluginContext != null)
- {
- pluginContext.ParentContext = parentPluginContext;
- pluginContext.Depth = parentPluginContext.Depth + 1;
- pluginContext.ExtensionDepth = parentPluginContext.ExtensionDepth + 1;
- parentPluginContext.ExtensionDepth = pluginContext.ExtensionDepth;
- }
-
- var buRef = GetBusinessUnit(userRef);
- pluginContext.BusinessUnitId = buRef.Id;
-
- // Get the request message from the mapping, if present, otherwise use the RequestName
- var requestMessage = Mappings.RequestToEventOperation.TryGetValue(request.GetType(), out var eventOperation)
- ? eventOperation.ToString()
- : request.RequestName;
-
- var entityInfo = GetEntityInfo(request);
-
- var settings = MockupExecutionContext.GetSettings(request);
- // Validation
- if (!settings.SetUnsettableFields && (request is UpdateRequest || request is CreateRequest))
- {
- var entity = request is UpdateRequest
- ? (request as UpdateRequest).Target
- : (request as CreateRequest).Target;
- Utility.RemoveUnsettableAttributes(request.RequestName,
- metadata.EntityMetadata.GetMetadata(entity.LogicalName), entity);
- }
-
- var shouldTrigger = settings.TriggerProcesses && entityInfo != null;
-
- var entityCollection = entityInfo?.Item1 as EntityCollection;
-
- Entity preImage = TryRetrieve(primaryRef);
- if (preImage != null)
- primaryRef.Id = preImage.Id;
-
- // Populate IPluginExecutionContext4 pre-images collection for Multiple operations
- if (entityCollection != null)
- {
- pluginContext.PreEntityImagesCollection = entityCollection.Entities
- .Select(e => {
- var img = new EntityImageCollection();
- var pre = e.Id != Guid.Empty ? TryRetrieve(e.ToEntityReference()) : null;
- if (pre != null) img["PreImage"] = pre;
- return img;
- }).ToArray();
- }
-
- if (shouldTrigger)
- {
- // System Pre-validation
- pluginManager.TriggerSystem(requestMessage, ExecutionStage.PreValidation, entityInfo.Item1, preImage, null, pluginContext, this);
- // Pre-validation
- pluginManager.TriggerSync(requestMessage, ExecutionStage.PreValidation, entityInfo.Item1, preImage, null, pluginContext, this, (_) => true);
- }
-
- //perform security checks for the request
- CheckRequestSecurity(request, userRef);
-
- //perform initialization of preoperation
- InitializePreOperation(request, userRef, preImage);
-
- if (shouldTrigger)
- {
- // Shared variables should be moved to parent context when transitioning from 10 to 20.
- pluginContext.ParentContext = pluginContext.Clone();
- pluginContext.SharedVariables.Clear();
-
- // Pre-operation
- pluginManager.TriggerSync(requestMessage, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this, (p) => p.GetExecutionOrder() == 0);
-
- if (settings.TriggerWorkflows)
- {
- workflowManager.TriggerSync(requestMessage, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this);
- }
- pluginManager.TriggerSync(requestMessage, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this, (p) => p.GetExecutionOrder() != 0);
-
- // System Pre-operation
- pluginManager.TriggerSystem(requestMessage, ExecutionStage.PreOperation, entityInfo.Item1, preImage, null, pluginContext, this);
- }
-
- // Core operation
- OrganizationResponse response = ExecuteRequest(request, userRef, parentPluginContext, pluginContext);
-
- // Post-operation
- if (shouldTrigger)
- {
- // In RetrieveMultipleRequests, the OutputParameters bag contains the entity collection
- if (request is RetrieveMultipleRequest)
- {
- pluginContext.OutputParameters["BusinessEntityCollection"] =
- (response as RetrieveMultipleResponse)?.EntityCollection;
- }
- else if (request is RetrieveRequest)
- {
- pluginContext.OutputParameters["BusinessEntity"] = TryRetrieve((request as RetrieveRequest).Target);
- }
-
- // Populate IPluginExecutionContext4 post-images collection for Multiple operations
- if (entityCollection != null)
- {
- // For CreateMultiple, the original entities may not have their Ids set.
- // Use the response Ids (from CreateMultipleResponse) when available.
- var responseIds = response.Results.TryGetValue("Ids", out var idsObj) ? idsObj as Guid[] : null;
-
- pluginContext.PostEntityImagesCollection = entityCollection.Entities
- .Select((e, i) => {
- var img = new EntityImageCollection();
- var entityId = responseIds != null && i < responseIds.Length ? responseIds[i] : e.Id;
- var post = entityId != Guid.Empty
- ? TryRetrieve(new EntityReference(entityCollection.EntityName ?? e.LogicalName, entityId))
- : null;
- if (post != null) img["PostImage"] = post;
- return img;
- }).ToArray();
- }
-
- var syncPostImage = TryRetrieve(primaryRef);
-
- //copy the createon etc system attributes onto the target so they are available for postoperation processing
- if (syncPostImage != null)
- {
- CopySystemAttributes(syncPostImage, entityInfo.Item1 as Entity);
- }
-
- pluginManager.TriggerSystem(requestMessage, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this);
-
- pluginManager.TriggerSync(requestMessage, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this, (p) => p.GetExecutionOrder() == 0);
-
- if (settings.TriggerWorkflows)
- {
- workflowManager.TriggerSync(requestMessage, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this);
- }
-
- pluginManager.TriggerSync(requestMessage, ExecutionStage.PostOperation, entityInfo.Item1, preImage, syncPostImage, pluginContext, this, (p) => p.GetExecutionOrder() != 0);
-
- var asyncPostImage = TryRetrieve(primaryRef);
- pluginManager.StageAsync(requestMessage, ExecutionStage.PostOperation, entityInfo.Item1, preImage, asyncPostImage, pluginContext, this);
-
- if (settings.TriggerWorkflows)
- {
- workflowManager.StageAsync(requestMessage, ExecutionStage.PostOperation, entityInfo.Item1, preImage, asyncPostImage, pluginContext, this);
- }
-
- //When last Sync has been executed we trigger the Async jobs.
- if (parentPluginContext == null)
- {
- pluginManager.TriggerAsyncWaitingJobs();
-
- if (settings.TriggerWorkflows)
- {
- workflowManager.TriggerAsync(this);
- }
- }
+ return pipeline.Execute(request, userRef, parentPluginContext);
+ }
- if (settings.TriggerWorkflows)
- {
- workflowManager.ExecuteWaitingWorkflows(pluginContext, this);
- }
- }
+ // ── ICoreOperations — infrastructure methods the pipeline calls back into Core for ──
- // Trigger Extension
- if (this.settings.MockUpExtensions.Count != 0)
- {
- /*
- * When moving business units, more than eight layers occur...
- */
- if (pluginContext.ExtensionDepth > 8)
- {
- throw new FaultException(
- "This workflow job was canceled because the workflow that started it included an infinite loop." +
- " Correct the workflow logic and try again.");
- }
- }
-
- switch (request.RequestName)
- {
- case "Create":
- var createResponse = (CreateResponse)response;
- var entityLogicalName = ((Entity)request.Parameters["Target"]).LogicalName;
- var reference = primaryRef ?? new EntityReference(entityLogicalName, createResponse.id);
-
- var createdEntity = TryRetrieve(reference);
- TriggerExtension(
- new MockupService(this, userRef.Id, pluginContext), request,
- createdEntity, null, userRef);
- break;
- case "Update":
- var updatedEntity = TryRetrieve(primaryRef);
- TriggerExtension(
- new MockupService(this, userRef.Id, pluginContext), request,
- updatedEntity, preImage, userRef);
- break;
- case "Delete":
- TriggerExtension(
- new MockupService(this, userRef.Id, pluginContext), request,
- null, preImage, userRef);
- break;
- }
+ public bool HasMockupExtensions => settings.MockUpExtensions.Count != 0;
- return response;
+ public EntityReference GetBusinessUnit(EntityReference owner)
+ {
+ return Utility.GetBusinessUnit(db, owner);
}
- private void CopySystemAttributes(Entity postImage, Entity item1)
+ public void CopySystemAttributes(Entity postImage, Entity target)
{
- if (item1 == null)
- {
- return;
- }
+ if (target == null) return;
- foreach (var systemAttributeName in this.systemAttributeNames)
+ foreach (var systemAttributeName in systemAttributeNames)
{
if (postImage.Contains(systemAttributeName))
{
if (postImage[systemAttributeName] is EntityReference)
{
- item1[systemAttributeName] = new EntityReference(
+ target[systemAttributeName] = new EntityReference(
postImage.GetAttributeValue(systemAttributeName).LogicalName,
postImage.GetAttributeValue(systemAttributeName).Id);
}
else if (postImage[systemAttributeName] is DateTime)
{
- item1[systemAttributeName] = postImage.GetAttributeValue(systemAttributeName);
+ target[systemAttributeName] = postImage.GetAttributeValue(systemAttributeName);
}
}
}
}
- internal void HandleInternalPreOperations(OrganizationRequest request, EntityReference userRef)
+ public void HandleInternalPreOperations(OrganizationRequest request, EntityReference userRef)
{
if (request.RequestName == "Create")
{
var entity = request["Target"] as Entity;
if (entity.Id == Guid.Empty)
- {
entity.Id = Guid.NewGuid();
- }
if (entity.GetAttributeValue("ownerid") == null &&
Utility.IsValidAttribute("ownerid", metadata.EntityMetadata.GetMetadata(entity.LogicalName)))
@@ -1040,110 +811,44 @@ internal void HandleInternalPreOperations(OrganizationRequest request, EntityRef
}
}
- internal void AddTime(TimeSpan offset)
+ public OrganizationResponse ExecuteAction(OrganizationRequest request)
{
- this.TimeOffset = this.TimeOffset.Add(offset);
- TriggerWaitingWorkflows();
+ return ExecuteActionInternal(request);
}
- private OrganizationResponse ExecuteRequest(
- OrganizationRequest request,
- EntityReference userRef,
- PluginContext parentPluginContext,
- PluginContext pluginContext)
- {
- if (request is AssignRequest assignRequest)
- {
- var targetEntity = db.GetEntityOrNull(assignRequest.Target);
- if (targetEntity.GetAttributeValue("ownerid") != assignRequest.Assignee)
- {
- var req = new UpdateRequest
- {
- Target = new Entity(assignRequest.Target.LogicalName, assignRequest.Target.Id)
- };
- req.Target.Attributes["ownerid"] = assignRequest.Assignee;
- Execute(req, userRef, parentPluginContext);
- }
-
- return new AssignResponse();
- }
-
- if (request is SetStateRequest setstateRequest)
- {
- var targetEntity = db.GetEntityOrNull(setstateRequest.EntityMoniker);
- if (targetEntity.GetAttributeValue("statecode") != setstateRequest.State ||
- targetEntity.GetAttributeValue("statuscode") != setstateRequest.Status)
- {
- var req = new UpdateRequest
- {
- Target = new Entity(setstateRequest.EntityMoniker.LogicalName, setstateRequest.EntityMoniker.Id)
- };
- req.Target.Attributes["statecode"] = setstateRequest.State;
- req.Target.Attributes["statuscode"] = setstateRequest.Status;
- Execute(req, userRef, parentPluginContext);
- }
-
- return new SetStateResponse();
- }
-
- if (workflowManager.GetActionDefaultNull(request.RequestName) != null)
- {
- return ExecuteAction(request);
- }
+ public bool HandlesCustomApi(string requestName) => customApiManager.HandlesRequest(requestName);
- if (customApiManager.HandlesRequest(request.RequestName))
- {
- return customApiManager.Execute(request, this, pluginContext);
- }
-
- var handler = RequestHandlers.FirstOrDefault(x => x.HandlesRequest(request.RequestName));
- if (handler != null)
- {
- return handler.Execute(request, userRef);
- }
-
- if (settings.ExceptionFreeRequests?.Contains(request.RequestName) ?? false)
- {
- return new OrganizationResponse();
- }
+ public OrganizationResponse ExecuteCustomApi(OrganizationRequest request, PluginContext pluginContext)
+ {
+ return customApiManager.Execute(request, pluginContext);
+ }
- throw new NotImplementedException(
- $"Execute for the request '{request.RequestName}' has not been implemented yet.");
+ public bool IsExceptionFreeRequest(string requestName)
+ {
+ return settings.ExceptionFreeRequests?.Contains(requestName) ?? false;
}
- private void CheckRequestSecurity(OrganizationRequest request, EntityReference userRef)
+ public IOrganizationService CreateMockupService(Guid? userId, PluginContext pluginContext)
{
- var handler = RequestHandlers.FirstOrDefault(x => x.HandlesRequest(request.RequestName));
- if (handler != null)
- {
- handler.CheckSecurity(request, userRef);
- }
+ return new MockupService(this, userId, pluginContext);
}
- private void InitializePreOperation(OrganizationRequest request, EntityReference userRef, Entity preImage)
+ public MockupServiceProviderAndFactory CreateServiceProviderAndFactory(PluginContext pluginContext)
{
- var handler = RequestHandlers.FirstOrDefault(x => x.HandlesRequest(request.RequestName));
- if(handler != null)
- {
- handler.InitializePreOperation(request, userRef, preImage);
- }
+ return new MockupServiceProviderAndFactory(this, pluginContext, TracingServiceFactory);
}
- private string RequestNameToMessageName(string requestName)
+ // ──────────────────────────────────────────────────────────────────────────
+
+ internal void AddTime(TimeSpan offset)
{
- switch (requestName)
- {
- case "LoseOpportunity": return "Lose";
- case "WinOpportunity": return "Win";
- case "CloseQuote": return "Lose";
- case "WinQuote": return "Win";
- default: return requestName;
- }
+ this.TimeOffset = this.TimeOffset.Add(offset);
+ TriggerWaitingWorkflows();
}
internal void TriggerWaitingWorkflows()
{
- workflowManager.ExecuteWaitingWorkflows(null, this);
+ workflowManager.ExecuteWaitingWorkflows(null);
}
internal void AddWorkflow(Entity workflow)
@@ -1203,7 +908,7 @@ internal bool HasPermission(EntityReference entityRef, AccessRights access, Enti
return security.HasPermission(entityRef, access, principleRef);
}
- private OrganizationResponse ExecuteAction(OrganizationRequest request)
+ private OrganizationResponse ExecuteActionInternal(OrganizationRequest request)
{
var action = workflowManager.GetActionDefaultNull(request.RequestName);
@@ -1224,7 +929,7 @@ private OrganizationResponse ExecuteAction(OrganizationRequest request)
workflow.Variables.Add(argumentName, input.Value);
}
- var postExecution = workflowManager.ExecuteWorkflow(workflow, entity, null, this);
+ var postExecution = workflowManager.ExecuteWorkflow(workflow, entity, null);
var outputs = workflow.Output.Where(a => postExecution.Variables.ContainsKey(a.Name))
.Select(a => new KeyValuePair(a.Name, postExecution.Variables[a.Name]));
@@ -1317,15 +1022,11 @@ private Tuple