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 sequences, List 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 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"); } }