Include missing job states.
[jalview.git] / src / jalview / ws / slivkaws / SlivkaWSInstance.java
1 package jalview.ws.slivkaws;
2
3 import jalview.datamodel.SequenceI;
4 import jalview.gui.WebserviceInfo;
5 import jalview.ws.api.JalviewServiceEndpointProviderI;
6 import jalview.ws.api.JalviewWebServiceI;
7 import jalview.ws.api.JobId;
8 import jalview.ws.api.ServiceWithParameters;
9 import jalview.ws.gui.WsJob;
10 import jalview.ws.params.ArgumentI;
11 import jalview.ws.params.ParamDatastoreI;
12 import jalview.ws.params.ParamManager;
13 import jalview.ws.params.WsParamSetI;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.IOError;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.InputStreamReader;
20 import java.util.Arrays;
21 import java.util.EnumMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Optional;
25 import java.util.Set;
26
27 import uk.ac.dundee.compbio.slivkaclient.FieldType;
28 import uk.ac.dundee.compbio.slivkaclient.FormField;
29 import uk.ac.dundee.compbio.slivkaclient.FormValidationException;
30 import uk.ac.dundee.compbio.slivkaclient.JobState;
31 import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
32 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
33 import uk.ac.dundee.compbio.slivkaclient.SlivkaForm;
34 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
35 import uk.ac.dundee.compbio.slivkaclient.ValidationException;
36
37 public abstract class SlivkaWSInstance extends ServiceWithParameters
38     implements JalviewServiceEndpointProviderI, JalviewWebServiceI
39 {
40   protected final SlivkaClient client;
41
42   protected final SlivkaService service;
43
44   protected SlivkaDatastore store = null;
45
46   protected static final EnumMap<JobState, WsJob.JobState> stateMap = new EnumMap<>(JobState.class);
47   {
48     stateMap.put(JobState.PENDING, WsJob.JobState.QUEUED);
49     stateMap.put(JobState.REJECTED, WsJob.JobState.INVALID);
50     stateMap.put(JobState.ACCEPTED, WsJob.JobState.QUEUED);
51     stateMap.put(JobState.QUEUED, WsJob.JobState.QUEUED);
52     stateMap.put(JobState.RUNNING, WsJob.JobState.RUNNING);
53     stateMap.put(JobState.COMPLETED, WsJob.JobState.FINISHED);
54     stateMap.put(JobState.INTERRUPED, WsJob.JobState.CANCELLED);
55     stateMap.put(JobState.DELETED, WsJob.JobState.CANCELLED);
56     stateMap.put(JobState.FAILED, WsJob.JobState.FAILED);
57     stateMap.put(JobState.ERROR, WsJob.JobState.SERVERERROR);
58     stateMap.put(JobState.UNKNOWN, WsJob.JobState.UNKNOWN);
59   }
60   protected final Set<WsJob.JobState> failedStates = new HashSet<>(Arrays.asList(
61       WsJob.JobState.INVALID, WsJob.JobState.BROKEN, WsJob.JobState.FAILED,
62       WsJob.JobState.SERVERERROR, WsJob.JobState.CANCELLED
63   ));
64
65   public SlivkaWSInstance(SlivkaClient client, SlivkaService service, String action)
66   {
67     super(service.getName(), action, service.getLabel(), "Slivka", client.getUrl().toString());
68     this.client = client;
69     this.service = service;
70   }
71
72   protected final JobId submit(List<SequenceI> sequences,
73           WsParamSetI preset, List<ArgumentI> args) throws Throwable
74   {
75     SlivkaForm form = service.getForm();
76     Optional<FormField> inputField = form.getFields().stream()
77             .filter(f -> f.getType() == FieldType.FILE).findFirst();
78     if (inputField.isPresent())
79     {
80       StringBuilder builder = new StringBuilder();
81       for (SequenceI seq : sequences)
82       {
83         builder.append(">").append(seq.getName()).append("\n")
84                 .append(seq.getSequence()).append("\n");
85       }
86       InputStream stream = new ByteArrayInputStream(
87               builder.toString().getBytes());
88       RemoteFile file = client.uploadFile(stream, "input.fa",
89               "application/fasta");
90       form.insert(inputField.get().getName(), file);
91     }
92     if (args != null)
93     {
94       for (ArgumentI arg : args)
95       {
96         String fieldName = arg.getName().split("\\$", 2)[0];
97         FormField field = form.getField(fieldName);
98         if (field.getType() == FieldType.BOOLEAN)
99         {
100           form.insert(fieldName,
101                   (arg.getValue() != null && !arg.getValue().isBlank())
102                           ? true
103                           : false);
104         }
105         else
106         {
107           form.insert(fieldName, field.valueOf(arg.getValue()));
108         }
109       }
110     }
111     return new JobId(service.getName(), service.getName(), form.submit());
112   }
113
114   @Override
115   public final void updateStatus(WsJob job)
116   {
117     try
118     {
119       job.setState(stateMap.get(client.getJobState(job.getJobId())));
120     } catch (IOException e)
121     {
122       throw new IOError(e);
123     }
124   }
125
126   @Override
127   public final boolean updateJobProgress(WsJob job) throws IOException
128   {
129     List<RemoteFile> files = client.getJobResults(job.getJobId());
130     Optional<RemoteFile> logFile = files.stream()
131         .filter(f -> f.getLabel().equals("log")).findFirst();
132     boolean newContent = false;
133     if (logFile.isPresent())
134     {
135       InputStream stream = logFile.get().getContent();
136       long nextChunk = stream.skip(job.getNextChunk());
137       int len = appendJobStatus(job, stream);
138       job.setnextChunk(nextChunk + len);
139       newContent |= len > 0;
140     }
141     if (failedStates.contains(job.getJobState()))
142     {
143       Optional<RemoteFile> errLogFile = files.stream()
144           .filter(f -> f.getLabel().equals("error-log")).findFirst();
145       if (errLogFile.isPresent())
146       {
147         newContent |= appendJobStatus(job, errLogFile.get().getContent()) > 0;
148       }
149     }
150     return newContent;
151   }
152
153   private int appendJobStatus(WsJob job, InputStream stream) throws IOException
154   {
155     StringBuilder builder = new StringBuilder(job.getStatus());
156     InputStreamReader reader = new InputStreamReader(stream);
157     char[] buffer = new char[4096];
158     int chunkLen = 0;
159     int len = 0;
160     while ((len = reader.read(buffer)) != -1)
161     {
162       chunkLen += len;
163       builder.append(buffer, 0, len);
164     }
165     job.setStatus(builder.toString());
166     return chunkLen;
167   }
168
169   @Override
170   public final boolean handleSubmitError(Throwable _lex, WsJob j, WebserviceInfo wsInfo)
171   {
172     if (_lex instanceof FormValidationException)
173     {
174       FormValidationException formError = (FormValidationException) _lex;
175       String[] messages = new String[formError.getErrors().size()];
176       int i = 0;
177       for (ValidationException e : formError.getErrors())
178       {
179         messages[i++] = String.format("%s: %s,", e.getField().getName(), e.getMessage());
180       }
181       j.setState(WsJob.JobState.INVALID);
182       j.setStatus(String.join(", ", messages));
183       return true;
184     }
185     return false;
186   }
187
188   @Override
189   public final boolean handleCollectionException(Exception e, WsJob msjob, WebserviceInfo wsInfo)
190   {
191     // TODO
192     return false;
193   }
194
195   final SlivkaService getService()
196   {
197     return service;
198   }
199
200   @Override
201   public final Object getEndpoint()
202   {
203     return this;
204   }
205
206   @Override
207   public final void initParamStore(ParamManager userParameterStore)
208   {
209     if (store == null)
210     {
211       try
212       {
213         store = new SlivkaDatastore(service);
214       } catch (IOException e)
215       {
216         throw new IOError(e);
217       }
218     }
219   }
220
221   @Override
222   public boolean hasParameters()
223   {
224     return true;
225   }
226
227   @Override
228   public final ParamDatastoreI getParamStore()
229   {
230     if (store == null)
231     {
232       initParamStore(null);
233     }
234     return store;
235   }
236
237 }