// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System; using System.Text; namespace Microsoft.SqlTools.ServiceLayer.Management { /// /// defines mandatory interface that an action must implement /// public interface IManagementAction { ExecutionMode LastExecutionResult { get; } /// /// performs custom action when user requests to cancel execution. /// /// void OnCancel(object sender); /// /// Overridable function that allow a derived class to implement its /// OnScript functionality. /// /// /// text of the generated script string OnScript(object sender); /// /// Overridable function that allow a derived class to implement its /// OnRunNow functionality /// /// void OnRunNow(object sender); /// /// Overridable function that allow a derived class to implement /// a finalizing action after a RunNow or RunNowAndClose were sucesfully executed /// NOTE: same as OnGatherUiInformation, this method is always called from UI thread /// /// void OnTaskCompleted(object sender, ExecutionMode executionMode, RunType executionType); } /// /// This class is responsible for executing panels one by one. /// It is reused by ViewSwitcherControlsManager and treepanelform classes /// internal class ExecutionHandlerDelegate { private object cancelCriticalSection = new object(); private IManagementAction managementAction; public ExecutionHandlerDelegate(IManagementAction managementAction) { this.managementAction = managementAction; } /// /// /// /// /// execution result public ExecutionMode Run(RunType runType, object sender) { //dispatch the call to the right method switch (runType) { case RunType.RunNow: this.managementAction.OnRunNow(sender); break; case RunType.ScriptToWindow: this.managementAction.OnScript(sender); break; default: throw new InvalidOperationException(SR.UnexpectedRunType); } if((this.managementAction.LastExecutionResult == ExecutionMode.Failure) || (this.managementAction.LastExecutionResult == ExecutionMode.Cancel)) { return this.managementAction.LastExecutionResult; } // if we're here, everything went fine return ExecutionMode.Success; } /// /// performs custom action wen user requests a cancel /// this is called from the UI thread /// /// public void Cancel(object sender) { lock (this.cancelCriticalSection) { this.managementAction.OnCancel(sender); } } } /// /// manager that hooks up tree view with the individual views /// internal sealed class ExecutionHandler : IDisposable { /// /// handler that we delegate execution related tasks to /// private ExecutionHandlerDelegate executionHandlerDelegate; /// /// class that describes available views that is also aware of execution /// private IExecutionAwareManagementAction managementAction; /// /// result of the last execution /// private ExecutionMode executionResult; /// /// exception that caused execution failure /// private Exception executionFailureException; /// /// text of the generated script if RunNow method was called last time with scripting option /// private StringBuilder script; /// /// creates instance of the class and returns service provider that aggregates the provider /// provider with extra services /// /// service provider from the host /// /// aggregates service provider that is derived from the host service provider and /// is extended with extra services and/or overriden services. The host should be /// using this provider whenever it has to specify an IServiceProvider to a component /// that will be managed by this class /// public ExecutionHandler(IExecutionAwareManagementAction managementAction) { this.managementAction = managementAction; this.executionHandlerDelegate = new ExecutionHandlerDelegate(managementAction); } #region public interface public ExecutionMode ExecutionResult { get { return this.executionResult; } } /// /// exception that caused execution failure /// public Exception ExecutionFailureException { get { return this.executionFailureException; } } /// /// text of the generated script if RunNow method was called last time with scripting option /// public string ScriptTextFromLastRun { get { if (this.script != null) { return this.script.ToString(); } else { return string.Empty; } } } /// /// we call the run now implementaion of the management action. /// If any exception is generated we stop the execution and we set the execution mode flag to failure. /// /// public void RunNow(RunType runType, object sender) { try { // reset some internal vars this.executionResult = ExecutionMode.Failure; // ensure that we have valid StringBulder for scripting if (IsScripting(runType)) { EnsureValidScriptBuilder(); } // do preprocess action. It is possible to do entire execution from inside this method if (this.managementAction != null) { PreProcessExecutionInfo preProcessInfo = new PreProcessExecutionInfo(runType); if (!this.managementAction.PreProcessExecution(preProcessInfo, out this.executionResult)) { // In case of scripting preProcessInfo.Script must contain text of the script if (executionResult == ExecutionMode.Success && IsScripting(runType) && preProcessInfo.Script != null) { this.script.Append(preProcessInfo.Script); } return; // result of execution is in executionResult } } // NOTE: post process action is done in finally block below // start executing this.executionResult = this.executionHandlerDelegate.Run(runType, sender); } #region error handling catch (OutOfMemoryException) { throw; } catch (System.Threading.ThreadAbortException) { throw; } catch (OperationCanceledException) { this.executionResult = ExecutionMode.Cancel; } catch (Exception e) { ProcessExceptionDuringExecution(e); return; } finally { //do postprocess action if (this.managementAction != null) { this.managementAction.PostProcessExecution(runType, this.executionResult); } } #endregion } /// /// Kicks off Cancel operation /// /// public void InitiateCancel(object sender) { if (this.managementAction != null) { if (!this.managementAction.Cancel()) { //everything was done inside this method this.executionResult = ExecutionMode.Cancel; return; } } //otherwise do cancel ourselves // if everything goes OK, Run() method will return with Cancel result this.executionHandlerDelegate.Cancel(sender); } /// /// is called by the host to do post execution actions /// /// /// /// public void OnTaskCompleted(object sender, ExecutionMode executionResult, RunType executionType) { } /// /// enables deterministic cleanup /// public void Dispose() { IDisposable managementActionAsDisposable = this.managementAction as IDisposable; if (managementActionAsDisposable != null) { managementActionAsDisposable.Dispose(); } } #endregion #region private helpers /// /// determines whether given run type corresponds to scripting or not /// /// /// private bool IsScripting(RunType runType) { return (runType == RunType.ScriptToClipboard || runType == RunType.ScriptToFile || runType == RunType.ScriptToWindow || runType == RunType.ScriptToJob); } /// /// ensure that we have valid StringBulder for scripting /// private void EnsureValidScriptBuilder() { if (this.script == null) { this.script = new StringBuilder(256); } else { this.script.Length = 0; } } /// /// helper function that is called when we caught an exception during execution /// /// private void ProcessExceptionDuringExecution(Exception ex) { // show the error this.executionResult = ExecutionMode.Failure; this.executionFailureException = ex; } #endregion } }