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.datamodel.Alignment;
19 import jalview.datamodel.AlignmentAnnotation;
20 import jalview.datamodel.AlignmentI;
21 import jalview.datamodel.SequenceI;
22 import jalview.datamodel.features.FeatureMatcherSetI;
23 import jalview.io.AnnotationFile;
24 import jalview.io.DataSourceType;
25 import jalview.io.FeaturesFile;
26 import jalview.io.FileFormat;
27 import jalview.io.FormatAdapter;
28 import jalview.ws.params.ArgumentI;
29 import jalview.ws2.api.Credentials;
30 import jalview.ws2.api.JobStatus;
31 import jalview.ws2.api.WebServiceJobHandle;
32 import jalview.ws2.client.api.AlignmentWebServiceClientI;
33 import jalview.ws2.client.api.AnnotationWebServiceClientI;
34 import jalview.ws2.client.api.WebServiceClientI;
35 import uk.ac.dundee.compbio.slivkaclient.Job;
36 import uk.ac.dundee.compbio.slivkaclient.Parameter;
37 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
38 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
40 import static java.lang.String.format;
42 public class SlivkaWSClient implements WebServiceClientI
44 final SlivkaService service;
46 final SlivkaClient client;
48 SlivkaWSClient(SlivkaService service)
50 this.service = service;
51 this.client = service.getClient();
55 public String getUrl()
57 return client.getUrl().toString();
61 public String getClientName()
66 // pattern for matching media types
67 static final Pattern mediaTypePattern = Pattern.compile(
68 "(?:text|application)\\/(?:x-)?([\\w-]+)");
71 public WebServiceJobHandle submit(List<SequenceI> sequences,
72 List<ArgumentI> args, Credentials credentials) throws IOException
74 var request = new uk.ac.dundee.compbio.slivkaclient.JobRequest();
75 for (Parameter param : service.getParameters())
77 // TODO: restrict input sequences parameter name to "sequences"
78 if (param instanceof Parameter.FileParameter)
80 Parameter.FileParameter fileParam = (Parameter.FileParameter) param;
81 FileFormat format = null;
82 var match = mediaTypePattern.matcher(fileParam.getMediaType());
85 String fmt = match.group(1);
86 if (fmt.equalsIgnoreCase("pfam"))
87 format = FileFormat.Pfam;
88 else if (fmt.equalsIgnoreCase("stockholm"))
89 format = FileFormat.Stockholm;
90 else if (fmt.equalsIgnoreCase("clustal"))
91 format = FileFormat.Clustal;
92 else if (fmt.equalsIgnoreCase("fasta"))
93 format = FileFormat.Fasta;
97 Cache.log.warn(String.format(
98 "Unknown input format %s, assuming fasta.",
99 fileParam.getMediaType()));
100 format = FileFormat.Fasta;
102 InputStream stream = new ByteArrayInputStream(format.getWriter(null)
103 .print(sequences.toArray(new SequenceI[0]), false)
105 request.addFile(param.getId(), stream);
110 for (ArgumentI arg : args)
112 // multiple choice field names are name$number to avoid duplications
113 // the number is stripped here
114 String paramId = arg.getName().split("\\$", 2)[0];
115 Parameter param = service.getParameter(paramId);
116 if (param instanceof Parameter.FlagParameter)
118 if (arg.getValue() != null && !arg.getValue().isBlank())
119 request.addData(paramId, true);
121 request.addData(paramId, false);
123 else if (param instanceof Parameter.FileParameter)
125 request.addFile(paramId, new File(arg.getValue()));
129 request.addData(paramId, arg.getValue());
133 var job = service.submitJob(request);
134 return createJobHandle(job.getId());
137 protected WebServiceJobHandle createJobHandle(String jobId)
139 return new WebServiceJobHandle(
140 getClientName(), service.getName(), client.getUrl().toString(),
145 public JobStatus getStatus(WebServiceJobHandle job) throws IOException
147 var slivkaJob = client.getJob(job.getJobId());
148 return statusMap.getOrDefault(slivkaJob.getStatus(), JobStatus.UNKNOWN);
151 protected static final EnumMap<Job.Status, JobStatus> statusMap = new EnumMap<>(Job.Status.class);
154 statusMap.put(Job.Status.PENDING, JobStatus.SUBMITTED);
155 statusMap.put(Job.Status.REJECTED, JobStatus.INVALID);
156 statusMap.put(Job.Status.ACCEPTED, JobStatus.SUBMITTED);
157 statusMap.put(Job.Status.QUEUED, JobStatus.QUEUED);
158 statusMap.put(Job.Status.RUNNING, JobStatus.RUNNING);
159 statusMap.put(Job.Status.COMPLETED, JobStatus.COMPLETED);
160 statusMap.put(Job.Status.INTERRUPTED, JobStatus.CANCELLED);
161 statusMap.put(Job.Status.DELETED, JobStatus.CANCELLED);
162 statusMap.put(Job.Status.FAILED, JobStatus.FAILED);
163 statusMap.put(Job.Status.ERROR, JobStatus.SERVER_ERROR);
164 statusMap.put(Job.Status.UNKNOWN, JobStatus.UNKNOWN);
168 public String getLog(WebServiceJobHandle job) throws IOException
170 var slivkaJob = client.getJob(job.getJobId());
171 for (var f : slivkaJob.getResults())
173 if (f.getLabel().equals("log"))
175 ByteArrayOutputStream stream = new ByteArrayOutputStream();
177 return stream.toString(StandardCharsets.UTF_8);
184 public String getErrorLog(WebServiceJobHandle job) throws IOException
186 var slivkaJob = client.getJob(job.getJobId());
187 for (var f : slivkaJob.getResults())
189 if (f.getLabel().equals("error-log"))
191 ByteArrayOutputStream stream = new ByteArrayOutputStream();
193 return stream.toString(StandardCharsets.UTF_8);
200 public void cancel(WebServiceJobHandle job)
201 throws IOException, UnsupportedOperationException
204 "slivka client does not support job cancellation");
208 class SlivkaAlignmentWSClient extends SlivkaWSClient
209 implements AlignmentWebServiceClientI
212 SlivkaAlignmentWSClient(SlivkaService service)
218 public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
220 var slivkaJob = client.getJob(job.getJobId());
221 for (var f : slivkaJob.getResults())
223 // TODO: restrict result file label to "alignment"
225 var match = mediaTypePattern.matcher(f.getMediaType());
228 String fmt = match.group(1);
229 if (fmt.equalsIgnoreCase("clustal"))
230 format = FileFormat.Clustal;
231 else if (fmt.equalsIgnoreCase("fasta"))
232 format = FileFormat.Fasta;
235 return new FormatAdapter().readFile(f.getContentUrl().toString(),
236 DataSourceType.URL, format);
238 Cache.log.warn("No alignment found on the server");
239 throw new IOException("no alignment found");
244 class SlivkaAnnotationWSClient extends SlivkaWSClient
245 implements AnnotationWebServiceClientI
247 SlivkaAnnotationWSClient(SlivkaService service)
253 public List<AlignmentAnnotation> attachAnnotations(WebServiceJobHandle job,
254 List<SequenceI> sequences, Map<String, FeatureColourI> colours,
255 Map<String, FeatureMatcherSetI> filters) throws IOException
257 var slivkaJob = client.getJob(job.getJobId());
258 var aln = new Alignment(sequences.toArray(new SequenceI[sequences.size()]));
259 boolean featPresent = false, annotPresent = false;
260 for (var f : slivkaJob.getResults())
262 // TODO: restrict file label to "annotations" or "features"
263 var match = mediaTypePattern.matcher(f.getMediaType());
266 String fmt = match.group(1);
267 if (fmt.equalsIgnoreCase("jalview-annotations"))
269 annotPresent = new AnnotationFile().readAnnotationFileWithCalcId(
270 aln, service.getId(), f.getContentUrl().toString(),
273 Cache.log.debug(format("loaded annotations for %s", service.getId()));
275 else if (fmt.equalsIgnoreCase("jalview-features"))
277 FeaturesFile ff = new FeaturesFile(f.getContentUrl().toString(),
279 featPresent = ff.parse(aln, colours, true);
281 Cache.log.debug(format("loaded features for %s", service.getId()));
285 Cache.log.debug(format("no annotations found for %s", service.getId()));
287 Cache.log.debug(format("no features found for %s", service.getId()));
288 return aln.getAlignmentAnnotation() != null ? Arrays.asList(aln.getAlignmentAnnotation())
289 : Collections.emptyList();