Implement simple media type recognition.
[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.io.FileFormat;
6 import jalview.io.FormatAdapter;
7 import jalview.ws.api.JalviewServiceEndpointProviderI;
8 import jalview.ws.api.JalviewWebServiceI;
9 import jalview.ws.api.JobId;
10 import jalview.ws.api.ServiceWithParameters;
11 import jalview.ws.gui.WsJob;
12 import jalview.ws.params.ArgumentI;
13 import jalview.ws.params.ParamDatastoreI;
14 import jalview.ws.params.ParamManager;
15 import jalview.ws.params.WsParamSetI;
16
17 import java.io.ByteArrayInputStream;
18 import java.io.IOError;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InputStreamReader;
22 import java.util.Arrays;
23 import java.util.EnumMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Optional;
27 import java.util.Set;
28
29 import uk.ac.dundee.compbio.slivkaclient.FieldType;
30 import uk.ac.dundee.compbio.slivkaclient.FileField;
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.INTERRUPED, 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(service.getName(), 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     for (FormField field : form.getFields())
80     {
81       if (field.getType() == FieldType.FILE)
82       {
83         FormatAdapter fa = new FormatAdapter();
84         fa.setNewlineString("\r\n");
85         FileField fileField = (FileField) field;
86         FileFormat format;
87         switch (fileField.getMediaType())
88         {
89         case "application/pfam":
90           format = FileFormat.Pfam;
91           break;
92         case "application/stockholm":
93           format = FileFormat.Stockholm;
94           break;
95         default:
96         case "application/fasta":
97           format = FileFormat.Fasta;
98           break;
99         }
100         InputStream stream = new ByteArrayInputStream(
101             fa.formatSequences(format, sequences.toArray(new SequenceI[0]))
102                 .getBytes());
103         RemoteFile rf = client.uploadFile(stream, "input",
104             fileField.getMediaType());
105         form.insert(field.getName(), rf);
106       }
107     }
108     if (args != null)
109     {
110       for (ArgumentI arg : args)
111       {
112         // multiple choice field names are name$number to avoid duplications
113         // the number is stripped here
114         String fieldName = arg.getName().split("\\$", 2)[0];
115         FormField field = form.getField(fieldName);
116         if (field.getType() == FieldType.BOOLEAN)
117         {
118           form.insert(fieldName,
119                   (arg.getValue() != null && !arg.getValue().isBlank())
120                           ? true
121                           : false);
122         }
123         else
124         {
125           form.insert(fieldName, field.valueOf(arg.getValue()));
126         }
127       }
128     }
129     return new JobId(service.getName(), service.getName(), form.submit());
130   }
131
132   @Override
133   public final void updateStatus(WsJob job)
134   {
135     try
136     {
137       job.setState(stateMap.get(client.getJobState(job.getJobId())));
138     } catch (IOException e)
139     {
140       throw new IOError(e);
141     }
142   }
143
144   @Override
145   public final boolean updateJobProgress(WsJob job) throws IOException
146   {
147     List<RemoteFile> files = client.getJobResults(job.getJobId());
148     Optional<RemoteFile> logFile = files.stream()
149         .filter(f -> f.getLabel().equals("log")).findFirst();
150     boolean newContent = false;
151     if (logFile.isPresent())
152     {
153       InputStream stream = logFile.get().getContent();
154       long nextChunk = stream.skip(job.getNextChunk());
155       int len = appendJobStatus(job, stream);
156       job.setnextChunk(nextChunk + len);
157       newContent |= len > 0;
158     }
159     if (failedStates.contains(job.getJobState()))
160     {
161       Optional<RemoteFile> errLogFile = files.stream()
162           .filter(f -> f.getLabel().equals("error-log")).findFirst();
163       if (errLogFile.isPresent())
164       {
165         newContent |= appendJobStatus(job, errLogFile.get().getContent()) > 0;
166       }
167     }
168     return newContent;
169   }
170
171   private int appendJobStatus(WsJob job, InputStream stream) throws IOException
172   {
173     StringBuilder builder = new StringBuilder(job.getStatus());
174     InputStreamReader reader = new InputStreamReader(stream);
175     char[] buffer = new char[4096];
176     int chunkLen = 0;
177     int len = 0;
178     while ((len = reader.read(buffer)) != -1)
179     {
180       chunkLen += len;
181       builder.append(buffer, 0, len);
182     }
183     job.setStatus(builder.toString());
184     return chunkLen;
185   }
186
187   @Override
188   public final boolean handleSubmitError(Throwable _lex, WsJob j, WebserviceInfo wsInfo)
189   {
190     if (_lex instanceof FormValidationException)
191     {
192       FormValidationException formError = (FormValidationException) _lex;
193       String[] messages = new String[formError.getErrors().size()];
194       int i = 0;
195       for (ValidationException e : formError.getErrors())
196       {
197         messages[i++] = String.format("%s: %s,", e.getField().getName(), e.getMessage());
198       }
199       j.setState(WsJob.JobState.INVALID);
200       j.setStatus(String.join(", ", messages));
201       return true;
202     }
203     return false;
204   }
205
206   @Override
207   public final boolean handleCollectionException(Exception e, WsJob msjob, WebserviceInfo wsInfo)
208   {
209     // TODO
210     return false;
211   }
212
213   final SlivkaService getService()
214   {
215     return service;
216   }
217
218   @Override
219   public final Object getEndpoint()
220   {
221     return this;
222   }
223
224   @Override
225   public final void initParamStore(ParamManager userParameterStore)
226   {
227     if (store == null)
228     {
229       try
230       {
231         store = new SlivkaDatastore(service);
232       } catch (IOException e)
233       {
234         throw new IOError(e);
235       }
236     }
237   }
238
239   @Override
240   public boolean hasParameters()
241   {
242     return true;
243   }
244
245   @Override
246   public final ParamDatastoreI getParamStore()
247   {
248     if (store == null)
249     {
250       initParamStore(null);
251     }
252     return store;
253   }
254
255 }