package jalview.ws.slivkaws; import jalview.datamodel.SequenceI; import jalview.gui.WebserviceInfo; import jalview.ws.api.JalviewServiceEndpointProviderI; import jalview.ws.api.JalviewWebServiceI; import jalview.ws.api.JobId; import jalview.ws.api.ServiceWithParameters; import jalview.ws.gui.WsJob; import jalview.ws.params.ArgumentI; import jalview.ws.params.ParamDatastoreI; import jalview.ws.params.ParamManager; import jalview.ws.params.WsParamSetI; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOError; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.EnumMap; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import uk.ac.dundee.compbio.slivkaclient.FieldType; import uk.ac.dundee.compbio.slivkaclient.FormField; import uk.ac.dundee.compbio.slivkaclient.FormValidationException; import uk.ac.dundee.compbio.slivkaclient.JobState; import uk.ac.dundee.compbio.slivkaclient.RemoteFile; import uk.ac.dundee.compbio.slivkaclient.SlivkaClient; import uk.ac.dundee.compbio.slivkaclient.SlivkaForm; import uk.ac.dundee.compbio.slivkaclient.SlivkaService; import uk.ac.dundee.compbio.slivkaclient.ValidationException; public abstract class SlivkaWSInstance extends ServiceWithParameters implements JalviewServiceEndpointProviderI, JalviewWebServiceI { protected final SlivkaClient client; protected final SlivkaService service; protected SlivkaDatastore store = null; protected static final EnumMap stateMap = new EnumMap<>(JobState.class); { stateMap.put(JobState.PENDING, WsJob.JobState.QUEUED); stateMap.put(JobState.REJECTED, WsJob.JobState.INVALID); stateMap.put(JobState.ACCEPTED, WsJob.JobState.QUEUED); stateMap.put(JobState.QUEUED, WsJob.JobState.QUEUED); stateMap.put(JobState.RUNNING, WsJob.JobState.RUNNING); stateMap.put(JobState.COMPLETED, WsJob.JobState.FINISHED); stateMap.put(JobState.INTERRUPED, WsJob.JobState.CANCELLED); stateMap.put(JobState.DELETED, WsJob.JobState.CANCELLED); stateMap.put(JobState.FAILED, WsJob.JobState.FAILED); stateMap.put(JobState.ERROR, WsJob.JobState.SERVERERROR); stateMap.put(JobState.UNKNOWN, WsJob.JobState.UNKNOWN); } protected final Set failedStates = new HashSet<>(Arrays.asList( WsJob.JobState.INVALID, WsJob.JobState.BROKEN, WsJob.JobState.FAILED, WsJob.JobState.SERVERERROR, WsJob.JobState.CANCELLED )); public SlivkaWSInstance(SlivkaClient client, SlivkaService service, String action) { super(service.getName(), action, service.getLabel(), "Slivka", client.getUrl().toString()); this.client = client; this.service = service; } protected final JobId submit(List sequences, WsParamSetI preset, List args) throws Throwable { SlivkaForm form = service.getForm(); Optional inputField = form.getFields().stream() .filter(f -> f.getType() == FieldType.FILE).findFirst(); if (inputField.isPresent()) { StringBuilder builder = new StringBuilder(); for (SequenceI seq : sequences) { builder.append(">").append(seq.getName()).append("\n") .append(seq.getSequence()).append("\n"); } InputStream stream = new ByteArrayInputStream( builder.toString().getBytes()); RemoteFile file = client.uploadFile(stream, "input.fa", "application/fasta"); form.insert(inputField.get().getName(), file); } if (args != null) { for (ArgumentI arg : args) { // multiple choice field names are name$number to avoid duplications // the number is stripped here String fieldName = arg.getName().split("\\$", 2)[0]; FormField field = form.getField(fieldName); if (field.getType() == FieldType.BOOLEAN) { form.insert(fieldName, (arg.getValue() != null && !arg.getValue().isBlank()) ? true : false); } else { form.insert(fieldName, arg.getValue()); } } } return new JobId(service.getName(), service.getName(), form.submit()); } @Override public final void updateStatus(WsJob job) { try { job.setState(stateMap.get(client.getJobState(job.getJobId()))); } catch (IOException e) { throw new IOError(e); } } @Override public final boolean updateJobProgress(WsJob job) throws IOException { List files = client.getJobResults(job.getJobId()); Optional logFile = files.stream() .filter(f -> f.getLabel().equals("log")).findFirst(); boolean newContent = false; if (logFile.isPresent()) { ByteArrayOutputStream output = new ByteArrayOutputStream(); logFile.get().writeTo(output); if (output.size() > job.getNextChunk()) { newContent = true; job.setStatus(output.toString("UTF-8")); job.setnextChunk(output.size()); } } if (failedStates.contains(job.getJobState())) { Optional errLogFile = files.stream() .filter(f -> f.getLabel().equals("error-log")).findFirst(); if (errLogFile.isPresent()) { ByteArrayOutputStream output = new ByteArrayOutputStream(); errLogFile.get().writeTo(output); if (output.size() > 0) { newContent = true; job.setStatus(job.getStatus() + "\n" + output.toString("UTF-8")); } } } return newContent; } @Override public final boolean handleSubmitError(Throwable _lex, WsJob j, WebserviceInfo wsInfo) { if (_lex instanceof FormValidationException) { FormValidationException formError = (FormValidationException) _lex; String[] messages = new String[formError.getErrors().size()]; int i = 0; for (ValidationException e : formError.getErrors()) { messages[i++] = String.format("%s: %s,", e.getField().getName(), e.getMessage()); } j.setState(WsJob.JobState.INVALID); j.setStatus(String.join(", ", messages)); return true; } return false; } @Override public final boolean handleCollectionException(Exception e, WsJob msjob, WebserviceInfo wsInfo) { // TODO return false; } final SlivkaService getService() { return service; } @Override public final Object getEndpoint() { return this; } @Override public final void initParamStore(ParamManager userParameterStore) { if (store == null) { try { store = new SlivkaDatastore(service); } catch (IOException e) { throw new IOError(e); } } } @Override public boolean hasParameters() { return true; } @Override public final ParamDatastoreI getParamStore() { if (store == null) { initParamStore(null); } return store; } }