From cc9beed83591c431e8a14f7b7c9e07ad104620a8 Mon Sep 17 00:00:00 2001 From: Kate Shin Date: Thu, 26 Oct 2017 10:54:22 -0700 Subject: [PATCH] Fix file browser cancel/connetion issues (#528) * clean bindingqueue after cancel * fileborwser fixes * code cleanup * re-initialize filetree whenever filter is changed * fix test issues * address pr comments --- .../Contracts/FileBrowserCloseRequest.cs | 10 + .../FileBrowser/FileBrowserBase.cs | 8 +- .../FileBrowser/FileBrowserOperation.cs | 37 +-- .../FileBrowser/FileBrowserService.cs | 241 +++++++++++------- .../LanguageServices/BindingQueue.cs | 14 + .../LanguageServices/IBindingContext.cs | 2 +- .../FileBrowser/FileBrowserServiceTests.cs | 4 +- .../FileBrowser/FileBrowserTests.cs | 4 +- 8 files changed, 195 insertions(+), 125 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs index 28dd3d55..67646ed3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/Contracts/FileBrowserCloseRequest.cs @@ -42,4 +42,14 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts RequestType Type = RequestType.Create("filebrowser/close"); } + + /// + /// Notification for close completion + /// + public class FileBrowserClosedNotification + { + public static readonly + EventType Type = + EventType.Create("filebrowser/closecomplete"); + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs index 0152d307..62a51129 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserBase.cs @@ -30,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser public abstract class FileBrowserBase { private Enumerator enumerator = null; - protected SqlConnection sqlConnection = null; + protected ServerConnection connection = null; protected Enumerator Enumerator { @@ -49,7 +49,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser /// Returns the PathSeparator values of the Server. /// /// PathSeparator - internal static char GetPathSeparator(Enumerator enumerator, SqlConnection connection) + internal static char GetPathSeparator(Enumerator enumerator, ServerConnection connection) { var req = new Request(); req.Urn = "Server"; @@ -78,7 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser /// /// /// - internal static IEnumerable EnumerateDrives(Enumerator enumerator, SqlConnection connection) + internal static IEnumerable EnumerateDrives(Enumerator enumerator, ServerConnection connection) { // if not supplied, server name will be obtained from urn Request req = new Request(); @@ -145,7 +145,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser /// /// /// - internal static IEnumerable EnumerateFilesInFolder(Enumerator enumerator, SqlConnection connection, string path) + internal static IEnumerable EnumerateFilesInFolder(Enumerator enumerator, ServerConnection connection, string path) { var request = new Request { diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs index f5b99004..a744b37e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserOperation.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; using System.Globalization; using System.Text.RegularExpressions; using System.Threading; +using Microsoft.SqlServer.Management.Common; using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts; namespace Microsoft.SqlTools.ServiceLayer.FileBrowser @@ -30,21 +30,13 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser /// /// Initializes a new instance of the class. /// - public FileBrowserOperation() + /// The connection object + /// The file extension filters + public FileBrowserOperation(ServerConnection connection, string expandPath, string[] fileFilters = null) { - this.fileTree = new FileTree(); this.cancelSource = new CancellationTokenSource(); this.cancelToken = cancelSource.Token; - } - - /// - /// Initializes a new instance of the class. - /// - /// The connection info - /// The file extension filters - public FileBrowserOperation(SqlConnection connectionInfo, string expandPath, string[] fileFilters = null): this() - { - this.sqlConnection = connectionInfo; + this.connection = connection; this.Initialize(expandPath, fileFilters); } @@ -76,14 +68,6 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser } } - public SqlConnection SqlConnection - { - get - { - return this.sqlConnection; - } - } - public bool IsCancellationRequested { get @@ -100,6 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser public void Initialize(string expandPath, string[] fileFilters) { + this.fileTree = new FileTree(); this.expandPath = expandPath; if (fileFilters == null) { @@ -113,9 +98,9 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser public void Dispose() { - if (this.sqlConnection != null) + if (this.connection != null) { - this.sqlConnection.Close(); + this.connection.Disconnect(); } this.cancelSource.Dispose(); } @@ -123,7 +108,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser public void PopulateFileTree() { this.fileTreeCreated = false; - this.PathSeparator = GetPathSeparator(this.Enumerator, this.sqlConnection); + this.PathSeparator = GetPathSeparator(this.Enumerator, this.connection); PopulateDrives(); ExpandSelectedNode(this.expandPath); this.fileTreeCreated = true; @@ -191,7 +176,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser bool first = true; if (!cancelToken.IsCancellationRequested) { - foreach (var fileInfo in EnumerateDrives(Enumerator, sqlConnection)) + foreach (var fileInfo in EnumerateDrives(Enumerator, connection)) { if (cancelToken.IsCancellationRequested) { @@ -221,7 +206,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser public List GetChildren(string filePath) { List children = new List(); - foreach (var file in EnumerateFilesInFolder(Enumerator, sqlConnection, filePath)) + foreach (var file in EnumerateFilesInFolder(Enumerator, connection, filePath)) { bool isFile = !string.IsNullOrEmpty(file.fileName); FileTreeNode treeNode = new FileTreeNode(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs index cd3c2da0..34f06d46 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/FileBrowser/FileBrowserService.cs @@ -13,6 +13,7 @@ using Microsoft.SqlTools.ServiceLayer.FileBrowser.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.FileBrowser { @@ -28,8 +29,8 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser private readonly ConcurrentDictionary ownerToFileBrowserMap = new ConcurrentDictionary(); private readonly ConcurrentDictionary validatePathsCallbackMap = new ConcurrentDictionary(); private ConnectionService connectionService; - private ConnectedBindingQueue expandNodeQueue = new ConnectedBindingQueue(needsMetadata: false); - private static int DefaultExpandTimeout = 120000; + private ConnectedBindingQueue fileBrowserQueue = new ConnectedBindingQueue(needsMetadata: false); + private static int DefaultTimeout = 120000; private string serviceName = "FileBrowser"; /// @@ -45,7 +46,7 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser if (connectionService == null) { connectionService = ConnectionService.Instance; - connectionService.RegisterConnectedQueue(this.serviceName, this.expandNodeQueue); + connectionService.RegisterConnectedQueue(this.serviceName, this.fileBrowserQueue); } return connectionService; } @@ -94,8 +95,9 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser .ContinueWithOnFaulted(null); await requestContext.SendResult(true); } - catch + catch (Exception ex) { + Logger.Write(LogLevel.Error, "Unexpected exception while handling file browser open request: " + ex.Message); await requestContext.SendResult(false); } } @@ -108,8 +110,9 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser .ContinueWithOnFaulted(null); await requestContext.SendResult(true); } - catch + catch (Exception ex) { + Logger.Write(LogLevel.Error, "Unexpected exception while handling file browser expand request: " + ex.Message); await requestContext.SendResult(false); } } @@ -122,8 +125,9 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser .ContinueWithOnFaulted(null); await requestContext.SendResult(true); } - catch + catch (Exception ex) { + Logger.Write(LogLevel.Error, "Unexpected exception while handling file browser validate request: " + ex.Message); await requestContext.SendResult(false); } } @@ -132,90 +136,139 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser FileBrowserCloseParams fileBrowserParams, RequestContext requestContext) { - FileBrowserCloseResponse response = new FileBrowserCloseResponse(); - FileBrowserOperation removedOperation; - response.Succeeded = ownerToFileBrowserMap.TryRemove(fileBrowserParams.OwnerUri, out removedOperation); - - if (removedOperation != null && this.expandNodeQueue != null) + try { - bool hasPendingQueueItems = this.expandNodeQueue.HasPendingQueueItems; - if (removedOperation.FileTreeCreated && !hasPendingQueueItems) - { - removedOperation.Dispose(); - this.expandNodeQueue.CloseConnections(removedOperation.SqlConnection.DataSource, removedOperation.SqlConnection.Database, DefaultExpandTimeout); - } - else if (!removedOperation.FileTreeCreated) - { - removedOperation.Cancel(); - } - else if (hasPendingQueueItems) - { - this.expandNodeQueue.StopQueueProcessor(DefaultExpandTimeout); - } + var task = Task.Run(() => RunFileBrowserCloseTask(fileBrowserParams, requestContext)) + .ContinueWithOnFaulted(null); + await requestContext.SendResult(new FileBrowserCloseResponse() { Succeeded = true }); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Unexpected exception while handling file browser close request: " + ex.Message); + await requestContext.SendResult(new FileBrowserCloseResponse() { Message = ex.Message }); } - - await requestContext.SendResult(response); } #endregion public void Dispose() { - this.expandNodeQueue.Dispose(); + this.fileBrowserQueue.Dispose(); + } + + internal async Task RunFileBrowserCloseTask(FileBrowserCloseParams fileBrowserParams, RequestContext requestContext) + { + FileBrowserCloseResponse result = new FileBrowserCloseResponse(); + try + { + FileBrowserOperation operation = null; + ConnectionInfo connInfo; + ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out operation); + this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo); + + if (operation != null && connInfo != null) + { + if (!operation.FileTreeCreated) + { + operation.Cancel(); + } + + // Clear queued items + this.fileBrowserQueue.ClearQueuedItems(); + + // Queue operation to clean up resources + QueueItem queueItem = fileBrowserQueue.QueueBindingOperation( + key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName), + bindingTimeout: DefaultTimeout, + waitForLockTimeout: DefaultTimeout, + bindOperation: (bindingContext, cancelToken) => + { + FileBrowserOperation removedOperation = null; + ownerToFileBrowserMap.TryRemove(fileBrowserParams.OwnerUri, out removedOperation); + if (removedOperation != null) + { + removedOperation.Dispose(); + } + result.Succeeded = true; + return result; + }); + + queueItem.ItemProcessed.WaitOne(); + if (queueItem.GetResultAsT() != null) + { + result = queueItem.GetResultAsT(); + } + + this.fileBrowserQueue.CloseConnections(connInfo.ConnectionDetails.ServerName, connInfo.ConnectionDetails.DatabaseName, DefaultTimeout); + } + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error, "Unexpected exception while closing file browser: " + ex.Message); + result.Message = ex.Message; + } + + await requestContext.SendEvent(FileBrowserClosedNotification.Type, result); } internal async Task RunFileBrowserOpenTask(FileBrowserOpenParams fileBrowserParams, RequestContext requestContext) { FileBrowserOpenedParams result = new FileBrowserOpenedParams(); - SqlConnection conn = null; FileBrowserOperation browser = null; bool isCancelRequested = false; - if (this.expandNodeQueue.IsCancelRequested) - { - this.expandNodeQueue.StartQueueProcessor(); - } - try { - if (!fileBrowserParams.ChangeFilter) + ConnectionInfo connInfo; + this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo); + if (connInfo != null) { - ConnectionInfo connInfo; - this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo); - if (connInfo != null) - { - // Open new connection for each Open request - conn = ConnectionService.OpenSqlConnection(connInfo, this.serviceName); - browser = new FileBrowserOperation(conn, fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters); - } - } - else - { - ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out browser); - if (browser != null) - { - browser.Initialize(fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters); - } - } + QueueItem queueItem = fileBrowserQueue.QueueBindingOperation( + key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName), + bindingTimeout: DefaultTimeout, + waitForLockTimeout: DefaultTimeout, + bindOperation: (bindingContext, cancelToken) => + { + if (!fileBrowserParams.ChangeFilter) + { + browser = new FileBrowserOperation(bindingContext.ServerConnection, fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters); + } + else + { + ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out browser); + if (browser != null) + { + browser.Initialize(fileBrowserParams.ExpandPath, fileBrowserParams.FileFilters); + } + } - if (browser != null) - { - ownerToFileBrowserMap.AddOrUpdate(fileBrowserParams.OwnerUri, browser, (key, value) => browser); + if (browser != null) + { + ownerToFileBrowserMap.AddOrUpdate(fileBrowserParams.OwnerUri, browser, (key, value) => browser); - // Create file browser tree - browser.PopulateFileTree(); + // Create file browser tree + browser.PopulateFileTree(); - // Check if cancel was requested - if (browser.IsCancellationRequested) + if (browser.IsCancellationRequested) + { + isCancelRequested = true; + } + else + { + result.OwnerUri = fileBrowserParams.OwnerUri; + result.FileTree = browser.FileTree; + result.Succeeded = true; + } + } + + return result; + }); + + queueItem.ItemProcessed.WaitOne(); + + if (queueItem.GetResultAsT() != null) { - browser.Dispose(); - isCancelRequested = true; - } - else - { - result.OwnerUri = fileBrowserParams.OwnerUri; - result.FileTree = browser.FileTree; - result.Succeeded = true; + result = queueItem.GetResultAsT(); } } } @@ -235,32 +288,29 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser FileBrowserExpandedParams result = new FileBrowserExpandedParams(); try { - FileBrowserOperation operation; + FileBrowserOperation operation = null; ConnectionInfo connInfo; - result.Succeeded = ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out operation); + ownerToFileBrowserMap.TryGetValue(fileBrowserParams.OwnerUri, out operation); this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo); - if (result.Succeeded && operation != null && connInfo != null) + if (operation != null && connInfo != null) { - QueueItem queueItem = expandNodeQueue.QueueBindingOperation( - key: expandNodeQueue.AddConnectionContext(connInfo, this.serviceName), - bindingTimeout: DefaultExpandTimeout, - waitForLockTimeout: DefaultExpandTimeout, + QueueItem queueItem = fileBrowserQueue.QueueBindingOperation( + key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName), + bindingTimeout: DefaultTimeout, + waitForLockTimeout: DefaultTimeout, bindOperation: (bindingContext, cancelToken) => { result.ExpandPath = fileBrowserParams.ExpandPath; result.Children = operation.GetChildren(fileBrowserParams.ExpandPath).ToArray(); result.OwnerUri = fileBrowserParams.OwnerUri; + result.Succeeded = true; return result; }); queueItem.ItemProcessed.WaitOne(); - if (this.expandNodeQueue.IsCancelRequested) - { - this.expandNodeQueue.CloseConnections(operation.SqlConnection.DataSource, operation.SqlConnection.Database, DefaultExpandTimeout); - } - else if (queueItem.GetResultAsT() != null) + if (queueItem.GetResultAsT() != null) { result = queueItem.GetResultAsT(); } @@ -268,7 +318,6 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser } catch (Exception ex) { - result.Succeeded = false; result.Message = ex.Message; } @@ -282,22 +331,36 @@ namespace Microsoft.SqlTools.ServiceLayer.FileBrowser try { ValidatePathsCallback callback; + ConnectionInfo connInfo; + this.ConnectionServiceInstance.TryFindConnection(fileBrowserParams.OwnerUri, out connInfo); if (validatePathsCallbackMap.TryGetValue(fileBrowserParams.ServiceType, out callback) && callback != null + && connInfo != null && fileBrowserParams.SelectedFiles != null && fileBrowserParams.SelectedFiles.Length > 0) { - string errorMessage; - result.Succeeded = callback(new FileBrowserValidateEventArgs - { - ServiceType = fileBrowserParams.ServiceType, - OwnerUri = fileBrowserParams.OwnerUri, - FilePaths = fileBrowserParams.SelectedFiles - }, out errorMessage); + QueueItem queueItem = fileBrowserQueue.QueueBindingOperation( + key: fileBrowserQueue.AddConnectionContext(connInfo, this.serviceName), + bindingTimeout: DefaultTimeout, + waitForLockTimeout: DefaultTimeout, + bindOperation: (bindingContext, cancelToken) => + { + string errorMessage; + result.Succeeded = callback(new FileBrowserValidateEventArgs + { + ServiceType = fileBrowserParams.ServiceType, + OwnerUri = fileBrowserParams.OwnerUri, + FilePaths = fileBrowserParams.SelectedFiles + }, out errorMessage); + result.Message = errorMessage; + return result; + }); - if (!string.IsNullOrEmpty(errorMessage)) + queueItem.ItemProcessed.WaitOne(); + + if (queueItem.GetResultAsT() != null) { - result.Message = errorMessage; + result = queueItem.GetResultAsT(); } } else diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs index 62be60e3..71dfa99a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs @@ -388,6 +388,20 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } + /// + /// Clear queued items + /// + public void ClearQueuedItems() + { + lock (this.bindingQueueLock) + { + if (this.bindingQueue.Count > 0) + { + this.bindingQueue.Clear(); + } + } + } + public void Dispose() { if (this.processQueueCancelToken != null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs index aa4637b3..605635d7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs @@ -76,6 +76,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Gets or sets the database compatibility level /// - DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; } + DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/FileBrowser/FileBrowserServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/FileBrowser/FileBrowserServiceTests.cs index bc5f2332..4193e094 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/FileBrowser/FileBrowserServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/FileBrowser/FileBrowserServiceTests.cs @@ -91,9 +91,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.FileBrowser }; await service.HandleFileBrowserCloseRequest(inputParams, requestContext.Object); - - // Result should return false since it's trying to close a filebrowser that was never opened - requestContext.Verify(x => x.SendResult(It.Is(p => p.Succeeded == false))); + requestContext.Verify(x => x.SendResult(It.Is(p => p.Succeeded == true))); } #endregion diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/FileBrowser/FileBrowserTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/FileBrowser/FileBrowserTests.cs index 026645ca..5e1fd8c9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/FileBrowser/FileBrowserTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/FileBrowser/FileBrowserTests.cs @@ -28,7 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.FileBrowser [Fact] public void FilterFilesTest() { - FileBrowserOperation operation = new FileBrowserOperation(); + FileBrowserOperation operation = new FileBrowserOperation(null, "", null); string[] supportedFilePaths = new string[] {"te\\s/t1.txt", "te!s.t2.bak" }; string[] unsupportedFilePaths = new string[] { "te.s*/t3.jpg", "t_est4.trn" }; string[] filters = new string[] { "*.txt", "*.bak"}; @@ -47,7 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.FileBrowser [Fact] public void ExpandNodeShouldThrowExceptionForInvalidPath() { - FileBrowserOperation operation = new FileBrowserOperation(); + FileBrowserOperation operation = new FileBrowserOperation(null, "", null); Exception exception = null; try