You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 11:22:37 UTC

[14/51] [partial] incubator-taverna-workbench git commit: Revert "temporarily empty repository"

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java
new file mode 100644
index 0000000..0e033cc
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/BioCatalogueClient.java
@@ -0,0 +1,785 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import java.io.*;
+import java.net.*;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import net.sf.taverna.biocatalogue.model.BioCataloguePluginConstants;
+import net.sf.taverna.biocatalogue.model.Pair;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.SoapOperationIdentity;
+import net.sf.taverna.biocatalogue.model.SoapOperationPortIdentity;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceIndex;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.integration.config.BioCataloguePluginConfiguration;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.Annotations;
+import org.biocatalogue.x2009.xml.rest.AnnotationsDocument;
+import org.biocatalogue.x2009.xml.rest.CollectionCoreStatistics;
+import org.biocatalogue.x2009.xml.rest.Filters;
+import org.biocatalogue.x2009.xml.rest.FiltersDocument;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+import org.biocatalogue.x2009.xml.rest.RestMethod;
+import org.biocatalogue.x2009.xml.rest.RestMethodDocument;
+import org.biocatalogue.x2009.xml.rest.RestMethods;
+import org.biocatalogue.x2009.xml.rest.RestMethodsDocument;
+import org.biocatalogue.x2009.xml.rest.Search;
+import org.biocatalogue.x2009.xml.rest.SearchDocument;
+import org.biocatalogue.x2009.xml.rest.Service;
+import org.biocatalogue.x2009.xml.rest.ServiceDocument;
+import org.biocatalogue.x2009.xml.rest.ServiceProvider;
+import org.biocatalogue.x2009.xml.rest.ServiceProviderDocument;
+import org.biocatalogue.x2009.xml.rest.ServiceProviders;
+import org.biocatalogue.x2009.xml.rest.ServiceProvidersDocument;
+import org.biocatalogue.x2009.xml.rest.Services;
+import org.biocatalogue.x2009.xml.rest.ServicesDocument;
+import org.biocatalogue.x2009.xml.rest.SoapInput;
+import org.biocatalogue.x2009.xml.rest.SoapInputDocument;
+import org.biocatalogue.x2009.xml.rest.SoapOperation;
+import org.biocatalogue.x2009.xml.rest.SoapOperationDocument;
+import org.biocatalogue.x2009.xml.rest.SoapOperations;
+import org.biocatalogue.x2009.xml.rest.SoapOperationsDocument;
+import org.biocatalogue.x2009.xml.rest.SoapOutput;
+import org.biocatalogue.x2009.xml.rest.SoapOutputDocument;
+import org.biocatalogue.x2009.xml.rest.SoapService;
+import org.biocatalogue.x2009.xml.rest.SoapServiceDocument;
+import org.biocatalogue.x2009.xml.rest.Tag;
+import org.biocatalogue.x2009.xml.rest.TagDocument;
+import org.biocatalogue.x2009.xml.rest.Tags;
+import org.biocatalogue.x2009.xml.rest.TagsDocument;
+import org.biocatalogue.x2009.xml.rest.User;
+import org.biocatalogue.x2009.xml.rest.UserDocument;
+import org.biocatalogue.x2009.xml.rest.Users;
+import org.biocatalogue.x2009.xml.rest.UsersDocument;
+
+import com.google.gson.Gson;
+
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class BioCatalogueClient
+{
+  // ******* CONSTANTS *******
+  // plugin details
+  public static final String PLUGIN_VERSION = "0.1.1";
+  public static final String PLUGIN_USER_AGENT = "Taverna2-ServiceCatalogue-plugin/" +
+                                                 PLUGIN_VERSION +
+                                                 " Java/" + System.getProperty("java.version");
+  
+  public static final String XML_MIME_TYPE = "application/xml";
+  public static final String JSON_MIME_TYPE = "application/json";
+  public static final String LITE_JSON_MIME_TYPE = "application/biocat-lite+json";
+  
+  public static final String XML_DATA_FORMAT = ".xml";
+  public static final String JSON_DATA_FORMAT = ".json";
+  public static final String LITE_JSON_DATA_FORMAT = ".bljson";
+  
+  
+  
+  // API URLs
+  public static final String DEFAULT_API_SANDBOX_BASE_URL = "http://sandbox.biocatalogue.org";
+  public static final String DEFAULT_API_TEST_SERVER_BASE_URL = "http://test.biocatalogue.org";
+  public static final String DEFAULT_API_LIVE_SERVER_BASE_URL = "http://www.biocatalogue.org";
+  
+  private static String BASE_URL;    // BioCatalogue base URL to use (can be updated at runtime)
+  
+  public static String API_REGISTRIES_URL;
+  public static String API_SERVICE_PROVIDERS_URL;
+  public static String API_USERS_URL;
+  public static String API_USER_FILTERS_URL;
+  public static String API_SERVICES_URL;
+  public static String API_SERVICE_FILTERS_URL;
+  public static String API_SOAP_OPERATIONS_URL;
+  public static String API_SOAP_OPERATION_FILTERS_URL;
+  public static String API_REST_METHODS_URL;
+  public static String API_REST_METHOD_FILTERS_URL;
+  public static String API_TAG_CLOUD_URL;
+  public static String API_SEARCH_URL;
+  public static String API_LOOKUP_URL;
+  
+  // URL modifiers
+  public static final Map<String,String> API_INCLUDE_SUMMARY = Collections.singletonMap("include","summary");          // for fetching Service
+  public static final Map<String,String> API_INCLUDE_ANCESTORS = Collections.singletonMap("include", "ancestors,inputs,outputs");     // for fetching SOAP Operations and REST Methods
+  public static final String[] API_SORT_BY_NAME = {"sort","name"};                   // for tag cloud
+  public static final String[] API_SORT_BY_COUNTS = {"sort","counts"};               // for tag cloud
+  public static final String[] API_ALSO_INPUTS_OUTPUTS = {"also","inputs,outputs"};  // for annotations on SOAP operation
+  
+  public static final String API_PER_PAGE_PARAMETER = "per_page";
+  public static final String API_PAGE_PARAMETER = "page";
+  public static final String API_LIMIT_PARAMETER = "limit";
+  public static final String API_SERVICE_MONITORING_URL_SUFFIX = "/monitoring";
+  public static final String API_FILTERED_INDEX_SUFFIX = "/filtered_index";
+  
+  // API Request scope
+  public static final String API_SCOPE_PARAMETER = "scope";
+  public static final String API_SCOPE_SOAP_OPERATIONS = "soap_operations";
+  public static final String API_SCOPE_REST_METHODS = "rest_methods";
+  public static final String API_SCOPE_SERVICES = "services";
+  public static final String API_SCOPE_SERVICE_PROVIDERS = "service_providers";
+  public static final String API_SCOPE_REGISTRIES = "registries";
+  public static final String API_SCOPE_USERS = "users";
+  
+  public static final String API_TAG_PARAMETER = "tag";
+  
+  public static final String API_LOOKUP_WSDL_LOCATION_PARAMETER = "wsdl_location";
+  public static final String API_LOOKUP_OPERATION_NAME_PARAMETER = "operation_name";
+  public static final String API_LOOKUP_SOAP_INPUT_NAME_PARAMETER = "input_name";
+  public static final String API_LOOKUP_SOAP_OUTPUT_NAME_PARAMETER = "output_name";
+  
+  
+  // *************************
+  
+  // universal date formatters
+  private static final DateFormat DATE_FORMATTER = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy");
+  private static final DateFormat SHORT_DATE_FORMATTER = new SimpleDateFormat("HH:mm 'on' dd/MM/yyyy");
+  private static final DateFormat API_LOGGING_TIMESTAMP_FORMATTER = DateFormat.getDateTimeInstance();
+  
+  
+  // SETTINGS
+  private Properties iniSettings;    // settings that are read/stored from/to INI file
+  
+  private File fAPIOperationLog;
+  private PrintWriter pwAPILogWriter;
+  
+  // the logger
+  private Logger logger = Logger.getLogger(BioCatalogueClient.class);
+  
+  private static BioCatalogueClient INSTANCE;
+  
+  // default constructor
+  private BioCatalogueClient()
+  {
+    // TODO: load any config settings (if necessary)
+    
+    // load the BioCatalogue API base URL from the plugin's configuration settings
+    this.setBaseURL(BioCataloguePluginConfiguration.getInstance().
+            getProperty(BioCataloguePluginConfiguration.SERVICE_CATALOGUE_BASE_URL));
+    
+    // open API operation log file, if necessary
+    if (BioCataloguePluginConstants.PERFORM_API_RESPONSE_TIME_LOGGING || 
+        BioCataloguePluginConstants.PERFORM_API_XML_DATA_BINDING_TIME_LOGGING )
+    {
+      try {
+        BioCataloguePluginConstants.LOG_FILE_FOLDER.mkdirs(); // just in case this log file was never written - create the folder as well
+        fAPIOperationLog = new File(BioCataloguePluginConstants.LOG_FILE_FOLDER, 
+                                    BioCataloguePluginConstants.API_OPERATION_LOG_FILENAME);
+        pwAPILogWriter = new PrintWriter(new FileOutputStream(fAPIOperationLog, true), true);  // auto-flush makes sure that even if app crashes, log will not be lost
+      }
+      catch (NullPointerException e) {
+        pwAPILogWriter = new PrintWriter(System.out, true);
+        logger.error("ERROR: Folder to log API operation details is unknown (using System.out instead)... Details:", e);
+      }
+      catch (FileNotFoundException e) {
+        logger.error("ERROR: Couldn't open API operation log file... Details:", e);
+      }
+    }
+  }
+  
+  public static synchronized BioCatalogueClient getInstance() {
+	  if (INSTANCE == null) {
+		  INSTANCE = new BioCatalogueClient();
+	  }
+	  return INSTANCE;
+  }
+  
+  
+  public String getBaseURL() {
+    return this.BASE_URL;
+  }
+  
+  /**
+   * Updates the base API URL and also
+   * updates derived URLs of sub-URLs
+   * (e.g. BASE_URL + /services, etc)
+   * 
+   * @param baseURL The new value for the BioCatalogue API base URL.
+   */
+  public void setBaseURL(String baseURL)
+  {
+    // make sure the base URL doesn't have a slash at the end
+    // (otherwise double slashes may occur during URL manipulation)
+    while (baseURL.endsWith("/")) { baseURL = baseURL.substring(0, baseURL.length() - 1); }
+    
+    this.BASE_URL = baseURL;
+    
+    API_REGISTRIES_URL = BASE_URL + "/registries";
+    API_SERVICE_PROVIDERS_URL = BASE_URL + "/service_providers";
+    API_USERS_URL = BASE_URL + "/users";
+    API_USER_FILTERS_URL = API_USERS_URL + "/filters";
+    API_SERVICES_URL = BASE_URL + "/services";
+    API_SERVICE_FILTERS_URL = API_SERVICES_URL + "/filters";
+    API_SOAP_OPERATIONS_URL = BASE_URL + "/soap_operations";
+    API_SOAP_OPERATION_FILTERS_URL = API_SOAP_OPERATIONS_URL + "/filters";
+    API_REST_METHODS_URL = BASE_URL + "/rest_methods";
+    API_REST_METHOD_FILTERS_URL = API_REST_METHODS_URL + "/filters";
+    API_TAG_CLOUD_URL = BASE_URL + "/tags";
+    API_SEARCH_URL = BASE_URL + "/search";
+    API_LOOKUP_URL = BASE_URL + "/lookup";
+  }
+  
+  public File getAPIOperationLog() {
+    return fAPIOperationLog;
+  }
+  
+  public PrintWriter getAPILogWriter() {
+    return pwAPILogWriter;
+  }
+  
+  
+  // ************ METHODS FOR RETRIEVAL OF SPECIALISED OBJECT FROM THE API VIA XML ************
+  
+  public Annotations getBioCatalogueAnnotations(String strAnnotationsURL) throws Exception {
+    return (parseAPIResponseStream(Annotations.class, doBioCatalogueGET(strAnnotationsURL)));
+  }
+  
+  public Filters getBioCatalogueFilters(String strURL) throws Exception {
+    return (parseAPIResponseStream(Filters.class, doBioCatalogueGET(strURL)));
+  }
+  
+  public Services getBioCatalogueServices(String strURL) throws Exception {
+    return (parseAPIResponseStream(Services.class, doBioCatalogueGET(strURL)));
+  }
+  
+  public Service getBioCatalogueService(String serviceURL) throws Exception {
+    return (parseAPIResponseStream(Service.class, doBioCatalogueGET(serviceURL)));
+  }
+  
+  public Service getBioCatalogueServiceSummary(String serviceURL) throws Exception {
+    return (parseAPIResponseStream(Service.class, doBioCatalogueGET(Util.appendAllURLParameters(serviceURL, API_INCLUDE_SUMMARY))));
+  }
+  
+  public Service getBioCatalogueServiceMonitoringData(String serviceURL) throws Exception
+  {
+    return (parseAPIResponseStream(Service.class,
+                                   doBioCatalogueGET(serviceURL + API_SERVICE_MONITORING_URL_SUFFIX))
+           );
+  }
+  
+  public SoapService getBioCatalogueSoapService(String soapServiceURL) throws Exception {
+    return (parseAPIResponseStream(SoapService.class, doBioCatalogueGET(soapServiceURL)));
+  }
+  
+  public SoapOperation getBioCatalogueSoapOperation(String soapOperationURL) throws Exception {
+    return (parseAPIResponseStream(SoapOperation.class, doBioCatalogueGET(soapOperationURL)));
+  }
+  
+  public RestMethod getBioCatalogueRestMethod(String restMethodURL) throws Exception {
+    return (parseAPIResponseStream(RestMethod.class, doBioCatalogueGET(restMethodURL)));
+  }
+  
+  public Search getBioCatalogueSearchData(String searchURL) throws Exception {
+    return (parseAPIResponseStream(Search.class, doBioCatalogueGET(searchURL)));
+  }
+  
+  public Tag getBioCatalogueTag(String searchByTagURL) throws Exception {
+    return (parseAPIResponseStream(Tag.class, doBioCatalogueGET(searchByTagURL)));
+  }
+  
+  public Tags getBioCatalogueTags(String tagsURL) throws Exception {
+    return (parseAPIResponseStream(Tags.class, doBioCatalogueGET(tagsURL)));
+  }
+  
+  
+  public ResourceLink getBioCatalogueResource(Class<? extends ResourceLink> classOfResourceToFetch, String resourceURL) throws Exception {
+    return (parseAPIResponseStream(classOfResourceToFetch, doBioCatalogueGET(resourceURL)));
+  }
+  
+  
+  public <T extends ResourceLink> Pair<CollectionCoreStatistics, List<T>> getListOfItemsFromResourceCollectionIndex(
+      Class<T> classOfCollectionOfRequiredReturnedObjects, BioCatalogueAPIRequest filteringRequest) throws Exception
+  {
+    ResourceLink matchingItems = null;
+    if (filteringRequest.getRequestType() == BioCatalogueAPIRequest.TYPE.GET) {
+      matchingItems = parseAPIResponseStream(classOfCollectionOfRequiredReturnedObjects, doBioCatalogueGET(filteringRequest.getURL()));
+    }
+    else {
+      matchingItems = parseAPIResponseStream(classOfCollectionOfRequiredReturnedObjects,
+                           doBioCataloguePOST_SendJSON_AcceptXML(filteringRequest.getURL(), filteringRequest.getData()));
+    }
+    
+    CollectionCoreStatistics statistics = null;
+    
+    List<T> matchingItemList = new ArrayList<T>();
+    
+    // SOAP Operations
+    if (classOfCollectionOfRequiredReturnedObjects.equals(SoapOperations.class)) {
+      SoapOperations soapOperations = (SoapOperations)matchingItems;
+      matchingItemList.addAll((Collection<? extends T>)(soapOperations.getResults().getSoapOperationList()));
+      statistics = soapOperations.getStatistics();
+    }
+    
+    // REST Methods
+    else if (classOfCollectionOfRequiredReturnedObjects.equals(RestMethods.class)) {
+      RestMethods restMethods = (RestMethods)matchingItems;
+      matchingItemList.addAll((Collection<? extends T>)(restMethods.getResults().getRestMethodList()));
+      statistics = restMethods.getStatistics();
+    }
+    
+    // Services
+    else if (classOfCollectionOfRequiredReturnedObjects.equals(Services.class)) {
+      Services services = (Services)matchingItems;
+      matchingItemList.addAll((Collection<? extends T>)(services.getResults().getServiceList()));
+      statistics = services.getStatistics();
+    }
+    
+    // Service Providers
+    else if (classOfCollectionOfRequiredReturnedObjects.equals(ServiceProviders.class)) {
+      ServiceProviders serviceProviders = (ServiceProviders)matchingItems;
+      matchingItemList.addAll((Collection<? extends T>)(serviceProviders.getResults().getServiceProviderList()));
+      statistics = serviceProviders.getStatistics();
+    }
+    
+    // Users
+    else if (classOfCollectionOfRequiredReturnedObjects.equals(Users.class)) {
+      Users users = (Users)matchingItems;
+      matchingItemList.addAll((Collection<? extends T>)(users.getResults().getUserList()));
+      statistics = users.getStatistics();
+    }
+    
+    // no such option - error
+    else {
+      return null;
+    }
+    
+    return new Pair<CollectionCoreStatistics, List<T>>(statistics, matchingItemList);
+  }
+  
+  
+  
+  
+  /**
+   * @param wsdlLocation
+   * @param operationName
+   * @return SoapOperation instance or <code>null</code> if nothing was found (or error occurred).
+   * @throws Exception
+   */
+  public SoapOperation lookupSoapOperation(SoapOperationIdentity soapOperationDetails) throws Exception
+  {
+    // first of all check for any problems with input data
+    if (soapOperationDetails == null || soapOperationDetails.hasError() ||
+        soapOperationDetails.getWsdlLocation() == null || soapOperationDetails.getWsdlLocation().length() == 0 ||
+        soapOperationDetails.getOperationName() == null || soapOperationDetails.getOperationName().length() == 0)
+    {
+      // something's not right - return null
+      return (null);
+    }
+    
+    String lookupURL = Util.appendURLParameter(API_LOOKUP_URL, API_LOOKUP_WSDL_LOCATION_PARAMETER, soapOperationDetails.getWsdlLocation());
+    lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_OPERATION_NAME_PARAMETER, soapOperationDetails.getOperationName());
+    
+    ServerResponseStream lookupResponse = doBioCatalogueGET(lookupURL);
+    if (lookupResponse.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+      return null;
+    }
+    return (parseAPIResponseStream(SoapOperation.class, lookupResponse));
+  }
+  
+  
+  public <T extends ResourceLink> T lookupSoapOperationPort(Class<T> requiredResultClass, SoapOperationPortIdentity portDetails) throws Exception
+  {
+    // first of all check for any problems with port details
+    if (portDetails == null || portDetails.hasError() ||
+        portDetails.getWsdlLocation() == null || portDetails.getWsdlLocation().length() == 0 ||
+        portDetails.getOperationName() == null || portDetails.getOperationName().length() == 0 ||
+        portDetails.getPortName() == null || portDetails.getPortName().length() == 0)
+    {
+      // something's not right - return null
+      return (null);
+    }
+    
+    // now check that specified class matches the port type
+    if (portDetails.isInput() && !requiredResultClass.equals(SoapInput.class) ||
+        !portDetails.isInput() && !requiredResultClass.equals(SoapOutput.class))
+    {
+      return (null);
+    }
+    
+    String lookupURL = Util.appendURLParameter(API_LOOKUP_URL, API_LOOKUP_WSDL_LOCATION_PARAMETER, portDetails.getWsdlLocation());
+    lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_OPERATION_NAME_PARAMETER, portDetails.getOperationName());
+    if (portDetails.isInput()) {
+      lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_SOAP_INPUT_NAME_PARAMETER, portDetails.getPortName());
+    }
+    else {
+      lookupURL = Util.appendURLParameter(lookupURL, API_LOOKUP_SOAP_OUTPUT_NAME_PARAMETER, portDetails.getPortName());
+    }
+    
+    ServerResponseStream lookupResponse = doBioCatalogueGET(lookupURL);
+    if (lookupResponse.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+      return null;
+    }
+    return (parseAPIResponseStream(requiredResultClass, lookupResponse));
+  }
+  
+  
+  public Service lookupParentService(SoapOperationIdentity soapOperationDetails) throws Exception
+  {
+    SoapOperation soapOperation = this.lookupSoapOperation(soapOperationDetails);
+    if (soapOperation != null) {
+      return (getBioCatalogueService(soapOperation.getAncestors().getService().getHref()));
+    }
+    else {
+      // lookup didn't find the SOAP operation or there
+      // was some problem with the input data
+      return (null);
+    }
+  }
+  
+  
+  public Service lookupParentServiceMonitoringData(SoapOperationIdentity soapOperationDetails) throws Exception
+  {
+    SoapOperation soapOperation = this.lookupSoapOperation(soapOperationDetails);
+    if (soapOperation != null) {
+      return (getBioCatalogueServiceMonitoringData(soapOperation.getAncestors().getService().getHref()));
+    }
+    else {
+      // lookup didn't find the SOAP operation or there
+      // was some problem with the input data
+      return (null);
+    }
+  }
+  
+  
+  // ************ METHODS FOR RETRIEVAL OF SPECIALISED OBJECT FROM THE API VIA LITE JSON ************
+  
+  public BeansForJSONLiteAPI.ResourceIndex getBioCatalogueResourceLiteIndex(TYPE resourceType, String resourceIndexURL) throws Exception
+  {
+    ServerResponseStream response = doBioCatalogueGET_LITE_JSON(resourceIndexURL);
+    
+    Gson gson = new Gson();
+    return (ResourceIndex)(gson.fromJson(new InputStreamReader(response.getResponseStream()), resourceType.getJsonLiteAPIBindingBeanClass()));
+  }
+  
+  
+  public BeansForJSONLiteAPI.ResourceIndex postBioCatalogueResourceLiteIndex(TYPE resourceType, String resourceIndexURL, String postData) throws Exception
+  {
+    ServerResponseStream response = doBioCataloguePOST_SendJSON_AcceptLITEJSON(resourceIndexURL, postData);
+    
+    Gson gson = new Gson();
+    return (ResourceIndex)(gson.fromJson(new InputStreamReader(response.getResponseStream()), resourceType.getJsonLiteAPIBindingBeanClass()));
+  }
+  
+  
+  // ************ GENERIC API CONNECTIVITY METHODS ************
+  
+  /**
+   * Generic method to issue GET requests to BioCatalogue server.
+   * 
+   * This is a convenience method to be used instead of {@link BioCatalogueClient#doBioCatalogueGET_XML(String)}.
+   * 
+   * @param strURL The URL on BioCatalogue to issue GET request to.
+   * @return TODO
+   * @throws Exception
+   */
+  public ServerResponseStream doBioCatalogueGET(String strURL) throws Exception {
+    return (doBioCatalogueGET_XML(strURL));
+  }
+  
+  public ServerResponseStream doBioCatalogueGET_XML(String strURL) throws Exception {
+    return (doBioCatalogueGET(strURL, XML_MIME_TYPE, XML_DATA_FORMAT));
+  }
+  
+  public ServerResponseStream doBioCatalogueGET_JSON(String strURL) throws Exception {
+    return (doBioCatalogueGET(strURL, JSON_MIME_TYPE, JSON_DATA_FORMAT));
+  }
+  
+  public ServerResponseStream doBioCatalogueGET_LITE_JSON(String strURL) throws Exception {
+    return (doBioCatalogueGET(strURL, LITE_JSON_MIME_TYPE, LITE_JSON_DATA_FORMAT));
+  }
+  
+  
+  public ServerResponseStream doBioCatalogueGET(String strURL, String ACCEPT_HEADER, String REQUESTED_DATA_FORMAT) throws Exception
+  {
+    // TODO - HACK to speed up processing append .xml / .json / .bljson to all URLs to avoid LinkedData content negotiation
+    strURL = Util.appendStringBeforeParametersOfURL(strURL, REQUESTED_DATA_FORMAT);
+    
+    // open server connection using provided URL (with no further modifications to it)
+    URL url = new URL(strURL);
+    
+    Calendar requestStartedAt = Calendar.getInstance();
+    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+    conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+    conn.setRequestProperty("Accept", ACCEPT_HEADER);
+    
+//    if(LOGGED_IN) {
+//      // if the user has "logged in", also add authentication details
+//      conn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+//    }
+    
+    // fetch server's response
+    ServerResponseStream serverResponse = doBioCatalogueReceiveServerResponse(conn, strURL, true);
+    
+    if (BioCataloguePluginConstants.PERFORM_API_RESPONSE_TIME_LOGGING) {
+      logAPIOperation(requestStartedAt, "GET", serverResponse);
+    }
+    return (serverResponse);
+  }
+  
+  
+  
+  public ServerResponseStream doBioCataloguePOST_SendJSON_AcceptXML(String strURL, String strDataBody) throws Exception {
+    return (doBioCataloguePOST(strURL, strDataBody, JSON_MIME_TYPE, XML_MIME_TYPE, XML_DATA_FORMAT));
+  }
+  
+  public ServerResponseStream doBioCataloguePOST_SendJSON_AcceptLITEJSON(String strURL, String strDataBody) throws Exception {
+    return (doBioCataloguePOST(strURL, strDataBody, JSON_MIME_TYPE, LITE_JSON_MIME_TYPE, LITE_JSON_DATA_FORMAT));
+  }
+  
+  
+  /**
+   * Generic method to execute POST requests to BioCatalogue server.
+   * 
+   * @param strURL The URL on BioCatalogue to POST to. 
+   * @param strDataBody Body of the message to be POSTed to <code>strURL</code>. 
+   * @return An object containing server's response body as an InputStream and
+   *         a response code.
+   * @param CONTENT_TYPE_HEADER MIME type of the sent data.
+   * @param ACCEPT_HEADER MIME type of the data to be received.
+   * @param REQUESTED_DATA_FORMAT
+   * @throws Exception
+   */
+  public ServerResponseStream doBioCataloguePOST(String strURL, String strDataBody, String CONTENT_TYPE_HEADER,
+                                                 String ACCEPT_HEADER, String REQUESTED_DATA_FORMAT) throws Exception
+  {
+    // TODO - HACK to speed up processing append .xml / .json / .bljson to all URLs to avoid LinkedData content negotiation
+    strURL = Util.appendStringBeforeParametersOfURL(strURL, REQUESTED_DATA_FORMAT);
+    
+    // open server connection using provided URL (with no further modifications to it)
+    URL url = new URL (strURL);
+    
+    Calendar requestStartedAt = Calendar.getInstance();
+    HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+    urlConn.setRequestMethod("POST");
+    urlConn.setDoOutput(true);
+    urlConn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+    urlConn.setRequestProperty("Content-Type", CONTENT_TYPE_HEADER);
+    urlConn.setRequestProperty("Accept", ACCEPT_HEADER);
+    
+    // prepare and POST XML data
+    OutputStreamWriter out = new OutputStreamWriter(urlConn.getOutputStream());
+    out.write(strDataBody);
+    out.close();
+    
+    
+    // fetch server's response
+    ServerResponseStream serverResponse = doBioCatalogueReceiveServerResponse(urlConn, strURL, false);
+    
+    if (BioCataloguePluginConstants.PERFORM_API_RESPONSE_TIME_LOGGING) {
+      logAPIOperation(requestStartedAt, "POST", serverResponse);
+    }
+    return (serverResponse);
+  }
+  
+  
+  /**
+   * Generic method to execute DELETE requests to myExperiment server.
+   * This is only to be called when a user is logged in. 
+   * 
+   * @param strURL The URL on myExperiment to direct DELETE request to.
+   * @return An object containing XML Document with server's response body and
+   *         a response code. Response body XML document might be null if there
+   *         was an error or the user wasn't authorised to perform a certain action.
+   *         Response code will always be set.
+   * @throws Exception
+   */
+  /*public ServerResponse doMyExperimentDELETE(String strURL) throws Exception
+  {
+    // open server connection using provided URL (with no modifications to it)
+    URL url = new URL(strURL);
+    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+    
+    // "tune" the connection
+    conn.setRequestMethod("DELETE");
+    conn.setRequestProperty("User-Agent", PLUGIN_USER_AGENT);
+    conn.setRequestProperty("Authorization", "Basic " + AUTH_STRING);
+    
+    // check server's response
+    return (doMyExperimentReceiveServerResponse(conn, strURL, true));
+  }*/
+  
+  
+  /**
+   * A common method for retrieving BioCatalogue server's response for both
+   * GET and POST requests.
+   * 
+   * @param conn Instance of the established URL connection to poll for server's response.
+   * @param strURL The URL on BioCatalogue with which the connection is established.
+   * @param bIsGetRequest Flag for identifying type of the request. True when the current 
+   *        connection executes GET request; false when it executes a POST / DELETE request.
+   * @return TODO
+   */
+  @SuppressWarnings("unchecked")
+  private ServerResponseStream doBioCatalogueReceiveServerResponse(HttpURLConnection conn, String strURL, boolean bIsGETRequest) throws Exception
+  {
+    int iResponseCode = conn.getResponseCode();
+    
+    switch (iResponseCode)
+    {
+      case HttpURLConnection.HTTP_OK:
+        // regular operation path - simply return the reference to the data input stream
+        return (new ServerResponseStream(iResponseCode, conn.getInputStream(), strURL));
+        
+      case HttpURLConnection.HTTP_BAD_REQUEST:
+        // this was a bad XML request - need full XML response to retrieve the error message from it;
+        // Java throws IOException if getInputStream() is used when non HTTP_OK response code was received -
+        // hence can use getErrorStream() straight away to fetch the error document
+        return (new ServerResponseStream(iResponseCode, conn.getErrorStream(), strURL));
+        
+      case HttpURLConnection.HTTP_UNAUTHORIZED:
+        // this content is not authorised for current user
+        return (new ServerResponseStream(iResponseCode, null, strURL));
+      
+      case HttpURLConnection.HTTP_NOT_FOUND:
+        // nothing was found at the provided URL
+        return (new ServerResponseStream(iResponseCode, conn.getErrorStream(), strURL));
+      
+      default:
+        // unexpected response code - raise an exception
+        throw new IOException("Received unexpected HTTP response code (" + iResponseCode + ") while " +
+            (bIsGETRequest ? "fetching data at " : "posting data to ") + strURL);
+    }
+  }
+  
+  
+  /**
+   * This method is here to make sure that *all* parsing of received input stream data
+   * from the API is parsed ("bound") into Java objects in a central place - so it's
+   * possible to measure performance of XmlBeans for various inputs.
+   * 
+   * NB! There is a serious limitation in Java's generics. Generic methods cannot
+   *     access any of the static context of the classes of type parameters, because
+   *     it wasn't designed for this. The only purpose of type parameters is compile-time
+   *     type-checking.
+   *     This means that even though all classes that could potentially be supplied as a
+   *     type-parameter would have certain static functionality, it's not possible to access
+   *     that through using the type-parameter like it's done in normal polymorhic situations.
+   *     Therefore, some switching based on the class of the type-parameter for this method is
+   *     done...
+   * 
+   * @param <T>
+   * @param classOfRequiredReturnedObject Class of the object that the caller expects to receive
+   *                                      after parsing provided server's response. For example,
+   *                                      a call to /tags.xml return the <pre>[tags]...[/tags]</pre>
+   *                                      document. <code>TagsDocument</code> should be used to access
+   *                                      its static factory and parse the input stream - the return
+   *                                      value will have type <code>Tags</code> -- <code>Tags.class</code>
+   *                                      is the required input value for this parameter in this situation then.
+   * @param serverResponse This object should contain the input stream obtained from the API in return
+   *                       to the call on some URL.
+   * @return               InputStream data parsed into the Java object of the supplied type [T].
+   * @throws Exception
+   */
+  @SuppressWarnings("unchecked")
+  private <T extends ResourceLink> T parseAPIResponseStream(Class<T> classOfRequiredReturnedObject, ServerResponseStream serverResponse) throws Exception
+  {
+    T parsedObject = null;
+    InputStream xmlInputStream = serverResponse.getResponseStream();
+    
+    // choose a factory to parse the response and perform parsing
+    Calendar parsingStartedAt = Calendar.getInstance();
+    if (classOfRequiredReturnedObject.equals(Annotations.class)) {
+      parsedObject = (T)AnnotationsDocument.Factory.parse(xmlInputStream).getAnnotations();
+    }
+    else if (classOfRequiredReturnedObject.equals(Filters.class)) {
+      parsedObject = (T)FiltersDocument.Factory.parse(xmlInputStream).getFilters();
+    }
+    else if (classOfRequiredReturnedObject.equals(RestMethods.class)) {
+      parsedObject = (T)RestMethodsDocument.Factory.parse(xmlInputStream).getRestMethods();
+    }
+    else if (classOfRequiredReturnedObject.equals(RestMethod.class)) {
+      parsedObject = (T)RestMethodDocument.Factory.parse(xmlInputStream).getRestMethod();
+    }
+    else if (classOfRequiredReturnedObject.equals(Search.class)) {
+      parsedObject = (T)SearchDocument.Factory.parse(xmlInputStream).getSearch();
+    }
+    else if (classOfRequiredReturnedObject.equals(Services.class)) {
+      parsedObject = (T)ServicesDocument.Factory.parse(xmlInputStream).getServices();
+    }
+    else if (classOfRequiredReturnedObject.equals(Service.class)) {
+      parsedObject = (T)ServiceDocument.Factory.parse(xmlInputStream).getService();
+    }
+    else if (classOfRequiredReturnedObject.equals(ServiceProviders.class)) {
+      parsedObject = (T)ServiceProvidersDocument.Factory.parse(xmlInputStream).getServiceProviders();
+    }
+    else if (classOfRequiredReturnedObject.equals(ServiceProvider.class)) {
+      parsedObject = (T)ServiceProviderDocument.Factory.parse(xmlInputStream).getServiceProvider();
+    }
+    else if (classOfRequiredReturnedObject.equals(SoapOperations.class)) {
+      parsedObject = (T)SoapOperationsDocument.Factory.parse(xmlInputStream).getSoapOperations();
+    }
+    else if (classOfRequiredReturnedObject.equals(SoapOperation.class)) {
+      parsedObject = (T)SoapOperationDocument.Factory.parse(xmlInputStream).getSoapOperation();
+    }
+    else if (classOfRequiredReturnedObject.equals(SoapService.class)) {
+      parsedObject = (T)SoapServiceDocument.Factory.parse(xmlInputStream).getSoapService();
+    }
+    else if (classOfRequiredReturnedObject.equals(SoapInput.class)) {
+      parsedObject = (T)SoapInputDocument.Factory.parse(xmlInputStream).getSoapInput();
+    }
+    else if (classOfRequiredReturnedObject.equals(SoapOutput.class)) {
+      parsedObject = (T)SoapOutputDocument.Factory.parse(xmlInputStream).getSoapOutput();
+    }
+    else if (classOfRequiredReturnedObject.equals(Tags.class)) {
+      parsedObject = (T)TagsDocument.Factory.parse(xmlInputStream).getTags();
+    }
+    else if (classOfRequiredReturnedObject.equals(Tag.class)) {
+      parsedObject = (T)TagDocument.Factory.parse(xmlInputStream).getTag();
+    }
+    else if (classOfRequiredReturnedObject.equals(Users.class)) {
+      parsedObject = (T)UsersDocument.Factory.parse(xmlInputStream).getUsers();
+    }
+    else if (classOfRequiredReturnedObject.equals(User.class)) {
+      parsedObject = (T)UserDocument.Factory.parse(xmlInputStream).getUser();
+    }
+    
+     
+    // log the operation if necessary
+    if (BioCataloguePluginConstants.PERFORM_API_XML_DATA_BINDING_TIME_LOGGING) {
+      logAPIOperation(parsingStartedAt, null, serverResponse);
+    }
+    
+    return (parsedObject);
+  }
+  
+  
+  // ************ HELPERS ************
+  
+  public static DateFormat getDateFormatter() {
+    return(BioCatalogueClient.DATE_FORMATTER);
+  }
+  
+  public static DateFormat getShortDateFormatter() {
+    return(BioCatalogueClient.SHORT_DATE_FORMATTER);
+  }
+  
+  
+  /**
+   * This is a helper to facilitate performance monitoring of the API usage.
+   * 
+   * @param opearationStartedAt Instance of Calendar initialised with the date/time value of
+   *                            when the logged operation was started.
+   * @param requestType "GET" or "POST" to indicate that this was the actual URL connection with the BioCatalogue server
+   *                    to fetch some data; <code>null</code> to indicate an xml-binding operation using XmlBeans.
+   * @param serverResponse Will be used to extract the request URL.
+   */
+  private void logAPIOperation(Calendar opearationStartedAt, String requestType, ServerResponseStream serverResponse)
+  {
+    // just in case check that the writer was initialised
+    if (pwAPILogWriter != null) {
+      pwAPILogWriter.println(API_LOGGING_TIMESTAMP_FORMATTER.format(opearationStartedAt.getTime()) + ", " +
+                             (System.currentTimeMillis() - opearationStartedAt.getTimeInMillis()) + ", " +
+                             (requestType == null ? "xml_parsing" : requestType) + ", " +
+                             serverResponse.getRequestURL());
+    }
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java
new file mode 100644
index 0000000..d79235d
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponse.java
@@ -0,0 +1,40 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import net.sf.taverna.biocatalogue.model.Util;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public abstract class ServerResponse
+{
+  // this code is to be used when a local failure is encountered and the
+  // server response is a blank / invalid one
+  public static int LOCAL_FAILURE_CODE = -1;
+  
+  // server response code - in theory should correspond to HTTP response codes 
+  private int iResponseCode;
+  
+  // URL that was used to make the request
+  private String requestURL;
+  
+  
+  public ServerResponse() {
+    // do nothing - empty constructor
+  }
+  
+  public ServerResponse(int responseCode, String requestURL) {
+    super();
+    this.iResponseCode = responseCode;
+    this.requestURL = Util.urlDecodeQuery(requestURL);
+  }
+  
+  
+  public int getResponseCode() {
+    return (this.iResponseCode);
+  }
+  
+  public String getRequestURL() {
+    return requestURL;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java
new file mode 100644
index 0000000..d4ebb56
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/connectivity/ServerResponseStream.java
@@ -0,0 +1,30 @@
+package net.sf.taverna.biocatalogue.model.connectivity;
+
+import java.io.InputStream;
+
+/**
+ * This class is a custom version of ServerResponse which contains the
+ * InputStream with the the actual server response data.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class ServerResponseStream extends ServerResponse
+{
+  private InputStream responseStream;
+  
+  public ServerResponseStream(int responseCode, InputStream serverResponseStream, String requestURL)
+  {
+    super(responseCode, requestURL);
+    this.setResponseStream(serverResponseStream);
+  }
+  
+  public void setResponseStream(InputStream responseStream)
+  {
+    this.responseStream = responseStream;
+  }
+  
+  public InputStream getResponseStream()
+  {
+    return responseStream;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java
new file mode 100644
index 0000000..0c4018a
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchEngine.java
@@ -0,0 +1,221 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+//import javax.annotation.Resource;
+
+import org.apache.log4j.Logger;
+import org.biocatalogue.x2009.xml.rest.CollectionCoreStatistics;
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+import com.google.gson.Gson;
+
+import net.sf.taverna.biocatalogue.model.Pair;
+import net.sf.taverna.biocatalogue.model.Tag;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.model.connectivity.BeanForPOSTToFilteredIndex;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueAPIRequest;
+import net.sf.taverna.biocatalogue.model.connectivity.BioCatalogueClient;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceIndex;
+import net.sf.taverna.biocatalogue.model.search.SearchInstance.TYPE;
+import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsRenderer;
+import net.sf.taverna.t2.ui.perspectives.biocatalogue.MainComponentFactory;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+
+/**
+ * @author Sergejs Aleksejevs
+ */
+public class SearchEngine
+{
+  private static Logger logger = Logger.getLogger(SearchEngine.class);
+  
+  protected SearchInstance searchInstance;
+  protected final BioCatalogueClient client;
+  protected final SearchInstanceTracker activeSearchInstanceTracker;
+  protected final CountDownLatch doneSignal;
+  protected final SearchResultsRenderer renderer;
+  
+  
+  public SearchEngine(SearchInstance searchInstance, 
+                              SearchInstanceTracker activeSearchInstanceTracker,
+                              CountDownLatch doneSignal,
+                              SearchResultsRenderer renderer)
+  {
+    
+    this.searchInstance = searchInstance;
+    this.client = BioCatalogueClient.getInstance();
+    this.activeSearchInstanceTracker = activeSearchInstanceTracker;
+    this.doneSignal = doneSignal;
+    this.renderer = renderer;
+  }
+  
+  
+  
+  /**
+   * @return <code>true</code> if the thread launched by this search engine is still
+   *         the one treated as 'active' in the context of the user actions in the plugin;<br/>
+   *         <code>false</code> - otherwise.
+   */
+  protected boolean isCurrentSearch() {
+    return (activeSearchInstanceTracker.isCurrentSearchInstance(
+              this.searchInstance.getResourceTypeToSearchFor(), searchInstance));
+  }
+  
+  
+  
+  /**
+   * Primary API request is the one that is *generated* when the search is first executed --
+   * for further requests (like fetching more data) it won't be fully generated, but rather
+   * will be derived from this primary one.
+   */
+  protected BioCatalogueAPIRequest generateSearchRequest() {
+    return (generateSearchRequest(searchInstance.getSearchType()));
+  }
+  
+  protected BioCatalogueAPIRequest generateSearchRequest(TYPE searchType)
+  {
+    // construct search request to execute on BioCatalogue server
+    BioCatalogueAPIRequest.TYPE requestType = BioCatalogueAPIRequest.TYPE.GET;
+    String requestURL = null;
+    String requestData = null;
+    
+    switch (searchType) {
+      case QuerySearch:
+        requestURL = Util.appendURLParameter(searchInstance.getResourceTypeToSearchFor().getAPIResourceCollectionIndex(), "q", searchInstance.getSearchString());
+        break;
+        
+      case TagSearch:
+        List<String> tags = new ArrayList<String>();
+        for (Tag t : searchInstance.getSearchTags()) {
+          tags.add(t.getFullTagName());
+        }
+        String tagParamValue = Util.join(tags, "[", "]", ",");
+        requestURL = Util.appendURLParameter(searchInstance.getResourceTypeToSearchFor().getAPIResourceCollectionIndex(), "tag", tagParamValue);
+        break;
+      
+      case Filtering:
+        requestType = BioCatalogueAPIRequest.TYPE.POST;
+        
+        // get search URL for the 'base' search upon which the filtering is based
+        requestURL = generateSearchRequest(searchInstance.getServiceFilteringBasedOn()).getURL();
+        requestURL = Util.appendStringBeforeParametersOfURL(requestURL, BioCatalogueClient.API_FILTERED_INDEX_SUFFIX, true);
+        
+        // the base URL was prepared, now prepare filtering parameters as POST data
+        BeanForPOSTToFilteredIndex dataBean = new BeanForPOSTToFilteredIndex();
+        dataBean.filters = searchInstance.getFilteringSettings().getFilteringURLParameters();
+        Gson gson = new Gson();
+        requestData = gson.toJson(dataBean);
+        break;
+    }
+    
+    // make sure that the URL was generated
+    if (requestURL == null) {
+      logger.error("Primary search URL couldn't be generated; Search engine must have encountered " +
+          "an unexpected search instance type: " + searchInstance.getSearchType());
+      return (null);
+    }
+    
+    // Sort by name (for REST and SOAP only at the moment. Parent Web services do not have the sort by name facility yet.)
+//    if (!searchInstance.getResourceTypeToSearchFor().equals(net.sf.taverna.biocatalogue.model.Resource.TYPE.Service)){
+        requestURL = Util.appendURLParameter(requestURL, "sort_by", "name");
+        requestURL = Util.appendURLParameter(requestURL, "sort_order", "asc");
+        requestURL = Util.appendURLParameter(requestURL, "include", "ancestors");
+//    }
+    logger.info("Service Catalogue Plugin: Request URL for search: " + requestURL);
+    
+    // append some search-type-independent parameters and return the URL
+    requestURL = Util.appendAllURLParameters(requestURL, searchInstance.getResourceTypeToSearchFor().getAPIResourceCollectionIndexAdditionalParameters());
+    return (new BioCatalogueAPIRequest(requestType, requestURL, requestData));
+  }
+  
+  
+
+  public void startNewSearch()
+  {
+    // construct API request for this search
+    BioCatalogueAPIRequest searchRequest = generateSearchRequest();
+    
+    // perform the actual search operation
+    try
+    {
+      ResourceIndex resourceIndex = null;
+      if (searchRequest.getRequestType() == BioCatalogueAPIRequest.TYPE.GET) {
+        resourceIndex = client.getBioCatalogueResourceLiteIndex(searchInstance.getResourceTypeToSearchFor(), searchRequest.getURL());
+      }
+      else {
+        // can only be POST then!
+        resourceIndex = client.postBioCatalogueResourceLiteIndex(searchInstance.getResourceTypeToSearchFor(), searchRequest.getURL(), searchRequest.getData());
+      }
+      SearchResults searchResults = new SearchResults(searchInstance.getResourceTypeToSearchFor(), resourceIndex);
+      
+      // only update search results of the associated search instance if the caller thread of
+      // this operation is still active - synchronisation helps to make sure that the results
+      // will definitely only be rendered if the current search instance is definitely active:
+      // this way searches finishing in quick succession will 'flash' the results for a short
+      // while before being updated, but that will happen in the correct order
+      synchronized (activeSearchInstanceTracker) {
+        if (isCurrentSearch()) {
+          searchInstance.setSearchResults(searchResults);
+          renderer.renderInitialResults(searchInstance);
+        }
+      }
+    }
+    catch (Exception e) {
+      logger.error("ERROR: Couldn't execute initial phase of a search by query, details below:", e);
+    }
+    
+    // no matter if search was completed or interrupted by a new search, notify the caller  // FIXME - is this needed?
+    searchCompleteNotifyCaller();
+  }
+  
+  
+  @SuppressWarnings("unchecked")
+  public void fetchMoreResults(int resultPageNumber)
+  {
+    if (resultPageNumber < 1 || resultPageNumber > searchInstance.getSearchResults().getTotalResultPageNumber()) {
+      logger.error("Prevented attempt to fetch an invalid result page: " + resultPageNumber + ". Returning...");
+      return;
+    }
+    
+    // construct search URL to hit on BioCatalogue server --
+    // it is exactly as the one for the initial search, but with a page number
+    // parameter being added
+    BioCatalogueAPIRequest searchRequest = generateSearchRequest();
+    searchRequest.setURL(Util.appendURLParameter(searchRequest.getURL(), BioCatalogueClient.API_PAGE_PARAMETER, ""+resultPageNumber));
+    
+    // fetch required result page
+    try 
+    {
+      Pair<CollectionCoreStatistics,List<ResourceLink>> newResultBatch = client.getListOfItemsFromResourceCollectionIndex(
+          searchInstance.getResourceTypeToSearchFor().getXmlBeansGeneratedCollectionClass(), searchRequest);
+      
+      int firstNewEntryIndex = searchInstance.getSearchResults().getFirstItemIndexOn(resultPageNumber);
+      searchInstance.getSearchResults().addSearchResults(newResultBatch.getSecondObject(), firstNewEntryIndex);
+      
+      // only update search results of the associated search instance if the caller thread of
+      // this operation is still active
+      if (isCurrentSearch()) {
+        renderer.renderFurtherResults(searchInstance, firstNewEntryIndex, searchInstance.getResourceTypeToSearchFor().getApiResourceCountPerIndexPage());
+      }
+    }
+    catch (Exception e) {
+      // FIXME
+    }
+    
+    
+    // no matter if search was completed or interrupted by a new search, notify the caller  // FIXME - is this needed?
+    searchCompleteNotifyCaller();
+  }
+  
+  
+  /**
+   * This method is used for notifying the object that has started the
+   * search of this particular search operation being complete.
+   */
+  protected void searchCompleteNotifyCaller() {
+    this.doneSignal.countDown();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java
new file mode 100644
index 0000000..fa43632
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstance.java
@@ -0,0 +1,490 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import javax.swing.Icon;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+import net.sf.taverna.biocatalogue.model.ResourceManager;
+import net.sf.taverna.biocatalogue.model.Tag;
+import net.sf.taverna.biocatalogue.model.Util;
+import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsRenderer;
+
+
+/**
+ * Class to hold settings for search instance. Objects of this type will
+ * be used to re-run a search instance at a later time -- or to apply
+ * filtering onto a previously executed search.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class SearchInstance implements Comparable<SearchInstance>, Serializable
+{
+  private static final long serialVersionUID = -5236966374301885370L;
+  
+  // CONSTANTS
+  public static enum TYPE
+  {
+    QuerySearch(ResourceManager.getImageIcon(ResourceManager.SEARCH_ICON)),
+    TagSearch(ResourceManager.getImageIcon(ResourceManager.TAG_ICON)),
+    Filtering(ResourceManager.getImageIcon(ResourceManager.FILTER_ICON));
+    
+    private Icon icon;
+    
+    /**
+     * @param icon Icon to represent search instances in different listings
+     *             - for example in search history.
+     */
+    TYPE(Icon icon) {
+      this.icon = icon;
+    }
+    
+    /**
+     * @return An icon that is most suitable to display search instance of this type in a UI component.
+     */
+    public Icon getIcon() {
+      return this.icon;
+    }
+  }
+  
+  
+  
+  // SEARCH SETTINGS - for either search by query or search by tag
+  private TYPE searchType;
+  private final TYPE serviceFilteringBasedOn; // service filtering may be based on {@link TYPE.QuerySearch} or {@link TYPE.TagSearch}
+  private final Resource.TYPE resourceTypeToSearchFor;
+  
+  private final String searchString;
+  private final List<Tag> searchTags;
+  
+  
+  // SERVICE FILTERING settings
+  private ServiceFilteringSettings filteringSettings;
+  
+  // SEARCH RESULTS
+  private transient SearchResults searchResults; // don't want to store search results when serialising...
+  
+  
+  
+  /**
+   * Constructs a query search instance for finding instance of a specific resource type.
+   * 
+   * @param searchString
+   * @param resourceTypeToSearchFor
+   */
+  public SearchInstance(String searchString, Resource.TYPE resourceTypeToSearchFor)
+  {
+    this.searchType = TYPE.QuerySearch;
+    this.serviceFilteringBasedOn = null;
+    
+    this.resourceTypeToSearchFor = resourceTypeToSearchFor; 
+    
+    this.searchString = searchString;
+    this.searchTags = null;
+  }
+  
+  
+  
+  /**
+   * Constructing a search instance for finding instance of a specific resource type by a single tag.
+   * 
+   * @param searchTag
+   * @param resourceTypeToSearchFor
+   */
+  public SearchInstance(Tag searchTag, Resource.TYPE resourceTypeToSearchFor)
+  {
+    this.searchType = TYPE.TagSearch;
+    this.serviceFilteringBasedOn = null;
+    
+    this.resourceTypeToSearchFor = resourceTypeToSearchFor;
+    
+    this.searchTags = Collections.singletonList(searchTag);
+    this.searchString = null;
+  }
+  
+  
+  /**
+   * Constructing a search instance for finding instance of a specific resource type by a list of tags.
+   * 
+   * @param searchTags
+   * @param resourceTypeToSearchFor
+   */
+  public SearchInstance(List<Tag> searchTags, Resource.TYPE resourceTypeToSearchFor)
+  {
+    this.searchType = TYPE.TagSearch;
+    this.serviceFilteringBasedOn = null;
+    
+    this.resourceTypeToSearchFor = resourceTypeToSearchFor;
+    
+    this.searchTags = searchTags;
+    this.searchString = null;
+  }
+  
+  
+  
+  /**
+   * Constructing service filtering search instance.
+   * 
+   * @param si SearchInstance to base the current on.
+   *           Can be either {@link TYPE#TagSearch} or {@link TYPE#QuerySearch} type of SearchInstance.
+   * @param filteringSettings Filtering settings associated with this search instance.
+   */
+  public SearchInstance(SearchInstance si, ServiceFilteringSettings filteringSettings) throws IllegalArgumentException
+  {
+    if (!si.isTagSearch() && !si.isQuerySearch()) {
+      throw new IllegalArgumentException("Cannot create Service Filtering search instance - " +
+                                         "supplied base search instance must be either QuerySearch or TagSearch");
+    }
+    
+    this.searchType = TYPE.Filtering;
+    this.serviceFilteringBasedOn = si.searchType;
+    
+    this.resourceTypeToSearchFor = si.resourceTypeToSearchFor;
+    
+    // this search instance inherits search term (i.e. search query or the tag) from the supplied search instance
+    this.searchString = si.isQuerySearch() ? si.searchString : null;
+    this.searchTags = si.isTagSearch() ? si.searchTags : null;
+    
+    // also, store the filtering settings that are to be applied to the newly
+    // created search instance
+    this.filteringSettings = filteringSettings;
+  }
+  
+  
+  /**
+   * Determines whether the two search instances are identical.
+   */
+  // TODO - fix the equals() method
+  public boolean equals(Object other)
+  {
+    if (other instanceof SearchInstance)
+    {
+      SearchInstance s = (SearchInstance)other;
+      
+      boolean bSearchTypesMatch = (this.searchType == s.getSearchType());
+      if (bSearchTypesMatch) {
+        switch (this.searchType) {
+          case QuerySearch:  bSearchTypesMatch = this.searchString.equals(s.getSearchString()); break;
+          
+          case TagSearch:    bSearchTypesMatch = this.searchTags.equals(s.getSearchTags()); break;
+          
+          case Filtering:    bSearchTypesMatch = this.serviceFilteringBasedOn == s.getServiceFilteringBasedOn();
+                             if (bSearchTypesMatch) {
+                               if (this.serviceFilteringBasedOn == TYPE.QuerySearch) {
+                                 bSearchTypesMatch = this.searchString.equals(s.getSearchString());
+                               }
+                               else {
+                                 bSearchTypesMatch = this.searchTags.equals(s.getSearchTags());
+                               }
+                             }
+                             if (bSearchTypesMatch) {
+                               if (this.filteringSettings != null) {
+                                 bSearchTypesMatch = this.filteringSettings.equals(s.getFilteringSettings());
+                               }
+                               else if (s.filteringSettings != null) {
+                                 // other isn't null, this one is - so 'false'
+                                 bSearchTypesMatch = false;
+                               }
+                               else {
+                                 // both could be null
+                                 bSearchTypesMatch = (this.filteringSettings == s.getFilteringSettings());
+                               }
+                             }
+                             break;
+          default: bSearchTypesMatch = false;
+        }
+      }
+      
+      return (bSearchTypesMatch &&
+              /* TODO re-enable this when limits are implemented -- this.iResultCountLimit == s.getResultCountLimit() && */
+              this.resourceTypeToSearchFor == s.getResourceTypeToSearchFor());
+    }
+    else
+      return (false);
+  }
+  
+  
+  public int compareTo(SearchInstance 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()));
+    }
+  }
+  
+  
+  /**
+   * See {@link SearchInstance#getDescriptionStringForSearchStatus(SearchInstance)}
+   */
+  public String getDescriptionStringForSearchStatus() {
+    return (getDescriptionStringForSearchStatus(this));
+  }
+  
+  
+  /**
+   * @param si {@link SearchInstance} for which the method is executed.
+   * @return String that can be used as a description of the provided {@link SearchInstance}
+   *         in the search status label. Returned strings may look like: <br/>
+   *         - <code>empty search string</code><br/>
+   *         - <code>query "[search_query]"</code><br/>
+   *         - <code>tag "[search_tag]"</code><br/>
+   *         - <code>tags "[tag1]", "[tag2]", "[tag3]"</code><br/>
+   *         - <code>query "[search_query]" and X filter(s)</code><br/>
+   *         - <code>tag "[search_tag]" and X filter(s)</code><br/>
+   *         - <code>tags "[tag1]", "[tag2]", "[tag3]" and X filter(s)</code><br/>
+   */
+  public static String getDescriptionStringForSearchStatus(SearchInstance si)
+  {
+    switch (si.searchType)
+    {
+      case QuerySearch: String searchQuery = si.getSearchTerm();
+                        return (searchQuery.length() == 0 ?
+                                "empty search string" :
+                                "query " + si.getSearchTerm());
+      
+      case TagSearch:   return (Util.pluraliseNoun("tag", si.getSearchTags().size()) + " " + si.getSearchTerm());
+      
+      case Filtering:   int filterNumber = si.getFilteringSettings().getNumberOfFilteringCriteria();
+      
+                        SearchInstance tempBaseSI = si.deepCopy();
+                        tempBaseSI.searchType = si.getServiceFilteringBasedOn();
+                        return getDescriptionStringForSearchStatus(tempBaseSI) + " and " + filterNumber + " " + Util.pluraliseNoun("filter", filterNumber);
+                        
+      default:          return ("unexpected type of search");
+    }
+  }
+  
+  
+  public String toString()
+  {
+    String out = "<html>";
+    
+    if (this.isQuerySearch() || this.isTagSearch()) {
+      out += (this.isTagSearch() ? "Tag s" : "S") + "earch: '" + getSearchTerm() + "' [" + this.detailsAsString() + "]";
+    }
+    else if (this.isServiceFilteringSearch()) {
+      out += "Filter:<br>" +
+             (getSearchTerm().length() > 0 ? ("- based on " + (this.isQuerySearch() ? "term" : "tag") + " '" + getSearchTerm() + "'<br>") : "") +
+             "- scope: " + detailsAsString() + "<br>" +
+             "- " + this.filteringSettings.detailsAsString();
+    }
+    
+    out += "</html>";
+    
+    return (out);
+  }
+  
+  
+  /**
+   * @return A string representation of search settings held in this object;
+   *         actual search value (string/tag) are ignored - this only affects
+   *         types to search and the number of objects to fetch.
+   */
+  public String detailsAsString()
+  {
+    // include the name of the resource type collection that is to be / was searched for
+    String str = this.getResourceTypeToSearchFor().getCollectionName();
+    
+    // add the rest to the string representation of the search instance
+    str = str /* TODO re-enable when limits are implemented -- "; limit: " + this.iResultCountLimit +*/;
+    
+    return (str);
+  }
+  
+  
+  
+  // ***** Getters for all fields *****
+  
+  /**
+   * @return Type of this search instance.
+   */
+  public TYPE getSearchType() {
+    return (this.searchType);
+  }
+  
+  
+  /**
+   * @return True if this search settings instance describes a search by tag.
+   */
+  public boolean isTagSearch() {
+    return (this.searchType == TYPE.TagSearch);
+  }
+  
+  
+  /**
+   * @return True if this search settings instance describes a search by query.
+   */
+  public boolean isQuerySearch() {
+    return (this.searchType == TYPE.QuerySearch);
+  }
+  
+  
+  /**
+   * @return True if this search settings instance describes service filtering operation.
+   */
+  public boolean isServiceFilteringSearch() {
+    return (this.searchType == TYPE.Filtering);
+  }
+  
+  
+  /**
+   * Allows to test which type of search this filtering operation is based on -- any filtering
+   * operation can be:
+   * <li>derived from an initial search by tag(s) or by free-text query</li>
+   * <li>or can be just a standalone filtering operation, where filtering criteria are
+   *     applied to all resources of the specified type, without prior search.</li> 
+   * 
+   * @return Value {@link TYPE#QuerySearch} or {@link TYPE#TagSearch} if this filtering operation has a known "parent",<br/>
+   *         <code>null</code> if this is a proper search (not a filtering!) operation, or
+   *         if this filtering operation was not based on any search. 
+   */
+  public TYPE getServiceFilteringBasedOn() {
+    return serviceFilteringBasedOn;
+  }
+  
+  
+  public Resource.TYPE getResourceTypeToSearchFor() {
+    return this.resourceTypeToSearchFor;
+  }
+  
+  
+  /**
+   * @return Search string; only valid when SearchSettings object holds data about a search by query, not a tag search.
+   */
+  public String getSearchString() {
+    return searchString;
+  }
+  
+  public List<Tag> getSearchTags() {
+    return searchTags;
+  }
+  
+  /**
+   * This method is to be used when the type of search is not checked - in
+   * case of query search the method returns the search string, otherwise
+   * the tag(s) that is to be searched.
+   * 
+   * @return The value will be returned in double quotes.
+   */
+  public String getSearchTerm()
+  {
+    if (this.searchType == TYPE.QuerySearch || this.serviceFilteringBasedOn == TYPE.QuerySearch) {
+      return (this.searchString.length() == 0 ?
+              "" :
+              "\"" + this.searchString + "\"");
+    }
+    else {
+      List<String> tagDisplayNames = new ArrayList<String>();
+      for (Tag t : this.searchTags) {
+        tagDisplayNames.add(t.getTagDisplayName());
+      }
+      return (Util.join(tagDisplayNames, "\"", "\"", ", "));
+    }
+  }
+  
+  
+  public ServiceFilteringSettings getFilteringSettings() {
+    return filteringSettings;
+  }
+  public void setFilteringSettings(ServiceFilteringSettings filteringSettings) {
+    this.filteringSettings = filteringSettings;
+  }
+  
+  
+  public SearchResults getSearchResults() {
+    return searchResults;
+  }
+  public void setSearchResults(SearchResults searchResults) {
+    this.searchResults = searchResults;
+  }
+  
+  /**
+   * @return True if search results are available;
+   *         False if no search results are available - probably search hasn't been carried out yet.
+   */
+  public boolean hasSearchResults() {
+    return (searchResults != null);
+  }
+  
+  /**
+   * @return True if this is a new search; false otherwise.
+   *         (Search is currently treated as new if there are no search results available yet.)
+   */
+  public boolean isNewSearch() {
+    return (!hasSearchResults());
+  }
+  
+  /**
+   * Removes any previous search results; after execution of
+   * this method this search instance is treated as "new search".
+   */
+  public void clearSearchResults() {
+    this.searchResults = null;
+  }
+
+  
+  
+  
+  // *** Methods that call SearchEngine in order to start new / resume result fetching for a previous search ***
+  //
+  // They are used to relay external calls to these methods to the underlying instance
+  // of SearchEngine which will perform the actual search operations for this search instance.
+  
+  /**
+   * @param activeSearchInstanceTracker Tracker of current search instances for different resource types -
+   *                                    aids in early termination of older searches.
+   * @param doneSignal Means of notifying the parentSeachThread of completing the requested search operation.
+   *                   The parent thread will block until doneSignal is activated.
+   * @param renderer   {@link SearchResultsRenderer} that will render results of this search.
+   */
+  public void startNewSearch(SearchInstanceTracker activeSearchInstanceTracker,
+                             CountDownLatch doneSignal, SearchResultsRenderer renderer)
+  {
+    new SearchEngine(this, activeSearchInstanceTracker, doneSignal, renderer).startNewSearch();
+  }
+  
+  
+  /**
+   * @param activeSearchInstanceTracker Tracker of current search instances for different resource types -
+   *                                    aids in early termination of older searches.
+   * @param doneSignal Means of notifying the parentSeachThread of completing the requested search operation.
+   *                   The parent thread will block until doneSignal is activated.
+   * @param renderer   {@link SearchResultsRenderer} that will render results of this search.
+   * @param resultPageNumber
+   */
+  public void fetchMoreResults(SearchInstanceTracker activeSearchInstanceTracker,
+                               CountDownLatch doneSignal, SearchResultsRenderer renderer, int resultPageNumber)
+  {
+    new SearchEngine(this, activeSearchInstanceTracker, doneSignal, renderer).fetchMoreResults(resultPageNumber);
+  }
+  
+  
+  
+  
+  /**
+   * Used in the plugin, for example, to transfer search results from Search tab to
+   * Filtering tab. This way both tabs will remain completely independent.
+   * 
+   * @return Deep copy of this SearchInstance object. If deep copying doesn't succeed,
+   *         <code>null</code> is returned.
+   */
+  public SearchInstance deepCopy() {
+    return (SearchInstance)Util.deepCopy(this);
+  }
+  
+  public boolean isEmptySearch() {
+	  return ((searchString == null) || searchString.isEmpty()) &&
+	  ((searchTags == null) || searchTags.isEmpty()) &&
+			  ((filteringSettings == null) || (filteringSettings.getNumberOfFilteringCriteria() == 0));
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java
new file mode 100644
index 0000000..906ded1
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchInstanceTracker.java
@@ -0,0 +1,57 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import net.sf.taverna.biocatalogue.model.Resource;
+
+/**
+ * Implementations of this interface will keep track of
+ * current search instances for different resource types
+ * (under assumption that {@link SearchInstance} classes
+ * can only deal with one resource type per instance).
+ * 
+ * In the BioCatalogue plugin it is one of the UI components
+ * that needs to keep track of active search instances. This
+ * interface helps to decouple the search engine model from the
+ * UI.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public interface SearchInstanceTracker
+{
+  /**
+   * Clears all records of previous search instances.
+   */
+  public void clearPreviousSearchInstances();
+  
+  /**
+   * Registers an instance of {@link SearchInstance} class
+   * as a current one for a specific resource type.
+   * 
+   * Repeated calls to this method with the same parameter
+   * should overwrite old values.
+   * 
+   * @param searchType Resource type to associate the registered
+   *                   {@link SearchInstance} with.
+   */
+  public void registerSearchInstance(Resource.TYPE searchType, SearchInstance searchInstance);
+  
+  
+  /**
+   * Tests if provided {@link SearchInstance} is registered as the
+   * current one.
+   * 
+   * @param searchType Resource type to perform the test for.
+   * @param searchInstance {@link SearchInstance} object that is expected to be
+   *                       the current search instance for the specified resource type.
+   * @return <code>true</code> - if the provided <code>searchInstance</code> is indeed
+   *                       currently registered as the active one for the given resouce type;<br/>
+   *         <code>false</code> - otherwise.
+   */
+  public boolean isCurrentSearchInstance(Resource.TYPE searchType, SearchInstance searchInstance);
+  
+  
+  /**
+   * @param searchType
+   * @return Currently active {@link SearchInstance} object for the specified resource type.
+   */
+  public SearchInstance getCurrentSearchInstance(Resource.TYPE searchType);
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java
new file mode 100644
index 0000000..cf5e7db
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchOptions.java
@@ -0,0 +1,70 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.util.Collections;
+import java.util.List;
+
+import net.sf.taverna.biocatalogue.model.Tag;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.ui.SearchOptionsPanel;
+
+/**
+ * Instances of this class can store the state of the
+ * {@link SearchOptionsPanel} / {@link TagSelectionDialog} in
+ * order to help instantiate {@link SearchInstance} objects.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class SearchOptions
+{
+  private SearchInstance preconfiguredSearchInstance;
+  private SearchInstance.TYPE searchType;
+  private String searchString;
+  private List<Tag> searchTags;
+  private List<TYPE> resourceTypesToSearchFor;
+  
+  public SearchOptions(String searchString, List<TYPE> searchTypes) {
+    this.preconfiguredSearchInstance = null;
+    this.searchType = SearchInstance.TYPE.QuerySearch;
+    this.searchString = searchString;
+    this.searchTags = null;
+    this.resourceTypesToSearchFor = searchTypes;
+  }
+  
+  public SearchOptions(List<Tag> searchTags, List<TYPE> searchTypes) {
+    this.preconfiguredSearchInstance = null;
+    this.searchType = SearchInstance.TYPE.TagSearch;
+    this.searchString = null;
+    this.searchTags = searchTags;
+    this.resourceTypesToSearchFor = searchTypes;
+  }
+  
+  public SearchOptions(SearchInstance preconfiguredSearchInstance) {
+    this.preconfiguredSearchInstance = preconfiguredSearchInstance;
+    this.searchType = preconfiguredSearchInstance.getSearchType();
+    this.searchString = preconfiguredSearchInstance.getSearchString();
+    this.searchTags = preconfiguredSearchInstance.getSearchTags();
+    this.resourceTypesToSearchFor = Collections.singletonList(preconfiguredSearchInstance.getResourceTypeToSearchFor());
+  }
+  
+  
+  public SearchInstance getPreconfiguredSearchInstance() {
+    return preconfiguredSearchInstance;
+  }
+  
+  public SearchInstance.TYPE getSearchType() {
+    return searchType;
+  }
+  
+  public String getSearchString() {
+    return searchString;
+  }
+  
+  public List<Tag> getSearchTags() {
+    return searchTags;
+  }
+  
+  public List<TYPE> getResourceTypesToSearchFor() {
+    return resourceTypesToSearchFor;
+  }
+  
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java
new file mode 100644
index 0000000..d18076c
--- /dev/null
+++ b/taverna-workbench-perspective-biocatalogue/src/main/java/net/sf/taverna/biocatalogue/model/search/SearchResults.java
@@ -0,0 +1,214 @@
+package net.sf.taverna.biocatalogue.model.search;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.biocatalogue.model.LoadingResource;
+import net.sf.taverna.biocatalogue.model.Resource.TYPE;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceIndex;
+import net.sf.taverna.biocatalogue.model.connectivity.BeansForJSONLiteAPI.ResourceLinkWithName;
+
+import org.apache.log4j.Logger;
+
+import org.biocatalogue.x2009.xml.rest.ResourceLink;
+
+
+/**
+ * Generic class for any kinds of search results.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+public class SearchResults implements Serializable
+{
+  private static final long serialVersionUID = 6994685875323246165L;
+  
+  private transient Logger logger; // don't want to serialise the logger...
+  
+  private final TYPE typeOfResourcesInTheResultSet;
+  private final int totalResultCount;
+  
+  // Data store for found items
+  protected ArrayList<ResourceLink> foundItems;
+  private int fullyFetchedItemCount;
+
+  
+  
+  public SearchResults(TYPE typeOfResourcesInTheResultSet, BeansForJSONLiteAPI.ResourceIndex resourceIndex)
+  {
+    this.typeOfResourcesInTheResultSet = typeOfResourcesInTheResultSet;
+    this.totalResultCount = resourceIndex.getResources().length;
+    this.fullyFetchedItemCount = 0;
+    
+    this.logger = Logger.getLogger(this.getClass());
+    
+    initialiseSearchResultCollection(resourceIndex);
+  }
+  
+  
+  /**
+   * The collection of results is initialised to cater for the expected number of
+   * values - placeholder with just a name and URL for each of the expected result entries is stored.
+   * 
+   * @param resourceIndex
+   */
+  protected void initialiseSearchResultCollection(ResourceIndex resourceIndex)
+  {
+    foundItems = new ArrayList<ResourceLink>();
+    foundItems.ensureCapacity(getTotalMatchingItemCount());
+    
+    ResourceLinkWithName resourceLink = null;
+    for (int i = 0; i < getTotalMatchingItemCount(); i++) {
+      resourceLink = resourceIndex.getResources()[i];
+      this.foundItems.add(new LoadingResource(resourceLink.getURL(), resourceLink.getName()));
+    }
+  }
+  
+  
+  public synchronized void addSearchResults(List<ResourceLink> searchResultsData, int positionToStartAddingResults)
+  {
+    // only update a specific portion of results
+    for (int i = 0; i < searchResultsData.size(); i++) {
+      this.foundItems.set(i + positionToStartAddingResults, searchResultsData.get(i));
+    }
+    
+    fullyFetchedItemCount += searchResultsData.size();
+  }
+  
+  
+  public TYPE getTypeOfResourcesInTheResultSet() {
+    return typeOfResourcesInTheResultSet;
+  }
+  
+  
+  /**
+   * @return List of resources that have matched the search query
+   *         and/or specified filtering criteria. 
+   */
+  public List<ResourceLink> getFoundItems() {
+    return (this.foundItems);
+  }
+  
+  
+  /**
+   * @return Number of resources that have matched the search query
+   *         (and/or specified filtering criteria) that have already been
+   *         fetched.
+   */
+  public int getFetchedItemCount() {
+    return (this.fullyFetchedItemCount);
+  }
+  
+  
+  /**
+   * @return Total number of resources that have matched the search query
+   *         (and/or specified filtering criteria) - most of these will
+   *         likely not be fetched yet.
+   */
+  public int getTotalMatchingItemCount() {
+    return (this.totalResultCount);
+  }
+  
+  
+  /**
+   * @return Total number of pages in the current result set.
+   */
+  public int getTotalResultPageNumber() {
+    int numberOfResourcesPerPageForThisResourceType = this.getTypeOfResourcesInTheResultSet().getApiResourceCountPerIndexPage();
+    return (int)(Math.ceil((double)getTotalMatchingItemCount() / numberOfResourcesPerPageForThisResourceType));
+  }
+  
+  
+  /**
+   * List of matching items will be partial and populated sequentially
+   * based on user actions. Therefore, this method helps to check
+   * which list entries are still not populated.
+   * 
+   * @param startIndex Beginning of the range to check.
+   * @param endIndex End of the range to check.
+   * @return Zero-based index of the first entry in the list of
+   *         matching resources that hasn't been fetched yet.
+   *         Will return <code>-1</code> if the provided range
+   *         parameters are incorrect or if all items in the
+   *         specified range are already available.
+   */
+  public int getFirstMatchingItemIndexNotYetFetched(int startIndex, int endIndex)
+  {
+    // check the specified range is correct
+    if (startIndex < 0 || endIndex > getTotalMatchingItemCount() - 1) {
+      return (-1);
+    }
+    
+    // go through the search results in the specified range
+    // in an attempt to find an item that hasn't been fetched
+    // just yet
+    for (int i = startIndex; i <= endIndex; i++) {
+      ResourceLink item = this.foundItems.get(i);
+      if (item != null && item instanceof LoadingResource && !((LoadingResource)item).isLoading()) {
+        return (i);
+      }
+    }
+    
+    // apparently, all items in the provided range are fetched
+    return (-1);
+  }
+  
+  
+  
+  /**
+   * @param matchingItemIndex Index of the matching item from search results.
+   * @return Index (starting from "1") of page in the search results, where
+   *         the matching item with a specified index is located. If the
+   *         <code>matchingItemIndex</code> is wrong, <code>-1</code> is returned.
+   */
+  public int getMatchingItemPageNumberFor(int matchingItemIndex)
+  {
+    // check the specified index is correct
+    if (matchingItemIndex < 0 || matchingItemIndex > getTotalMatchingItemCount() - 1) {
+      return (-1);
+    }
+    
+    int resultsPerPageForThisType = this.getTypeOfResourcesInTheResultSet().getApiResourceCountPerIndexPage();
+    return (matchingItemIndex / resultsPerPageForThisType + 1);
+  }
+  
+  
+  /**
+   * @param resultPageNumber Number of the page, for which the calculations are to be done.
+   * @return Index of the first result entry on the specified result page. If <code>resultPageNumber</code>
+   *         is less than <code>1</code> or greater than the total number of pages in the result set,
+   *         a value of <code>-1</code> will be returned.
+   */
+  public int getFirstItemIndexOn(int resultPageNumber)
+  {
+    // page number must be in a valid range - starting with 1..onwards
+    if (resultPageNumber < 1 || resultPageNumber > getTotalResultPageNumber()) {
+      return (-1);
+    }
+    
+    int numberOfResourcesPerPageForThisResourceType = this.getTypeOfResourcesInTheResultSet().getApiResourceCountPerIndexPage();
+    return ((resultPageNumber - 1) * numberOfResourcesPerPageForThisResourceType);
+  }
+  
+  
+  
+  /**
+   * Mainly for testing - outputs number of search results per item type.
+   */
+  public String toString()
+  {
+    // FIXME
+    
+//    StringBuilder out = new StringBuilder("Breakdown of item counts by type:\n");
+//    for (Map.Entry<Integer,String> itemTypeNamePair : Resource.ALL_SUPPORTED_RESOURCE_COLLECTION_NAMES.entrySet()) {
+//      out.append(itemTypeNamePair.getValue() + ": " +getFetchedItemCount(itemTypeNamePair.getKey()) +
+//                 "/" + getTotalItemCount(itemTypeNamePair.getKey()) + "\n");
+//    }
+//    
+//    return (out.toString());
+    
+    return ("search results... not implemented!!!");
+  }
+  
+}