Merge branch 'bug/JAL-3807_jpred-with-slivka' into alpha/JAL-3066_Jalview_212_slivka...
[jalview.git] / src / jalview / ws / slivkaws / SlivkaWSInstance.java
1 package jalview.ws.slivkaws;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOError;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.util.Arrays;
9 import java.util.EnumMap;
10 import java.util.HashSet;
11 import java.util.List;
12 import java.util.Optional;
13 import java.util.Set;
14
15 import jalview.datamodel.AlignmentI;
16 import jalview.datamodel.SequenceI;
17 import jalview.gui.WebserviceInfo;
18 import jalview.io.DataSourceType;
19 import jalview.io.FileFormat;
20 import jalview.io.FormatAdapter;
21 import jalview.ws.api.JalviewServiceEndpointProviderI;
22 import jalview.ws.api.JalviewWebServiceI;
23 import jalview.ws.api.JobId;
24 import jalview.ws.api.ServiceWithParameters;
25 import jalview.ws.gui.WsJob;
26 import jalview.ws.params.ArgumentI;
27 import jalview.ws.params.ParamDatastoreI;
28 import jalview.ws.params.ParamManager;
29 import jalview.ws.params.WsParamSetI;
30
31 import uk.ac.dundee.compbio.slivkaclient.FieldType;
32 import uk.ac.dundee.compbio.slivkaclient.FileField;
33 import uk.ac.dundee.compbio.slivkaclient.FormField;
34 import uk.ac.dundee.compbio.slivkaclient.FormValidationException;
35 import uk.ac.dundee.compbio.slivkaclient.JobState;
36 import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
37 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
38 import uk.ac.dundee.compbio.slivkaclient.SlivkaForm;
39 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
40 import uk.ac.dundee.compbio.slivkaclient.ValidationException;
41
42 public abstract class SlivkaWSInstance extends ServiceWithParameters
43     implements JalviewServiceEndpointProviderI, JalviewWebServiceI
44 {
45   protected final SlivkaClient client;
46
47   protected final SlivkaService service;
48
49   protected SlivkaDatastore store = null;
50
51   protected static final EnumMap<JobState, WsJob.JobState> stateMap = new EnumMap<>(JobState.class);
52   {
53     stateMap.put(JobState.PENDING, WsJob.JobState.QUEUED);
54     stateMap.put(JobState.REJECTED, WsJob.JobState.INVALID);
55     stateMap.put(JobState.ACCEPTED, WsJob.JobState.QUEUED);
56     stateMap.put(JobState.QUEUED, WsJob.JobState.QUEUED);
57     stateMap.put(JobState.RUNNING, WsJob.JobState.RUNNING);
58     stateMap.put(JobState.COMPLETED, WsJob.JobState.FINISHED);
59     stateMap.put(JobState.INTERRUPTED, WsJob.JobState.CANCELLED);
60     stateMap.put(JobState.DELETED, WsJob.JobState.CANCELLED);
61     stateMap.put(JobState.FAILED, WsJob.JobState.FAILED);
62     stateMap.put(JobState.ERROR, WsJob.JobState.SERVERERROR);
63     stateMap.put(JobState.UNKNOWN, WsJob.JobState.UNKNOWN);
64   }
65   protected final Set<WsJob.JobState> failedStates = new HashSet<>(Arrays.asList(
66       WsJob.JobState.INVALID, WsJob.JobState.BROKEN, WsJob.JobState.FAILED,
67       WsJob.JobState.SERVERERROR, WsJob.JobState.CANCELLED
68   ));
69
70   public SlivkaWSInstance(SlivkaClient client, SlivkaService service, String action)
71   {
72     super(action, action, service.getLabel(), "Slivka", client.getUrl().toString());
73     this.client = client;
74     this.service = service;
75   }
76
77   protected final JobId submit(List<SequenceI> sequences,
78           WsParamSetI preset, List<ArgumentI> args) throws Throwable
79   {
80     SlivkaForm form = service.getForm();
81     for (FormField field : form.getFields())
82     {
83       if (field.getType() == FieldType.FILE)
84       {
85         FormatAdapter fa = new FormatAdapter();
86         fa.setNewlineString("\r\n");
87         FileField fileField = (FileField) field;
88         FileFormat format;
89         switch (fileField.getMediaType())
90         {
91         case "application/pfam":
92           format = FileFormat.Pfam;
93           break;
94         case "application/stockholm":
95           format = FileFormat.Stockholm;
96           break;
97         default:
98         case "application/fasta":
99           format = FileFormat.Fasta;
100           break;
101         }
102         InputStream stream = new ByteArrayInputStream(
103             fa.formatSequences(format, sequences.toArray(new SequenceI[0]))
104                 .getBytes());
105         RemoteFile rf = client.uploadFile(stream, "input",
106             fileField.getMediaType());
107         form.insert(field.getName(), rf);
108       }
109     }
110     if (args != null)
111     {
112       for (ArgumentI arg : args)
113       {
114         // multiple choice field names are name$number to avoid duplications
115         // the number is stripped here
116         String fieldName = arg.getName().split("\\$", 2)[0];
117         FormField field = form.getField(fieldName);
118         if (field.getType() == FieldType.BOOLEAN)
119         {
120           form.insert(fieldName,
121                   (arg.getValue() != null && !arg.getValue().isBlank())
122                           ? true
123                           : false);
124         }
125         else
126         {
127           form.insert(fieldName, arg.getValue());
128         }
129       }
130     }
131     return new JobId(service.getName(), service.getName(), form.submit());
132   }
133
134   @Override
135   public final void updateStatus(WsJob job)
136   {
137     try
138     {
139       job.setState(stateMap.get(client.getJobState(job.getJobId())));
140     } catch (IOException e)
141     {
142       throw new IOError(e);
143     }
144   }
145
146   @Override
147   public final boolean updateJobProgress(WsJob job) throws IOException
148   {
149     List<RemoteFile> files = client.getJobResults(job.getJobId());
150     RemoteFile logFile=null;
151     for (RemoteFile f : files)
152     {
153       if (f.getLabel().equals("log"))
154       {
155         logFile = f; break;
156       }
157     }
158
159     boolean newContent = false;
160     if (logFile!=null)
161     {
162       ByteArrayOutputStream output = new ByteArrayOutputStream();
163       logFile.writeTo(output);
164       if (output.size() > job.getNextChunk())
165       {
166         newContent = true;
167         job.setStatus(output.toString("UTF-8"));
168         job.setnextChunk(output.size());
169       }
170     }
171     if (failedStates.contains(job.getJobState()))
172     {
173       
174       RemoteFile errLogFile = null;
175       for (RemoteFile f : files)
176       {
177         if (f.getLabel().equals("error-log"))
178         {
179           errLogFile = f;
180           break;
181         }
182       }
183
184       if (errLogFile!=null)
185       {
186         ByteArrayOutputStream output = new ByteArrayOutputStream();
187         errLogFile.writeTo(output);
188         if (output.size() > 0)
189         {
190           newContent = true;
191           job.setStatus(job.getStatus() + "\n" + output.toString("UTF-8"));
192         }
193       }
194     }
195     return newContent;
196   }
197
198   @Override
199   public final boolean handleSubmitError(Throwable _lex, WsJob j, WebserviceInfo wsInfo)
200   {
201     if (_lex instanceof FormValidationException)
202     {
203       FormValidationException formError = (FormValidationException) _lex;
204       String[] messages = new String[formError.getErrors().size()];
205       int i = 0;
206       for (ValidationException e : formError.getErrors())
207       {
208         messages[i++] = String.format("%s: %s,", e.getField().getName(), e.getMessage());
209       }
210       j.setState(WsJob.JobState.INVALID);
211       j.setStatus(String.join(", ", messages));
212       return true;
213     }
214     return false;
215   }
216
217   @Override
218   public final boolean handleCollectionException(Exception e, WsJob msjob, WebserviceInfo wsInfo)
219   {
220     // TODO
221     return false;
222   }
223
224   final SlivkaService getService()
225   {
226     return service;
227   }
228
229   @Override
230   public final Object getEndpoint()
231   {
232     return this;
233   }
234
235   @Override
236   public final void initParamStore(ParamManager userParameterStore)
237   {
238     if (store == null)
239     {
240       try
241       {
242         store = new SlivkaDatastore(service);
243       } catch (IOException e)
244       {
245         throw new IOError(e);
246       }
247     }
248   }
249
250   @Override
251   public boolean hasParameters()
252   {
253     return true;
254   }
255
256   @Override
257   public final ParamDatastoreI getParamStore()
258   {
259     if (store == null)
260     {
261       initParamStore(null);
262     }
263     return store;
264   }
265   
266   public static AlignmentI readAlignment(RemoteFile f) throws IOException
267   {
268     final var mimetype = f.getMimeType();
269     FileFormat format;
270     if (mimetype == "application/clustal")
271       format = FileFormat.Clustal;
272     else if (mimetype == "application/fasta")
273       format = FileFormat.Fasta;
274     else
275       return null;
276     return new FormatAdapter().readFile(f.getURL().toString(),
277         DataSourceType.URL, format);
278   }
279
280 }