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