// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Channel; namespace Microsoft.SqlTools.Utility { /// /// SQL Tools Service request handler for any utility services. Provides the entire JSON RPC /// implementation for sending/receiving JSON requests and dispatching the requests to /// handlers that are registered prior to startup. /// public sealed class UtilityServiceHost : ServiceHostBase { /// /// This timeout limits the amount of time that shutdown tasks can take to complete /// prior to the process shutting down. /// private const int ShutdownTimeoutInSeconds = 120; #region Singleton Instance Code /// /// Singleton instance of the service host for internal storage /// private static readonly Lazy instance = new Lazy(() => new UtilityServiceHost()); /// /// Current instance of the ServiceHost /// public static UtilityServiceHost Instance { get { return instance.Value; } } /// /// Constructs new instance of ServiceHost using the host and profile details provided. /// Access is private to ensure only one instance exists at a time. /// private UtilityServiceHost() : base(new StdioServerChannel()) { // Initialize the shutdown activities shutdownCallbacks = new List(); initializeCallbacks = new List(); } /// /// Provide initialization that must occur after the service host is started /// public void InitializeRequestHandlers() { // Register the requests that this service host will handle this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); this.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequest); this.SetRequestHandler(VersionRequest.Type, HandleVersionRequest); } #endregion #region Member Variables /// /// Delegate definition for the host shutdown event /// /// /// public delegate Task ShutdownCallback(object shutdownParams, RequestContext shutdownRequestContext); /// /// Delegate definition for the host initialization event /// /// /// public delegate Task InitializeCallback(InitializeRequest startupParams, RequestContext requestContext); private readonly List shutdownCallbacks; private readonly List initializeCallbacks; private static readonly Version serviceVersion = Assembly.GetEntryAssembly().GetName().Version; #endregion #region Public Methods /// /// Adds a new callback to be called when the shutdown request is submitted /// /// Callback to perform when a shutdown request is submitted public void RegisterShutdownTask(ShutdownCallback callback) { shutdownCallbacks.Add(callback); } /// /// Add a new method to be called when the initialize request is submitted /// /// Callback to perform when an initialize request is submitted public void RegisterInitializeTask(InitializeCallback callback) { initializeCallbacks.Add(callback); } #endregion #region Request Handlers /// /// Handles the shutdown event for the Language Server /// private async Task HandleShutdownRequest(object shutdownParams, RequestContext requestContext) { Logger.Write(TraceEventType.Information, "Service host is shutting down..."); // Call all the shutdown methods provided by the service components Task[] shutdownTasks = shutdownCallbacks.Select(t => t(shutdownParams, requestContext)).ToArray(); TimeSpan shutdownTimeout = TimeSpan.FromSeconds(ShutdownTimeoutInSeconds); // shut down once all tasks are completed, or after the timeout expires, whichever comes first. await Task.WhenAny(Task.WhenAll(shutdownTasks), Task.Delay(shutdownTimeout)).ContinueWith(t => Environment.Exit(0)); } /// /// Handles the initialization request /// /// /// /// internal async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext requestContext) { // Call all tasks that registered on the initialize request var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext)); await Task.WhenAll(initializeTasks); // TODO: Figure out where this needs to go to be agnostic of the language // Send back what this server can do await requestContext.SendResult( new InitializeResult { Capabilities = new ServerCapabilities { DefinitionProvider = false, ReferencesProvider = false, DocumentFormattingProvider = false, DocumentRangeFormattingProvider = false, DocumentHighlightProvider = false, HoverProvider = false } }); } /// /// Handles the version request. Sends back the server version as result. /// private static async Task HandleVersionRequest( object versionRequestParams, RequestContext requestContext) { await requestContext.SendResult(serviceVersion.ToString()); } #endregion } }