JAL-3878 Set job status to cancelled on job cancellation.
[jalview.git] / src / jalview / ws2 / slivka / SlivkaWebService.java
1 package jalview.ws2.slivka;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.Collection;
10 import java.util.EnumMap;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Set;
14
15 import jalview.api.AlignViewportI;
16 import jalview.bin.Cache;
17 import jalview.datamodel.Alignment;
18 import jalview.datamodel.AlignmentAnnotation;
19 import jalview.datamodel.AlignmentI;
20 import jalview.datamodel.SequenceI;
21 import jalview.io.AnnotationFile;
22 import jalview.io.DataSourceType;
23 import jalview.io.FeaturesFile;
24 import jalview.io.FileFormat;
25 import jalview.io.FileFormatI;
26 import jalview.io.FormatAdapter;
27 import jalview.ws.gui.WsJob;
28 import jalview.ws.params.ArgumentI;
29 import jalview.ws.params.ParamDatastoreI;
30 import jalview.ws.params.WsParamSetI;
31 import jalview.ws.slivkaws.SlivkaDatastore;
32 import jalview.ws2.WebServiceI;
33 import jalview.ws2.operations.Operation;
34 import jalview.ws2.ResultSupplier;
35 import jalview.ws2.WSJob;
36 import jalview.ws2.WSJobStatus;
37 import javajs.http.ClientProtocolException;
38 import uk.ac.dundee.compbio.slivkaclient.Job;
39 import uk.ac.dundee.compbio.slivkaclient.Parameter;
40 import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
41 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
42 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
43
44 public class SlivkaWebService implements WebServiceI
45 {
46   protected final SlivkaClient client;
47
48   protected final SlivkaService service;
49
50   protected SlivkaDatastore store = null;
51
52   protected final ArrayList<Operation> operations = new ArrayList<>();
53
54   protected int typeFlags = 0;
55
56   protected static final EnumMap<Job.Status, WSJobStatus> statusMap = new EnumMap<>(
57           Job.Status.class);
58   {
59     statusMap.put(Job.Status.PENDING, WSJobStatus.SUBMITTED);
60     statusMap.put(Job.Status.REJECTED, WSJobStatus.INVALID);
61     statusMap.put(Job.Status.ACCEPTED, WSJobStatus.QUEUED);
62     statusMap.put(Job.Status.QUEUED, WSJobStatus.QUEUED);
63     statusMap.put(Job.Status.RUNNING, WSJobStatus.RUNNING);
64     statusMap.put(Job.Status.COMPLETED, WSJobStatus.FINISHED);
65     statusMap.put(Job.Status.INTERRUPTED, WSJobStatus.CANCELLED);
66     statusMap.put(Job.Status.DELETED, WSJobStatus.CANCELLED);
67     statusMap.put(Job.Status.FAILED, WSJobStatus.FAILED);
68     statusMap.put(Job.Status.ERROR, WSJobStatus.SERVER_ERROR);
69     statusMap.put(Job.Status.UNKNOWN, WSJobStatus.UNKNOWN);
70   }
71
72   public SlivkaWebService(SlivkaClient client, SlivkaService service)
73   {
74     this.client = client;
75     this.service = service;
76   }
77
78   @Override
79   public String getHostName()
80   {
81     return client.getUrl().toString();
82   }
83
84   @Override
85   public String getProviderName()
86   {
87     return "slivka";
88   }
89
90   @Override
91   public String getName()
92   {
93     return service.getName();
94   }
95
96   @Override
97   public String getDescription()
98   {
99     return service.getDescription();
100   }
101
102   @Override
103   public List<Operation> getOperations()
104   {
105     return operations;
106   }
107
108   void addOperation(Operation operation)
109   {
110     operations.add(operation);
111   }
112
113   void removeOperation(Operation operation)
114   {
115     operations.remove(operation);
116   }
117
118   @Override
119   public boolean hasParameters()
120   {
121     return getParamStore().getServiceParameters().size() > 0;
122   }
123
124   @Override
125   public ParamDatastoreI getParamStore()
126   {
127     if (store == null)
128     {
129       store = new SlivkaDatastore(service);
130     }
131     return store;
132   }
133
134   @Override
135   public String submit(List<SequenceI> sequences, List<ArgumentI> args)
136           throws IOException
137   {
138     var request = new uk.ac.dundee.compbio.slivkaclient.JobRequest();
139     for (Parameter param : service.getParameters())
140     {
141       if (param instanceof Parameter.FileParameter)
142       {
143         // if finds a file input, gives it sequences stream
144         Parameter.FileParameter fileParam = (Parameter.FileParameter) param;
145         FileFormat format;
146         switch (fileParam.getMediaType())
147         {
148         case "application/pfam":
149           format = FileFormat.Pfam;
150           break;
151         case "application/stockholm":
152           format = FileFormat.Stockholm;
153           break;
154         case "application/clustal":
155           format = FileFormat.Clustal;
156           break;
157         case "application/fasta":
158         default:
159           format = FileFormat.Fasta;
160           break;
161         }
162         InputStream stream = new ByteArrayInputStream(format.getWriter(null)
163                 .print(sequences.toArray(new SequenceI[0]), false)
164                 .getBytes());
165         request.addFile(param.getId(), stream);
166       }
167     }
168     if (args != null)
169     {
170       for (ArgumentI arg : args)
171       {
172         // multiple choice field names are name$number to avoid duplications
173         // the number is stripped here
174         String paramId = arg.getName().split("\\$", 2)[0];
175         Parameter param = service.getParameter(paramId);
176         if (param instanceof Parameter.FlagParameter)
177         {
178           if (arg.getValue() != null && !arg.getValue().isBlank())
179             request.addData(paramId, true);
180           else
181             request.addData(paramId, false);
182         }
183         else
184         {
185           request.addData(paramId, arg.getValue());
186         }
187       }
188     }
189     var job = service.submitJob(request);
190     return job.getId();
191   }
192
193   @Override
194   public void updateProgress(WSJob job) throws IOException
195   {
196     var slivkaJob = client.getJob(job.getJobId());
197     job.setStatus(statusMap.get(slivkaJob.getStatus()));
198     Collection<RemoteFile> files = slivkaJob.getResults();
199     for (RemoteFile f : files)
200     {
201       if (f.getLabel().equals("log"))
202       {
203         ByteArrayOutputStream stream = new ByteArrayOutputStream();
204         f.writeTo(stream);
205         job.setLog(stream.toString("UTF-8"));
206       }
207       else if (f.getLabel().equals("error-log"))
208       {
209         ByteArrayOutputStream stream = new ByteArrayOutputStream();
210         f.writeTo(stream);
211         job.setErrorLog(stream.toString("UTF-8"));
212       }
213     }
214   }
215
216   @Override
217   public void cancel(WSJob job) throws IOException
218   {
219     job.setStatus(WSJobStatus.CANCELLED);
220     Cache.log.warn("Slivka does not support job cancellation yet.");
221   }
222
223   @Override
224   public boolean handleSubmissionError(WSJob job, Exception ex)
225   {
226     if (ex instanceof ClientProtocolException)
227     {
228       Cache.log.error("Job submission failed due to exception.", ex);
229       return true;
230     }
231     return false;
232   }
233
234   @Override
235   public boolean handleCollectionError(WSJob job, Exception ex)
236   {
237     // TODO Auto-generated method stub
238     return false;
239   }
240
241   public AlignmentI getAlignment(WSJob job, List<SequenceI> dataset,
242       AlignViewportI viewport) throws IOException
243   {
244     Collection<RemoteFile> files;
245     var slivkaJob = client.getJob(job.getJobId());
246     files = slivkaJob.getResults();
247     for (RemoteFile f : files)
248     {
249       if (f.getMediaType().equals("application/clustal"))
250       {
251         return new FormatAdapter().readFile(f.getContentUrl().toString(),
252                 DataSourceType.URL, FileFormat.Clustal);
253       }
254       else if (f.getMediaType().equals("application/fasta"))
255       {
256         return new FormatAdapter().readFile(f.getContentUrl().toString(),
257                 DataSourceType.URL, FileFormat.Fasta);
258       }
259     }
260     return null;
261   }
262
263   public FeaturesFile getFeaturesFile(WSJob job,
264       List<SequenceI> dataset, AlignViewportI viewport) throws IOException
265   {
266     var slivkaJob = client.getJob(job.getJobId());
267     Collection<RemoteFile> files = slivkaJob.getResults();
268     for (RemoteFile f : files)
269     {
270       if (f.getMediaType().equals("application/jalview-features"))
271       {
272         return new FeaturesFile(f.getContentUrl().toString(), DataSourceType.URL);
273       }
274     }
275     return null;
276   }
277
278   public List<AlignmentAnnotation> getAnnotationFile(WSJob job,
279       List<SequenceI> dataset, AlignViewportI viewport) throws IOException
280   {
281     var slivkaJob = client.getJob(job.getJobId());
282     Collection<RemoteFile> files = slivkaJob.getResults();
283     for (RemoteFile f : files)
284     {
285       if (f.getMediaType().equals("application/jalview-annotations"))
286       {
287         Alignment aln = new Alignment(dataset.toArray(new SequenceI[0]));
288         AnnotationFile af = new AnnotationFile();
289         boolean valid = af.readAnnotationFileWithCalcId(aln, service.getId(),
290             f.getContentUrl().toString(), DataSourceType.URL);
291         if (valid)
292         {
293           return Arrays.asList(aln.getAlignmentAnnotation());
294         }
295         else
296         {
297           throw new IOException("Unable to read annotations from file " +
298               f.getContentUrl().toString());
299         }
300       }
301     }
302     return null;
303   }
304
305   @Override
306   public String toString()
307   {
308     return String.format("SlivkaWebService[%s]", getName());
309   }
310 }