--- /dev/null
+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