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