JAL-4199 Update slivka client
[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(SlivkaClient client, SlivkaService service)
50   {
51     this.service = service;
52     this.client = client;
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.RequestValues();
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 jobId = client.submitJob(service, request);
135     return createJobHandle(jobId);
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     return statusMap.getOrDefault(client.fetchJobStatus(job.getJobId()), JobStatus.UNKNOWN);
149   }
150
151   protected static final EnumMap<Job.Status, JobStatus> statusMap = new EnumMap<>(Job.Status.class);
152   static
153   {
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);
165   }
166
167   @Override
168   public String getLog(WebServiceJobHandle job) throws IOException
169   {
170     for (var f : client.fetchFilesList(job.getJobId()))
171     {
172       if (f.getLabel().equals("log"))
173       {
174         ByteArrayOutputStream stream = new ByteArrayOutputStream();
175         client.writeFileTo(f, stream);
176         return stream.toString("UTF-8");
177       }
178     }
179     return "";
180   }
181
182   @Override
183   public String getErrorLog(WebServiceJobHandle job) throws IOException
184   {
185     for (var f : client.fetchFilesList(job.getJobId()))
186     {
187       if (f.getLabel().equals("error-log"))
188       {
189         ByteArrayOutputStream stream = new ByteArrayOutputStream();
190         client.writeFileTo(f, stream);
191         return stream.toString("UTF-8");
192       }
193     }
194     return "";
195   }
196
197   @Override
198   public void cancel(WebServiceJobHandle job)
199       throws IOException, UnsupportedOperationException
200   {
201     Console.warn(
202         "slivka client does not support job cancellation");
203   }
204 }
205
206 class SlivkaAlignmentWSClient extends SlivkaWSClient
207     implements AlignmentWebServiceClientI
208 {
209
210   SlivkaAlignmentWSClient(SlivkaClient client, SlivkaService service)
211   {
212     super(client, service);
213   }
214
215   @Override
216   public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
217   {
218     for (var f : client.fetchFilesList(job.getJobId()))
219     {
220       // TODO: restrict result file label to "alignment"
221       FileFormat format;
222       var match = mediaTypePattern.matcher(f.getMediaType());
223       if (!match.find())
224         continue;
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;
230       else
231         continue;
232       return new FormatAdapter().readFile(f.getContentUrl().toString(),
233           DataSourceType.URL, format);
234     }
235     Console.warn("No alignment found on the server");
236     throw new IOException("no alignment found");
237   }
238
239 }
240
241 class SlivkaAnnotationWSClient extends SlivkaWSClient
242     implements AnnotationWebServiceClientI
243 {
244   SlivkaAnnotationWSClient(SlivkaClient client, SlivkaService service)
245   {
246     super(client, service);
247   }
248
249   @Override
250   public List<AlignmentAnnotation> attachAnnotations(WebServiceJobHandle job,
251       List<SequenceI> sequences, Map<String, FeatureColourI> colours,
252       Map<String, FeatureMatcherSetI> filters) throws IOException
253   {
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()))
257     {
258       // TODO: restrict file label to "annotations" or "features"
259       var match = mediaTypePattern.matcher(f.getMediaType());
260       if (!match.find())
261         continue;
262       String fmt = match.group(1);
263       if (fmt.equalsIgnoreCase("jalview-annotations"))
264       {
265         annotPresent = new AnnotationFile().readAnnotationFileWithCalcId(
266             aln, service.getId(), f.getContentUrl().toString(),
267             DataSourceType.URL);
268         if (annotPresent)
269           Console.debug(format("loaded annotations for %s", service.getId()));
270       }
271       else if (fmt.equalsIgnoreCase("jalview-features"))
272       {
273         FeaturesFile ff = new FeaturesFile(f.getContentUrl().toString(),
274             DataSourceType.URL);
275         featPresent = ff.parse(aln, colours, true);
276         if (featPresent)
277           Console.debug(format("loaded features for %s", service.getId()));
278       }
279     }
280     if (!annotPresent)
281       Console.debug(format("no annotations found for %s", service.getId()));
282     if (!featPresent)
283       Console.debug(format("no features found for %s", service.getId()));
284     return aln.getAlignmentAnnotation() != null ? Arrays.asList(aln.getAlignmentAnnotation())
285         : Collections.emptyList();
286   }
287 }