1 package jalview.ws2.client.slivka;
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.nio.charset.StandardCharsets;
9 import java.util.Arrays;
10 import java.util.Collections;
11 import java.util.EnumMap;
12 import java.util.List;
14 import java.util.regex.Pattern;
16 import jalview.api.FeatureColourI;
17 import jalview.bin.Cache;
18 import jalview.bin.Console;
19 import jalview.datamodel.Alignment;
20 import jalview.datamodel.AlignmentAnnotation;
21 import jalview.datamodel.AlignmentI;
22 import jalview.datamodel.SequenceI;
23 import jalview.datamodel.features.FeatureMatcherSetI;
24 import jalview.io.AnnotationFile;
25 import jalview.io.DataSourceType;
26 import jalview.io.FeaturesFile;
27 import jalview.io.FileFormat;
28 import jalview.io.FormatAdapter;
29 import jalview.ws.params.ArgumentI;
30 import jalview.ws2.api.Credentials;
31 import jalview.ws2.api.JobStatus;
32 import jalview.ws2.api.WebServiceJobHandle;
33 import jalview.ws2.client.api.AlignmentWebServiceClientI;
34 import jalview.ws2.client.api.AnnotationWebServiceClientI;
35 import jalview.ws2.client.api.WebServiceClientI;
36 import uk.ac.dundee.compbio.slivkaclient.Job;
37 import uk.ac.dundee.compbio.slivkaclient.Parameter;
38 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
39 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
41 import static java.lang.String.format;
43 public class SlivkaWSClient implements WebServiceClientI
45 final SlivkaService service;
47 final SlivkaClient client;
49 SlivkaWSClient(SlivkaService service)
51 this.service = service;
52 this.client = service.getClient();
56 public String getUrl()
58 return client.getUrl().toString();
62 public String getClientName()
67 // pattern for matching media types
68 static final Pattern mediaTypePattern = Pattern.compile(
69 "(?:text|application)\\/(?:x-)?([\\w-]+)");
72 public WebServiceJobHandle submit(List<SequenceI> sequences,
73 List<ArgumentI> args, Credentials credentials) throws IOException
75 var request = new uk.ac.dundee.compbio.slivkaclient.JobRequest();
76 for (Parameter param : service.getParameters())
78 // TODO: restrict input sequences parameter name to "sequences"
79 if (param instanceof Parameter.FileParameter)
81 Parameter.FileParameter fileParam = (Parameter.FileParameter) param;
82 FileFormat format = null;
83 var match = mediaTypePattern.matcher(fileParam.getMediaType());
86 String fmt = match.group(1);
87 if (fmt.equalsIgnoreCase("pfam"))
88 format = FileFormat.Pfam;
89 else if (fmt.equalsIgnoreCase("stockholm"))
90 format = FileFormat.Stockholm;
91 else if (fmt.equalsIgnoreCase("clustal"))
92 format = FileFormat.Clustal;
93 else if (fmt.equalsIgnoreCase("fasta"))
94 format = FileFormat.Fasta;
98 Console.warn(String.format(
99 "Unknown input format %s, assuming fasta.",
100 fileParam.getMediaType()));
101 format = FileFormat.Fasta;
103 InputStream stream = new ByteArrayInputStream(format.getWriter(null)
104 .print(sequences.toArray(new SequenceI[0]), false)
106 request.addFile(param.getId(), stream);
111 for (ArgumentI arg : args)
113 // multiple choice field names are name$number to avoid duplications
114 // the number is stripped here
115 String paramId = arg.getName().split("\\$", 2)[0];
116 Parameter param = service.getParameter(paramId);
117 if (param instanceof Parameter.FlagParameter)
119 if (arg.getValue() != null && !arg.getValue().isEmpty())
120 request.addData(paramId, true);
122 request.addData(paramId, false);
124 else if (param instanceof Parameter.FileParameter)
126 request.addFile(paramId, new File(arg.getValue()));
130 request.addData(paramId, arg.getValue());
134 var job = service.submitJob(request);
135 return createJobHandle(job.getId());
138 protected WebServiceJobHandle createJobHandle(String jobId)
140 return new WebServiceJobHandle(
141 getClientName(), service.getName(), client.getUrl().toString(),
146 public JobStatus getStatus(WebServiceJobHandle job) throws IOException
148 var slivkaJob = client.getJob(job.getJobId());
149 return statusMap.getOrDefault(slivkaJob.getStatus(), JobStatus.UNKNOWN);
152 protected static final EnumMap<Job.Status, JobStatus> statusMap = new EnumMap<>(Job.Status.class);
155 statusMap.put(Job.Status.PENDING, JobStatus.SUBMITTED);
156 statusMap.put(Job.Status.REJECTED, JobStatus.INVALID);
157 statusMap.put(Job.Status.ACCEPTED, JobStatus.SUBMITTED);
158 statusMap.put(Job.Status.QUEUED, JobStatus.QUEUED);
159 statusMap.put(Job.Status.RUNNING, JobStatus.RUNNING);
160 statusMap.put(Job.Status.COMPLETED, JobStatus.COMPLETED);
161 statusMap.put(Job.Status.INTERRUPTED, JobStatus.CANCELLED);
162 statusMap.put(Job.Status.DELETED, JobStatus.CANCELLED);
163 statusMap.put(Job.Status.FAILED, JobStatus.FAILED);
164 statusMap.put(Job.Status.ERROR, JobStatus.SERVER_ERROR);
165 statusMap.put(Job.Status.UNKNOWN, JobStatus.UNKNOWN);
169 public String getLog(WebServiceJobHandle job) throws IOException
171 var slivkaJob = client.getJob(job.getJobId());
172 for (var f : slivkaJob.getResults())
174 if (f.getLabel().equals("log"))
176 ByteArrayOutputStream stream = new ByteArrayOutputStream();
178 return stream.toString("UTF-8");
185 public String getErrorLog(WebServiceJobHandle job) throws IOException
187 var slivkaJob = client.getJob(job.getJobId());
188 for (var f : slivkaJob.getResults())
190 if (f.getLabel().equals("error-log"))
192 ByteArrayOutputStream stream = new ByteArrayOutputStream();
194 return stream.toString("UTF-8");
201 public void cancel(WebServiceJobHandle job)
202 throws IOException, UnsupportedOperationException
205 "slivka client does not support job cancellation");
209 class SlivkaAlignmentWSClient extends SlivkaWSClient
210 implements AlignmentWebServiceClientI
213 SlivkaAlignmentWSClient(SlivkaService service)
219 public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
221 var slivkaJob = client.getJob(job.getJobId());
222 for (var f : slivkaJob.getResults())
224 // TODO: restrict result file label to "alignment"
226 var match = mediaTypePattern.matcher(f.getMediaType());
229 String fmt = match.group(1);
230 if (fmt.equalsIgnoreCase("clustal"))
231 format = FileFormat.Clustal;
232 else if (fmt.equalsIgnoreCase("fasta"))
233 format = FileFormat.Fasta;
236 return new FormatAdapter().readFile(f.getContentUrl().toString(),
237 DataSourceType.URL, format);
239 Console.warn("No alignment found on the server");
240 throw new IOException("no alignment found");
245 class SlivkaAnnotationWSClient extends SlivkaWSClient
246 implements AnnotationWebServiceClientI
248 SlivkaAnnotationWSClient(SlivkaService service)
254 public List<AlignmentAnnotation> attachAnnotations(WebServiceJobHandle job,
255 List<SequenceI> sequences, Map<String, FeatureColourI> colours,
256 Map<String, FeatureMatcherSetI> filters) throws IOException
258 var slivkaJob = client.getJob(job.getJobId());
259 var aln = new Alignment(sequences.toArray(new SequenceI[sequences.size()]));
260 boolean featPresent = false, annotPresent = false;
261 for (var f : slivkaJob.getResults())
263 // TODO: restrict file label to "annotations" or "features"
264 var match = mediaTypePattern.matcher(f.getMediaType());
267 String fmt = match.group(1);
268 if (fmt.equalsIgnoreCase("jalview-annotations"))
270 annotPresent = new AnnotationFile().readAnnotationFileWithCalcId(
271 aln, service.getId(), f.getContentUrl().toString(),
274 Console.debug(format("loaded annotations for %s", service.getId()));
276 else if (fmt.equalsIgnoreCase("jalview-features"))
278 FeaturesFile ff = new FeaturesFile(f.getContentUrl().toString(),
280 featPresent = ff.parse(aln, colours, true);
282 Console.debug(format("loaded features for %s", service.getId()));
286 Console.debug(format("no annotations found for %s", service.getId()));
288 Console.debug(format("no features found for %s", service.getId()));
289 return aln.getAlignmentAnnotation() != null ? Arrays.asList(aln.getAlignmentAnnotation())
290 : Collections.emptyList();