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