JAL-3070 refactored Client specific code from jalview.ws.jws2.MSA* as implementors...
[jalview.git] / src / jalview / ws / gui / MsaWSThread.java
similarity index 54%
rename from src/jalview/ws/jws2/MsaWSThread.java
rename to src/jalview/ws/gui/MsaWSThread.java
index b1d6452..7141e73 100644 (file)
  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
  * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
-package jalview.ws.jws2;
+package jalview.ws.gui;
 
-import jalview.analysis.AlignSeq;
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
 import jalview.gui.SplitFrame;
 import jalview.gui.WebserviceInfo;
 import jalview.util.MessageManager;
+import jalview.ws.AWSThread;
 import jalview.ws.AWsJob;
 import jalview.ws.JobStateSummary;
 import jalview.ws.WSClientI;
-import jalview.ws.jws2.dm.JabaWsParamSet;
+import jalview.ws.api.CancellableI;
+import jalview.ws.api.JobId;
+import jalview.ws.api.MultipleSequenceAlignmentI;
+import jalview.ws.gui.WsJob.JobState;
 import jalview.ws.params.ArgumentI;
 import jalview.ws.params.WsParamSetI;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
 import javax.swing.JInternalFrame;
 
-import compbio.data.msa.MsaWS;
-import compbio.metadata.Argument;
-import compbio.metadata.ChunkHolder;
-import compbio.metadata.JobStatus;
-import compbio.metadata.Preset;
-
-class MsaWSThread extends AWS2Thread implements WSClientI
+public class MsaWSThread extends AWSThread implements WSClientI
 {
   boolean submitGaps = false; // pass sequences including gaps to alignment
 
@@ -63,377 +58,13 @@ class MsaWSThread extends AWS2Thread implements WSClientI
 
   // order
 
-  class MsaWSJob extends JWs2Job
-  {
-    long lastChunk = 0;
-
-    /**
-     * input
-     */
-    ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<>();
-
-    /**
-     * 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;
-      }
-
-    }
-
-    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(MessageManager.getString(
-                "error.implementation_error_minlen_must_be_greater_zero"));
-      }
-      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.
-     */
-    @Override
-    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 = 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 = 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.
-     */
-    @Override
-    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 alignmentProgram = null;
-
-    public String getAlignmentProgram()
-    {
-      return alignmentProgram;
-    }
-
-    public boolean hasArguments()
-    {
-      return (arguments != null && arguments.size() > 0)
-              || (preset != null && preset instanceof JabaWsParamSet);
-    }
-
-    public List<Argument> getJabaArguments()
-    {
-      List<Argument> newargs = new ArrayList<>();
-      if (preset != null && preset instanceof JabaWsParamSet)
-      {
-        newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
-      }
-      if (arguments != null && arguments.size() > 0)
-      {
-        newargs.addAll(JabaParamStore.getJabafromJwsArgs(arguments));
-      }
-      return newargs;
-    }
-
-    /**
-     * add a progess header to status string containing presets/args used
-     */
-    public void addInitialStatus()
-    {
-      if (preset != null)
-      {
-        jobProgress.append("Using "
-                + (preset instanceof JabaPreset ? "Server" : "User")
-                + "Preset: " + preset.getName());
-        if (preset instanceof JabaWsParamSet)
-        {
-          for (Argument opt : getJabaArguments())
-          {
-            jobProgress.append(
-                    opt.getName() + " " + opt.getDefaultValue() + "\n");
-          }
-        }
-      }
-      if (arguments != null && arguments.size() > 0)
-      {
-        jobProgress.append("With custom parameters : \n");
-        // merge arguments with preset's own arguments.
-        for (Argument opt : getJabaArguments())
-        {
-          jobProgress.append(
-                  opt.getName() + " " + opt.getDefaultValue() + "\n");
-        }
-      }
-      jobProgress.append("\nJob Output:\n");
-    }
-
-    public boolean isPresetJob()
-    {
-      return preset != null && preset instanceof JabaPreset;
-    }
-
-    public Preset getServerPreset()
-    {
-      return (isPresetJob()) ? ((JabaPreset) preset).p : null;
-    }
-  }
-
   String alTitle; // name which will be used to form new alignment window.
 
   AlignmentI dataset; // dataset to which the new alignment will be
 
   // associated.
 
-  @SuppressWarnings("unchecked")
-  MsaWS server = null;
+  MultipleSequenceAlignmentI server = null;
 
   /**
    * set basic options for this (group) of Msa jobs
@@ -443,7 +74,8 @@ class MsaWSThread extends AWS2Thread implements WSClientI
    * @param presorder
    *          boolean
    */
-  private MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
+  private MsaWSThread(MultipleSequenceAlignmentI server, String wsUrl,
+          WebserviceInfo wsinfo,
           jalview.gui.AlignFrame alFrame, AlignmentView alview,
           String wsname, boolean subgaps, boolean presorder)
   {
@@ -454,7 +86,7 @@ class MsaWSThread extends AWS2Thread implements WSClientI
   }
 
   /**
-   * create one or more Msa jobs to align visible seuqences in _msa
+   * create one or more Msa jobs to align visible sequences in _msa
    * 
    * @param title
    *          String
@@ -467,7 +99,8 @@ class MsaWSThread extends AWS2Thread implements WSClientI
    * @param seqset
    *          Alignment
    */
-  MsaWSThread(MsaWS server2, WsParamSetI preset, List<ArgumentI> paramset,
+  public MsaWSThread(MultipleSequenceAlignmentI server2, WsParamSetI preset,
+          List<ArgumentI> paramset,
           String wsUrl, WebserviceInfo wsinfo,
           jalview.gui.AlignFrame alFrame, String wsname, String title,
           AlignmentView _msa, boolean subgaps, boolean presorder,
@@ -487,11 +120,11 @@ class MsaWSThread extends AWS2Thread implements WSClientI
       {
         if (j != 0)
         {
-          jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
+          jobs[j] = new MsaWSJob(this, wsinfo.addJobPane(), conmsa[j]);
         }
         else
         {
-          jobs[j] = new MsaWSJob(0, conmsa[j]);
+          jobs[j] = new MsaWSJob(this, 0, conmsa[j]);
         }
         if (jobs[j].hasValidInput())
         {
@@ -525,12 +158,14 @@ class MsaWSThread extends AWS2Thread implements WSClientI
   @Override
   public boolean isCancellable()
   {
-    return true;
+    return server instanceof CancellableI;
   }
 
   @Override
   public void cancelJob()
   {
+    // TODO decide if when some jobs are not cancellable to shut down the thread
+    // anyhow ?
     if (!jobComplete && jobs != null)
     {
       boolean cancelled = true;
@@ -541,13 +176,11 @@ class MsaWSThread extends AWS2Thread implements WSClientI
           String cancelledMessage = "";
           try
           {
-            boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
-            if (true) // cancelledJob || true)
+            CancellableI service = (CancellableI) server;
+            boolean cancelledJob = service.cancel((WsJob) jobs[job]);
+            if (cancelledJob)
             {
               // CANCELLED_JOB
-              // if the Jaba server indicates the job can't be cancelled, its
-              // because its running on the server's local execution engine
-              // so we just close the window anyway.
               cancelledMessage = "Job cancelled.";
               ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
                                                // ugliness -
@@ -607,42 +240,8 @@ class MsaWSThread extends AWS2Thread implements WSClientI
     // this is standard code, but since the interface doesn't comprise of a
     // basic one that implements (getJobStatus, pullExecStatistics) we have to
     // repeat the code for all jw2s services.
-    j.setjobStatus(server.getJobStatus(job.getJobId()));
-    updateJobProgress(j);
-  }
-
-  /**
-   * 
-   * @param j
-   * @return true if more job progress data was available
-   * @throws Exception
-   */
-  protected boolean updateJobProgress(MsaWSJob j) throws Exception
-  {
-    StringBuffer response = j.jobProgress;
-    long lastchunk = j.getLastChunk();
-    boolean changed = false;
-    do
-    {
-      j.setLastChunk(lastchunk);
-      ChunkHolder chunk = server.pullExecStatistics(j.getJobId(),
-              lastchunk);
-      if (chunk != null)
-      {
-        changed |= chunk.getChunk().length() > 0;
-        response.append(chunk.getChunk());
-        lastchunk = chunk.getNextPosition();
-        try
-        {
-          Thread.sleep(50);
-        } catch (InterruptedException x)
-        {
-        }
-        ;
-      }
-      ;
-    } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
-    return changed;
+    server.updateStatus(j);
+    server.updateJobProgress(j);
   }
 
   @Override
@@ -671,26 +270,37 @@ class MsaWSThread extends AWS2Thread implements WSClientI
     if (j.seqs == null || j.seqs.size() == 0)
     {
       // special case - selection consisted entirely of empty sequences...
-      j.setjobStatus(JobStatus.FINISHED);
+      j.setState(JobState.FINISHED);
       j.setStatus(MessageManager.getString("label.empty_alignment_job"));
     }
     try
     {
       j.addInitialStatus(); // list the presets/parameters used for the job in
                             // status
-      if (j.isPresetJob())
+      try
       {
-        j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
-      }
-      else if (j.hasArguments())
-      {
-        j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
-      }
-      else
+        JobId jobHandle = server.align(j.seqs, j.getPreset(),
+                j.getArguments());
+        if (jobHandle != null)
+        {
+          j.setJobHandle(jobHandle);
+        }
+
+      } catch (Throwable throwable)
       {
-        j.setJobId(server.align(j.seqs));
+        if (!server.handleSubmitError(throwable, j, wsInfo))
+        {
+          if (throwable instanceof Exception)
+          {
+            throw ((Exception) throwable);
+          }
+          if (throwable instanceof Error)
+          {
+            throw ((Error) throwable);
+          }
+        }
       }
-
+      ///// generic
       if (j.getJobId() != null)
       {
         j.setSubmitted(true);
@@ -705,40 +315,11 @@ class MsaWSThread extends AWS2Thread implements WSClientI
                 new String[]
                 { WsUrl }));
       }
-    } catch (compbio.metadata.UnsupportedRuntimeException _lex)
-    {
-      lex = _lex;
-      wsInfo.appendProgressText(MessageManager.formatMessage(
-              "info.job_couldnt_be_run_server_doesnt_support_program",
-              new String[]
-              { _lex.getMessage() }));
-      wsInfo.warnUser(_lex.getMessage(),
-              MessageManager.getString("warn.service_not_supported"));
-      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
-      wsInfo.setStatus(j.getJobnum(),
-              WebserviceInfo.STATE_STOPPED_SERVERERROR);
-    } catch (compbio.metadata.LimitExceededException _lex)
-    {
-      lex = _lex;
-      wsInfo.appendProgressText(MessageManager.formatMessage(
-              "info.job_couldnt_be_run_exceeded_hard_limit", new String[]
-              { _lex.getMessage() }));
-      wsInfo.warnUser(_lex.getMessage(),
-              MessageManager.getString("warn.input_is_too_big"));
-      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
-      wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
-    } catch (compbio.metadata.WrongParameterException _lex)
-    {
-      lex = _lex;
-      wsInfo.warnUser(_lex.getMessage(),
-              MessageManager.getString("warn.invalid_job_param_set"));
-      wsInfo.appendProgressText(MessageManager.formatMessage(
-              "info.job_couldnt_be_run_incorrect_param_setting",
-              new String[]
-              { _lex.getMessage() }));
-      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
-      wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
-    } catch (Error e)
+    }
+    //// jabaws specific
+
+    //// generic
+    catch (Error e)
     {
       // For unexpected errors
       System.err.println(WebServiceName
@@ -794,7 +375,7 @@ class MsaWSThread extends AWS2Thread implements WSClientI
           {
             try
             {
-              jpchanged = updateJobProgress(msjob);
+              jpchanged = server.updateJobProgress(msjob);
               jpex = false;
               if (jpchanged)
               {
@@ -835,25 +416,18 @@ class MsaWSThread extends AWS2Thread implements WSClientI
             System.out.println("*** End of status");
 
           }
+          ///// jabaws specific(ish) Get Result from Server when available
           try
           {
-            msjob.alignment = server.getResult(msjob.getJobId());
-          } catch (compbio.metadata.ResultNotAvailableException e)
-          {
-            // job has failed for some reason - probably due to invalid
-            // parameters
-            Cache.log.debug(
-                    "Results not available for finished job - marking as broken job.",
-                    e);
-            msjob.jobProgress.append(
-                    "\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
-                            + e.getLocalizedMessage());
-            msjob.setjobStatus(JobStatus.FAILED);
+            msjob.alignment = server.getAlignmentFor(msjob.getJobHandle());
           } catch (Exception e)
           {
-            Cache.log.error("Couldn't get Alignment for job.", e);
-            // TODO: Increment count and retry ?
-            msjob.setjobStatus(JobStatus.UNDEFINED);
+            if (!server.handleCollectionException(e, msjob, wsInfo))
+            {
+              Cache.log.error("Couldn't get Alignment for job.", e);
+              // TODO: Increment count and retry ?
+              msjob.setState(JobState.SERVERERROR);
+            }
           }
         }
         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
@@ -861,19 +435,6 @@ class MsaWSThread extends AWS2Thread implements WSClientI
                 && jobs[j].hasResults())
         {
           results++;
-          compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
-          if (alignment != null)
-          {
-            // server.close(jobs[j].getJobnum());
-            // 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)