JAL-3066 Create labels for fields and hack it to work with multiple valued parameters
[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.QUEUED, WsJob.JobState.QUEUED);
50     stateMap.put(JobState.RUNNING, WsJob.JobState.RUNNING);
51     stateMap.put(JobState.COMPLETED, WsJob.JobState.FINISHED);
52     stateMap.put(JobState.FAILED, WsJob.JobState.FAILED);
53     stateMap.put(JobState.ERROR, WsJob.JobState.SERVERERROR);
54     stateMap.put(JobState.UNKNOWN, WsJob.JobState.UNKNOWN);
55   }
56   protected final Set<WsJob.JobState> failedStates = new HashSet<>(Arrays.asList(
57       WsJob.JobState.INVALID, WsJob.JobState.BROKEN, WsJob.JobState.FAILED,
58       WsJob.JobState.SERVERERROR, WsJob.JobState.CANCELLED
59   ));
60
61   public SlivkaWSInstance(SlivkaClient client, SlivkaService service, String action)
62   {
63     super(service.getName(), action, service.getLabel(), "Slivka", client.getUrl().toString());
64     this.client = client;
65     this.service = service;
66   }
67
68   protected final JobId submit(List<SequenceI> sequences,
69           WsParamSetI preset, List<ArgumentI> args) throws Throwable
70   {
71     SlivkaForm form = service.getForm();
72     Optional<FormField> inputField = form.getFields().stream()
73             .filter(f -> f.getType() == FieldType.FILE).findFirst();
74     if (inputField.isPresent())
75     {
76       StringBuilder builder = new StringBuilder();
77       for (SequenceI seq : sequences)
78       {
79         builder.append(">").append(seq.getName()).append("\n")
80                 .append(seq.getSequence()).append("\n");
81       }
82       InputStream stream = new ByteArrayInputStream(
83               builder.toString().getBytes());
84       RemoteFile file = client.uploadFile(stream, "input.fa",
85               "application/fasta");
86       form.insert(inputField.get().getName(), file);
87     }
88     if (args != null)
89     {
90       for (ArgumentI arg : args)
91       {
92         String fieldName = arg.getName().split("\\$", 2)[0];
93         FormField field = form.getField(fieldName);
94         if (field.getType() == FieldType.BOOLEAN)
95         {
96           form.insert(fieldName,
97                   (arg.getValue() != null && !arg.getValue().isBlank())
98                           ? true
99                           : false);
100         }
101         else
102         {
103           form.insert(fieldName, field.valueOf(arg.getValue()));
104         }
105       }
106     }
107     return new JobId(service.getName(), service.getName(), form.submit());
108   }
109
110   @Override
111   public final void updateStatus(WsJob job)
112   {
113     try
114     {
115       job.setState(stateMap.get(client.getJobState(job.getJobId())));
116     } catch (IOException e)
117     {
118       throw new IOError(e);
119     }
120   }
121
122   @Override
123   public final boolean updateJobProgress(WsJob job) throws IOException
124   {
125     List<RemoteFile> files = client.getJobResults(job.getJobId());
126     Optional<RemoteFile> logFile = files.stream()
127         .filter(f -> f.getLabel().equals("log")).findFirst();
128     boolean newContent = false;
129     if (logFile.isPresent())
130     {
131       InputStream stream = logFile.get().getContent();
132       long nextChunk = stream.skip(job.getNextChunk());
133       int len = appendJobStatus(job, stream);
134       job.setnextChunk(nextChunk + len);
135       newContent |= len > 0;
136     }
137     if (failedStates.contains(job.getJobState()))
138     {
139       Optional<RemoteFile> errLogFile = files.stream()
140           .filter(f -> f.getLabel().equals("error-log")).findFirst();
141       if (errLogFile.isPresent())
142       {
143         newContent |= appendJobStatus(job, errLogFile.get().getContent()) > 0;
144       }
145     }
146     return newContent;
147   }
148
149   private int appendJobStatus(WsJob job, InputStream stream) throws IOException
150   {
151     StringBuilder builder = new StringBuilder(job.getStatus());
152     InputStreamReader reader = new InputStreamReader(stream);
153     char[] buffer = new char[4096];
154     int chunkLen = 0;
155     int len = 0;
156     while ((len = reader.read(buffer)) != -1)
157     {
158       chunkLen += len;
159       builder.append(buffer, 0, len);
160     }
161     job.setStatus(builder.toString());
162     return chunkLen;
163   }
164
165   @Override
166   public final boolean handleSubmitError(Throwable _lex, WsJob j, WebserviceInfo wsInfo)
167   {
168     if (_lex instanceof FormValidationException)
169     {
170       FormValidationException formError = (FormValidationException) _lex;
171       String[] messages = new String[formError.getErrors().size()];
172       int i = 0;
173       for (ValidationException e : formError.getErrors())
174       {
175         messages[i++] = String.format("%s: %s,", e.getField().getName(), e.getMessage());
176       }
177       j.setState(WsJob.JobState.INVALID);
178       j.setStatus(String.join(", ", messages));
179       return true;
180     }
181     return false;
182   }
183
184   @Override
185   public final boolean handleCollectionException(Exception e, WsJob msjob, WebserviceInfo wsInfo)
186   {
187     // TODO
188     return false;
189   }
190
191   final SlivkaService getService()
192   {
193     return service;
194   }
195
196   @Override
197   public final Object getEndpoint()
198   {
199     return this;
200   }
201
202   @Override
203   public final void initParamStore(ParamManager userParameterStore)
204   {
205     if (store == null)
206     {
207       try
208       {
209         store = new SlivkaDatastore(service);
210       } catch (IOException e)
211       {
212         throw new IOError(e);
213       }
214     }
215   }
216
217   @Override
218   public boolean hasParameters()
219   {
220     return true;
221   }
222
223   @Override
224   public final ParamDatastoreI getParamStore()
225   {
226     if (store == null)
227     {
228       initParamStore(null);
229     }
230     return store;
231   }
232
233 }