bef502ba00dd8eb3ce6ade3fa649e9aa3d82b345
[jalview.git] / src / jalview / ws2 / client / slivka / SlivkaWSClient.java
1 package jalview.ws2.client.slivka;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.File;
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;
13 import java.util.Map;
14 import java.util.regex.Pattern;
15
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;
40
41 import static java.lang.String.format;
42
43 public class SlivkaWSClient implements WebServiceClientI
44 {
45   final SlivkaService service;
46
47   final SlivkaClient client;
48
49   SlivkaWSClient(SlivkaService service)
50   {
51     this.service = service;
52     this.client = service.getClient();
53   }
54
55   @Override
56   public String getUrl()
57   {
58     return client.getUrl().toString();
59   }
60
61   @Override
62   public String getClientName()
63   {
64     return "slivka";
65   }
66
67   // pattern for matching media types
68   static final Pattern mediaTypePattern = Pattern.compile(
69       "(?:text|application)\\/(?:x-)?([\\w-]+)");
70
71   @Override
72   public WebServiceJobHandle submit(List<SequenceI> sequences,
73       List<ArgumentI> args, Credentials credentials) throws IOException
74   {
75     var request = new uk.ac.dundee.compbio.slivkaclient.JobRequest();
76     for (Parameter param : service.getParameters())
77     {
78       // TODO: restrict input sequences parameter name to "sequences"
79       if (param instanceof Parameter.FileParameter)
80       {
81         Parameter.FileParameter fileParam = (Parameter.FileParameter) param;
82         FileFormat format = null;
83         var match = mediaTypePattern.matcher(fileParam.getMediaType());
84         if (match.find())
85         {
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;
95         }
96         if (format == null)
97         {
98           Console.warn(String.format(
99               "Unknown input format %s, assuming fasta.",
100               fileParam.getMediaType()));
101           format = FileFormat.Fasta;
102         }
103         InputStream stream = new ByteArrayInputStream(format.getWriter(null)
104             .print(sequences.toArray(new SequenceI[0]), false)
105             .getBytes());
106         request.addFile(param.getId(), stream);
107       }
108     }
109     if (args != null)
110     {
111       for (ArgumentI arg : args)
112       {
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)
118         {
119           if (arg.getValue() != null && !arg.getValue().isEmpty())
120             request.addData(paramId, true);
121           else
122             request.addData(paramId, false);
123         }
124         else if (param instanceof Parameter.FileParameter)
125         {
126           request.addFile(paramId, new File(arg.getValue()));
127         }
128         else
129         {
130           request.addData(paramId, arg.getValue());
131         }
132       }
133     }
134     var job = service.submitJob(request);
135     return createJobHandle(job.getId());
136   }
137
138   protected WebServiceJobHandle createJobHandle(String jobId)
139   {
140     return new WebServiceJobHandle(
141         getClientName(), service.getName(), client.getUrl().toString(),
142         jobId);
143   }
144
145   @Override
146   public JobStatus getStatus(WebServiceJobHandle job) throws IOException
147   {
148     var slivkaJob = client.getJob(job.getJobId());
149     return statusMap.getOrDefault(slivkaJob.getStatus(), JobStatus.UNKNOWN);
150   }
151
152   protected static final EnumMap<Job.Status, JobStatus> statusMap = new EnumMap<>(Job.Status.class);
153   static
154   {
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);
166   }
167
168   @Override
169   public String getLog(WebServiceJobHandle job) throws IOException
170   {
171     var slivkaJob = client.getJob(job.getJobId());
172     for (var f : slivkaJob.getResults())
173     {
174       if (f.getLabel().equals("log"))
175       {
176         ByteArrayOutputStream stream = new ByteArrayOutputStream();
177         f.writeTo(stream);
178         return stream.toString("UTF-8");
179       }
180     }
181     return "";
182   }
183
184   @Override
185   public String getErrorLog(WebServiceJobHandle job) throws IOException
186   {
187     var slivkaJob = client.getJob(job.getJobId());
188     for (var f : slivkaJob.getResults())
189     {
190       if (f.getLabel().equals("error-log"))
191       {
192         ByteArrayOutputStream stream = new ByteArrayOutputStream();
193         f.writeTo(stream);
194         return stream.toString("UTF-8");
195       }
196     }
197     return "";
198   }
199
200   @Override
201   public void cancel(WebServiceJobHandle job)
202       throws IOException, UnsupportedOperationException
203   {
204     Console.warn(
205         "slivka client does not support job cancellation");
206   }
207 }
208
209 class SlivkaAlignmentWSClient extends SlivkaWSClient
210     implements AlignmentWebServiceClientI
211 {
212
213   SlivkaAlignmentWSClient(SlivkaService service)
214   {
215     super(service);
216   }
217
218   @Override
219   public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
220   {
221     var slivkaJob = client.getJob(job.getJobId());
222     for (var f : slivkaJob.getResults())
223     {
224       // TODO: restrict result file label to "alignment"
225       FileFormat format;
226       var match = mediaTypePattern.matcher(f.getMediaType());
227       if (!match.find())
228         continue;
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;
234       else
235         continue;
236       return new FormatAdapter().readFile(f.getContentUrl().toString(),
237           DataSourceType.URL, format);
238     }
239     Console.warn("No alignment found on the server");
240     throw new IOException("no alignment found");
241   }
242
243 }
244
245 class SlivkaAnnotationWSClient extends SlivkaWSClient
246     implements AnnotationWebServiceClientI
247 {
248   SlivkaAnnotationWSClient(SlivkaService service)
249   {
250     super(service);
251   }
252
253   @Override
254   public List<AlignmentAnnotation> attachAnnotations(WebServiceJobHandle job,
255       List<SequenceI> sequences, Map<String, FeatureColourI> colours,
256       Map<String, FeatureMatcherSetI> filters) throws IOException
257   {
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())
262     {
263       // TODO: restrict file label to "annotations" or "features"
264       var match = mediaTypePattern.matcher(f.getMediaType());
265       if (!match.find())
266         continue;
267       String fmt = match.group(1);
268       if (fmt.equalsIgnoreCase("jalview-annotations"))
269       {
270         annotPresent = new AnnotationFile().readAnnotationFileWithCalcId(
271             aln, service.getId(), f.getContentUrl().toString(),
272             DataSourceType.URL);
273         if (annotPresent)
274           Console.debug(format("loaded annotations for %s", service.getId()));
275       }
276       else if (fmt.equalsIgnoreCase("jalview-features"))
277       {
278         FeaturesFile ff = new FeaturesFile(f.getContentUrl().toString(),
279             DataSourceType.URL);
280         featPresent = ff.parse(aln, colours, true);
281         if (featPresent)
282           Console.debug(format("loaded features for %s", service.getId()));
283       }
284     }
285     if (!annotPresent)
286       Console.debug(format("no annotations found for %s", service.getId()));
287     if (!featPresent)
288       Console.debug(format("no features found for %s", service.getId()));
289     return aln.getAlignmentAnnotation() != null ? Arrays.asList(aln.getAlignmentAnnotation())
290         : Collections.emptyList();
291   }
292 }