JAL-3683 retrieve job status to output stream
[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.ByteArrayOutputStream;
17 import java.io.IOError;
18 import java.io.IOException;
19 import java.io.InputStream;
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         // multiple choice field names are name$number to avoid duplications
97         // the number is stripped here
98         String fieldName = arg.getName().split("\\$", 2)[0];
99         FormField field = form.getField(fieldName);
100         if (field.getType() == FieldType.BOOLEAN)
101         {
102           form.insert(fieldName,
103                   (arg.getValue() != null && !arg.getValue().isBlank())
104                           ? true
105                           : false);
106         }
107         else
108         {
109           form.insert(fieldName, arg.getValue());
110         }
111       }
112     }
113     return new JobId(service.getName(), service.getName(), form.submit());
114   }
115
116   @Override
117   public final void updateStatus(WsJob job)
118   {
119     try
120     {
121       job.setState(stateMap.get(client.getJobState(job.getJobId())));
122     } catch (IOException e)
123     {
124       throw new IOError(e);
125     }
126   }
127
128   @Override
129   public final boolean updateJobProgress(WsJob job) throws IOException
130   {
131     List<RemoteFile> files = client.getJobResults(job.getJobId());
132     Optional<RemoteFile> logFile = files.stream()
133             .filter(f -> f.getLabel().equals("log")).findFirst();
134     boolean newContent = false;
135     if (logFile.isPresent())
136     {
137       ByteArrayOutputStream output = new ByteArrayOutputStream();
138       logFile.get().writeTo(output);
139       if (output.size() > job.getNextChunk())
140       {
141         newContent = true;
142         job.setStatus(output.toString("UTF-8"));
143         job.setnextChunk(output.size());
144       }
145     }
146     if (failedStates.contains(job.getJobState()))
147     {
148       Optional<RemoteFile> errLogFile = files.stream()
149               .filter(f -> f.getLabel().equals("error-log")).findFirst();
150       if (errLogFile.isPresent())
151       {
152         ByteArrayOutputStream output = new ByteArrayOutputStream();
153         errLogFile.get().writeTo(output);
154         if (output.size() > 0)
155         {
156           newContent = true;
157           job.setStatus(job.getStatus() + "\n" + output.toString("UTF-8"));
158         }
159       }
160     }
161     return newContent;
162   }
163
164   @Override
165   public final boolean handleSubmitError(Throwable _lex, WsJob j, WebserviceInfo wsInfo)
166   {
167     if (_lex instanceof FormValidationException)
168     {
169       FormValidationException formError = (FormValidationException) _lex;
170       String[] messages = new String[formError.getErrors().size()];
171       int i = 0;
172       for (ValidationException e : formError.getErrors())
173       {
174         messages[i++] = String.format("%s: %s,", e.getField().getName(), e.getMessage());
175       }
176       j.setState(WsJob.JobState.INVALID);
177       j.setStatus(String.join(", ", messages));
178       return true;
179     }
180     return false;
181   }
182
183   @Override
184   public final boolean handleCollectionException(Exception e, WsJob msjob, WebserviceInfo wsInfo)
185   {
186     // TODO
187     return false;
188   }
189
190   final SlivkaService getService()
191   {
192     return service;
193   }
194
195   @Override
196   public final Object getEndpoint()
197   {
198     return this;
199   }
200
201   @Override
202   public final void initParamStore(ParamManager userParameterStore)
203   {
204     if (store == null)
205     {
206       try
207       {
208         store = new SlivkaDatastore(service);
209       } catch (IOException e)
210       {
211         throw new IOError(e);
212       }
213     }
214   }
215
216   @Override
217   public boolean hasParameters()
218   {
219     return true;
220   }
221
222   @Override
223   public final ParamDatastoreI getParamStore()
224   {
225     if (store == null)
226     {
227       initParamStore(null);
228     }
229     return store;
230   }
231
232 }