Пример #1
0
        public async Task ExecuteTasksAsync(DeferredTaskContext context)
        {
            for (var i = 0; i < _deferredTaskState.Tasks.Count; i++)
            {
                var task = _deferredTaskState.Tasks[i];

                try
                {
                    await task(context);
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "An error occured while processing a deferred task");
                }
            }

            _deferredTaskState.Tasks.Clear();
        }
Пример #2
0
        public async Task <string> ExecuteAsync(string executionId, RecipeDescriptor recipeDescriptor)
        {
            await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutingAsync(executionId, recipeDescriptor));

            try
            {
                var parsersForFileExtension = _recipeOptions
                                              .RecipeFileExtensions
                                              .Where(rfx => Path.GetExtension(rfx.Key) == Path.GetExtension(recipeDescriptor.RecipeFileInfo.PhysicalPath));

                using (var stream = recipeDescriptor.RecipeFileInfo.CreateReadStream())
                {
                    RecipeResult result = new RecipeResult {
                        ExecutionId = executionId
                    };
                    List <RecipeStepResult> stepResults = new List <RecipeStepResult>();

                    foreach (var parserForFileExtension in parsersForFileExtension)
                    {
                        var recipeParser = _recipeParsers.First(x => x.GetType() == parserForFileExtension.Value);

                        await recipeParser.ProcessRecipeAsync(stream, (recipe, recipeStep) =>
                        {
                            // TODO, create Result prior to run
                            stepResults.Add(new RecipeStepResult
                            {
                                ExecutionId = executionId,
                                RecipeName  = recipeDescriptor.Name,
                                StepId      = recipeStep.Id,
                                StepName    = recipeStep.Name
                            });
                            return(Task.CompletedTask);
                        });
                    }

                    result.Steps = stepResults;
                    await _recipeStore.UpdateAsync(result);
                }

                using (var stream = recipeDescriptor.RecipeFileInfo.CreateReadStream())
                {
                    foreach (var parserForFileExtension in parsersForFileExtension)
                    {
                        var recipeParser = _recipeParsers.First(x => x.GetType() == parserForFileExtension.Value);

                        await recipeParser.ProcessRecipeAsync(stream, async (recipe, recipeStep) =>
                        {
                            var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings);
                            using (var scope = shellContext.CreateServiceScope())
                            {
                                if (!shellContext.IsActivated)
                                {
                                    var eventBus = scope.ServiceProvider.GetService <IEventBus>();
                                    await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatingAsync());
                                    await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatedAsync());

                                    shellContext.IsActivated = true;
                                }

                                var recipeStepExecutor = scope.ServiceProvider.GetRequiredService <IRecipeStepExecutor>();

                                if (_applicationLifetime.ApplicationStopping.IsCancellationRequested)
                                {
                                    throw new OrchardException(T["Recipe cancelled, application is restarting"]);
                                }

                                await recipeStepExecutor.ExecuteAsync(executionId, recipeStep);
                            }

                            // The recipe execution might have invalidated the shell by enabling new features,
                            // so the deferred tasks need to run on an updated shell context if necessary.
                            shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings);
                            using (var scope = shellContext.CreateServiceScope())
                            {
                                var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                                // The recipe might have added some deferred tasks to process
                                if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                                {
                                    var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                                    await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                                }
                            }
                        });
                    }
                }

                await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutedAsync(executionId, recipeDescriptor));

                return(executionId);
            }
            catch (Exception)
            {
                await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.ExecutionFailedAsync(executionId, recipeDescriptor));

                throw;
            }
        }
Пример #3
0
        public async Task <string> SetupInternalAsync(SetupContext context)
        {
            string executionId;

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name);
            }

            // Features to enable for Setup
            string[] hardcoded =
            {
                "OrchardCore.Commons",
                "OrchardCore.Features",
                "OrchardCore.Recipes",
                "OrchardCore.Scripting"
            };

            context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList();

            // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
            _shellSettings.State = TenantState.Initializing;

            var shellSettings = new ShellSettings(_shellSettings.Configuration);

            if (string.IsNullOrEmpty(shellSettings.DatabaseProvider))
            {
                shellSettings.DatabaseProvider = context.DatabaseProvider;
                shellSettings.ConnectionString = context.DatabaseConnectionString;
                shellSettings.TablePrefix      = context.DatabaseTablePrefix;
            }

            // Creating a standalone environment based on a "minimum shell descriptor".
            // In theory this environment can be used to resolve any normal components by interface, and those
            // components will exist entirely in isolation - no crossover between the safemode container currently in effect
            // It is used to initialize the database before the recipe is run.

            var shellDescriptor = new ShellDescriptor
            {
                Features = context.EnabledFeatures.Select(id => new ShellFeature {
                    Id = id
                }).ToList()
            };

            using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor))
            {
                using (var scope = shellContext.EnterServiceScope())
                {
                    var store = scope.ServiceProvider.GetRequiredService <IStore>();

                    try
                    {
                        await store.InitializeAsync();
                    }
                    catch (Exception e)
                    {
                        // Tables already exist or database was not found

                        // The issue is that the user creation needs the tables to be present,
                        // if the user information is not valid, the next POST will try to recreate the
                        // tables. The tables should be rolled back if one of the steps is invalid,
                        // unless the recipe is executing?

                        _logger.LogError("An error occurred while initializing the datastore.", e);
                        context.Errors.Add("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]);
                        return(null);
                    }

                    // Create the "minimum shell descriptor"
                    await scope
                    .ServiceProvider
                    .GetService <IShellDescriptorManager>()
                    .UpdateShellDescriptorAsync(0,
                                                shellContext.Blueprint.Descriptor.Features,
                                                shellContext.Blueprint.Descriptor.Parameters);

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                    if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                    {
                        var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                    }
                }

                executionId = Guid.NewGuid().ToString("n");

                // Create a new scope for the recipe thread to prevent race issues with other scoped
                // services from the request.
                using (var scope = shellContext.EnterServiceScope())
                {
                    var recipeExecutor = scope.ServiceProvider.GetService <IRecipeExecutor>();

                    // Right now we run the recipe in the same thread, later use polling from the setup screen
                    // to query the current execution.
                    //await Task.Run(async () =>
                    //{
                    await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new
                    {
                        SiteName                 = context.SiteName,
                        AdminUsername            = context.AdminUsername,
                        AdminEmail               = context.AdminEmail,
                        AdminPassword            = context.AdminPassword,
                        DatabaseProvider         = context.DatabaseProvider,
                        DatabaseConnectionString = context.DatabaseConnectionString,
                        DatabaseTablePrefix      = context.DatabaseTablePrefix
                    });

                    //});
                }
            }

            // Reloading the shell context as the recipe  has probably updated its features
            using (var shellContext = await _orchardHost.CreateShellContextAsync(shellSettings))
            {
                using (var scope = shellContext.EnterServiceScope())
                {
                    var hasErrors = false;

                    Action <string, string> reportError = (key, message) => {
                        hasErrors           = true;
                        context.Errors[key] = message;
                    };

                    // Invoke modules to react to the setup event
                    var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>();
                    var logger             = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >();

                    await setupEventHandlers.InvokeAsync(x => x.Setup(
                                                             context.SiteName,
                                                             context.AdminUsername,
                                                             context.AdminEmail,
                                                             context.AdminPassword,
                                                             context.DatabaseProvider,
                                                             context.DatabaseConnectionString,
                                                             context.DatabaseTablePrefix,
                                                             reportError
                                                             ), logger);

                    if (hasErrors)
                    {
                        return(executionId);
                    }

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                    if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                    {
                        var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                    }
                }
            }

            // Update the shell state
            shellSettings.State = TenantState.Running;
            _orchardHost.UpdateShellSettings(shellSettings);

            return(executionId);
        }
        private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep)
        {
            var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings);

            using (var scope = shellContext.EnterServiceScope())
            {
                if (!shellContext.IsActivated)
                {
                    var tenantEvents = scope.ServiceProvider
                                       .GetServices <IModularTenantEvents>();

                    foreach (var tenantEvent in tenantEvents)
                    {
                        tenantEvent.ActivatingAsync().Wait();
                    }

                    shellContext.IsActivated = true;

                    foreach (var tenantEvent in tenantEvents)
                    {
                        tenantEvent.ActivatedAsync().Wait();
                    }
                }

                var recipeStepHandlers = scope.ServiceProvider.GetServices <IRecipeStepHandler>();
                var scriptingManager   = scope.ServiceProvider.GetRequiredService <IScriptingManager>();
                scriptingManager.GlobalMethodProviders.Add(_environmentMethodProvider);

                // Substitutes the script elements by their actual values
                EvaluateScriptNodes(recipeStep, scriptingManager);

                foreach (var recipeStepHandler in recipeStepHandlers)
                {
                    if (Logger.IsEnabled(LogLevel.Information))
                    {
                        Logger.LogInformation("Executing recipe step '{0}'.", recipeStep.Name);
                    }

                    await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutingAsync(recipeStep), Logger);

                    await recipeStepHandler.ExecuteAsync(recipeStep);

                    await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutedAsync(recipeStep), Logger);

                    if (Logger.IsEnabled(LogLevel.Information))
                    {
                        Logger.LogInformation("Finished executing recipe step '{0}'.", recipeStep.Name);
                    }
                }
            }

            // The recipe execution might have invalidated the shell by enabling new features,
            // so the deferred tasks need to run on an updated shell context if necessary.
            shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings);
            using (var scope = shellContext.EnterServiceScope())
            {
                var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                // The recipe might have added some deferred tasks to process
                if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                {
                    var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                    await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                }
            }
        }
Пример #5
0
        public async Task <string> SetupInternalAsync(SetupContext context)
        {
            string executionId;

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name);
            }

            // Features to enable for Setup
            string[] hardcoded =
            {
                "Orchard.Hosting" // shortcut for built-in features
            };

            context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList();

            // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
            _shellSettings.State = TenantState.Initializing;

            var shellSettings = new ShellSettings(_shellSettings);

            if (string.IsNullOrEmpty(shellSettings.DatabaseProvider))
            {
                shellSettings.DatabaseProvider = context.DatabaseProvider;
                shellSettings.ConnectionString = context.DatabaseConnectionString;
                shellSettings.TablePrefix      = context.DatabaseTablePrefix;
            }

            // Creating a standalone environment based on a "minimum shell descriptor".
            // In theory this environment can be used to resolve any normal components by interface, and those
            // components will exist entirely in isolation - no crossover between the safemode container currently in effect
            // It is used to initialize the database before the recipe is run.

            using (var shellContext = _orchardHost.CreateShellContext(shellSettings))
            {
                using (var scope = shellContext.CreateServiceScope())
                {
                    executionId = CreateTenantData(context, shellContext);

                    var store = scope.ServiceProvider.GetRequiredService <IStore>();
                    await store.InitializeAsync();

                    // Create the "minimum shell descriptor"
                    await scope
                    .ServiceProvider
                    .GetService <IShellDescriptorManager>()
                    .UpdateShellDescriptorAsync(
                        0,
                        shellContext.Blueprint.Descriptor.Features,
                        shellContext.Blueprint.Descriptor.Parameters);

                    // Apply all migrations for the newly initialized tenant
                    var dataMigrationManager = scope.ServiceProvider.GetService <IDataMigrationManager>();
                    await dataMigrationManager.UpdateAllFeaturesAsync();

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                    if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                    {
                        var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                    }

                    // Invoke modules to react to the setup event
                    var eventBus = scope.ServiceProvider.GetService <IEventBus>();
                    await eventBus.NotifyAsync <ISetupEventHandler>(x => x.Setup(
                                                                        context.SiteName,
                                                                        context.AdminUsername,
                                                                        context.AdminEmail,
                                                                        context.AdminPassword,
                                                                        context.DatabaseProvider,
                                                                        context.DatabaseConnectionString,
                                                                        context.DatabaseTablePrefix)
                                                                    );

                    if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                    {
                        var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                    }
                }
            }

            shellSettings.State = TenantState.Running;
            _runningShellRouterTable.Remove(shellSettings.Name);
            _orchardHost.UpdateShellSettings(shellSettings);
            return(executionId);
        }
Пример #6
0
        public async Task Invoke(HttpContext httpContext)
        {
            // Ensure all ShellContext are loaded and available.
            await _shellHost.InitializeAsync();

            var shellSettings = _runningShellTable.Match(httpContext);

            // We only serve the next request if the tenant has been resolved.
            if (shellSettings != null)
            {
                if (shellSettings.State == TenantState.Initializing)
                {
                    httpContext.Response.Headers.Add(HeaderNames.RetryAfter, "10");
                    httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
                    await httpContext.Response.WriteAsync("The requested tenant is currently initializing.");

                    return;
                }

                var hasPendingTasks = false;

                // We need to get a scope and the ShellContext that created it
                var(scope, shellContext) = await _shellHost.GetScopeAndContextAsync(shellSettings);

                using (scope)
                {
                    // Register the shell context as a custom feature
                    httpContext.Features.Set(shellContext);

                    if (!shellContext.IsActivated)
                    {
                        var semaphore = _semaphores.GetOrAdd(shellSettings.Name, (name) => new SemaphoreSlim(1));

                        await semaphore.WaitAsync();

                        try
                        {
                            // The tenant gets activated here
                            if (!shellContext.IsActivated)
                            {
                                using (var activatingScope = await _shellHost.GetScopeAsync(shellSettings))
                                {
                                    var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>();

                                    foreach (var tenantEvent in tenantEvents)
                                    {
                                        await tenantEvent.ActivatingAsync();
                                    }

                                    foreach (var tenantEvent in tenantEvents.Reverse())
                                    {
                                        await tenantEvent.ActivatedAsync();
                                    }
                                }

                                shellContext.IsActivated = true;
                            }
                        }
                        finally
                        {
                            semaphore.Release();
                            _semaphores.TryRemove(shellSettings.Name, out semaphore);
                        }
                    }

                    await _next.Invoke(httpContext);

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();
                    hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false;
                }

                // Create a new scope only if there are pending tasks
                if (hasPendingTasks)
                {
                    using (var pendingScope = await _shellHost.GetScopeAsync(shellSettings))
                    {
                        if (pendingScope != null)
                        {
                            var deferredTaskEngine = pendingScope.ServiceProvider.GetService <IDeferredTaskEngine>();
                            var context            = new DeferredTaskContext(pendingScope.ServiceProvider);
                            await deferredTaskEngine.ExecuteTasksAsync(context);
                        }
                    }
                }
            }
        }
Пример #7
0
        public async Task <string> SetupInternalAsync(SetupContext context)
        {
            string executionId;

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name);
            }

            // Features to enable for Setup
            string[] hardcoded =
            {
                "Orchard.Hosting" // shortcut for built-in features
            };

            context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty <string>()).Distinct().ToList();

            // Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
            _shellSettings.State = TenantState.Initializing;

            var shellSettings = new ShellSettings(_shellSettings);

            if (string.IsNullOrEmpty(shellSettings.DatabaseProvider))
            {
                shellSettings.DatabaseProvider = context.DatabaseProvider;
                shellSettings.ConnectionString = context.DatabaseConnectionString;
                shellSettings.TablePrefix      = context.DatabaseTablePrefix;
            }

            // Creating a standalone environment based on a "minimum shell descriptor".
            // In theory this environment can be used to resolve any normal components by interface, and those
            // components will exist entirely in isolation - no crossover between the safemode container currently in effect
            // It is used to initialize the database before the recipe is run.

            using (var shellContext = _orchardHost.CreateShellContext(shellSettings))
            {
                using (var scope = shellContext.CreateServiceScope())
                {
                    executionId = CreateTenantData(context, shellContext);

                    var store = scope.ServiceProvider.GetRequiredService <IStore>();

                    try
                    {
                        await store.InitializeAsync();
                    }
                    catch
                    {
                        // Tables already exist or database was not found

                        // The issue is that the user creation needs the tables to be present,
                        // if the user information is not valid, the next POST will try to recreate the
                        // tables. The tables should be rollbacked if one of the steps is invalid,
                        // unless the recipe is executing?
                    }

                    // Create the "minimum shell descriptor"
                    await scope
                    .ServiceProvider
                    .GetService <IShellDescriptorManager>()
                    .UpdateShellDescriptorAsync(
                        0,
                        shellContext.Blueprint.Descriptor.Features,
                        shellContext.Blueprint.Descriptor.Parameters);

                    // Apply all migrations for the newly initialized tenant
                    var dataMigrationManager = scope.ServiceProvider.GetService <IDataMigrationManager>();
                    await dataMigrationManager.UpdateAllFeaturesAsync();

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                    if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                    {
                        var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                    }

                    bool hasErrors = false;

                    Action <string, string> reportError = (key, message) => {
                        hasErrors           = true;
                        context.Errors[key] = message;
                    };

                    // Invoke modules to react to the setup event
                    var eventBus = scope.ServiceProvider.GetService <IEventBus>();
                    await eventBus.NotifyAsync <ISetupEventHandler>(x => x.Setup(
                                                                        context.SiteName,
                                                                        context.AdminUsername,
                                                                        context.AdminEmail,
                                                                        context.AdminPassword,
                                                                        context.DatabaseProvider,
                                                                        context.DatabaseConnectionString,
                                                                        context.DatabaseTablePrefix,
                                                                        reportError
                                                                        ));

                    if (hasErrors)
                    {
                        // TODO: check why the tables creation is not reverted
                        var session = scope.ServiceProvider.GetService <YesSql.Core.Services.ISession>();
                        session.Cancel();

                        return(executionId);
                    }

                    if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                    {
                        var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                    }
                }
            }

            shellSettings.State = TenantState.Running;
            _orchardHost.UpdateShellSettings(shellSettings);
            return(executionId);
        }
Пример #8
0
        public async Task <string> ExecuteAsync(string executionId, RecipeDescriptor recipeDescriptor)
        {
            await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutingAsync(executionId, recipeDescriptor));

            try
            {
                RecipeResult result = new RecipeResult {
                    ExecutionId = executionId
                };
                List <RecipeStepResult> stepResults = new List <RecipeStepResult>();

                using (StreamReader file = File.OpenText(recipeDescriptor.RecipeFileInfo.PhysicalPath))
                {
                    using (var reader = new JsonTextReader(file))
                    {
                        VariablesMethodProvider variablesMethodProvider = null;

                        // Go to Steps, then iterate.
                        while (reader.Read())
                        {
                            if (reader.Path == "variables")
                            {
                                reader.Read();

                                var variables = JObject.Load(reader);
                                variablesMethodProvider = new VariablesMethodProvider(variables);
                            }

                            if (reader.Path == "steps" && reader.TokenType == JsonToken.StartArray)
                            {
                                int stepId = 0;
                                while (reader.Read() && reader.Depth > 1)
                                {
                                    if (reader.Depth == 2)
                                    {
                                        var child = JToken.Load(reader);

                                        var recipeStep = new RecipeStepDescriptor
                                        {
                                            Id   = (stepId++).ToString(),
                                            Name = child.Value <string>("name"),
                                            Step = child
                                        };

                                        var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings);
                                        using (var scope = shellContext.CreateServiceScope())
                                        {
                                            if (!shellContext.IsActivated)
                                            {
                                                var eventBus = scope.ServiceProvider.GetService <IEventBus>();
                                                await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatingAsync());

                                                await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatedAsync());

                                                shellContext.IsActivated = true;
                                            }

                                            var recipeStepExecutor = scope.ServiceProvider.GetRequiredService <IRecipeStepExecutor>();
                                            var scriptingManager   = scope.ServiceProvider.GetRequiredService <IScriptingManager>();

                                            if (variablesMethodProvider != null)
                                            {
                                                variablesMethodProvider.ScriptingManager = scriptingManager;
                                                scriptingManager.GlobalMethodProviders.Add(variablesMethodProvider);
                                            }

                                            if (_applicationLifetime.ApplicationStopping.IsCancellationRequested)
                                            {
                                                throw new OrchardException(T["Recipe cancelled, application is restarting"]);
                                            }

                                            EvaluateJsonTree(scriptingManager, recipeStep.Step);

                                            await recipeStepExecutor.ExecuteAsync(executionId, recipeStep);
                                        }

                                        // The recipe execution might have invalidated the shell by enabling new features,
                                        // so the deferred tasks need to run on an updated shell context if necessary.
                                        shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings);
                                        using (var scope = shellContext.CreateServiceScope())
                                        {
                                            var recipeStepExecutor = scope.ServiceProvider.GetRequiredService <IRecipeStepExecutor>();

                                            var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();

                                            // The recipe might have added some deferred tasks to process
                                            if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                                            {
                                                var taskContext = new DeferredTaskContext(scope.ServiceProvider);
                                                await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutedAsync(executionId, recipeDescriptor));

                return(executionId);
            }
            catch (Exception)
            {
                await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.ExecutionFailedAsync(executionId, recipeDescriptor));

                throw;
            }
        }
Пример #9
0
        private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep)
        {
            IServiceScope    scope;
            ShellContext     shellContext;
            IServiceProvider serviceProvider;

            if (recipeStep.RecipeDescriptor.RequireNewScope)
            {
                (scope, shellContext) = await _shellHost.GetScopeAndContextAsync(_shellSettings);

                serviceProvider = scope.ServiceProvider;
            }
            else
            {
                (scope, shellContext) = (null, null);
                serviceProvider       = _httpContextAccessor.HttpContext.RequestServices;
            }

            using (scope)
            {
                if (recipeStep.RecipeDescriptor.RequireNewScope && !shellContext.IsActivated)
                {
                    using (var activatingScope = shellContext.CreateScope())
                    {
                        var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>();

                        foreach (var tenantEvent in tenantEvents)
                        {
                            await tenantEvent.ActivatingAsync();
                        }

                        foreach (var tenantEvent in tenantEvents.Reverse())
                        {
                            await tenantEvent.ActivatedAsync();
                        }
                    }

                    shellContext.IsActivated = true;
                }

                var recipeStepHandlers = serviceProvider.GetServices <IRecipeStepHandler>();
                var scriptingManager   = serviceProvider.GetRequiredService <IScriptingManager>();
                scriptingManager.GlobalMethodProviders.Add(_environmentMethodProvider);

                // Substitutes the script elements by their actual values
                EvaluateScriptNodes(recipeStep, scriptingManager);

                foreach (var recipeStepHandler in recipeStepHandlers)
                {
                    if (Logger.IsEnabled(LogLevel.Information))
                    {
                        Logger.LogInformation("Executing recipe step '{RecipeName}'.", recipeStep.Name);
                    }

                    await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutingAsync(recipeStep), Logger);

                    await recipeStepHandler.ExecuteAsync(recipeStep);

                    await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutedAsync(recipeStep), Logger);

                    if (Logger.IsEnabled(LogLevel.Information))
                    {
                        Logger.LogInformation("Finished executing recipe step '{RecipeName}'.", recipeStep.Name);
                    }
                }
            }

            // E.g if we run migrations defined in a recipe.
            if (!recipeStep.RecipeDescriptor.RequireNewScope)
            {
                return;
            }

            // The recipe execution might have invalidated the shell by enabling new features,
            // so the deferred tasks need to run on an updated shell context if necessary.
            using (var localScope = await _shellHost.GetScopeAsync(_shellSettings))
            {
                var deferredTaskEngine = localScope.ServiceProvider.GetService <IDeferredTaskEngine>();

                // The recipe might have added some deferred tasks to process
                if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks)
                {
                    var taskContext = new DeferredTaskContext(localScope.ServiceProvider);
                    await deferredTaskEngine.ExecuteTasksAsync(taskContext);
                }
            }
        }
Пример #10
0
        public async Task Invoke(HttpContext httpContext)
        {
            // Ensure all tenants are loaded and available.
            _platoHost.Initialize();

            // Get ShellSettings for current tenant
            var shellSettings = _runningShellTable.Match(httpContext);

            // Register shell settings as a custom feature
            httpContext.Features[typeof(ShellSettings)] = shellSettings;

            // Only serve the next request if the tenant has been resolved.
            if (shellSettings != null)
            {
                var hasDeferredTasks = false;
                var shellContext     = _platoHost.GetOrCreateShellContext(shellSettings);
                using (var scope = shellContext.CreateServiceScope())
                {
                    // Mimic the services provided by our host and tenant
                    httpContext.RequestServices = scope.ServiceProvider;

                    // Ensure the tenant is activated
                    if (!shellContext.IsActivated)
                    {
                        lock (shellSettings)
                        {
                            // Activate the tenant
                            if (!shellContext.IsActivated)
                            {
                                // BuildPipeline ensures we always rebuild routes for new tenants
                                httpContext.Items["BuildPipeline"] = true;
                                shellContext.IsActivated           = true;

                                // Activate message broker subscriptions
                                var subscribers = scope.ServiceProvider.GetServices <IBrokerSubscriber>();
                                foreach (var subscriber in subscribers)
                                {
                                    subscriber.Subscribe();
                                }

                                // Activate background tasks
                                var backgroundTaskManager = scope.ServiceProvider.GetService <IBackgroundTaskManager>();
                                backgroundTaskManager?.StartTasks();
                            }
                        }
                    }

                    // Invoke the next middleware in pipeline
                    await _next.Invoke(httpContext);

                    // At the end determine if we need to process deferred tasks
                    var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>();
                    hasDeferredTasks = deferredTaskManager?.Process(httpContext) ?? false;
                }

                // Execute deferred tasks (if any) within a new scope
                // once the entire response has been sent to the client.
                if (hasDeferredTasks)
                {
                    httpContext.Response.OnCompleted(async() =>
                    {
                        using (var scope = shellContext.CreateServiceScope())
                        {
                            var context             = new DeferredTaskContext(scope.ServiceProvider);
                            var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>();
                            await deferredTaskManager.ExecuteTaskAsync(context);
                        }
                    });
                }
            }
        }
Пример #11
0
        public async Task Invoke(HttpContext httpContext)
        {
            // Ensure all tenants are loaded and available.
            _platoHost.Initialize();

            // Attempt to match the request to a specific tenant
            var shellSettings = _runningShellTable.Match(httpContext, _logger);

            // Register shell settings on current context
            httpContext.SetShellSettings(shellSettings);

            // Only serve the next request if the tenant has been resolved.
            if (shellSettings != null)
            {
                // If the shell is disabled or invalid redirect to host 404 page
                if (shellSettings.State == TenantState.Disabled ||
                    shellSettings.State == TenantState.Invalid)
                {
                    httpContext.Response.StatusCode = StatusCodes.Status302Found;
                    httpContext.Response.Headers.Add(HeaderNames.Location, StatusCodePagePaths.NotFound);
                    return;
                }

                var hasDeferredTasks = false;
                var shellContext     = _platoHost.GetOrCreateShellContext(shellSettings);
                using (var scope = shellContext.CreateServiceScope())
                {
                    // Mimic the services provided by our host and tenant
                    httpContext.RequestServices = scope.ServiceProvider;

                    // Ensure the tenant is activated
                    if (!shellContext.IsActivated)
                    {
                        lock (shellSettings)
                        {
                            // Activate the tenant
                            if (!shellContext.IsActivated)
                            {
                                // BuildPipeline ensures we always rebuild routes for new tenants
                                httpContext.Items["BuildPipeline"] = true;
                                shellContext.IsActivated           = true;

                                // Activate message broker subscriptions
                                var subscribers = scope.ServiceProvider.GetServices <IBrokerSubscriber>();
                                foreach (var subscriber in subscribers)
                                {
                                    subscriber.Subscribe();
                                }

                                // Activate background tasks
                                var backgroundTaskManager = scope.ServiceProvider.GetService <IBackgroundTaskManager>();
                                backgroundTaskManager?.StartTasks(scope.ServiceProvider);
                            }
                        }
                    }

                    // Invoke the next middle ware in pipeline
                    await _next.Invoke(httpContext);

                    // At the end determine if we need to process deferred tasks
                    var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>();
                    hasDeferredTasks = deferredTaskManager?.Process(httpContext) ?? false;
                }

                // Execute deferred tasks (if any) within a new scope
                // once the entire response has been sent to the client.
                if (hasDeferredTasks)
                {
                    httpContext.Response.OnCompleted(async() =>
                    {
                        using (var scope = shellContext.CreateServiceScope())
                        {
                            var context             = new DeferredTaskContext(scope.ServiceProvider);
                            var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>();
                            await deferredTaskManager.ExecuteTaskAsync(context);
                        }
                    });
                }
            }
        }
Пример #12
0
        public async Task Invoke(HttpContext httpContext)
        {
            // Ensure all ShellContext are loaded and available.
            _orchardHost.Initialize();

            var shellSettings = _runningShellTable.Match(httpContext);

            // We only serve the next request if the tenant has been resolved.
            if (shellSettings != null)
            {
                var shellContext = _orchardHost.GetOrCreateShellContext(shellSettings);

                var hasPendingTasks = false;
                using (var scope = shellContext.EnterServiceScope())
                {
                    // Register the shell context as a custom feature.
                    httpContext.Features.Set(shellContext);

                    if (!shellContext.IsActivated)
                    {
                        var semaphore = _semaphores.GetOrAdd(shellSettings.Name, (name) => new SemaphoreSlim(1));

                        await semaphore.WaitAsync();

                        try
                        {
                            // The tenant gets activated here
                            if (!shellContext.IsActivated)
                            {
                                using (var activatingScope = shellContext.EnterServiceScope())
                                {
                                    var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>();

                                    foreach (var tenantEvent in tenantEvents)
                                    {
                                        await tenantEvent.ActivatingAsync();
                                    }

                                    httpContext.Items["BuildPipeline"] = true;

                                    foreach (var tenantEvent in tenantEvents.Reverse())
                                    {
                                        await tenantEvent.ActivatedAsync();
                                    }
                                }

                                shellContext.IsActivated = true;
                            }
                        }
                        finally
                        {
                            semaphore.Release();
                            _semaphores.TryRemove(shellSettings.Name, out semaphore);
                        }
                    }

                    await _next.Invoke(httpContext);

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();
                    hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false;
                }

                // Create a new scope only if there are pending tasks
                if (hasPendingTasks)
                {
                    shellContext = _orchardHost.GetOrCreateShellContext(shellSettings);

                    using (var scope = shellContext.EnterServiceScope())
                    {
                        var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();
                        var context            = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(context);
                    }
                }
            }
        }
        public async Task Invoke(HttpContext httpContext)
        {
            _host.Initialize();

            var engineSettings = _runningEngineTable.Match(httpContext);

            if (engineSettings != null)
            {
                var engineContext = _host.GetOrCreateEngineContext(engineSettings);

                var hasPendingTasks = false;
                using (var scope = engineContext.EnterServiceScope())
                {
                    httpContext.Features.Set(engineContext);
                    // 把租户前缀加到 cookie 里
                    httpContext.Response.Cookies.Append("tenant_prefix", engineSettings.RequestUrlPrefix ?? "");
                    httpContext.Response.Cookies.Append("tenant_host", engineSettings.RequestUrlHost ?? "");

                    if (!engineContext.IsActivated)
                    {
                        var semaphore = _semaphores.GetOrAdd(engineSettings.Name, (name) => new SemaphoreSlim(1));

                        await semaphore.WaitAsync();

                        try
                        {
                            if (!engineContext.IsActivated)
                            {
                                using (var activatingScope = engineContext.EnterServiceScope())
                                {
                                    var tenantEvents = activatingScope.ServiceProvider.GetServices <IModuleTenantEvents>();

                                    foreach (var tenantEvent in tenantEvents)
                                    {
                                        await tenantEvent.ActivatingAsync();
                                    }

                                    httpContext.Items["BuildPipeline"] = true;

                                    foreach (var tenantEvent in tenantEvents.Reverse())
                                    {
                                        await tenantEvent.ActivatedAsync();
                                    }
                                }

                                engineContext.IsActivated = true;
                            }
                        }
                        finally
                        {
                            semaphore.Release();
                            _semaphores.TryRemove(engineSettings.Name, out semaphore);
                        }
                    }

                    await _next.Invoke(httpContext);

                    var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();
                    hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false;
                }

                if (hasPendingTasks)
                {
                    engineContext = _host.GetOrCreateEngineContext(engineSettings);

                    using (var scope = engineContext.EnterServiceScope())
                    {
                        var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>();
                        var context            = new DeferredTaskContext(scope.ServiceProvider);
                        await deferredTaskEngine.ExecuteTasksAsync(context);
                    }
                }
            }
        }
        private static async Task FlushAsync(DeferredTaskContext context, IEnumerable <IndexingTask> tasks)
        {
            var localQueue = new List <IndexingTask>(tasks);

            var serviceProvider = context.ServiceProvider;

            var dbConnectionAccessor = serviceProvider.GetService <IDbConnectionAccessor>();
            var shellSettings        = serviceProvider.GetService <ShellSettings>();
            var logger      = serviceProvider.GetService <ILogger <IndexingTaskManager> >();
            var tablePrefix = shellSettings["TablePrefix"];

            if (!String.IsNullOrEmpty(tablePrefix))
            {
                tablePrefix += '_';
            }

            var contentItemIds = new HashSet <string>();

            // Remove duplicate tasks, only keep the last one
            for (var i = localQueue.Count; i > 0; i--)
            {
                var task = localQueue[i - 1];

                if (contentItemIds.Contains(task.ContentItemId))
                {
                    localQueue.RemoveAt(i - 1);
                }
                else
                {
                    contentItemIds.Add(task.ContentItemId);
                }
            }

            // At this point, content items ids should be unique in localQueue
            var ids   = localQueue.Select(x => x.ContentItemId).ToArray();
            var table = $"{tablePrefix}{nameof(IndexingTask)}";

            using (var connection = dbConnectionAccessor.CreateConnection())
            {
                await connection.OpenAsync();

                using (var transaction = connection.BeginTransaction())
                {
                    var dialect = SqlDialectFactory.For(transaction.Connection);

                    try
                    {
                        if (logger.IsEnabled(LogLevel.Debug))
                        {
                            logger.LogDebug($"Updating indexing tasks: {String.Join(", ", tasks.Select(x => x.ContentItemId))}");
                        }

                        // Page delete statements to prevent the limits from IN sql statements
                        var pageSize = 100;

                        var deleteCmd = $"delete from {dialect.QuoteForTableName(table)} where {dialect.QuoteForColumnName("ContentItemId")} {dialect.InOperator("@Ids")};";

                        do
                        {
                            var pageOfIds = ids.Take(pageSize).ToArray();

                            if (pageOfIds.Any())
                            {
                                await transaction.Connection.ExecuteAsync(deleteCmd, new { Ids = pageOfIds }, transaction);

                                ids = ids.Skip(pageSize).ToArray();
                            }
                        } while (ids.Any());

                        var insertCmd = $"insert into {dialect.QuoteForTableName(table)} ({dialect.QuoteForColumnName("CreatedUtc")}, {dialect.QuoteForColumnName("ContentItemId")}, {dialect.QuoteForColumnName("Type")}) values (@CreatedUtc, @ContentItemId, @Type);";
                        await transaction.Connection.ExecuteAsync(insertCmd, localQueue, transaction);

                        transaction.Commit();
                    }
                    catch (Exception e)
                    {
                        logger.LogError(e, "An error occured while updating indexing tasks");
                        throw;
                    }
                }
            }
        }