JAL-3878 Create ws2 client for slivka services
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Fri, 18 Mar 2022 16:08:53 +0000 (17:08 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Fri, 25 Mar 2022 13:24:45 +0000 (14:24 +0100)
src/jalview/ws2/client/slivka/SlivkaWSClient.java [new file with mode: 0644]

diff --git a/src/jalview/ws2/client/slivka/SlivkaWSClient.java b/src/jalview/ws2/client/slivka/SlivkaWSClient.java
new file mode 100644 (file)
index 0000000..16c9d3a
--- /dev/null
@@ -0,0 +1,229 @@
+package jalview.ws2.client.slivka;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FormatAdapter;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.api.Credentials;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.api.WebServiceJobHandle;
+import jalview.ws2.client.api.AlignmentWebServiceClientI;
+import jalview.ws2.client.api.WebServiceClientI;
+import uk.ac.dundee.compbio.slivkaclient.Job;
+import uk.ac.dundee.compbio.slivkaclient.Parameter;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
+import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
+
+public class SlivkaWSClient implements WebServiceClientI
+{
+  final SlivkaService service;
+
+  final SlivkaClient client;
+
+  SlivkaWSClient(SlivkaService service)
+  {
+    this.service = service;
+    this.client = service.getClient();
+  }
+
+  @Override
+  public String getUrl()
+  {
+    return client.getUrl().toString();
+  }
+
+  @Override
+  public String getClientName()
+  {
+    return "slivka";
+  }
+
+  // pattern for matching media types
+  static final Pattern mediaTypePattern = Pattern.compile("(?:text|application)\\/(?:x-)?(\\w+)");
+
+  @Override
+  public WebServiceJobHandle submit(List<SequenceI> sequences,
+      List<ArgumentI> args, Credentials credentials) throws IOException
+  {
+    var request = new uk.ac.dundee.compbio.slivkaclient.JobRequest();
+    for (Parameter param : service.getParameters())
+    {
+      // TODO: restrict input sequences parameter name to "sequences"
+      if (param instanceof Parameter.FileParameter)
+      {
+        Parameter.FileParameter fileParam = (Parameter.FileParameter) param;
+        FileFormat format = null;
+        var match = mediaTypePattern.matcher(fileParam.getMediaType());
+        if (match.find())
+        {
+          String fmt = match.group(1);
+          if (fmt.equalsIgnoreCase("pfam"))
+            format = FileFormat.Pfam;
+          else if (fmt.equalsIgnoreCase("stockholm"))
+            format = FileFormat.Stockholm;
+          else if (fmt.equalsIgnoreCase("clustal"))
+            format = FileFormat.Clustal;
+          else if (fmt.equalsIgnoreCase("fasta"))
+            format = FileFormat.Fasta;
+        }
+        if (format == null)
+        {
+          Cache.log.warn(String.format(
+              "Unknown input format %s, assuming fasta.",
+              fileParam.getMediaType()));
+          format = FileFormat.Fasta;
+        }
+        InputStream stream = new ByteArrayInputStream(format.getWriter(null)
+            .print(sequences.toArray(new SequenceI[0]), false)
+            .getBytes());
+        request.addFile(param.getId(), stream);
+      }
+    }
+    if (args != null)
+    {
+      for (ArgumentI arg : args)
+      {
+        // multiple choice field names are name$number to avoid duplications
+        // the number is stripped here
+        String paramId = arg.getName().split("\\$", 2)[0];
+        Parameter param = service.getParameter(paramId);
+        if (param instanceof Parameter.FlagParameter)
+        {
+          if (arg.getValue() != null && !arg.getValue().isBlank())
+            request.addData(paramId, true);
+          else
+            request.addData(paramId, false);
+        }
+        else if (param instanceof Parameter.FileParameter)
+        {
+          request.addFile(paramId, new File(arg.getValue()));
+        }
+        else
+        {
+          request.addData(paramId, arg.getValue());
+        }
+      }
+    }
+    var job = service.submitJob(request);
+    return createJobHandle(job.getId());
+  }
+
+  protected WebServiceJobHandle createJobHandle(String jobId)
+  {
+    return new WebServiceJobHandle(
+        getClientName(), service.getName(), client.getUrl().toString(),
+        jobId);
+  }
+
+  @Override
+  public JobStatus getStatus(WebServiceJobHandle job) throws IOException
+  {
+    var slivkaJob = client.getJob(job.getJobId());
+    return statusMap.getOrDefault(slivkaJob.getStatus(), JobStatus.UNKNOWN);
+  }
+
+  protected static final EnumMap<Job.Status, JobStatus> statusMap = new EnumMap<>(Job.Status.class);
+  static
+  {
+    statusMap.put(Job.Status.PENDING, JobStatus.SUBMITTED);
+    statusMap.put(Job.Status.REJECTED, JobStatus.INVALID);
+    statusMap.put(Job.Status.ACCEPTED, JobStatus.SUBMITTED);
+    statusMap.put(Job.Status.QUEUED, JobStatus.QUEUED);
+    statusMap.put(Job.Status.RUNNING, JobStatus.RUNNING);
+    statusMap.put(Job.Status.COMPLETED, JobStatus.COMPLETED);
+    statusMap.put(Job.Status.INTERRUPTED, JobStatus.CANCELLED);
+    statusMap.put(Job.Status.DELETED, JobStatus.CANCELLED);
+    statusMap.put(Job.Status.FAILED, JobStatus.FAILED);
+    statusMap.put(Job.Status.ERROR, JobStatus.SERVER_ERROR);
+    statusMap.put(Job.Status.UNKNOWN, JobStatus.UNKNOWN);
+  }
+
+  @Override
+  public String getLog(WebServiceJobHandle job) throws IOException
+  {
+    var slivkaJob = client.getJob(job.getJobId());
+    for (var f : slivkaJob.getResults())
+    {
+      if (f.getLabel().equals("log"))
+      {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        f.writeTo(stream);
+        return stream.toString(StandardCharsets.UTF_8);
+      }
+    }
+    return "";
+  }
+
+  @Override
+  public String getErrorLog(WebServiceJobHandle job) throws IOException
+  {
+    var slivkaJob = client.getJob(job.getJobId());
+    for (var f : slivkaJob.getResults())
+    {
+      if (f.getLabel().equals("error-log"))
+      {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        f.writeTo(stream);
+        return stream.toString(StandardCharsets.UTF_8);
+      }
+    }
+    return "";
+  }
+
+  @Override
+  public void cancel(WebServiceJobHandle job)
+      throws IOException, UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException(
+        "slivka client does not support job cancellation");
+  }
+}
+
+class SlivkaAlignmentWSClient extends SlivkaWSClient
+    implements AlignmentWebServiceClientI
+{
+
+  SlivkaAlignmentWSClient(SlivkaService service)
+  {
+    super(service);
+  }
+
+  @Override
+  public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
+  {
+    var slivkaJob = client.getJob(job.getJobId());
+    for (var f : slivkaJob.getResults())
+    {
+      // TODO: restrict result file label to "alignment"
+      FileFormat format;
+      var match = mediaTypePattern.matcher(f.getMediaType());
+      if (!match.find())
+        continue;
+      String fmt = match.group(1);
+      if (fmt.equalsIgnoreCase("clustal"))
+        format = FileFormat.Clustal;
+      else if (fmt.equalsIgnoreCase("fasta"))
+        format = FileFormat.Fasta;
+      else
+        continue;
+      return new FormatAdapter().readFile(f.getContentUrl().toString(),
+          DataSourceType.URL, format);
+    }
+    Cache.log.warn("No alignment found on the server");
+    throw new IOException("no alignment found");
+  }
+
+}
\ No newline at end of file