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