Merge branch 'improvement/JAL-3848_slivka_0.8' into alpha/JAL-3066_Jalview_212_slivka...
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Wed, 28 Jul 2021 10:36:30 +0000 (12:36 +0200)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 29 Jul 2021 13:27:46 +0000 (15:27 +0200)
1  2 
j11lib/slivka-client.jar
src/jalview/ws/jws2/JPredThread.java
src/jalview/ws/slivkaws/SlivkaJPredServiceInstance.java
src/jalview/ws/slivkaws/SlivkaMsaServiceInstance.java
src/jalview/ws/slivkaws/SlivkaWSDiscoverer.java
src/jalview/ws/slivkaws/SlivkaWSInstance.java

index 68cc932,9cb3a61..49ab4fc
Binary files differ
index d10ca8a,0000000..67f44fa
mode 100644,000000..100644
--- /dev/null
@@@ -1,406 -1,0 +1,405 @@@
 +package jalview.ws.jws2;
 +
 +import static java.lang.String.format;
 +
 +import java.util.Hashtable;
 +import java.util.List;
 +
 +import jalview.analysis.SeqsetUtils;
 +import jalview.bin.Cache;
 +import jalview.commands.RemoveGapsCommand;
 +import jalview.datamodel.Alignment;
 +import jalview.datamodel.AlignmentAnnotation;
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.AlignmentView;
 +import jalview.datamodel.HiddenColumns;
 +import jalview.datamodel.SequenceI;
 +import jalview.gui.AlignFrame;
 +import jalview.gui.Desktop;
 +import jalview.gui.WebserviceInfo;
- import jalview.io.JPredFile;
 +import jalview.io.JnetAnnotationMaker;
 +import jalview.util.MessageManager;
 +import jalview.ws.AWSThread;
 +import jalview.ws.AWsJob;
 +import jalview.ws.JobStateSummary;
 +import jalview.ws.WSClientI;
 +import jalview.ws.api.CancellableI;
 +import jalview.ws.api.JPredServiceI;
 +import jalview.ws.gui.WsJob;
 +import jalview.ws.gui.WsJob.JobState;
 +
 +
 +public class JPredThread extends AWSThread implements WSClientI
 +{
 +
 +  private static class JPredJob extends WsJob
 +  {
 +    private final Hashtable<?, ?> sequenceInfo;
 +    private final List<SequenceI> msf;
 +    private final int[] delMap;
 +    private AlignmentI alignment = null;
 +    private HiddenColumns hiddenCols = null;
 +
 +    private JPredJob(Hashtable<?, ?> sequenceInfo, SequenceI[] msf, int[] delMap)
 +    {
 +      this.sequenceInfo = sequenceInfo;
 +      this.msf = List.of(msf);
 +      this.delMap = delMap;
 +    }
 +
 +    @Override
 +    public boolean hasValidInput()
 +    {
 +      return true;
 +    }
 +
 +    @Override
 +    public boolean hasResults()
 +    {
 +      return (isSubjobComplete() && alignment != null);
 +    }
 +
 +    public boolean isMSA()
 +    {
 +      return msf.size() > 1;
 +    }
 +  }
 +
 +
 +  private JPredServiceI server;
 +  private String title;
 +  private Hashtable<?, ?> sequenceInfo;
 +  private SequenceI[] msf;
 +  private int[] delMap;
 +
 +  public JPredThread(WebserviceInfo wsInfo, String title,
 +      JPredServiceI server, Hashtable<?, ?> sequenceInfo,
 +      SequenceI[] msf, int[] delMap, AlignmentView view, AlignFrame frame,
 +      String wsURL)
 +  {
 +    super(frame, wsInfo, view, wsURL);
 +    this.server = server;
 +    this.title = title;
 +    this.sequenceInfo = sequenceInfo;
 +    this.msf = msf;
 +    this.delMap = delMap;
 +    JPredJob job = new JPredJob(sequenceInfo, msf, delMap);
 +    this.jobs = new JPredJob[] { job };
 +  }
 +
 +  @Override
 +  public boolean isCancellable()
 +  {
 +    return server instanceof CancellableI;
 +  }
 +
 +  @Override
 +  public boolean canMergeResults()
 +  {
 +    return false;
 +  }
 +
 +  @Override
 +  public void cancelJob()
 +  {
 +    // TODO Auto-generated method stub
 +
 +  }
 +
 +  @Override
 +  public void pollJob(AWsJob job_) throws Exception
 +  {
 +    var job = (JPredJob) job_;
 +    server.updateStatus(job);
 +    server.updateJobProgress(job);
 +  }
 +
 +  @Override
 +  public void StartJob(AWsJob job_)
 +  {
 +    if (!(job_ instanceof JPredJob))
 +      throw new RuntimeException("Invalid job type");
 +    var job = (JPredJob) job_;
 +    if (job.isSubmitted())
 +    {
 +      return;
 +    }
 +    try {
 +      try
 +      {
 +        var jobHandle = server.predict(job.msf, job.isMSA());
 +        if (jobHandle != null)
 +          job.setJobHandle(jobHandle);
 +      }
 +      catch (Throwable th) {
 +        if (!server.handleSubmitError(th, job, wsInfo)) {
 +          throw th;
 +        }
 +      }
 +      if (job.getJobId() != null) {
 +        job.setSubmitted(true);
 +        job.setSubjobComplete(false);
 +        return;
 +      }
 +      else {
 +        throw new Exception(MessageManager.formatMessage(
 +                "exception.web_service_returned_null_try_later",
 +                new String[]
 +                { WsUrl }));
 +      }
 +    }
 +    catch (Throwable th)
 +    {
 +      // 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");
 +      th.printStackTrace(System.err);
 +      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
 +      wsInfo.setStatus(job.getJobnum(),
 +              WebserviceInfo.STATE_STOPPED_SERVERERROR);
 +
 +    }
 +    finally
 +    {
 +      if (!job.isSubmitted())
 +      {
 +        job.setAllowedServerExceptions(0);
 +        wsInfo.appendProgressText(job.getJobnum(), MessageManager.getString(
 +                "info.failed_to_submit_sequences_for_alignment"));
 +      }
 +    }
 +  }
 +
 +  @Override
 +  public void parseResult()
 +  {
 +    long progbar = (long) (Math.random() * ~(1L << 63));
 +    wsInfo.setProgressBar(
 +        MessageManager.getString("status.collecting_job_results"), progbar);
 +    int results = 0;
 +    var finalState = new JobStateSummary();
 +    try
 +    {
 +      for (int i = 0; i < jobs.length; i++) {
 +        final var job = (JPredJob) jobs[i];
 +        finalState.updateJobPanelState(wsInfo, OutputHeader, job);
 +        if (job.isFinished()) {
 +          try {
 +            server.updateJobProgress(job);
 +          }
 +          catch (Exception e) {
 +            Cache.log.warn(format(
 +                "Exception when retrieving remaining Job progress data " +
 +                "for job %s on server %s", job.getJobId(), WsUrl));
 +            e.printStackTrace();
 +          }
 +          // removed the waiting loop
 +          Cache.log.debug(format("Job Execution file for job: %s " +
 +              "on server %s%n%s", job.getJobId(), WsUrl, job.getStatus()));
 +          try {
 +            prepareJobResult(job);
 +          }
 +          catch (Exception e) {
 +            if (!server.handleCollectionException(e, job, wsInfo)) {
 +              Cache.log.error("Could not get alignment for job.", e);
 +              job.setState(JobState.SERVERERROR);
 +            }
 +          }
 +        }
 +        finalState.updateJobPanelState(wsInfo, OutputHeader, job);
 +        if (job.isSubmitted() && job.isSubjobComplete() && job.hasResults()) {
 +          results++;
 +        }
 +      }
 +    }
 +    catch (Exception e) {
 +      Cache.log.error(
 +          "Unexpected exception when processing results for " + title, e);
 +      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
 +    }
 +    if (results > 0) {
 +      wsInfo.showResultsNewFrame.addActionListener(
 +          (evt) -> displayResults(true));
 +      wsInfo.mergeResults.addActionListener(
 +          (evt) -> displayResults(false));
 +      wsInfo.setResultsReady();
 +    }
 +    else {
 +      wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
 +      wsInfo.appendInfoText("No jobs ran.");
 +      wsInfo.setFinishedNoResults();
 +    }
 +    updateGlobalStatus(finalState);
 +    wsInfo.removeProgressBar(progbar);
 +  }
 +
 +  static final int msaIndex = 0;
 +
 +  private void prepareJobResult(JPredJob job) throws Exception
 +  {
 +    HiddenColumns hiddenCols = null;
 +    int firstSeq = -1;
 +    AlignmentI alignment;
 +    var prediction = server.getPrediction(job.getJobHandle());
 +    var preds = prediction.getSeqsAsArray();
 +
 +    if (job.msf.size() > 1)
 +    {
 +      if (job.delMap != null)
 +      {
 +        Object[] alandcolsel = input
 +                .getAlignmentAndHiddenColumns(getGapChar());
 +        alignment = new Alignment((SequenceI[]) alandcolsel[0]);
 +        hiddenCols = (HiddenColumns) alandcolsel[1];
 +      }
 +      else
 +      {
 +        alignment = server.getAlignment(job.getJobHandle());
 +        var seqs = new SequenceI[alignment.getHeight()];
 +        for (int i = 0; i < alignment.getHeight(); i++)
 +        {
 +          seqs[i] = alignment.getSequenceAt(i);
 +        }
 +        if (!SeqsetUtils.deuniquify(sequenceInfo, seqs))
 +        {
 +          throw (new Exception(MessageManager.getString(
 +                  "exception.couldnt_recover_sequence_properties_for_alignment")));
 +        }
 +      }
 +      firstSeq = 0;
 +      if (currentView.getDataset() != null)
 +      {
 +        alignment.setDataset(currentView.getDataset());
 +      }
 +      else
 +      {
 +        alignment.setDataset(null);
 +      }
 +      JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, false,
 +              job.delMap);
 +    }
 +    else
 +    {
 +      alignment = new Alignment(preds);
 +      firstSeq = prediction.getQuerySeqPosition();
 +      if (job.delMap != null)
 +      {
 +        Object[] alanndcolsel = input.getAlignmentAndHiddenColumns(getGapChar());
 +        SequenceI[] seqs = (SequenceI[]) alanndcolsel[0];
 +        new RemoveGapsCommand(MessageManager.getString("label.remove_gaps"),
 +                new SequenceI[] {seqs[msaIndex]}, currentView);
 +        SequenceI profileSeq = alignment.getSequenceAt(firstSeq);
 +        profileSeq.setSequence(seqs[msaIndex].getSequenceAsString());
 +      }
 +      if (!SeqsetUtils.SeqCharacterUnhash(
 +              alignment.getSequenceAt(firstSeq), sequenceInfo))
 +      {
 +        throw new Exception(MessageManager.getString(
 +                "exception.couldnt_recover_sequence_props_for_jnet_query"));
 +      }
 +      alignment.setDataset(currentView.getDataset());
 +      JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, true,
 +              job.delMap);
 +      SequenceI profileSeq = alignment.getSequenceAt(0);
 +      alignToProfileSeq(alignment, profileSeq);
 +      if (job.delMap != null)
 +      {
 +        hiddenCols = alignment.propagateInsertions(profileSeq, input);
 +      }
 +    }
 +
 +    for (var annot : alignment.getAlignmentAnnotation())
 +    {
 +      if (annot.sequenceRef != null)
 +      {
 +        replaceAnnotationOnAlignmentWith(annot, annot.label,
 +                "jalview.ws.JPred", annot.sequenceRef);
 +      }
 +    }
 +    job.alignment = alignment;
 +    job.hiddenCols = hiddenCols;
 +  }
 +
 +  private static void replaceAnnotationOnAlignmentWith(
 +          AlignmentAnnotation newAnnot, String typeName, String calcId,
 +          SequenceI aSeq)
 +  {
 +    SequenceI dsseq = aSeq.getDatasetSequence();
 +    while (dsseq.getDatasetSequence() != null)
 +    {
 +      dsseq = dsseq.getDatasetSequence();
 +    }
 +    // look for same annotation on dataset and lift this one over
 +    List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
 +            typeName);
 +    if (dsan != null && dsan.size() > 0)
 +    {
 +      for (AlignmentAnnotation dssan : dsan)
 +      {
 +        dsseq.removeAlignmentAnnotation(dssan);
 +      }
 +    }
 +    AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
 +    dsseq.addAlignmentAnnotation(dssan);
 +    dssan.adjustForAlignment();
 +  }
 +
 +  private static void alignToProfileSeq(AlignmentI al, SequenceI profileseq)
 +  {
 +    char gc = al.getGapCharacter();
 +    int[] gapMap = profileseq.gapMap();
 +    // insert gaps into profile
 +    for (int lp = 0, r = 0; r < gapMap.length; r++)
 +    {
 +      if (gapMap[r] - lp > 1)
 +      {
 +        StringBuffer sb = new StringBuffer();
 +        for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
 +        {
 +          sb.append(gc);
 +        }
 +        for (int s = 1, ns = al.getHeight(); s < ns; s++)
 +        {
 +          String sq = al.getSequenceAt(s).getSequenceAsString();
 +          int diff = gapMap[r] - sq.length();
 +          if (diff > 0)
 +          {
 +            // pad gaps
 +            sq = sq + sb;
 +            while ((diff = gapMap[r] - sq.length()) > 0)
 +            {
 +              sq = sq + ((diff >= sb.length()) ? sb.toString()
 +                      : sb.substring(0, diff));
 +            }
 +            al.getSequenceAt(s).setSequence(sq);
 +          }
 +          else
 +          {
 +            al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
 +                    + sb.toString() + sq.substring(gapMap[r]));
 +          }
 +        }
 +      }
 +      lp = gapMap[r];
 +    }
 +  }
 +
 +  private void displayResults(boolean newWindow)
 +  {
 +    if (jobs == null || jobs.length == 0)
 +    {
 +      return;
 +    }
 +    var job = (JPredJob) jobs[0];
 +    if (job.hasResults() && newWindow)
 +    {
 +      job.alignment.setSeqrep(job.alignment.getSequenceAt(0));
 +      AlignFrame frame = new AlignFrame(job.alignment, job.hiddenCols,
 +              AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
 +      Desktop.addInternalFrame(frame, title, AlignFrame.DEFAULT_WIDTH,
 +              AlignFrame.DEFAULT_HEIGHT);
 +    }
 +  }
 +
 +}
index 462be28,0000000..3958198
mode 100644,000000..100644
--- /dev/null
@@@ -1,96 -1,0 +1,98 @@@
 +package jalview.ws.slivkaws;
 +
 +import java.io.IOError;
 +import java.io.IOException;
++import java.util.Collection;
 +import java.util.List;
 +
 +import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.SequenceI;
 +import jalview.io.DataSourceType;
 +import jalview.io.JPredFile;
 +import jalview.ws.api.JPredServiceI;
 +import jalview.ws.api.JobId;
 +import jalview.ws.params.ArgumentI;
 +import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
 +import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
 +import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
 +
 +public class SlivkaJPredServiceInstance extends SlivkaWSInstance
 +    implements JPredServiceI
 +{
 +
 +  private class InputFormatParameter implements ArgumentI
 +  {
 +    String value = "";
 +
 +    @Override
 +    public String getName()
 +    {
 +      return "format";
 +    }
 +
 +    @Override
 +    public String getValue()
 +    {
 +      return value;
 +    }
 +
 +    @Override
 +    public void setValue(String selectedItem)
 +    {
 +      value = selectedItem;
 +    }
 +  }
 +
 +
 +  public SlivkaJPredServiceInstance(SlivkaClient client,
 +      SlivkaService service, String action)
 +  {
 +    super(client, service, action);
 +    style = ServiceClient.JPREDWSCLIENT;
 +  }
 +
 +  @Override
 +  public JobId predict(List<SequenceI> sequences, boolean msa) throws Throwable
 +  {
 +    // Hack allowing to send both single and msa jobs
 +    // until msa and single sequence services are separated.
 +    var arg = new InputFormatParameter();
 +    arg.setValue(msa ? "fasta" : "seq");
 +    return super.submit(sequences, null, List.of(arg));
 +  }
 +
 +  @Override
 +  public AlignmentI getAlignment(JobId jobId) throws Exception
 +  {
-     List<RemoteFile> files;
++    Collection<RemoteFile> files;
 +    try {
-       files = client.getJobResults(jobId.getJobId());
++      var job = client.getJob(jobId.getJobId());
++      files = job.getResults();
 +      for (RemoteFile f : files) {
 +        var alignment = readAlignment(f);
 +        if (alignment != null)
 +        {
 +          return alignment;
 +        }
 +      }
 +    }
 +    catch (IOException e) {
 +      throw new IOError(e);
 +    }
 +    return null;
 +  }
 +
 +  @Override
 +  public JPredFile getPrediction(JobId jobId) throws Exception
 +  {
-     List<RemoteFile> files = client.getJobResults(jobId.getJobId());
++    Collection<RemoteFile> files = client.getJob(jobId.getJobId()).getResults();
 +    for (RemoteFile f : files)
 +    {
 +      if (f.getLabel().equals("concise"))
 +      {
-         return new JPredFile(f.getURL(), DataSourceType.URL);
++        return new JPredFile(f.getContentUrl(), DataSourceType.URL);
 +      }
 +    }
 +    return null;
 +  }
 +}
@@@ -11,8 -15,10 +15,9 @@@ import jalview.ws.params.WsParamSetI
  import java.io.IOError;
  import java.io.IOException;
  import java.rmi.ServerError;
+ import java.util.Collection;
  import java.util.List;
  
 -import compbio.data.msa.Category;
  import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
  import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
  import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
@@@ -4,6 -4,6 +4,8 @@@ import jalview.bin.Cache
  import jalview.ws.ServiceChangeListener;
  import jalview.ws.WSDiscovererI;
  import jalview.ws.api.ServiceWithParameters;
++import javajs.http.HttpClientFactory;
++
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
@@@ -1,21 -1,7 +1,20 @@@
  package jalview.ws.slivkaws;
  
 +import java.io.ByteArrayInputStream;
 +import java.io.ByteArrayOutputStream;
 +import java.io.IOError;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.util.Arrays;
 +import java.util.EnumMap;
 +import java.util.HashSet;
 +import java.util.List;
- import java.util.Optional;
 +import java.util.Set;
 +
 +import jalview.datamodel.AlignmentI;
  import jalview.datamodel.SequenceI;
  import jalview.gui.WebserviceInfo;
 +import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
  import jalview.io.FormatAdapter;
  import jalview.ws.api.JalviewServiceEndpointProviderI;
@@@ -27,16 -13,26 +26,15 @@@ import jalview.ws.params.ArgumentI
  import jalview.ws.params.ParamDatastoreI;
  import jalview.ws.params.ParamManager;
  import jalview.ws.params.WsParamSetI;
- import uk.ac.dundee.compbio.slivkaclient.FieldType;
- import uk.ac.dundee.compbio.slivkaclient.FileField;
- import uk.ac.dundee.compbio.slivkaclient.FormField;
- import uk.ac.dundee.compbio.slivkaclient.FormValidationException;
- import uk.ac.dundee.compbio.slivkaclient.JobState;
+ import javajs.http.ClientProtocolException;
 -import java.io.ByteArrayInputStream;
 -import java.io.ByteArrayOutputStream;
 -import java.io.IOError;
 -import java.io.IOException;
 -import java.io.InputStream;
 -import java.util.Arrays;
+ import java.util.Collection;
 -import java.util.EnumMap;
 -import java.util.HashSet;
 -import java.util.List;
 -import java.util.Set;
 -
+ import uk.ac.dundee.compbio.slivkaclient.Job;
+ import uk.ac.dundee.compbio.slivkaclient.JobRequest;
+ import uk.ac.dundee.compbio.slivkaclient.Parameter;
  import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
  import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
- import uk.ac.dundee.compbio.slivkaclient.SlivkaForm;
  import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
- import uk.ac.dundee.compbio.slivkaclient.ValidationException;
  
  public abstract class SlivkaWSInstance extends ServiceWithParameters
      implements JalviewServiceEndpointProviderI, JalviewWebServiceI
      }
      return store;
    }
 +  
 +  public static AlignmentI readAlignment(RemoteFile f) throws IOException
 +  {
-     final var mimetype = f.getMimeType();
++    final var mimetype = f.getMediaType();
 +    FileFormat format;
 +    if (mimetype.equals("application/clustal"))
 +      format = FileFormat.Clustal;
 +    else if (mimetype.equals("application/fasta"))
 +      format = FileFormat.Fasta;
 +    else
 +      return null;
-     return new FormatAdapter().readFile(f.getURL().toString(),
++    return new FormatAdapter().readFile(f.getContentUrl().toString(),
 +        DataSourceType.URL, format);
 +  }
  
  }