First patch for * JAL-493
authorjprocter <Jim Procter>
Fri, 14 May 2010 17:08:57 +0000 (17:08 +0000)
committerjprocter <Jim Procter>
Fri, 14 May 2010 17:08:57 +0000 (17:08 +0000)
21 files changed:
src/jalview/gui/AlignFrame.java
src/jalview/ws/AWSThread.java [moved from src/jalview/ws/WSThread.java with 52% similarity]
src/jalview/ws/AWsJob.java [new file with mode: 0644]
src/jalview/ws/Discoverer.java
src/jalview/ws/JPredClient.java
src/jalview/ws/JPredThread.java
src/jalview/ws/JWS1Thread.java [new file with mode: 0644]
src/jalview/ws/JobStateSummary.java [new file with mode: 0644]
src/jalview/ws/MsaWSClient.java
src/jalview/ws/MsaWSThread.java
src/jalview/ws/SeqSearchWSClient.java
src/jalview/ws/SeqSearchWSThread.java
src/jalview/ws/WS1Client.java [new file with mode: 0644]
src/jalview/ws/WSClient.java
src/jalview/ws/WSJob.java [new file with mode: 0644]
src/jalview/ws/jws2/AWS2Thread.java [new file with mode: 0644]
src/jalview/ws/jws2/JWs2Job.java [new file with mode: 0644]
src/jalview/ws/jws2/Jws2Client.java [new file with mode: 0644]
src/jalview/ws/jws2/Jws2Discoverer.java [new file with mode: 0644]
src/jalview/ws/jws2/MsaWSClient.java [new file with mode: 0644]
src/jalview/ws/jws2/MsaWSThread.java [new file with mode: 0644]

index 2e6ea52..a6caa51 100755 (executable)
@@ -36,6 +36,7 @@ import jalview.io.*;
 import jalview.jbgui.*;
 import jalview.schemes.*;
 import jalview.ws.*;
+import jalview.ws.jws2.Jws2Discoverer;
 
 /**
  * DOCUMENT ME!
@@ -3816,7 +3817,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         {
           final ext.vamsas.ServiceHandle sh = (ext.vamsas.ServiceHandle) msaws
                   .get(i);
-          jalview.ws.WSClient impl = jalview.ws.Discoverer
+          jalview.ws.WSMenuEntryProviderI impl = jalview.ws.Discoverer
                   .getServiceClient(sh);
           impl.attachWSMenuEntry(msawsmenu, this);
 
@@ -3831,7 +3832,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         {
           final ext.vamsas.ServiceHandle sh = (ext.vamsas.ServiceHandle) secstrpr
                   .get(i);
-          jalview.ws.WSClient impl = jalview.ws.Discoverer
+          jalview.ws.WSMenuEntryProviderI impl = jalview.ws.Discoverer
                   .getServiceClient(sh);
           impl.attachWSMenuEntry(secstrmenu, this);
         }
@@ -3845,12 +3846,22 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         {
           final ext.vamsas.ServiceHandle sh = (ext.vamsas.ServiceHandle) seqsrch
                   .elementAt(i);
-          jalview.ws.WSClient impl = jalview.ws.Discoverer
+          jalview.ws.WSMenuEntryProviderI impl = jalview.ws.Discoverer
                   .getServiceClient(sh);
           impl.attachWSMenuEntry(seqsrchmenu, this);
         }
         wsmenu.add(seqsrchmenu);
       }
+      // TODO: move into separate menu builder class.
+      {
+        Jws2Discoverer jws2servs = Jws2Discoverer.getDiscoverer();
+        if (jws2servs!=null && jws2servs.hasServices())
+        {
+          JMenu jws2men = new JMenu("Jalview 2 Services");
+          jws2servs.attachWSMenuEntry(jws2men, this);
+          wsmenu.add(jws2men);
+        }
+      }
       // finally, add the whole shebang onto the webservices menu
       resetWebServiceMenu();
       for (int i = 0, j = wsmenu.size(); i < j; i++)
similarity index 52%
rename from src/jalview/ws/WSThread.java
rename to src/jalview/ws/AWSThread.java
index 65c1c96..1de2c01 100644 (file)
-/*\r
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)\r
- * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle\r
- * \r
- * This file is part of Jalview.\r
- * \r
- * Jalview is free software: you can redistribute it and/or\r
- * modify it under the terms of the GNU General Public License \r
- * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\r
- * \r
- * Jalview is distributed in the hope that it will be useful, but \r
- * WITHOUT ANY WARRANTY; without even the implied warranty \r
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
- * PURPOSE.  See the GNU General Public License for more details.\r
- * \r
- * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
- */\r
-package jalview.ws;\r
-\r
-import javax.swing.*;\r
-\r
-import jalview.bin.*;\r
-import jalview.datamodel.*;\r
-import jalview.gui.*;\r
-import jalview.gui.FeatureRenderer.FeatureRendererSettings;\r
-\r
-public abstract class WSThread extends Thread\r
-{\r
-  /**\r
-   * Generic properties for Web Service Client threads.\r
-   */\r
-  /**\r
-   * view that this job was associated with\r
-   */\r
-  AlignmentI currentView = null;\r
-\r
-  /**\r
-   * feature settings from view that job was associated with\r
-   */\r
-  FeatureRendererSettings featureSettings = null;\r
-\r
-  /**\r
-   * metadata about this web service\r
-   */\r
-  WebserviceInfo wsInfo = null;\r
-\r
-  /**\r
-   * original input data for this job\r
-   */\r
-  AlignmentView input = null;\r
-\r
-  /**\r
-   * dataset sequence relationships to be propagated onto new results\r
-   */\r
-  AlignedCodonFrame[] codonframe = null;\r
-\r
-  /**\r
-   * are there jobs still running in this thread.\r
-   */\r
-  boolean jobComplete = false;\r
-\r
-  abstract class WSJob\r
-  {\r
-    /**\r
-     * Generic properties for an individual job within a Web Service Client\r
-     * thread\r
-     */\r
-    int jobnum = 0; // WebServiceInfo pane for this job\r
-\r
-    String jobId; // ws job ticket\r
-\r
-    /**\r
-     * has job been cancelled\r
-     */\r
-    boolean cancelled = false;\r
-\r
-    /**\r
-     * number of exceptions left before job dies\r
-     */\r
-    int allowedServerExceptions = 3;\r
-\r
-    /**\r
-     * has job been submitted\r
-     */\r
-    boolean submitted = false;\r
-\r
-    /**\r
-     * are all sub-jobs complete\r
-     */\r
-    boolean subjobComplete = false;\r
-\r
-    /**\r
-     * \r
-     * @return true if job has completed and valid results are available\r
-     */\r
-    abstract boolean hasResults();\r
-\r
-    /**\r
-     * \r
-     * @return boolean true if job can be submitted.\r
-     */\r
-    abstract boolean hasValidInput();\r
-\r
-    /**\r
-     * The last result object returned by the service.\r
-     */\r
-    vamsas.objects.simple.Result result;\r
-  }\r
-\r
-  class JobStateSummary\r
-  {\r
-    /**\r
-     * number of jobs running\r
-     */\r
-    int running = 0;\r
-\r
-    /**\r
-     * number of jobs queued\r
-     */\r
-    int queuing = 0;\r
-\r
-    /**\r
-     * number of jobs finished\r
-     */\r
-    int finished = 0;\r
-\r
-    /**\r
-     * number of jobs failed\r
-     */\r
-    int error = 0;\r
-\r
-    /**\r
-     * number of jobs stopped due to server error\r
-     */\r
-    int serror = 0;\r
-\r
-    /**\r
-     * number of jobs cancelled\r
-     */\r
-    int cancelled = 0;\r
-\r
-    /**\r
-     * number of jobs finished with results\r
-     */\r
-    int results = 0;\r
-\r
-    /**\r
-     * processes WSJob and updates job status counters and WebService status\r
-     * displays\r
-     * \r
-     * @param wsInfo\r
-     * @param OutputHeader\r
-     * @param j\r
-     */\r
-    void updateJobPanelState(WebserviceInfo wsInfo, String OutputHeader,\r
-            WSJob j)\r
-    {\r
-      if (j.result != null)\r
-      {\r
-        String progheader = "";\r
-        // Parse state of job[j]\r
-        if (j.result.isRunning())\r
-        {\r
-          running++;\r
-          wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_RUNNING);\r
-        }\r
-        else if (j.result.isQueued())\r
-        {\r
-          queuing++;\r
-          wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_QUEUING);\r
-        }\r
-        else if (j.result.isFinished())\r
-        {\r
-          finished++;\r
-          j.subjobComplete = true;\r
-          if (j.hasResults())\r
-          {\r
-            results++;\r
-          }\r
-          wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_OK);\r
-        }\r
-        else if (j.result.isFailed())\r
-        {\r
-          progheader += "Job failed.\n";\r
-          j.subjobComplete = true;\r
-          wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);\r
-          error++;\r
-        }\r
-        else if (j.result.isServerError())\r
-        {\r
-          serror++;\r
-          j.subjobComplete = true;\r
-          wsInfo.setStatus(j.jobnum,\r
-                  WebserviceInfo.STATE_STOPPED_SERVERERROR);\r
-        }\r
-        else if (j.result.isBroken() || j.result.isFailed())\r
-        {\r
-          error++;\r
-          j.subjobComplete = true;\r
-          wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);\r
-        }\r
-        // and pass on any sub-job messages to the user\r
-        wsInfo.setProgressText(j.jobnum, OutputHeader);\r
-        wsInfo.appendProgressText(j.jobnum, progheader);\r
-        if (j.result.getStatus() != null)\r
-        {\r
-          wsInfo.appendProgressText(j.jobnum, j.result.getStatus());\r
-        }\r
-      }\r
-      else\r
-      {\r
-        if (j.submitted && j.subjobComplete)\r
-        {\r
-          if (j.allowedServerExceptions == 0)\r
-          {\r
-            serror++;\r
-          }\r
-          else if (j.result == null)\r
-          {\r
-            error++;\r
-          }\r
-        }\r
-      }\r
-    }\r
-  }\r
-\r
-  /**\r
-   * one or more jobs being managed by this thread.\r
-   */\r
-  WSJob jobs[] = null;\r
-\r
-  /**\r
-   * full name of service\r
-   */\r
-  String WebServiceName = null;\r
-\r
-  String OutputHeader;\r
-\r
-  String WsUrl = null;\r
-\r
-  /**\r
-   * query web service for status of job. on return, job.result must not be null\r
-   * - if it is then it will be assumed that the job status query timed out and\r
-   * a server exception will be logged.\r
-   * \r
-   * @param job\r
-   * @throws Exception\r
-   *           will be logged as a server exception for this job\r
-   */\r
-  abstract void pollJob(WSJob job) throws Exception;\r
-\r
-  public void run()\r
-  {\r
-    JobStateSummary jstate = null;\r
-    if (jobs == null)\r
-    {\r
-      jobComplete = true;\r
-    }\r
-    while (!jobComplete)\r
-    {\r
-      jstate = new JobStateSummary();\r
-      for (int j = 0; j < jobs.length; j++)\r
-      {\r
-\r
-        if (!jobs[j].submitted && jobs[j].hasValidInput())\r
-        {\r
-          StartJob(jobs[j]);\r
-        }\r
-\r
-        if (jobs[j].submitted && !jobs[j].subjobComplete)\r
-        {\r
-          try\r
-          {\r
-            pollJob(jobs[j]);\r
-            if (jobs[j].result == null)\r
-            {\r
-              throw (new Exception(\r
-                      "Timed out when communicating with server\nTry again later.\n"));\r
-            }\r
-            jalview.bin.Cache.log.debug("Job " + j + " Result state "\r
-                    + jobs[j].result.getState() + "(ServerError="\r
-                    + jobs[j].result.isServerError() + ")");\r
-          } catch (Exception ex)\r
-          {\r
-            // Deal with Transaction exceptions\r
-            wsInfo.appendProgressText(jobs[j].jobnum, "\n" + WebServiceName\r
-                    + " Server exception!\n" + ex.getMessage());\r
-            Cache.log.warn(WebServiceName + " job(" + jobs[j].jobnum\r
-                    + ") Server exception: " + ex.getMessage());\r
-\r
-            if (jobs[j].allowedServerExceptions > 0)\r
-            {\r
-              jobs[j].allowedServerExceptions--;\r
-              Cache.log.debug("Sleeping after a server exception.");\r
-              try\r
-              {\r
-                Thread.sleep(5000);\r
-              } catch (InterruptedException ex1)\r
-              {\r
-              }\r
-            }\r
-            else\r
-            {\r
-              Cache.log.warn("Dropping job " + j + " " + jobs[j].jobId);\r
-              jobs[j].subjobComplete = true;\r
-              wsInfo.setStatus(jobs[j].jobnum,\r
-                      WebserviceInfo.STATE_STOPPED_SERVERERROR);\r
-            }\r
-          } catch (OutOfMemoryError er)\r
-          {\r
-            jobComplete = true;\r
-            jobs[j].subjobComplete = true;\r
-            jobs[j].result = null; // may contain out of date result object\r
-            wsInfo.setStatus(jobs[j].jobnum,\r
-                    WebserviceInfo.STATE_STOPPED_ERROR);\r
-            Cache.log.error("Out of memory when retrieving Job " + j\r
-                    + " id:" + WsUrl + "/" + jobs[j].jobId, er);\r
-            new jalview.gui.OOMWarning("retrieving result for "\r
-                    + WebServiceName, er);\r
-            System.gc();\r
-          }\r
-        }\r
-        jstate.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);\r
-      }\r
-      // Decide on overall state based on collected jobs[] states\r
-      if (jstate.running > 0)\r
-      {\r
-        wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);\r
-      }\r
-      else if (jstate.queuing > 0)\r
-      {\r
-        wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);\r
-      }\r
-      else\r
-      {\r
-        jobComplete = true;\r
-        if (jstate.finished > 0)\r
-        {\r
-          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);\r
-        }\r
-        else if (jstate.error > 0)\r
-        {\r
-          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);\r
-        }\r
-        else if (jstate.serror > 0)\r
-        {\r
-          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);\r
-        }\r
-      }\r
-      if (!jobComplete)\r
-      {\r
-        try\r
-        {\r
-          Thread.sleep(5000);\r
-        } catch (InterruptedException e)\r
-        {\r
-          Cache.log\r
-                  .debug("Interrupted sleep waiting for next job poll.", e);\r
-        }\r
-        // System.out.println("I'm alive "+alTitle);\r
-      }\r
-    }\r
-    if (jobComplete && jobs != null)\r
-    {\r
-      parseResult(); // tidy up and make results available to user\r
-    }\r
-    else\r
-    {\r
-      Cache.log\r
-              .debug("WebServiceJob poll loop finished with no jobs created.");\r
-      wsInfo.setFinishedNoResults();\r
-    }\r
-  }\r
-\r
-  /**\r
-   * submit job to web service\r
-   * \r
-   * @param job\r
-   */\r
-  abstract void StartJob(WSJob job);\r
-\r
-  /**\r
-   * process the set of WSJob objects into a set of results, and tidy up.\r
-   */\r
-  abstract void parseResult();\r
-\r
-  /**\r
-   * helper function to conserve dataset references to sequence objects returned\r
-   * from web services 1. Propagates AlCodonFrame data from\r
-   * <code>codonframe</code> to <code>al</code>\r
-   * \r
-   * @param al\r
-   */\r
-  protected void propagateDatasetMappings(Alignment al)\r
-  {\r
-    if (codonframe != null)\r
-    {\r
-      SequenceI[] alignment = al.getSequencesArray();\r
-      for (int sq = 0; sq < alignment.length; sq++)\r
-      {\r
-        for (int i = 0; i < codonframe.length; i++)\r
-        {\r
-          if (codonframe[i] != null\r
-                  && codonframe[i].involvesSequence(alignment[sq]))\r
-          {\r
-            al.addCodonFrame(codonframe[i]);\r
-            codonframe[i] = null;\r
-            break;\r
-          }\r
-        }\r
-      }\r
-    }\r
-  }\r
-\r
-  /**\r
-   * \r
-   * @param alignFrame\r
-   *          reference for copying mappings across\r
-   * @param wsInfo\r
-   *          gui attachment point\r
-   * @param input\r
-   *          input data for the calculation\r
-   * @param webServiceName\r
-   *          name of service\r
-   * @param wsUrl\r
-   *          url of the service being invoked\r
-   */\r
-  public WSThread(AlignFrame alignFrame, WebserviceInfo wsinfo,\r
-          AlignmentView input, String webServiceName, String wsUrl)\r
-  {\r
-    this(alignFrame, wsinfo, input, wsUrl);\r
-    WebServiceName = webServiceName;\r
-  }\r
-\r
-  char defGapChar = '-';\r
-\r
-  /**\r
-   * \r
-   * @return gap character to use for any alignment generation\r
-   */\r
-  public char getGapChar()\r
-  {\r
-    return defGapChar;\r
-  }\r
-\r
-  /**\r
-   * \r
-   * @param alframe\r
-   *          - reference for copying mappings and display styles across\r
-   * @param wsinfo2\r
-   *          - gui attachment point\r
-   * @param alview\r
-   *          - input data for the calculation\r
-   * @param wsurl2\r
-   *          - url of the service being invoked\r
-   */\r
-  public WSThread(AlignFrame alframe, WebserviceInfo wsinfo2,\r
-          AlignmentView alview, String wsurl2)\r
-  {\r
-    super();\r
-    // this.alignFrame = alframe;\r
-    currentView = alframe.getCurrentView().getAlignment();\r
-    featureSettings = alframe.getFeatureRenderer().getSettings();\r
-    defGapChar = alframe.getViewport().getGapCharacter();\r
-    this.wsInfo = wsinfo2;\r
-    this.input = alview;\r
-    WsUrl = wsurl2;\r
-    if (alframe != null)\r
-    {\r
-      AlignedCodonFrame[] cf = alframe.getViewport().getAlignment()\r
-              .getCodonFrames();\r
-      if (cf != null)\r
-      {\r
-        codonframe = new AlignedCodonFrame[cf.length];\r
-        System.arraycopy(cf, 0, codonframe, 0, cf.length);\r
-      }\r
-    }\r
-  }\r
-}\r
+package jalview.ws;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.WebserviceInfo;
+import jalview.gui.FeatureRenderer.FeatureRendererSettings;
+
+public abstract class AWSThread extends Thread
+{
+
+  /**
+   * view that this job was associated with
+   */
+  protected AlignmentI currentView = null;
+  /**
+   * feature settings from view that job was associated with
+   */
+  protected FeatureRendererSettings featureSettings = null;
+  /**
+   * metadata about this web service
+   */
+  protected WebserviceInfo wsInfo = null;
+  /**
+   * original input data for this job
+   */
+  protected AlignmentView input = null;
+  /**
+   * dataset sequence relationships to be propagated onto new results
+   */
+  protected AlignedCodonFrame[] codonframe = null;
+  /**
+   * are there jobs still running in this thread.
+   */
+  protected boolean jobComplete = false;
+  /**
+   * one or more jobs being managed by this thread.
+   */
+  protected AWsJob jobs[] = null;
+  /**
+   * full name of service
+   */
+  protected String WebServiceName = null;
+  protected char defGapChar = '-';
+  /**
+   * header prepended to all output from job
+   */
+  protected String OutputHeader;
+
+  /**
+   * only used when reporting a web service out of memory error - the job ID will be concatenated to the URL
+   */
+  protected String WsUrl = null;
+
+  /**
+   * generic web service job/subjob poll loop 
+   */
+  public void run()
+  {
+    JobStateSummary jstate = null;
+    if (jobs == null)
+    {
+      jobComplete = true;
+    }
+    while (!jobComplete)
+    {
+      jstate = new JobStateSummary();
+      for (int j = 0; j < jobs.length; j++)
+      {
+
+        if (!jobs[j].submitted && jobs[j].hasValidInput())
+        {
+          StartJob(jobs[j]);
+        }
+
+        if (jobs[j].submitted && !jobs[j].subjobComplete)
+        {
+          try
+          {
+            pollJob(jobs[j]);
+            if (!jobs[j].hasResponse())
+            {
+              throw (new Exception(
+                      "Timed out when communicating with server\nTry again later.\n"));
+            }
+            jalview.bin.Cache.log.debug("Job " + j + " Result state "
+                    + jobs[j].getState() + "(ServerError="
+                    + jobs[j].isServerError() + ")");
+          } catch (Exception ex)
+          {
+            if (Cache.log.isDebugEnabled())
+            {
+              Cache.log.debug(ex);
+            }
+            // Deal with Transaction exceptions
+            wsInfo.appendProgressText(jobs[j].jobnum, "\n" + WebServiceName
+                    + " Server exception!\n" + ex.getMessage());
+            Cache.log.warn(WebServiceName + " job(" + jobs[j].jobnum
+                    + ") Server exception: " + ex.getMessage());
+
+            if (jobs[j].allowedServerExceptions > 0)
+            {
+              jobs[j].allowedServerExceptions--;
+              Cache.log.debug("Sleeping after a server exception.");
+              try
+              {
+                Thread.sleep(5000);
+              } catch (InterruptedException ex1)
+              {
+              }
+            }
+            else
+            {
+              Cache.log.warn("Dropping job " + j + " " + jobs[j].jobId);
+              jobs[j].subjobComplete = true;
+              wsInfo.setStatus(jobs[j].jobnum,
+                      WebserviceInfo.STATE_STOPPED_SERVERERROR);
+            }
+          } catch (OutOfMemoryError er)
+          {
+            jobComplete = true;
+            jobs[j].subjobComplete = true;
+            jobs[j].clearResponse(); // may contain out of date result data
+            wsInfo.setStatus(jobs[j].jobnum,
+                    WebserviceInfo.STATE_STOPPED_ERROR);
+            Cache.log.error("Out of memory when retrieving Job " + j
+                    + " id:" + WsUrl + "/" + jobs[j].jobId, er);
+            new jalview.gui.OOMWarning("retrieving result for "
+                    + WebServiceName, er);
+            System.gc();
+          }
+        }
+        jstate.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
+      }
+      // Decide on overall state based on collected jobs[] states
+      if (jstate.running > 0)
+      {
+        wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
+      }
+      else if (jstate.queuing > 0)
+      {
+        wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
+      }
+      else
+      {
+        jobComplete = true;
+        if (jstate.finished > 0)
+        {
+          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
+        }
+        else if (jstate.error > 0)
+        {
+          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+        }
+        else if (jstate.serror > 0)
+        {
+          wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
+        }
+      }
+      if (!jobComplete)
+      {
+        try
+        {
+          Thread.sleep(5000);
+        } catch (InterruptedException e)
+        {
+          Cache.log
+                  .debug("Interrupted sleep waiting for next job poll.", e);
+        }
+        // System.out.println("I'm alive "+alTitle);
+      }
+    }
+    if (jobComplete && jobs != null)
+    {
+      parseResult(); // tidy up and make results available to user
+    }
+    else
+    {
+      Cache.log
+              .debug("WebServiceJob poll loop finished with no jobs created.");
+      wsInfo.setFinishedNoResults();
+    }
+  }
+
+
+  public AWSThread()
+  {
+    super();
+  }
+
+  public AWSThread(Runnable target)
+  {
+    super(target);
+  }
+
+  public AWSThread(String name)
+  {
+    super(name);
+  }
+
+  public AWSThread(ThreadGroup group, Runnable target)
+  {
+    super(group, target);
+  }
+
+  public AWSThread(ThreadGroup group, String name)
+  {
+    super(group, name);
+  }
+
+  public AWSThread(Runnable target, String name)
+  {
+    super(target, name);
+  }
+
+  public AWSThread(ThreadGroup group, Runnable target, String name)
+  {
+    super(group, target, name);
+  }
+
+  /**
+   * query web service for status of job. on return, job.result must not be null
+   * - if it is then it will be assumed that the job status query timed out and
+   * a server exception will be logged.
+   * 
+   * @param job
+   * @throws Exception
+   *           will be logged as a server exception for this job
+   */
+  public abstract void pollJob(AWsJob job) throws Exception;
+
+  /**
+   * submit job to web service
+   * 
+   * @param job
+   */
+  public abstract void StartJob(AWsJob job);
+
+  /**
+   * process the set of AWsJob objects into a set of results, and tidy up.
+   */
+  public abstract void parseResult();
+
+  /**
+   * helper function to conserve dataset references to sequence objects returned
+   * from web services 1. Propagates AlCodonFrame data from
+   * <code>codonframe</code> to <code>al</code>
+   * TODO: refactor to datamodel
+   * @param al
+   */
+  public void propagateDatasetMappings(Alignment al)
+  {
+    if (codonframe != null)
+    {
+      SequenceI[] alignment = al.getSequencesArray();
+      for (int sq = 0; sq < alignment.length; sq++)
+      {
+        for (int i = 0; i < codonframe.length; i++)
+        {
+          if (codonframe[i] != null
+                  && codonframe[i].involvesSequence(alignment[sq]))
+          {
+            al.addCodonFrame(codonframe[i]);
+            codonframe[i] = null;
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  public AWSThread(ThreadGroup group, Runnable target, String name,
+          long stackSize)
+  {
+    super(group, target, name, stackSize);
+  }
+
+  /**
+   * 
+   * @return gap character to use for any alignment generation
+   */
+  public char getGapChar()
+  {
+    return defGapChar;
+  }
+
+  /**
+   * 
+   * @param alignFrame
+   *          reference for copying mappings across
+   * @param wsInfo
+   *          gui attachment point
+   * @param input
+   *          input data for the calculation
+   * @param webServiceName
+   *          name of service
+   * @param wsUrl
+   *          url of the service being invoked
+   */
+  public AWSThread(AlignFrame alignFrame, WebserviceInfo wsinfo,
+          AlignmentView input, String webServiceName, String wsUrl)
+  {
+    this(alignFrame, wsinfo, input, wsUrl);
+    WebServiceName = webServiceName;
+  }
+
+  /**
+   * 
+   * @param alframe
+   *          - reference for copying mappings and display styles across
+   * @param wsinfo2
+   *          - gui attachment point
+   * @param alview
+   *          - input data for the calculation
+   * @param wsurl2
+   *          - url of the service being invoked
+   */
+  public AWSThread(AlignFrame alframe, WebserviceInfo wsinfo2,
+          AlignmentView alview, String wsurl2)
+  {
+    super();
+    // this.alignFrame = alframe;
+    currentView = alframe.getCurrentView().getAlignment();
+    featureSettings = alframe.getFeatureRenderer().getSettings();
+    defGapChar = alframe.getViewport().getGapCharacter();
+    this.wsInfo = wsinfo2;
+    this.input = alview;
+    WsUrl = wsurl2;
+    if (alframe != null)
+    {
+      AlignedCodonFrame[] cf = alframe.getViewport().getAlignment()
+              .getCodonFrames();
+      if (cf != null)
+      {
+        codonframe = new AlignedCodonFrame[cf.length];
+        System.arraycopy(cf, 0, codonframe, 0, cf.length);
+      }
+    }
+  }
+}
diff --git a/src/jalview/ws/AWsJob.java b/src/jalview/ws/AWsJob.java
new file mode 100644 (file)
index 0000000..2f036bf
--- /dev/null
@@ -0,0 +1,200 @@
+package jalview.ws;
+
+/**
+ * Generic properties for an individual job within a Web Service Client thread.
+ * Derived from jalview web services version 1 statuses, and revised for Jws2.
+ */
+
+public abstract class AWsJob
+{
+  protected int jobnum = 0;
+
+  protected String jobId;
+
+  /**
+   * @param jobId the jobId to set
+   */
+  public void setJobId(String jobId)
+  {
+    this.jobId = jobId;
+  }
+
+  /**
+   * has job been cancelled
+   */
+  protected boolean cancelled = false;
+
+  /**
+   * number of exceptions left before job dies
+   */
+  int allowedServerExceptions = 3;
+
+  /**
+   * @param allowedServerExceptions the allowedServerExceptions to set
+   */
+  public void setAllowedServerExceptions(int allowedServerExceptions)
+  {
+    this.allowedServerExceptions = allowedServerExceptions;
+  }
+
+  /**
+   * has job been submitted to server ? if false, then no state info is
+   * available.
+   */
+  protected boolean submitted = false;
+
+  /**
+   * @param jobnum the jobnum to set
+   */
+  public void setJobnum(int jobnum)
+  {
+    this.jobnum = jobnum;
+  }
+
+  /**
+   * @param submitted the submitted to set
+   */
+  public void setSubmitted(boolean submitted)
+  {
+    this.submitted = submitted;
+  }
+
+  /**
+   * @param subjobComplete the subjobComplete to set
+   */
+  public void setSubjobComplete(boolean subjobComplete)
+  {
+    this.subjobComplete = subjobComplete;
+  }
+
+  /**
+   * @return the jobnum
+   */
+  public int getJobnum()
+  {
+    return jobnum;
+  }
+
+  /**
+   * @return the jobId
+   */
+  public String getJobId()
+  {
+    return jobId;
+  }
+
+  /**
+   * @return the cancelled
+   */
+  public boolean isCancelled()
+  {
+    return cancelled;
+  }
+
+  /**
+   * @return the allowedServerExceptions
+   */
+  public int getAllowedServerExceptions()
+  {
+    return allowedServerExceptions;
+  }
+
+  /**
+   * @return the submitted
+   */
+  public boolean isSubmitted()
+  {
+    return submitted;
+  }
+
+  /**
+   * @return the subjobComplete
+   */
+  public boolean isSubjobComplete()
+  {
+    return subjobComplete;
+  }
+
+  /**
+   * are all sub-jobs complete
+   */
+  protected boolean subjobComplete = false;
+
+  public AWsJob()
+  {
+  }
+
+  /**
+   * 
+   * @return true if job has completed and valid results are available
+   */
+  abstract public boolean hasResults();
+
+  /**
+   * 
+   * @return boolean true if job can be submitted.
+   */
+  public abstract boolean hasValidInput();
+
+  /**
+   * 
+   * @return true if job is running
+   */
+  abstract public boolean isRunning();
+
+  /**
+   * 
+   * @return true if job is queued
+   */
+  abstract public boolean isQueued();
+
+  /**
+   * 
+   * @return true if job has finished
+   */
+  abstract public boolean isFinished();
+
+  /**
+   * 
+   * @return true if the job failed due to some problem with the input data or
+   *         parameters.
+   */
+  abstract public boolean isFailed();
+
+  /**
+   * 
+   * @return true if job failed due to an unhandled technical issue
+   */
+  abstract public boolean isBroken();
+
+  /**
+   * 
+   * @return true if there was a problem contacting the server.
+   */
+  abstract public boolean isServerError();
+
+  /**
+   * 
+   * @return true if the job has status text.
+   */
+  abstract public boolean hasStatus();
+
+  /**
+   * 
+   * @return status text for job to be displayed to user.
+   */
+  abstract public String getStatus();
+
+  abstract public boolean hasResponse();
+  abstract public void clearResponse();
+  abstract public String getState();
+  /**
+   * generates response using the abstract service flags.
+   * @return a standard state response 
+   */
+  protected String _defaultState() {
+    
+    String state = "";
+    return state;
+  }
+}
\ No newline at end of file
index f481aa1..5d20dd4 100755 (executable)
@@ -411,7 +411,7 @@ public class Discoverer extends Thread implements Runnable
    */
   private static Hashtable serviceClientBindings;
 
-  public static WSClient getServiceClient(ServiceHandle sh)
+  public static WS1Client getServiceClient(ServiceHandle sh)
   {
     if (serviceClientBindings == null)
     {
@@ -421,7 +421,7 @@ public class Discoverer extends Thread implements Runnable
       serviceClientBindings.put("SecStrPred", new JPredClient());
       serviceClientBindings.put("SeqSearch", new SeqSearchWSClient());
     }
-    WSClient instance = (WSClient) serviceClientBindings.get(sh
+    WS1Client instance = (WS1Client) serviceClientBindings.get(sh
             .getAbstractName());
     if (instance == null)
     {
index bb691dc..23f4de6 100755 (executable)
@@ -29,7 +29,7 @@ import jalview.bin.*;
 import jalview.datamodel.*;
 import jalview.gui.*;
 
-public class JPredClient extends WSClient
+public class JPredClient extends WS1Client
 {
   /**
    * crate a new GUI JPred Job
index 98c9506..3589c16 100644 (file)
@@ -22,17 +22,16 @@ import java.util.*;
 import jalview.analysis.*;\r
 import jalview.bin.*;\r
 import jalview.datamodel.*;\r
-import jalview.datamodel.Alignment;\r
 import jalview.gui.*;\r
 import jalview.io.*;\r
 import jalview.util.*;\r
 import vamsas.objects.simple.JpredResult;\r
 \r
-class JPredThread extends WSThread implements WSClientI\r
+class JPredThread extends JWS1Thread implements WSClientI\r
 {\r
   // TODO: put mapping between JPredJob input and input data here -\r
   // JNetAnnotation adding is done after result parsing.\r
-  class JPredJob extends WSThread.WSJob\r
+  class JPredJob extends WSJob\r
   {\r
     // TODO: make JPredJob deal only with what was sent to and received from a\r
     // JNet service\r
@@ -67,7 +66,7 @@ class JPredThread extends WSThread implements WSClientI
       return false;\r
     }\r
 \r
-    boolean hasValidInput()\r
+    public boolean hasValidInput()\r
     {\r
       if (sequence != null)\r
       {\r
@@ -421,7 +420,7 @@ class JPredThread extends WSThread implements WSClientI
     }\r
   }\r
 \r
-  void StartJob(WSJob j)\r
+  public void StartJob(AWsJob j)\r
   {\r
     if (!(j instanceof JPredJob))\r
     {\r
@@ -499,7 +498,7 @@ class JPredThread extends WSThread implements WSClientI
     }\r
   }\r
 \r
-  void parseResult()\r
+  public void parseResult()\r
   {\r
     int results = 0; // number of result sets received\r
     JobStateSummary finalState = new JobStateSummary();\r
@@ -642,9 +641,9 @@ class JPredThread extends WSThread implements WSClientI
     }\r
   }\r
 \r
-  void pollJob(WSJob job) throws Exception\r
+  public void pollJob(AWsJob job) throws Exception\r
   {\r
-    job.result = server.getresult(job.jobId);\r
+    ((JPredJob)job).result = server.getresult(job.jobId);\r
   }\r
 \r
   public boolean isCancellable()\r
diff --git a/src/jalview/ws/JWS1Thread.java b/src/jalview/ws/JWS1Thread.java
new file mode 100644 (file)
index 0000000..c4d4c16
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
+ * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ * 
+ * Jalview 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package jalview.ws;
+
+import javax.swing.*;
+
+import jalview.bin.*;
+import jalview.datamodel.*;
+import jalview.gui.*;
+
+/**
+ * specific methods for Jalview WS1 web service jobs
+ * (will probably disappear)
+ * @author JimP
+ *
+ */
+public abstract class JWS1Thread extends AWSThread
+{
+
+  public JWS1Thread(AlignFrame alFrame, WebserviceInfo wsinfo,
+          AlignmentView alview, String wsname, String wsUrl)
+  {
+    super(alFrame, wsinfo, alview, wsname, wsUrl);
+  }
+
+  public JWS1Thread(AlignFrame alframe, WebserviceInfo wsinfo,
+          AlignmentView alview, String wsurl)
+  {
+    super(alframe, wsinfo, alview, wsurl);
+  }
+
+}
diff --git a/src/jalview/ws/JobStateSummary.java b/src/jalview/ws/JobStateSummary.java
new file mode 100644 (file)
index 0000000..0983dc0
--- /dev/null
@@ -0,0 +1,129 @@
+package jalview.ws;
+
+import jalview.gui.WebserviceInfo;
+
+/**
+ * bookkeeper class for the WebServiceInfo GUI, maintaining records of web
+ * service jobs handled by the window and reflecting any status updates.
+ * 
+ * @author JimP
+ * 
+ */
+public class JobStateSummary
+{
+  /**
+   * number of jobs running
+   */
+  int running = 0;
+
+  /**
+   * number of jobs queued
+   */
+  int queuing = 0;
+
+  /**
+   * number of jobs finished
+   */
+  int finished = 0;
+
+  /**
+   * number of jobs failed
+   */
+  int error = 0;
+
+  /**
+   * number of jobs stopped due to server error
+   */
+  int serror = 0;
+
+  /**
+   * number of jobs cancelled
+   */
+  int cancelled = 0;
+
+  /**
+   * number of jobs finished with results
+   */
+  int results = 0;
+
+  /**
+   * processes an AWSJob's status and updates job status counters and WebService
+   * status displays
+   * 
+   * @param wsInfo
+   * @param OutputHeader
+   * @param j
+   */
+  public void updateJobPanelState(WebserviceInfo wsInfo, String OutputHeader,
+          AWsJob j)
+  {
+    if (j.submitted)
+    {
+      String progheader = "";
+      // Parse state of job[j]
+      if (j.isRunning())
+      {
+        running++;
+        wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_RUNNING);
+      }
+      else if (j.isQueued())
+      {
+        queuing++;
+        wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_QUEUING);
+      }
+      else if (j.isFinished())
+      {
+        finished++;
+        j.subjobComplete = true;
+        if (j.hasResults())
+        {
+          results++;
+        }
+        wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_OK);
+      }
+      else if (j.isFailed())
+      {
+        progheader += "Job failed.\n";
+        j.subjobComplete = true;
+        wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);
+        error++;
+      }
+      else if (j.isServerError())
+      {
+        serror++;
+        j.subjobComplete = true;
+        wsInfo
+                .setStatus(j.jobnum,
+                        WebserviceInfo.STATE_STOPPED_SERVERERROR);
+      }
+      else if (j.isBroken())
+      {
+        progheader += "Job was broken.\n";
+        error++;
+        j.subjobComplete = true;
+        wsInfo.setStatus(j.jobnum, WebserviceInfo.STATE_STOPPED_ERROR);
+      }
+      // and pass on any sub-job messages to the user
+      wsInfo.setProgressText(j.jobnum, OutputHeader);
+      wsInfo.appendProgressText(j.jobnum, progheader);
+      if (j.hasStatus())
+      {
+        wsInfo.appendProgressText(j.jobnum, j.getStatus());
+      }
+    }
+    else
+    {
+      if (j.submitted && j.subjobComplete)
+      {
+        if (j.allowedServerExceptions == 0)
+        {
+          serror++;
+        }
+        else if (!j.hasResults())
+        {
+          error++;
+        }
+      }
+    }
+  }
+}
index 19fa0d3..d58e2dc 100755 (executable)
@@ -32,7 +32,7 @@ import jalview.gui.*;
  * @author $author$
  * @version $Revision$
  */
-public class MsaWSClient extends WSClient
+public class MsaWSClient extends WS1Client
 {
   /**
    * server is a WSDL2Java generated stub for an archetypal MsaWSI service.
index 7142e5c..ab3dfbe 100644 (file)
@@ -22,7 +22,6 @@ import java.util.*;
 import jalview.analysis.*;\r
 import jalview.bin.*;\r
 import jalview.datamodel.*;\r
-import jalview.datamodel.Alignment;\r
 import jalview.gui.*;\r
 import vamsas.objects.simple.MsaResult;\r
 \r
@@ -46,7 +45,7 @@ import vamsas.objects.simple.MsaResult;
  * @author not attributable\r
  * @version 1.0\r
  */\r
-class MsaWSThread extends WSThread implements WSClientI\r
+class MsaWSThread extends JWS1Thread implements WSClientI\r
 {\r
   boolean submitGaps = false; // pass sequences including gaps to alignment\r
 \r
@@ -56,7 +55,7 @@ class MsaWSThread extends WSThread implements WSClientI
 \r
   // order\r
 \r
-  class MsaWSJob extends WSThread.WSJob\r
+  class MsaWSJob extends WSJob\r
   {\r
     // hold special input for this\r
     vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();\r
@@ -280,7 +279,7 @@ class MsaWSThread extends WSThread implements WSClientI
      * \r
      * @return boolean true if job can be submitted.\r
      */\r
-    boolean hasValidInput()\r
+    public boolean hasValidInput()\r
     {\r
       if (seqs.getSeqs() != null)\r
       {\r
@@ -438,12 +437,12 @@ class MsaWSThread extends WSThread implements WSClientI
     }\r
   }\r
 \r
-  void pollJob(WSJob job) throws Exception\r
+  public void pollJob(AWsJob job) throws Exception\r
   {\r
     ((MsaWSJob) job).result = server.getResult(((MsaWSJob) job).jobId);\r
   }\r
 \r
-  void StartJob(WSJob job)\r
+  public void StartJob(AWsJob job)\r
   {\r
     if (!(job instanceof MsaWSJob))\r
     {\r
@@ -531,7 +530,7 @@ class MsaWSThread extends WSThread implements WSClientI
     return msa;\r
   }\r
 \r
-  void parseResult()\r
+  public void parseResult()\r
   {\r
     int results = 0; // number of result sets received\r
     JobStateSummary finalState = new JobStateSummary();\r
@@ -544,7 +543,7 @@ class MsaWSThread extends WSThread implements WSClientI
                 && jobs[j].hasResults())\r
         {\r
           results++;\r
-          vamsas.objects.simple.Alignment valign = ((MsaResult) jobs[j].result)\r
+          vamsas.objects.simple.Alignment valign =  ((MsaResult)((MsaWSJob) jobs[j]).result)\r
                   .getMsa();\r
           if (valign != null)\r
           {\r
index 2e246ab..fd45209 100644 (file)
@@ -37,7 +37,7 @@ import jalview.gui.*;
  * @author $author$\r
  * @version $Revision$\r
  */\r
-public class SeqSearchWSClient extends WSClient\r
+public class SeqSearchWSClient extends WS1Client\r
 {\r
   /**\r
    * server is a WSDL2Java generated stub for an archetypal MsaWSI service.\r
index 2f28d64..550ae43 100644 (file)
@@ -47,13 +47,13 @@ import vamsas.objects.simple.SeqSearchResult;
  * @author not attributable\r
  * @version 1.0\r
  */\r
-class SeqSearchWSThread extends WSThread implements WSClientI\r
+class SeqSearchWSThread extends JWS1Thread implements WSClientI\r
 {\r
   String dbs = null;\r
 \r
   boolean profile = false;\r
 \r
-  class SeqSearchWSJob extends WSThread.WSJob\r
+  class SeqSearchWSJob extends WSJob\r
   {\r
     // hold special input for this\r
     vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();\r
@@ -293,7 +293,7 @@ class SeqSearchWSThread extends WSThread implements WSClientI
      * \r
      * @return boolean true if job can be submitted.\r
      */\r
-    boolean hasValidInput()\r
+    public boolean hasValidInput()\r
     {\r
       if (seqs.getSeqs() != null)\r
       {\r
@@ -451,13 +451,13 @@ class SeqSearchWSThread extends WSThread implements WSClientI
     }\r
   }\r
 \r
-  void pollJob(WSJob job) throws Exception\r
+  public void pollJob(AWsJob job) throws Exception\r
   {\r
     ((SeqSearchWSJob) job).result = server\r
             .getResult(((SeqSearchWSJob) job).jobId);\r
   }\r
 \r
-  void StartJob(WSJob job)\r
+  public void StartJob(AWsJob job)\r
   {\r
     if (!(job instanceof SeqSearchWSJob))\r
     {\r
@@ -545,7 +545,7 @@ class SeqSearchWSThread extends WSThread implements WSClientI
     return msa;\r
   }\r
 \r
-  void parseResult()\r
+  public void parseResult()\r
   {\r
     int results = 0; // number of result sets received\r
     JobStateSummary finalState = new JobStateSummary();\r
@@ -558,7 +558,7 @@ class SeqSearchWSThread extends WSThread implements WSClientI
                 && jobs[j].hasResults())\r
         {\r
           results++;\r
-          vamsas.objects.simple.Alignment valign = ((SeqSearchResult) jobs[j].result)\r
+          vamsas.objects.simple.Alignment valign = ((SeqSearchResult) ((SeqSearchWSJob)jobs[j]).result)\r
                   .getAlignment();\r
           if (valign != null)\r
           {\r
diff --git a/src/jalview/ws/WS1Client.java b/src/jalview/ws/WS1Client.java
new file mode 100644 (file)
index 0000000..a05bf7f
--- /dev/null
@@ -0,0 +1,111 @@
+/**
+ * 
+ */
+package jalview.ws;
+
+import jalview.gui.AlignFrame;
+import jalview.gui.WebserviceInfo;
+
+import javax.swing.JMenu;
+
+import ext.vamsas.ServiceHandle;
+
+/**
+ * JWS1 Specific UI attributes and methods  
+ * @author JimP
+ *
+ */
+public abstract class WS1Client extends WSClient implements WSMenuEntryProviderI
+{
+
+  /**
+   * original service handle that this client was derived from
+   */
+  ServiceHandle serviceHandle = null;
+
+  /**
+   * default constructor
+   */
+  public WS1Client()
+  {
+    super();
+  }
+
+  /**
+   * initialise WSClient service information attributes from the service handle
+   * 
+   * @param sh
+   * @return the service instance information GUI for this client and job.
+   */
+  protected WebserviceInfo setWebService(ServiceHandle sh)
+  {
+    return setWebService(sh, false);
+  }
+
+  /**
+   * initialise WSClient service information attributes from the service handle
+   * 
+   * @param sh
+   * @param headless
+   *          true implies no GUI objects will be created.
+   * @return the service instance information GUI for this client and job.
+   */
+  protected WebserviceInfo setWebService(ServiceHandle sh, boolean headless)
+  {
+    WebServiceName = sh.getName();
+    if (ServiceActions.containsKey(sh.getAbstractName()))
+    {
+      WebServiceJobTitle = sh.getName(); // TODO: control sh.Name specification
+      // properly
+      // add this for short names. +(String)
+      // ServiceActions.get(sh.getAbstractName());
+    }
+    else
+    {
+      WebServiceJobTitle = sh.getAbstractName() + " using " + sh.getName();
+  
+    }
+    WebServiceReference = sh.getDescription();
+    WsURL = sh.getEndpointURL();
+    WebserviceInfo wsInfo = null;
+    if (!headless)
+    {
+      wsInfo = new WebserviceInfo(WebServiceJobTitle, WebServiceReference);
+    }
+    return wsInfo;
+  }
+
+  /**
+   * convenience method to pass the serviceHandle reference that instantiated
+   * this service on to the menu entry constructor
+   * 
+   * @param wsmenu
+   *          the menu to which any menu entries/sub menus are to be attached
+   * @param alignFrame
+   *          the alignFrame instance that provides input data for the service
+   */
+  public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
+  {
+    if (serviceHandle == null)
+    {
+      throw new Error(
+              "IMPLEMENTATION ERROR: cannot attach WS Menu Entry without service handle reference!");
+    }
+    attachWSMenuEntry(wsmenu, serviceHandle, alignFrame);
+  }
+
+  /**
+   * method implemented by each concrete WS1Client implementation that creates menu
+   * entries that enact their service using data from alignFrame.
+   * 
+   * @param wsmenu
+   *          where new menu entries (and submenus) are to be attached
+   * @param serviceHandle
+   *          the serviceHandle document for the service that entries are
+   *          created for
+   * @param alignFrame
+   */
+  public abstract void attachWSMenuEntry(JMenu wsmenu, final ServiceHandle serviceHandle,
+          final AlignFrame alignFrame);
+
+}
index 1490da1..4a5c7dd 100755 (executable)
  */
 package jalview.ws;
 
-import javax.swing.JMenu;
 
-import ext.vamsas.*;
 import jalview.gui.*;
 
-public abstract class WSClient implements WSMenuEntryProviderI
+public abstract class WSClient // implements WSMenuEntryProviderI
 {
   /**
    * WSClient holds the basic attributes that are displayed to the user for all
@@ -74,86 +72,4 @@ public abstract class WSClient implements WSMenuEntryProviderI
   public WSClient()
   {
   }
-
-  /**
-   * initialise WSClient service information attributes from the service handle
-   * 
-   * @param sh
-   * @return the service instance information GUI for this client and job.
-   */
-  protected WebserviceInfo setWebService(ServiceHandle sh)
-  {
-    return setWebService(sh, false);
-  }
-
-  /**
-   * original service handle that this client was derived from
-   */
-  ServiceHandle serviceHandle = null;
-
-  /**
-   * initialise WSClient service information attributes from the service handle
-   * 
-   * @param sh
-   * @param headless
-   *          true implies no GUI objects will be created.
-   * @return the service instance information GUI for this client and job.
-   */
-  protected WebserviceInfo setWebService(ServiceHandle sh, boolean headless)
-  {
-    WebServiceName = sh.getName();
-    if (ServiceActions.containsKey(sh.getAbstractName()))
-    {
-      WebServiceJobTitle = sh.getName(); // TODO: control sh.Name specification
-      // properly
-      // add this for short names. +(String)
-      // ServiceActions.get(sh.getAbstractName());
-    }
-    else
-    {
-      WebServiceJobTitle = sh.getAbstractName() + " using " + sh.getName();
-
-    }
-    WebServiceReference = sh.getDescription();
-    WsURL = sh.getEndpointURL();
-    WebserviceInfo wsInfo = null;
-    if (!headless)
-    {
-      wsInfo = new WebserviceInfo(WebServiceJobTitle, WebServiceReference);
-    }
-    return wsInfo;
-  }
-
-  /**
-   * convenience method to pass the serviceHandle reference that instantiated
-   * this service on to the menu entry constructor
-   * 
-   * @param wsmenu
-   *          the menu to which any menu entries/sub menus are to be attached
-   * @param alignFrame
-   *          the alignFrame instance that provides input data for the service
-   */
-  public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
-  {
-    if (serviceHandle == null)
-    {
-      throw new Error(
-              "IMPLEMENTATION ERROR: cannot attach WS Menu Entry without service handle reference!");
-    }
-    attachWSMenuEntry(wsmenu, serviceHandle, alignFrame);
-  }
-
-  /**
-   * method implemented by each WSClient implementation that creates menu
-   * entries that enact their service using data from alignFrame.
-   * 
-   * @param wsmenu
-   *          where new menu entries (and submenus) are to be attached
-   * @param serviceHandle
-   *          the serviceHandle document for the service that entries are
-   *          created for
-   * @param alignFrame
-   */
-  public abstract void attachWSMenuEntry(JMenu wsmenu,
-          final ServiceHandle serviceHandle, final AlignFrame alignFrame);
 }
diff --git a/src/jalview/ws/WSJob.java b/src/jalview/ws/WSJob.java
new file mode 100644 (file)
index 0000000..2a2e315
--- /dev/null
@@ -0,0 +1,123 @@
+/**
+ * 
+ */
+package jalview.ws;
+
+abstract class WSJob extends AWsJob
+{
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#clearResponse()
+   */
+  @Override
+  public void clearResponse()
+  {
+    result = null;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#hasResponse()
+   */
+  @Override
+  public boolean hasResponse()
+  {
+    return result!=null;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#hasStatus()
+   */
+  @Override
+  public boolean hasStatus()
+  {
+    return result!=null && result.getStatus()!=null;
+  }
+
+  /**
+   * The last result object returned by the service.
+   */
+  vamsas.objects.simple.Result result;
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#getStatus()
+   */
+  public String getStatus()
+  {
+    return result==null ? null : result.getStatus();
+  }
+
+  public String getState() {
+    return result==null ? "NULL result" : ""+result.getState(); 
+  }
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isBroken()
+   */
+  public boolean isBroken()
+  {
+    return result!=null && result.isBroken();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isFailed()
+   */
+  public boolean isFailed()
+  {
+    return result!=null && result.isFailed();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isFinished()
+   */
+  public boolean isFinished()
+  {
+    return result!=null && result.isFinished();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isInvalid()
+   */
+  public boolean isInvalid()
+  {
+    return result!=null && result.isInvalid();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isJobFailed()
+   */
+  public boolean isJobFailed()
+  {
+    return result!=null && result.isJobFailed();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isQueued()
+   */
+  public boolean isQueued()
+  {
+    return result!=null && result.isQueued();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isRunning()
+   */
+  public boolean isRunning()
+  {
+    return result!=null && result.isRunning();
+  }
+
+  /**
+   * @return
+   * @see vamsas.objects.simple.Result#isServerError()
+   */
+  public boolean isServerError()
+  {
+    return result!=null && result.isServerError();
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/ws/jws2/AWS2Thread.java b/src/jalview/ws/jws2/AWS2Thread.java
new file mode 100644 (file)
index 0000000..a706f43
--- /dev/null
@@ -0,0 +1,16 @@
+package jalview.ws.jws2;
+
+import jalview.datamodel.AlignmentView;
+import jalview.gui.AlignFrame;
+import jalview.gui.WebserviceInfo;
+
+abstract public class AWS2Thread extends jalview.ws.AWSThread
+{
+
+  public AWS2Thread(AlignFrame alFrame, WebserviceInfo wsinfo,
+          AlignmentView alview, String wsname, String wsUrl)
+  {
+    super(alFrame, wsinfo, alview, wsname, wsUrl);
+  }
+
+}
diff --git a/src/jalview/ws/jws2/JWs2Job.java b/src/jalview/ws/jws2/JWs2Job.java
new file mode 100644 (file)
index 0000000..4bf97a3
--- /dev/null
@@ -0,0 +1,126 @@
+/**
+ * 
+ */
+package jalview.ws.jws2;
+
+import compbio.metadata.JobStatus;
+
+import jalview.ws.AWsJob;
+
+/**
+ * job status processing for JWS2 jobs. 
+ * @author JimP
+ *
+ */
+public abstract class JWs2Job extends AWsJob
+{
+  JobStatus status=null;
+  public void setjobStatus(JobStatus jobStatus)
+  {
+    status = jobStatus;
+    // update flags
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#clearResponse()
+   */
+  @Override
+  public void clearResponse()
+  {
+    status = null;
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#getState()
+   */
+  @Override
+  public String getState()
+  {
+    return status==null ? ("Unknown") : status.toString();
+  }
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#hasResponse()
+   */
+  @Override
+  public boolean hasResponse()
+  {
+    // TODO Auto-generated method stub
+    return status!=null;
+  }
+  /*
+  StringBuffer statusBuffer = null;
+  * (non-Javadoc)
+  * @see jalview.ws.AWsJob#getStatus()
+   *
+  @Override
+  public String getStatus()
+  {
+    return statusBuffer.toString();
+  }
+  * (non-Javadoc)
+   * @see jalview.ws.AWsJob#hasStatus()
+   *
+  @Override
+  public boolean hasStatus()
+  {
+    return statusBuffer!=null;
+  }
+*/
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#isBroken()
+   */
+  @Override
+  public boolean isBroken()
+  {
+    return status.equals(status.UNDEFINED);
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#isFailed()
+   */
+  @Override
+  public boolean isFailed()
+  {
+    return status.equals(status.FAILED);
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#isFinished()
+   */
+  @Override
+  public boolean isFinished()
+  {
+    return status.equals(status.FINISHED);
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#isQueued()
+   */
+  @Override
+  public boolean isQueued()
+  {
+    return status.equals(status.SUBMITTED) || status.equals(status.PENDING);
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#isRunning()
+   */
+  @Override
+  public boolean isRunning()
+  {
+    // TODO Auto-generated method stub
+    return status.equals(status.RUNNING) || status.equals(status.STARTED);
+  }
+
+  /* (non-Javadoc)
+   * @see jalview.ws.AWsJob#isServerError()
+   */
+  @Override
+  public boolean isServerError()
+  {
+    // server errors are raised as exceptions on the service method calls.  
+    return false; // status.equals(status.FAILED);
+  }
+
+}
diff --git a/src/jalview/ws/jws2/Jws2Client.java b/src/jalview/ws/jws2/Jws2Client.java
new file mode 100644 (file)
index 0000000..d44be01
--- /dev/null
@@ -0,0 +1,54 @@
+package jalview.ws.jws2;
+
+import javax.swing.JMenu;
+
+import jalview.gui.AlignFrame;
+import jalview.gui.WebserviceInfo;
+import jalview.ws.jws2.Jws2Discoverer.Jws2Instance;
+
+/**
+ * provides metadata for a jws2 service instance - resolves names, etc.
+ * 
+ * @author JimP
+ * 
+ */
+public abstract class Jws2Client extends jalview.ws.WSClient
+{
+  protected WebserviceInfo setWebService(Jws2Instance serv, boolean b)
+  {
+    // serviceHandle = serv;
+    String serviceInstance = serv.service.getClass().getName();
+    WebServiceName = serv.serviceType;
+    WebServiceJobTitle = serv.serviceType;
+    WsURL = serv.hosturl;
+    if (!b)
+    {
+      return new WebserviceInfo(WebServiceJobTitle, WebServiceJobTitle
+              + " using service hosted at " + serv.hosturl);
+    }
+    return null;
+  }
+  /*
+  Jws2Instance serviceHandle;
+   * (non-Javadoc)
+   * @see jalview.ws.WSMenuEntryProviderI#attachWSMenuEntry(javax.swing.JMenu, jalview.gui.AlignFrame)
+   *
+  @Override
+  public void attachWSMenuEntry(JMenu wsmenu, AlignFrame alignFrame)
+  {
+    if (serviceHandle==null)
+    {
+      throw new Error("Implementation error: No service handle for this Jws2 service.");
+    }
+    attachWSMenuEntry(wsmenu, serviceHandle, alignFrame);
+  }*/
+  /**
+   * add the menu item for a particular jws2 service instance
+   * @param wsmenu
+   * @param service
+   * @param alignFrame
+   */
+  abstract void attachWSMenuEntry(JMenu wsmenu,
+          final Jws2Instance service, final AlignFrame alignFrame);
+
+}
diff --git a/src/jalview/ws/jws2/Jws2Discoverer.java b/src/jalview/ws/jws2/Jws2Discoverer.java
new file mode 100644 (file)
index 0000000..da3eecb
--- /dev/null
@@ -0,0 +1,192 @@
+package jalview.ws.jws2;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashSet;
+import java.util.Vector;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import org.apache.log4j.Level;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentView;
+import jalview.gui.AlignFrame;
+import jalview.ws.WSMenuEntryProviderI;
+import compbio.data.msa.MsaWS;
+import compbio.ws.client.Jws2Base;
+import compbio.ws.client.Jws2Base.Services;
+
+/**
+ * discoverer for jws2 services. Follows the lightweight service discoverer
+ * pattern (archetyped by EnfinEnvision2OneWay)
+ * 
+ * @author JimP
+ * 
+ */
+public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
+{
+  compbio.data.msa.MsaWS service;
+  boolean running=false;
+  @Override
+  public void run()
+  {
+    if (running)
+    {
+      return;
+    }
+    running=true;
+//    Cache.initLogger();
+//    Cache.log.setLevel(Level.DEBUG);
+    // TODO: Document and PACK JWS2
+    String jwsservers = Cache.getDefault("JWS2HOSTURLS",
+            "http://webservices.compbio.dundee.ac.uk:8084/jws2");
+    try
+    {
+      if (Jws2Base.validURL(jwsservers))
+      {
+        // look for services
+        for (Services srv : Jws2Base.Services.values())
+        {
+          MsaWS service = null;
+          try
+          {
+            service = Jws2Base.connect(jwsservers, srv);
+          } catch (Exception e)
+          {
+          }
+          ;
+          if (service != null)
+          {
+            addService(jwsservers, srv, service);
+          }
+        }
+
+      }
+      else
+      {
+        Cache.log.info("Ignoring invalid Jws2 service url " + jwsservers);
+      }
+    } catch (Exception e)
+    {
+      Cache.log.warn("Exception when discovering Jws2 services.", e);
+    } catch (Error e)
+    {
+      Cache.log.error("Exception when discovering Jws2 services.", e);
+    }
+    running=false;
+
+  }
+
+  /**
+   * record this service endpoint so we can use it
+   * 
+   * @param jwsservers
+   * @param srv
+   * @param service2
+   */
+  private void addService(String jwsservers, Services srv, MsaWS service2)
+  {
+    Cache.log.debug("Discovered service: " + jwsservers + " "
+            + srv.toString());
+    if (services==null) {
+      services = new Vector<Jws2Instance>();
+    }
+    services.add(new Jws2Instance(jwsservers, "Align with "
+            + srv.toString(), service2));
+  }
+
+  public class Jws2Instance
+  {
+    String hosturl;
+
+    String serviceType;
+
+    MsaWS service;
+
+    public Jws2Instance(String hosturl, String serviceType, MsaWS service)
+    {
+      super();
+      this.hosturl = hosturl;
+      this.serviceType = serviceType;
+      this.service = service;
+    }
+
+  };
+
+  /**
+   * holds list of services.
+   */
+  Vector<Jws2Instance> services;
+
+  @Override
+  public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
+  {
+    if (running || services==null || services.size() == 0)
+    {
+      return;
+    }
+    /**
+     * eventually, JWS2 services will appear under the same align/etc submenus.
+     * for moment we keep them separate.
+     */
+    JMenu jws2 = new JMenu("JWS2 Alignment");
+    MsaWSClient msacl = new MsaWSClient();
+    for (final Jws2Instance service : services)
+    {
+      msacl.attachWSMenuEntry(jws2, service, alignFrame);
+      /*JMenuItem sitem = new JMenuItem(service.serviceType);
+      sitem.setToolTipText("Hosted at " + service.hosturl);
+      sitem.addActionListener(new ActionListener()
+      {
+
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          AlignmentView msa = alignFrame.gatherSequencesForAlignment();
+          MsaWSClient client = new MsaWSClient(service,
+                  "JWS2 Alignment of " + alignFrame.getTitle(), msa, false,
+                  true, alignFrame.getViewport().getAlignment().getDataset(),
+                  alignFrame);
+        }
+      });*/
+    }
+    if (services.size()>0)
+    {
+      wsmenu.add(jws2);
+    }
+    
+  }
+  public static void main(String[] args)
+  {
+    Thread runner = new Thread(getDiscoverer());
+    runner.start();
+    while (runner.isAlive())
+    {
+      try
+      {
+        Thread.sleep(50);
+      } catch (InterruptedException e)
+      {
+      }
+      ;
+    }
+  }
+  private static Jws2Discoverer discoverer;
+  public static Jws2Discoverer getDiscoverer()
+  {
+    if (discoverer==null)
+    {
+      discoverer = new Jws2Discoverer();
+    }
+    return discoverer;
+  }
+
+  public boolean hasServices()
+  {
+    // TODO Auto-generated method stub
+    return services!=null && services.size()>0;
+  }
+  
+}
diff --git a/src/jalview/ws/jws2/MsaWSClient.java b/src/jalview/ws/jws2/MsaWSClient.java
new file mode 100644 (file)
index 0000000..a557cf7
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
+ * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ * 
+ * Jalview 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package jalview.ws.jws2;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+
+import jalview.datamodel.*;
+import jalview.gui.*;
+import compbio.data.msa.MsaWS;
+import jalview.ws.jws2.Jws2Discoverer.Jws2Instance;
+
+/**
+ * DOCUMENT ME!
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class MsaWSClient extends Jws2Client
+{
+  /**
+   * server is a WSDL2Java generated stub for an archetypal MsaWSI service.
+   */
+  MsaWS server;
+  AlignFrame alignFrame;
+
+  /**
+   * Creates a new MsaWSClient object that uses a service given by an externally
+   * retrieved ServiceHandle
+   * 
+   * @param sh
+   *          service handle of type AbstractName(MsaWS)
+   * @param altitle
+   *          DOCUMENT ME!
+   * @param msa
+   *          DOCUMENT ME!
+   * @param submitGaps
+   *          DOCUMENT ME!
+   * @param preserveOrder
+   *          DOCUMENT ME!
+   */
+
+  public MsaWSClient(Jws2Discoverer.Jws2Instance sh, String altitle,
+          jalview.datamodel.AlignmentView msa, boolean submitGaps,
+          boolean preserveOrder, Alignment seqdataset,
+          AlignFrame _alignFrame)
+  {
+    super();
+    alignFrame = _alignFrame;
+    if (!(sh.service instanceof MsaWS))
+    {
+      // redundant at mo - but may change
+      JOptionPane
+              .showMessageDialog(
+                      Desktop.desktop,
+                      "The Service called \n"
+                              + sh.serviceType
+                              + "\nis not a \nMultiple Sequence Alignment Service !",
+                      "Internal Jalview Error", JOptionPane.WARNING_MESSAGE);
+
+      return;
+    }
+    server = sh.service;
+    if ((wsInfo = setWebService(sh, false)) == null)
+    {
+      JOptionPane.showMessageDialog(Desktop.desktop,
+              "The Multiple Sequence Alignment Service named "
+                      + sh.serviceType + " is unknown",
+              "Internal Jalview Error", JOptionPane.WARNING_MESSAGE);
+
+      return;
+    }
+    startMsaWSClient(altitle, msa, submitGaps, preserveOrder, seqdataset);
+
+  }
+
+  public MsaWSClient()
+  {
+    super();
+    // add a class reference to the list
+  }
+
+  private void startMsaWSClient(String altitle, AlignmentView msa,
+          boolean submitGaps, boolean preserveOrder, Alignment seqdataset)
+  {
+    //if (!locateWebService())
+   // {
+   //   return;
+   // }
+
+    wsInfo.setProgressText(((submitGaps) ? "Re-alignment" : "Alignment")
+            + " of " + altitle + "\nJob details\n");
+    String jobtitle = WebServiceName.toLowerCase();
+    if (jobtitle.endsWith("alignment"))
+    {
+      if (submitGaps
+              && (!jobtitle.endsWith("realignment") || jobtitle
+                      .indexOf("profile") == -1))
+      {
+        int pos = jobtitle.indexOf("alignment");
+        jobtitle = WebServiceName.substring(0, pos) + "re-alignment of "
+                + altitle;
+      }
+      else
+      {
+        jobtitle = WebServiceName + " of " + altitle;
+      }
+    }
+    else
+    {
+      jobtitle = WebServiceName + (submitGaps ? " re" : " ")
+              + "alignment of " + altitle;
+    }
+
+    MsaWSThread msathread = new MsaWSThread(server, WsURL, wsInfo,
+            alignFrame, WebServiceName, jobtitle, msa, submitGaps,
+            preserveOrder, seqdataset);
+    wsInfo.setthisService(msathread);
+    msathread.start();
+  }
+
+  protected String getServiceActionKey()
+  {
+    return "MsaWS";
+  }
+
+  protected String getServiceActionDescription()
+  {
+    return "Multiple Sequence Alignment";
+  }
+
+  /**
+   * look at ourselves and work out if we are a service that can take a profile
+   * and align to it
+   * 
+   * @return true if we can send gapped sequences to the alignment service
+   */
+  private boolean canSubmitGaps()
+  {
+    // TODO: query service or extract service handle props to check if we can
+    // realign
+    return (WebServiceName.indexOf("lustal") > -1); // cheat!
+  }
+
+  public void attachWSMenuEntry(JMenu msawsmenu,
+          final Jws2Instance service, final AlignFrame alignFrame)
+  {
+    setWebService(service, true); // headless
+    JMenuItem method = new JMenuItem(WebServiceName);
+    method.setToolTipText(WsURL);
+    method.addActionListener(new ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        AlignmentView msa = alignFrame.gatherSequencesForAlignment();
+        new MsaWSClient(service, alignFrame.getTitle(),
+                msa, false, true, alignFrame.getViewport().getAlignment()
+                        .getDataset(), alignFrame);
+
+      }
+
+    });
+    msawsmenu.add(method);
+    if (canSubmitGaps())
+    {
+      // We know that ClustalWS can accept partial alignments for refinement.
+      final JMenuItem methodR = new JMenuItem(WebServiceName 
+              + " (With Gaps)");
+      methodR.setToolTipText(WsURL);
+      methodR.addActionListener(new ActionListener()
+      {
+        public void actionPerformed(ActionEvent e)
+        {
+          AlignmentView msa = alignFrame.gatherSequencesForAlignment();
+          new MsaWSClient(service, alignFrame.getTitle(),
+                  msa, true, true, alignFrame.getViewport().getAlignment()
+                          .getDataset(), alignFrame);
+
+        }
+
+      });
+      msawsmenu.add(methodR);
+
+    }
+
+  }
+
+}
diff --git a/src/jalview/ws/jws2/MsaWSThread.java b/src/jalview/ws/jws2/MsaWSThread.java
new file mode 100644 (file)
index 0000000..c8c49c5
--- /dev/null
@@ -0,0 +1,765 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
+ * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ * 
+ * Jalview 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package jalview.ws.jws2;
+
+import java.util.*;
+
+import compbio.data.msa.MsaWS;
+import compbio.data.sequence.AlignmentMetadata;
+import compbio.data.sequence.Program;
+import compbio.metadata.ChunkHolder;
+import compbio.metadata.JobStatus;
+
+import jalview.analysis.*;
+import jalview.bin.*;
+import jalview.datamodel.*;
+import jalview.gui.*;
+import jalview.ws.AWsJob;
+import jalview.ws.WSClientI;
+import jalview.ws.JobStateSummary;
+
+/**
+ * <p>
+ * Title:
+ * </p>
+ * 
+ * <p>
+ * Description:
+ * </p>
+ * 
+ * <p>
+ * Copyright: Copyright (c) 2004
+ * </p>
+ * 
+ * <p>
+ * Company: Dundee University
+ * </p>
+ * 
+ * @author not attributable
+ * @version 1.0
+ */
+class MsaWSThread extends AWS2Thread implements WSClientI
+{
+  boolean submitGaps = false; // pass sequences including gaps to alignment
+
+  // service
+
+  boolean preserveOrder = true; // and always store and recover sequence
+
+  // order
+
+  class MsaWSJob extends JWs2Job
+  {
+    long lastChunk=0;
+    
+    /**
+     * input 
+     */
+    ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
+    /**
+     * output
+     */
+    compbio.data.sequence.Alignment alignment;
+    // set if the job didn't get run - then the input is simply returned to the user
+    private boolean returnInput=false;
+    
+    /**
+     * MsaWSJob
+     * 
+     * @param jobNum
+     *          int
+     * @param jobId
+     *          String
+     */
+    public MsaWSJob(int jobNum, SequenceI[] inSeqs)
+    {
+      this.jobnum = jobNum;
+      if (!prepareInput(inSeqs, 2))
+      {
+        submitted = true;
+        subjobComplete = true;
+        returnInput = true;
+      }
+
+    }
+
+    Hashtable<String,Map> SeqNames = new Hashtable();
+
+    Vector<String[]> emptySeqs = new Vector();
+
+    /**
+     * prepare input sequences for MsaWS service
+     * 
+     * @param seqs
+     *          jalview sequences to be prepared
+     * @param minlen
+     *          minimum number of residues required for this MsaWS service
+     * @return true if seqs contains sequences to be submitted to service.
+     */
+    // TODO: return compbio.seqs list or nothing to indicate validity.
+    private boolean prepareInput(SequenceI[] seqs, int minlen)
+    {
+      int nseqs = 0;
+      if (minlen < 0)
+      {
+        throw new Error(
+                "Implementation error: minlen must be zero or more.");
+      }
+      for (int i = 0; i < seqs.length; i++)
+      {
+        if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
+        {
+          nseqs++;
+        }
+      }
+      boolean valid = nseqs > 1; // need at least two seqs
+      compbio.data.sequence.FastaSequence seq;
+      for (int i = 0, n = 0; i < seqs.length; i++)
+      {
+
+        String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
+        // for
+        // any
+        // subjob
+        SeqNames.put(newname, jalview.analysis.SeqsetUtils
+                .SeqCharacterHash(seqs[i]));
+        if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
+        {
+          // make new input sequence with or without gaps
+          seq = new compbio.data.sequence.FastaSequence(newname,
+                  (submitGaps) ? seqs[i].getSequenceAsString()
+                  : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
+                          seqs[i].getSequenceAsString()));
+          this.seqs.add(seq);
+        }
+        else
+        {
+          String empty = null;
+          if (seqs[i].getEnd() >= seqs[i].getStart())
+          {
+            empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
+                    .extractGaps(jalview.util.Comparison.GapChars, seqs[i]
+                            .getSequenceAsString());
+          }
+          emptySeqs.add(new String[]
+          { newname, empty });
+        }
+      }
+      return valid;
+    }
+
+    /**
+     * 
+     * @return true if getAlignment will return a valid alignment result.
+     */
+    public boolean hasResults()
+    {
+      if (subjobComplete && isFinished() && (alignment!=null || (emptySeqs!=null && emptySeqs.size()>0)))
+      {
+        return true;
+      }
+      return false;
+    }
+
+    /**
+     * 
+     * get the alignment including any empty sequences in the original order with original ids. Caller must access the alignment.getMetadata() object to annotate the final result passsed to the user.
+     * @return { SequenceI[], AlignmentOrder }
+     */
+    public Object[] getAlignment()
+    {
+      // is this a generic subjob or a Jws2 specific Object[] return signature
+      if (hasResults())
+      {
+        SequenceI[] alseqs = null;
+        char alseq_gapchar = '-';
+        int alseq_l = 0;
+        if (alignment.getSequences().size()>0)
+        {
+          alseqs = new SequenceI[alignment.getSequences().size()];
+          for (compbio.data.sequence.FastaSequence seq: alignment.getSequences())
+          {
+            alseqs[alseq_l++] = new Sequence(seq.getId(),seq.getSequence());
+          }
+          alseq_gapchar = alignment.getMetadata().getGapchar();
+          
+        }
+        // add in the empty seqs.
+        if (emptySeqs.size() > 0)
+        {
+          SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
+          // get width
+          int i, w = 0;
+          if (alseq_l > 0)
+          {
+            for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
+            {
+              if (w < alseqs[i].getLength())
+              {
+                w = alseqs[i].getLength();
+              }
+              t_alseqs[i] = alseqs[i];
+              alseqs[i] = null;
+            }
+          }
+          // check that aligned width is at least as wide as emptySeqs width.
+          int ow = w, nw = w;
+          for (i = 0, w = emptySeqs.size(); i < w; i++)
+          {
+            String[] es = (String[]) emptySeqs.get(i);
+            if (es != null && es[1] != null)
+            {
+              int sw = es[1].length();
+              if (nw < sw)
+              {
+                nw = sw;
+              }
+            }
+          }
+          // make a gapped string.
+          StringBuffer insbuff = new StringBuffer(w);
+          for (i = 0; i < nw; i++)
+          {
+            insbuff.append(alseq_gapchar);
+          }
+          if (ow < nw)
+          {
+            for (i = 0; i < alseq_l; i++)
+            {
+              int sw = t_alseqs[i].getLength();
+              if (nw > sw)
+              {
+                // pad at end
+                alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
+                        + insbuff.substring(0, sw - nw));
+              }
+            }
+          }
+          for (i = 0, w = emptySeqs.size(); i < w; i++)
+          {
+            String[] es = (String[]) emptySeqs.get(i);
+            if (es[1] == null)
+            {
+              t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
+                      insbuff.toString(), 1, 0);
+            }
+            else
+            {
+              if (es[1].length() < nw)
+              {
+                t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
+                        es[0],
+                        es[1] + insbuff.substring(0, nw - es[1].length()),
+                        1, 1 + es[1].length());
+              }
+              else
+              {
+                t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
+                        es[0], es[1]);
+              }
+            }
+          }
+          alseqs = t_alseqs;
+        }
+        AlignmentOrder msaorder = new AlignmentOrder(alseqs);
+        // always recover the order - makes parseResult()'s life easier.
+        jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
+        // account for any missing sequences
+        jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
+        return new Object[]
+        { alseqs, msaorder };
+      }
+      return null;
+    }
+
+    /**
+     * mark subjob as cancelled and set result object appropriatly
+     */
+    void cancel()
+    {
+      cancelled = true;
+      subjobComplete = true;
+      alignment = null;
+    }
+
+    /**
+     * 
+     * @return boolean true if job can be submitted.
+     */
+    public boolean hasValidInput()
+    {
+      // TODO: get attributes for this MsaWS instance to check if it can do two sequence alignment.
+      if (seqs != null && seqs.size()>=2) // two or more sequences is valid ? 
+      {
+        return true;
+      }
+      return false;
+    }
+    StringBuffer jobProgress=new StringBuffer();
+    public void setStatus(String string)
+    {
+      jobProgress.setLength(0);
+      jobProgress.append(string);
+    }
+
+    @Override
+    public String getStatus()
+    {
+      return jobProgress.toString();
+    }
+
+    @Override
+    public boolean hasStatus()
+    {
+      return jobProgress!=null;
+    }
+
+    /**
+     * @return the lastChunk
+     */
+    public long getLastChunk()
+    {
+      return lastChunk;
+    }
+
+    /**
+     * @param lastChunk the lastChunk to set
+     */
+    public void setLastChunk(long lastChunk)
+    {
+      this.lastChunk = lastChunk;
+    }
+    
+  }
+
+  String alTitle; // name which will be used to form new alignment window.
+
+  Alignment dataset; // dataset to which the new alignment will be
+
+  // associated.
+
+  @SuppressWarnings("unchecked")
+  MsaWS server = null;
+
+  /**
+   * set basic options for this (group) of Msa jobs
+   * 
+   * @param subgaps
+   *          boolean
+   * @param presorder
+   *          boolean
+   */
+  MsaWSThread(MsaWS server, String wsUrl,
+          WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
+          AlignmentView alview, String wsname, boolean subgaps,
+          boolean presorder)
+  {
+    super(alFrame, wsinfo, alview, wsname, wsUrl);
+    this.server = server;
+    this.submitGaps = subgaps;
+    this.preserveOrder = presorder;
+  }
+
+  /**
+   * create one or more Msa jobs to align visible seuqences in _msa
+   * 
+   * @param title
+   *          String
+   * @param _msa
+   *          AlignmentView
+   * @param subgaps
+   *          boolean
+   * @param presorder
+   *          boolean
+   * @param seqset
+   *          Alignment
+   */
+  MsaWSThread(MsaWS server2, String wsUrl,
+          WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
+          String wsname, String title, AlignmentView _msa, boolean subgaps,
+          boolean presorder, Alignment seqset)
+  {
+    this(server2,wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
+    OutputHeader = wsInfo.getProgressText();
+    alTitle = title;
+    dataset = seqset;
+
+    SequenceI[][] conmsa = _msa.getVisibleContigs('-');
+    if (conmsa != null)
+    {
+      int njobs = conmsa.length;
+      jobs = new MsaWSJob[njobs];
+      for (int j = 0; j < njobs; j++)
+      {
+        if (j != 0)
+        {
+          jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
+        }
+        else
+        {
+          jobs[j] = new MsaWSJob(0, conmsa[j]);
+        }
+        if (njobs > 0)
+        {
+          wsinfo
+                  .setProgressName("region " + jobs[j].getJobnum(),
+                          jobs[j].getJobnum());
+        }
+        wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
+      }
+    }
+  }
+
+  public boolean isCancellable()
+  {
+    return true;
+  }
+
+  public void cancelJob()
+  {
+    if (!jobComplete && jobs != null)
+    {
+      boolean cancelled = true;
+      for (int job = 0; job < jobs.length; job++)
+      {
+        if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
+        {
+          String cancelledMessage = "";
+          try
+          {
+            boolean cancelledJob = server
+                    .cancelJob(jobs[job].getJobId());
+            if (cancelledJob)
+            {
+              // CANCELLED_JOB
+              cancelledMessage = "Job cancelled.";
+              ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this ugliness - 
+              wsInfo.setStatus(jobs[job].getJobnum(),
+                      WebserviceInfo.STATE_CANCELLED_OK);
+            } 
+            else 
+            {
+              // VALID UNSTOPPABLE JOB
+              cancelledMessage += "Server cannot cancel this job. just close the window.\n";
+              cancelled = false;
+              // wsInfo.setStatus(jobs[job].jobnum,
+              // WebserviceInfo.STATE_RUNNING);
+            }
+          } catch (Exception exc)
+          {
+            cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
+                    + exc + "\n");
+            Cache.log.warn(
+                    "Exception whilst cancelling " + jobs[job].getJobId(), exc);
+          }
+          wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
+                  + cancelledMessage + "\n");
+        }
+      }
+      if (cancelled)
+      {
+        wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
+        jobComplete = true;
+      }
+      this.interrupt(); // kick thread to update job states.
+    }
+    else
+    {
+      if (!jobComplete)
+      {
+        wsInfo
+                .setProgressText(OutputHeader
+                        + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
+      }
+    }
+  }
+
+  public void pollJob(AWsJob job) throws Exception
+  {
+    // TODO: investigate if we still need to cast here in J1.6
+    MsaWSJob j=((MsaWSJob) job);
+    j.setjobStatus(server.getJobStatus(job.getJobId()));
+    StringBuffer response = j.jobProgress;
+    ChunkHolder chunk = server.pullExecStatistics(job.getJobId(), j.getLastChunk());
+    response.append(chunk.getChunk());
+    j.setLastChunk(chunk.getNextPosition());
+     
+  }
+
+  public void StartJob(AWsJob job)
+  {
+    // boiler plate template
+    if (!(job instanceof MsaWSJob))
+    {
+      throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
+              + job.getClass());
+    }
+    MsaWSJob j = (MsaWSJob) job;
+    if (j.isSubmitted())
+    {
+      if (Cache.log.isDebugEnabled())
+      {
+        Cache.log.debug("Tried to submit an already submitted job "
+                + j.getJobId());
+      }
+      return;
+    }
+    // end boilerplate
+    
+    if (j.seqs == null || j.seqs.size()==0)
+    {
+      // special case - selection consisted entirely of empty sequences...
+      j.setjobStatus(JobStatus.FINISHED);
+      j.setStatus("Empty Alignment Job");
+    }
+    try
+    {
+      // TODO: get the parameters (if any) for this job and submit the job
+      j.setJobId(server.align(j.seqs));
+
+      if (j.getJobId()!= null)
+      {
+        j.setSubmitted(true);
+        j.setSubjobComplete(false);
+        // System.out.println(WsURL + " Job Id '" + jobId + "'");
+      }
+      else
+      {
+        throw new Exception(
+                  "Server at "
+                          + WsUrl
+                          + " returned null string for job id, it probably cannot be contacted. Try again later ?");
+        }
+    } catch (Exception e)
+    {
+      // Boilerplate code here
+      // TODO: JBPNote catch timeout or other fault types explicitly
+      // For unexpected errors
+      System.err
+              .println(WebServiceName
+                      + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
+                      + "When contacting Server:" + WsUrl + "\n"
+                      + e.toString() + "\n");
+      j.setAllowedServerExceptions(0);
+      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
+      wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
+      wsInfo
+              .appendProgressText(
+                      j.getJobnum(),
+                      "Failed to submit sequences for alignment.\n"
+                              + "It is most likely that there is a problem with the server.\n"
+                              + "Just close the window\n");
+
+      // e.printStackTrace(); // TODO: JBPNote DEBUG
+    }
+  }
+
+
+  public void parseResult()
+  {
+    int results = 0; // number of result sets received
+    JobStateSummary finalState = new JobStateSummary();
+    try
+    {
+      for (int j = 0; j < jobs.length; j++)
+      {
+        MsaWSJob msjob = ((MsaWSJob) jobs[j]);
+        if (jobs[j].isFinished() && msjob.alignment==null)
+        {
+          try {
+            msjob.alignment = server.getResult(msjob.getJobId());
+          }
+          catch (Exception e)
+          {
+            Cache.log.error("Couldn't get Alignment for job.",e);
+          }
+        }
+        finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
+        if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
+                && jobs[j].hasResults())
+        {
+          results++;
+          compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
+          if (alignment != null)
+          {
+            wsInfo.appendProgressText(jobs[j].getJobnum(),
+                    "\nAlignment Object Method Notes\n");
+            wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
+            // JBPNote The returned files from a webservice could be
+            // hidden behind icons in the monitor window that,
+            // when clicked, pop up their corresponding data
+          }
+        }
+      }
+    } catch (Exception ex)
+    {
+
+      Cache.log.error("Unexpected exception when processing results for "
+              + alTitle, ex);
+      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+    }
+    if (results > 0)
+    {
+      wsInfo.showResultsNewFrame
+              .addActionListener(new java.awt.event.ActionListener()
+              {
+                public void actionPerformed(java.awt.event.ActionEvent evt)
+                {
+                  displayResults(true);
+                }
+              });
+      wsInfo.mergeResults
+              .addActionListener(new java.awt.event.ActionListener()
+              {
+                public void actionPerformed(java.awt.event.ActionEvent evt)
+                {
+                  displayResults(false);
+                }
+              });
+      wsInfo.setResultsReady();
+    }
+    else
+    {
+      wsInfo.setFinishedNoResults();
+    }
+  }
+
+  void displayResults(boolean newFrame)
+  {
+    // view input or result data for each block
+    Vector alorders = new Vector();
+    SequenceI[][] results = new SequenceI[jobs.length][];
+    AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
+    String lastProgram = null;
+    MsaWSJob msjob;
+    for (int j = 0; j < jobs.length; j++)
+    {
+      if (jobs[j].hasResults())
+      {
+        msjob = (MsaWSJob)jobs[j];
+        Object[] res = msjob.getAlignment();
+        lastProgram = msjob.alignment.getMetadata().getProgram().name();
+        alorders.add(res[1]);
+        results[j] = (SequenceI[]) res[0];
+        orders[j] = (AlignmentOrder) res[1];
+
+        // SequenceI[] alignment = input.getUpdated
+      }
+      else
+      {
+        results[j] = null;
+      }
+    }
+    Object[] newview = input.getUpdatedView(results, orders, getGapChar());
+    // trash references to original result data
+    for (int j = 0; j < jobs.length; j++)
+    {
+      results[j] = null;
+      orders[j] = null;
+    }
+    SequenceI[] alignment = (SequenceI[]) newview[0];
+    ColumnSelection columnselection = (ColumnSelection) newview[1];
+    Alignment al = new Alignment(alignment);
+    // TODO: add 'provenance' property to alignment from the method notes
+    if (lastProgram!=null) {
+      al.setProperty("Alignment Program", lastProgram);
+    }
+    // accompanying each subjob
+    if (dataset != null)
+    {
+      al.setDataset(dataset);
+    }
+
+    propagateDatasetMappings(al);
+    // JBNote- TODO: warn user if a block is input rather than aligned data ?
+
+    if (newFrame)
+    {
+      AlignFrame af = new AlignFrame(al, columnselection,
+              AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+
+      // initialise with same renderer settings as in parent alignframe.
+      af.getFeatureRenderer().transferSettings(this.featureSettings);
+      // update orders
+      if (alorders.size() > 0)
+      {
+        if (alorders.size() == 1)
+        {
+          af.addSortByOrderMenuItem(WebServiceName + " Ordering",
+                  (AlignmentOrder) alorders.get(0));
+        }
+        else
+        {
+          // construct a non-redundant ordering set
+          Vector names = new Vector();
+          for (int i = 0, l = alorders.size(); i < l; i++)
+          {
+            String orderName = new String(" Region " + i);
+            int j = i + 1;
+
+            while (j < l)
+            {
+              if (((AlignmentOrder) alorders.get(i))
+                      .equals(((AlignmentOrder) alorders.get(j))))
+              {
+                alorders.remove(j);
+                l--;
+                orderName += "," + j;
+              }
+              else
+              {
+                j++;
+              }
+            }
+
+            if (i == 0 && j == 1)
+            {
+              names.add(new String(""));
+            }
+            else
+            {
+              names.add(orderName);
+            }
+          }
+          for (int i = 0, l = alorders.size(); i < l; i++)
+          {
+            af.addSortByOrderMenuItem(WebServiceName
+                    + ((String) names.get(i)) + " Ordering",
+                    (AlignmentOrder) alorders.get(i));
+          }
+        }
+      }
+
+      Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
+              AlignFrame.DEFAULT_HEIGHT);
+
+    }
+    else
+    {
+      System.out.println("MERGE WITH OLD FRAME");
+      // TODO: modify alignment in original frame, replacing old for new
+      // alignment using the commands.EditCommand model to ensure the update can
+      // be undone
+    }
+  }
+
+  public boolean canMergeResults()
+  {
+    return false;
+  }
+}