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(SlivkaClient client, SlivkaService service)
51 this.service = service;
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.RequestValues();
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 jobId = client.submitJob(service, request);
135 return createJobHandle(jobId);
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 return statusMap.getOrDefault(client.fetchJobStatus(job.getJobId()), 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 for (var f : client.fetchFilesList(job.getJobId()))
172 if (f.getLabel().equals("log"))
174 ByteArrayOutputStream stream = new ByteArrayOutputStream();
175 client.writeFileTo(f, stream);
176 return stream.toString("UTF-8");
183 public String getErrorLog(WebServiceJobHandle job) throws IOException
185 for (var f : client.fetchFilesList(job.getJobId()))
187 if (f.getLabel().equals("error-log"))
189 ByteArrayOutputStream stream = new ByteArrayOutputStream();
190 client.writeFileTo(f, stream);
191 return stream.toString("UTF-8");
198 public void cancel(WebServiceJobHandle job)
199 throws IOException, UnsupportedOperationException
202 "slivka client does not support job cancellation");
206 class SlivkaAlignmentWSClient extends SlivkaWSClient
207 implements AlignmentWebServiceClientI
210 SlivkaAlignmentWSClient(SlivkaClient client, SlivkaService service)
212 super(client, service);
216 public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
218 for (var f : client.fetchFilesList(job.getJobId()))
220 // TODO: restrict result file label to "alignment"
222 var match = mediaTypePattern.matcher(f.getMediaType());
225 String fmt = match.group(1);
226 if (fmt.equalsIgnoreCase("clustal"))
227 format = FileFormat.Clustal;
228 else if (fmt.equalsIgnoreCase("fasta"))
229 format = FileFormat.Fasta;
232 return new FormatAdapter().readFile(f.getContentUrl().toString(),
233 DataSourceType.URL, format);
235 Console.warn("No alignment found on the server");
236 throw new IOException("no alignment found");
241 class SlivkaAnnotationWSClient extends SlivkaWSClient
242 implements AnnotationWebServiceClientI
244 SlivkaAnnotationWSClient(SlivkaClient client, SlivkaService service)
246 super(client, service);
250 public List<AlignmentAnnotation> attachAnnotations(WebServiceJobHandle job,
251 List<SequenceI> sequences, Map<String, FeatureColourI> colours,
252 Map<String, FeatureMatcherSetI> filters) throws IOException
254 var aln = new Alignment(sequences.toArray(new SequenceI[sequences.size()]));
255 boolean featPresent = false, annotPresent = false;
256 for (var f : client.fetchFilesList(job.getJobId()))
258 // TODO: restrict file label to "annotations" or "features"
259 var match = mediaTypePattern.matcher(f.getMediaType());
262 String fmt = match.group(1);
263 if (fmt.equalsIgnoreCase("jalview-annotations"))
265 annotPresent = new AnnotationFile().readAnnotationFileWithCalcId(
266 aln, service.getId(), f.getContentUrl().toString(),
269 Console.debug(format("loaded annotations for %s", service.getId()));
271 else if (fmt.equalsIgnoreCase("jalview-features"))
273 FeaturesFile ff = new FeaturesFile(f.getContentUrl().toString(),
275 featPresent = ff.parse(aln, colours, true);
277 Console.debug(format("loaded features for %s", service.getId()));
281 Console.debug(format("no annotations found for %s", service.getId()));
283 Console.debug(format("no features found for %s", service.getId()));
284 return aln.getAlignmentAnnotation() != null ? Arrays.asList(aln.getAlignmentAnnotation())
285 : Collections.emptyList();