blob: 57b7a86927061654c26fd30c248af17c24fc402b [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2009 The University of Manchester
*
* Modifications to the initial code base are copyright of their respective
* authors, or their employers as appropriate.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package net.sf.taverna.t2.ui.perspectives.myexperiment.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import net.sf.taverna.t2.ui.perspectives.myexperiment.MainComponent;
import net.sf.taverna.t2.ui.perspectives.myexperiment.SearchResultsPanel;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
/**
* @author Sergejs Aleksejevs
*/
public class SearchEngine {
// holds a reference to the instance of the search thread in the current context
// that should be active at the moment (will aid early termination of older searches
// when new ones are started)
private Vector<Long> vCurrentSearchThreadID;
private boolean bSearchByTag; // indicates what kind of search this is
private String strSearchQuery; // stores the actual query
private SearchResultsPanel jpResultsPanel;
private MainComponent pluginMainComponent;
private MyExperimentClient myExperimentClient;
private Logger logger;
/**
* Creates an instance of a search engine that is aware of the current context
* of the search.
*/
public SearchEngine(Vector<Long> currentSearchThreadIDVector, boolean bSearchByTag, SearchResultsPanel resultsPanel, MainComponent pluginMainComponent, MyExperimentClient client, Logger logger) {
super();
this.vCurrentSearchThreadID = currentSearchThreadIDVector;
this.bSearchByTag = bSearchByTag;
this.jpResultsPanel = resultsPanel;
this.pluginMainComponent = pluginMainComponent;
this.myExperimentClient = client;
this.logger = logger;
}
public void searchAndPopulateResults(String tagQuery) {
this.strSearchQuery = tagQuery;
new Thread("Search by tag") {
public void run() {
// Record 'this' search thread and set it as the new "primary" one
// (this way it if a new search thread starts afterwards, it is possible to
// detect this and stop the 'older' search, because it is no longer relevant)
long lThisSearchThreadID = Thread.currentThread().getId();
vCurrentSearchThreadID.set(0, lThisSearchThreadID);
// set the status in the status bar
pluginMainComponent.getStatusBar().setStatus(pluginMainComponent.getTagBrowserTab().getClass().getName(), "Searching");
// get the search query (which is the tag name in this case)
strSearchQuery = Tag.instantiateTagFromActionCommand(strSearchQuery).getTagName();
jpResultsPanel.setCurrentSearchTerm(strSearchQuery);
jpResultsPanel.setStatus("Starting to search for a tag '"
+ strSearchQuery + "'...");
// Execute the search query
Document doc = myExperimentClient.searchByTag(strSearchQuery);
if (doc == null) {
logger.error("Failed while fetching tagged items. See the error message above. Can't continue.");
javax.swing.JOptionPane.showMessageDialog(null, "An error occurred while searching for the results of your query. Please try again.", "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
} else {
// search was successful, get tagged items' collection
Element root = doc.getRootElement();
processSearchResults(lThisSearchThreadID, root.getChildren());
}
}
}.start();
}
public void searchAndPopulateResults(QuerySearchInstance querySearchDetails) {
final QuerySearchInstance searchQuery = querySearchDetails;
this.strSearchQuery = searchQuery.getSearchQuery();
new Thread("Search by query") {
public void run() {
// Record 'this' search thread and set it as the new "primary" one
// (this way it if a new search thread starts afterwards, it is possible to
// detect this and stop the 'older' search, because it is no longer relevant)
long lThisSearchThreadID = Thread.currentThread().getId();
vCurrentSearchThreadID.set(0, lThisSearchThreadID);
// set the status in the status bar
pluginMainComponent.getStatusBar().setStatus(pluginMainComponent.getSearchTab().getClass().getName(), "Searching");
// strip out the leading "search:"
strSearchQuery = strSearchQuery.replaceFirst("search:", "");
jpResultsPanel.setCurrentSearchTerm(strSearchQuery);
jpResultsPanel.setStatus("Starting to search for '" + strSearchQuery
+ "'...");
// Execute the search query
Document doc = myExperimentClient.searchByQuery(searchQuery);
if (doc == null) {
logger.error("Failed while fetching search result XML document. See the error message above. Can't continue.");
javax.swing.JOptionPane.showMessageDialog(null, "An error occurred while searching for the results of your query. Please try again.", "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
} else {
// search was successful, get found items' collection
Element root = doc.getRootElement();
processSearchResults(lThisSearchThreadID, root.getChildren());
}
}
}.start();
}
/**
* Generic worker method to process search results from both search by tags
* and search by query.
*
* @param foundSearchItemElements
* List of Elements that comprise the search results.
*/
private void processSearchResults(Long thisSearchThreadID, List<Element> foundSearchItemElements) {
// prepare variables
final Long lThisSearchThreadID = thisSearchThreadID;
final List<Element> lFoundSearchItemElements = foundSearchItemElements;
final HashMap<Integer, ArrayList<Resource>> hmClassifiedResults = new HashMap<Integer, ArrayList<Resource>>();
// iterate through all found items, fetch more data about each and classify
// the results by resource type
for (Element foundItem : foundSearchItemElements) {
// check if the current thread is still the active one (that is if a new search
// thread hasn't been started yet - if the new search has been started, the
// current one should terminate)
if (!lThisSearchThreadID.equals(vCurrentSearchThreadID.get(0)))
return;
// PROCESS THE NEXT ELEMENT
// data required for generating full listing preview was fetched along with the list of items -
// so now can build the item directly from the received data
Resource res = Resource.buildFromXML(foundItem, myExperimentClient, logger);
// check if this is a new type of resource - if so, create new ArrayList for these
if (!hmClassifiedResults.containsKey(res.getItemType())) {
hmClassifiedResults.put(res.getItemType(), new ArrayList<Resource>());
}
// add the resource into the result set
hmClassifiedResults.get(res.getItemType()).add(res);
}
// populate the results when everything's done
// (only make a final check to make sure that the "top" search thread is still the current one -
// otherwise, it's not necessary to make any rendering of unwanted results)
if (lThisSearchThreadID.equals(vCurrentSearchThreadID.get(0))) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jpResultsPanel.setStatus(lFoundSearchItemElements.size()
+ " items found " + (bSearchByTag ? "with tag '" : "by query '")
+ jpResultsPanel.getCurrentSearchTerm() + "'");
jpResultsPanel.setSearchResultsData(hmClassifiedResults);
jpResultsPanel.refresh();
// if this search thread is displaying results, then this was the last search thread - can display
// "ready" status in the status bar now
JComponent jcTabForWhichToSetStatus = (bSearchByTag ? pluginMainComponent.getTagBrowserTab() : pluginMainComponent.getSearchTab());
pluginMainComponent.getStatusBar().setStatus(jcTabForWhichToSetStatus.getClass().getName(), null);
}
});
}
}
/**
* Class to hold settings for a query search. This will then be used to re-run
* a search instance at a later time.
*/
public static class QuerySearchInstance implements Comparable<QuerySearchInstance>, Serializable {
private String strSearchQuery;
private int iResultCountLimit;
private boolean bSearchWorkflows;
private boolean bSearchFiles;
private boolean bSearchPacks;
private boolean bSearchUsers;
private boolean bSearchGroups;
// constructor
public QuerySearchInstance(String searchQuery, int resultCountLimit, boolean searchWorkflows, boolean searchFiles, boolean searchPacks, boolean searchUsers, boolean searchGroups) {
this.strSearchQuery = searchQuery;
this.iResultCountLimit = resultCountLimit;
this.bSearchWorkflows = searchWorkflows;
this.bSearchFiles = searchFiles;
this.bSearchPacks = searchPacks;
this.bSearchUsers = searchUsers;
this.bSearchGroups = searchGroups;
}
// determines whether the two search instances are identical
public boolean equals(Object other) {
if (other instanceof QuerySearchInstance) {
QuerySearchInstance si = (QuerySearchInstance) other;
return (this.strSearchQuery.equals(si.getSearchQuery())
&& this.iResultCountLimit == si.getResultCountLimit()
&& this.bSearchWorkflows == si.getSearchWorkflows()
&& this.bSearchFiles == si.getSearchFiles()
&& this.bSearchPacks == si.getSearchPacks()
&& this.bSearchUsers == si.getSearchUsers() && this.bSearchGroups == si.getSearchGroups());
} else
return (false);
}
public int compareTo(QuerySearchInstance other) {
if (this.equals(other))
return (0);
else {
// this will return results in the descending order - which is
// fine, because the way this collection will be rendered will
// eventually traverse it from the rear end first; so results
// will be shown alphabetically
return (-1 * this.toString().compareTo(other.toString()));
}
}
public String toString() {
return ("Search: '" + this.strSearchQuery + "' " + this.detailsAsString());
}
public String detailsAsString() {
String str = "";
// output types that were searched for
int iCnt = 0;
if (this.bSearchWorkflows) {
str += "workflows, ";
iCnt++;
}
if (this.bSearchFiles) {
str += "files, ";
iCnt++;
}
if (this.bSearchPacks) {
str += "packs, ";
iCnt++;
}
if (this.bSearchUsers) {
str += "users, ";
iCnt++;
}
if (this.bSearchGroups) {
str += "groups, ";
iCnt++;
}
// if that's all types, have just the word 'all'
if (iCnt == 5)
str = "all";
else
str = str.substring(0, str.length() - 2); // remove trailing ", "
// add the rest to the string representation of the search instance
str = "[" + str + "; limit: " + this.iResultCountLimit + "]";
return (str);
}
public String getSearchQuery() {
return (this.strSearchQuery);
}
public int getResultCountLimit() {
return (this.iResultCountLimit);
}
public boolean getSearchWorkflows() {
return (this.bSearchWorkflows);
}
public boolean getSearchFiles() {
return (this.bSearchFiles);
}
public boolean getSearchPacks() {
return (this.bSearchPacks);
}
public boolean getSearchUsers() {
return (this.bSearchUsers);
}
public boolean getSearchGroups() {
return (this.bSearchGroups);
}
}
}