From e4185a85c644aee5648db7f925b114e8918d75e6 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 26 Jul 2021 16:05:38 -0400 Subject: [PATCH 1/4] 7802 initial move of file search by attrs off EDT --- .../autopsy/filesearch/FileSearchPanel.java | 156 +++++++++++------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index 4801227416..fb13a317fa 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,15 +29,19 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; @@ -56,9 +60,12 @@ import org.sleuthkit.datamodel.TskCoreException; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchPanel extends javax.swing.JPanel { + private static final Logger logger = Logger.getLogger(FileSearchPanel.class.getName()); + private static final long serialVersionUID = 1L; private final List filters = new ArrayList<>(); private static int resultWindowCount = 0; //keep track of result windows so they get unique names private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text"); + private static SwingWorker searchWorker = null; enum EVENT { CHECKED @@ -77,39 +84,39 @@ class FileSearchPanel extends javax.swing.JPanel { * This method is called from within the constructor to initialize the form. */ private void customizeComponents() { - + JLabel label = new JLabel(NbBundle.getMessage(this.getClass(), "FileSearchPanel.custComp.label.text")); label.setAlignmentX(Component.LEFT_ALIGNMENT); label.setBorder(new EmptyBorder(0, 0, 10, 0)); - + JPanel panel1 = new JPanel(); - panel1.setLayout(new GridLayout(1,2)); + panel1.setLayout(new GridLayout(1, 2)); panel1.add(new JLabel("")); JPanel panel2 = new JPanel(); - panel2.setLayout(new GridLayout(1,2, 20, 0)); + panel2.setLayout(new GridLayout(1, 2, 20, 0)); JPanel panel3 = new JPanel(); - panel3.setLayout(new GridLayout(1,2, 20, 0)); + panel3.setLayout(new GridLayout(1, 2, 20, 0)); JPanel panel4 = new JPanel(); - panel4.setLayout(new GridLayout(1,2, 20, 0)); + panel4.setLayout(new GridLayout(1, 2, 20, 0)); JPanel panel5 = new JPanel(); - panel5.setLayout(new GridLayout(1,2, 20, 0)); + panel5.setLayout(new GridLayout(1, 2, 20, 0)); // Create and add filter areas - NameSearchFilter nameFilter = new NameSearchFilter(); + NameSearchFilter nameFilter = new NameSearchFilter(); SizeSearchFilter sizeFilter = new SizeSearchFilter(); DateSearchFilter dateFilter = new DateSearchFilter(); KnownStatusSearchFilter knowStatusFilter = new KnownStatusSearchFilter(); HashSearchFilter hashFilter = new HashSearchFilter(); MimeTypeFilter mimeTypeFilter = new MimeTypeFilter(); DataSourceFilter dataSourceFilter = new DataSourceFilter(); - - panel2.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.name"),nameFilter)); - - panel3.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"),sizeFilter)); - - panel2.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), dateFilter)); + + panel2.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.name"), nameFilter)); + + panel3.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), sizeFilter)); + + panel2.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), dateFilter)); panel3.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.knownStatus"), knowStatusFilter)); - + panel5.add(new FilterArea(NbBundle.getMessage(this.getClass(), "HashSearchPanel.md5CheckBox.text"), hashFilter)); panel5.add(new JLabel("")); panel4.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), mimeTypeFilter)); @@ -119,7 +126,7 @@ class FileSearchPanel extends javax.swing.JPanel { filterPanel.add(panel3); filterPanel.add(panel4); filterPanel.add(panel5); - + filters.add(nameFilter); filters.add(sizeFilter); filters.add(dateFilter); @@ -127,7 +134,7 @@ class FileSearchPanel extends javax.swing.JPanel { filters.add(hashFilter); filters.add(mimeTypeFilter); filters.add(dataSourceFilter); - + for (FileSearchFilter filter : this.getFilters()) { filter.addPropertyChangeListener(new PropertyChangeListener() { @Override @@ -141,7 +148,7 @@ class FileSearchPanel extends javax.swing.JPanel { public void actionPerformed(ActionEvent e) { search(); } - }); + }); searchButton.setEnabled(isValidSearch()); } @@ -170,54 +177,77 @@ class FileSearchPanel extends javax.swing.JPanel { */ @NbBundle.Messages("FileSearchPanel.emptyNode.display.text=No results found.") private void search() { - // change the cursor to "waiting cursor" for this operation - this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + if (searchWorker != null && searchWorker.isDone()) { + searchWorker.cancel(true); + } try { if (this.isValidSearch()) { - String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); - String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); - + // change the cursor to "waiting cursor" for this operation + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // try to get the number of matches first Case currentCase = Case.getCurrentCaseThrows(); // get the most updated case - long totalMatches = 0; - List contentList = null; - try { - SleuthkitCase tskDb = currentCase.getSleuthkitCase(); - contentList = tskDb.findAllFilesWhere(this.getQuery()); + searchWorker = new SwingWorker() { + List contentList = null; + TableFilterNode tableFilterNode = null; - } catch (TskCoreException ex) { - Logger logger = Logger.getLogger(this.getClass().getName()); - logger.log(Level.WARNING, "Error while trying to get the number of matches.", ex); //NON-NLS - } + @Override + protected Void doInBackground() throws Exception { + try { + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + contentList = tskDb.findAllFilesWhere(getQuery()); - if (contentList == null) { - contentList = Collections.emptyList(); - } + } catch (TskCoreException ex) { + Logger logger = Logger.getLogger(this.getClass().getName()); + logger.log(Level.WARNING, "Error while trying to get the number of matches.", ex); //NON-NLS + } + if (contentList == null) { + contentList = Collections.emptyList(); + } + SearchNode sn = new SearchNode(contentList); + tableFilterNode = new TableFilterNode(sn, true, sn.getName()); + return null; + } - SearchNode sn = new SearchNode(contentList); - TableFilterNode tableFilterNode = new TableFilterNode(sn, true, sn.getName()); - final TopComponent searchResultWin; - if (contentList.isEmpty()) { - Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); - searchResultWin = DataResultTopComponent.createInstance(title, pathText, - emptyNode, 0); - } else { - searchResultWin = DataResultTopComponent.createInstance(title, pathText, - tableFilterNode, contentList.size()); - } - searchResultWin.requestActive(); // make it the active top component + @Override + protected void done() { - /** - * If total matches more than 1000, pop up a dialog box that say - * the performance maybe be slow and to increase the - * performance, tell the users to refine their search. - */ - if (totalMatches > 10000) { - // show info - String msg = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.msg", totalMatches); - String details = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.details"); - MessageNotifyUtil.Notify.info(msg, details); - } + try { + get(); + String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); + String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); + final TopComponent searchResultWin; + if (contentList.isEmpty() || tableFilterNode == null) { + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); + searchResultWin = DataResultTopComponent.createInstance(title, pathText, + emptyNode, 0); + } else { + searchResultWin = DataResultTopComponent.createInstance(title, pathText, + tableFilterNode, contentList.size()); + } + searchResultWin.requestActive(); // make it the active top component + + /** + * If total matches more than 1000, pop up a dialog + * box that say the performance maybe be slow and to + * increase the performance, tell the users to + * refine their search. + */ + if (contentList.size() > 10000) { + // show info + String msg = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.msg", contentList.size()); + String details = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.details"); + MessageNotifyUtil.Notify.info(msg, details); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Error while performing file search by attributes", ex); + } catch (CancellationException ex) { + logger.log(Level.INFO, "File search by attributes was cancelled", ex); + } finally { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }; + searchWorker.execute(); } else { throw new FilterValidationException( NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.exception.noFilterSelected.msg")); @@ -226,8 +256,6 @@ class FileSearchPanel extends javax.swing.JPanel { NotifyDescriptor d = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.validationErr.msg", ex.getMessage())); DialogDisplayer.getDefault().notify(d); - } finally { - this.setCursor(null); } } From 0e5598f4614ca75c45aa239365b0a63daaf4cdcc Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 30 Jul 2021 16:36:09 -0400 Subject: [PATCH 2/4] 7802 improve search in progress indicator and cancellation support --- .../filesearch/Bundle.properties-MERGED | 3 + .../autopsy/filesearch/FileSearchPanel.java | 60 ++++++++++++------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED index 3f2af1ea8a..075a0e7afb 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED @@ -1,7 +1,10 @@ DataSourceFilter.errorMessage.emptyDataSource=At least one data source must be selected. DateSearchFilter.errorMessage.endDateBeforeStartDate=The end date should be after the start date. DateSearchFilter.errorMessage.noCheckboxSelected=At least one date type checkbox must be selected. +FileSearchPanel.cancelledSearch.text=Search Was Cancelled FileSearchPanel.emptyNode.display.text=No results found. +FileSearchPanel.searchingNode.display.text=Performing file search by attributes. Please wait. +FileSearchPanel.searchingPath.text=File Search In Progress HashSearchFilter.errorMessage.emptyHash=Hash data is empty. HashSearchFilter.errorMessage.wrongCharacter=MD5 contains invalid hex characters. # {0} - hash data length diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index fb13a317fa..c76892a192 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -65,7 +65,7 @@ class FileSearchPanel extends javax.swing.JPanel { private final List filters = new ArrayList<>(); private static int resultWindowCount = 0; //keep track of result windows so they get unique names private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text"); - private static SwingWorker searchWorker = null; + private static SwingWorker searchWorker = null; enum EVENT { CHECKED @@ -175,23 +175,30 @@ class FileSearchPanel extends javax.swing.JPanel { * Action when the "Search" button is pressed. * */ - @NbBundle.Messages("FileSearchPanel.emptyNode.display.text=No results found.") + @NbBundle.Messages({"FileSearchPanel.emptyNode.display.text=No results found.", + "FileSearchPanel.searchingNode.display.text=Performing file search by attributes. Please wait.", + "FileSearchPanel.searchingPath.text=File Search In Progress", + "FileSearchPanel.cancelledSearch.text=Search Was Cancelled"}) private void search() { if (searchWorker != null && searchWorker.isDone()) { searchWorker.cancel(true); } try { if (this.isValidSearch()) { - // change the cursor to "waiting cursor" for this operation - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // try to get the number of matches first Case currentCase = Case.getCurrentCaseThrows(); // get the most updated case - searchWorker = new SwingWorker() { + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); + String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); + String pathText = Bundle.FileSearchPanel_searchingPath_text(); + final DataResultTopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText, + emptyNode, 0); + searchResultWin.requestActive(); // make it the active top component + searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + searchWorker = new SwingWorker() { List contentList = null; - TableFilterNode tableFilterNode = null; @Override - protected Void doInBackground() throws Exception { + protected TableFilterNode doInBackground() throws Exception { try { SleuthkitCase tskDb = currentCase.getSleuthkitCase(); contentList = tskDb.findAllFilesWhere(getQuery()); @@ -203,27 +210,23 @@ class FileSearchPanel extends javax.swing.JPanel { if (contentList == null) { contentList = Collections.emptyList(); } + if (contentList.isEmpty()) { + return new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); + } SearchNode sn = new SearchNode(contentList); - tableFilterNode = new TableFilterNode(sn, true, sn.getName()); - return null; + return new TableFilterNode(sn, true, sn.getName()); } @Override protected void done() { - + String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); try { - get(); - String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); - String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); - final TopComponent searchResultWin; - if (contentList.isEmpty() || tableFilterNode == null) { - Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); - searchResultWin = DataResultTopComponent.createInstance(title, pathText, - emptyNode, 0); - } else { - searchResultWin = DataResultTopComponent.createInstance(title, pathText, - tableFilterNode, contentList.size()); + TableFilterNode tableFilterNode = get(); + if (tableFilterNode == null) { //just incase this get() gets modified to return null or somehow can return null + tableFilterNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); } + + searchResultWin.setNode(tableFilterNode); searchResultWin.requestActive(); // make it the active top component /** @@ -241,12 +244,25 @@ class FileSearchPanel extends javax.swing.JPanel { } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, "Error while performing file search by attributes", ex); } catch (CancellationException ex) { + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_cancelledSearch_text()), true); + searchResultWin.setNode(emptyNode); + pathText = Bundle.FileSearchPanel_cancelledSearch_text(); logger.log(Level.INFO, "File search by attributes was cancelled", ex); } finally { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + searchResultWin.setPath(pathText); + searchResultWin.requestActive(); // make it the active top component + searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } }; + searchResultWin.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("tcClosed") && !searchWorker.isDone() && evt.getOldValue() == null) { + searchWorker.cancel(true); + } + } + }); searchWorker.execute(); } else { throw new FilterValidationException( From 802d1235cabe74060db98d83bb160b17fba3177c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 30 Jul 2021 16:46:12 -0400 Subject: [PATCH 3/4] 7802 fix text for search in progress node --- Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index c76892a192..c55ab46d17 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -187,7 +187,7 @@ class FileSearchPanel extends javax.swing.JPanel { if (this.isValidSearch()) { // try to get the number of matches first Case currentCase = Case.getCurrentCaseThrows(); // get the most updated case - Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_searchingNode_display_text()), true); String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); String pathText = Bundle.FileSearchPanel_searchingPath_text(); final DataResultTopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText, @@ -260,6 +260,7 @@ class FileSearchPanel extends javax.swing.JPanel { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("tcClosed") && !searchWorker.isDone() && evt.getOldValue() == null) { searchWorker.cancel(true); + logger.log(Level.INFO, "User has closed the results window while search was in progress, search will be cancelled"); } } }); From b37d5fd3516fb4215d0b96f3a2c21ee430875433 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 30 Jul 2021 18:05:28 -0400 Subject: [PATCH 4/4] 7802 protect against NPE when search cancelled --- .../autopsy/filesearch/FileSearchPanel.java | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index c55ab46d17..62f6332c83 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -225,10 +225,10 @@ class FileSearchPanel extends javax.swing.JPanel { if (tableFilterNode == null) { //just incase this get() gets modified to return null or somehow can return null tableFilterNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); } - - searchResultWin.setNode(tableFilterNode); - searchResultWin.requestActive(); // make it the active top component - + if (searchResultWin != null && searchResultWin.isOpened()) { + searchResultWin.setNode(tableFilterNode); + searchResultWin.requestActive(); // make it the active top component + } /** * If total matches more than 1000, pop up a dialog * box that say the performance maybe be slow and to @@ -244,26 +244,32 @@ class FileSearchPanel extends javax.swing.JPanel { } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, "Error while performing file search by attributes", ex); } catch (CancellationException ex) { - Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_cancelledSearch_text()), true); - searchResultWin.setNode(emptyNode); - pathText = Bundle.FileSearchPanel_cancelledSearch_text(); + if (searchResultWin != null && searchResultWin.isOpened()) { + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_cancelledSearch_text()), true); + searchResultWin.setNode(emptyNode); + pathText = Bundle.FileSearchPanel_cancelledSearch_text(); + } logger.log(Level.INFO, "File search by attributes was cancelled", ex); } finally { - searchResultWin.setPath(pathText); - searchResultWin.requestActive(); // make it the active top component - searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (searchResultWin != null && searchResultWin.isOpened()) { + searchResultWin.setPath(pathText); + searchResultWin.requestActive(); // make it the active top component + searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } } } }; - searchResultWin.addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals("tcClosed") && !searchWorker.isDone() && evt.getOldValue() == null) { - searchWorker.cancel(true); - logger.log(Level.INFO, "User has closed the results window while search was in progress, search will be cancelled"); + if (searchResultWin != null && searchResultWin.isOpened()) { + searchResultWin.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("tcClosed") && !searchWorker.isDone() && evt.getOldValue() == null) { + searchWorker.cancel(true); + logger.log(Level.INFO, "User has closed the results window while search was in progress, search will be cancelled"); + } } - } - }); + }); + } searchWorker.execute(); } else { throw new FilterValidationException(