package jalview.ws.slivkaws; 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.Set; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.gui.WebserviceInfo; import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.FormatAdapter; 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 javajs.http.ClientProtocolException; import java.util.Collection; import uk.ac.dundee.compbio.slivkaclient.Job; import uk.ac.dundee.compbio.slivkaclient.JobRequest; import uk.ac.dundee.compbio.slivkaclient.Parameter; import uk.ac.dundee.compbio.slivkaclient.RemoteFile; import uk.ac.dundee.compbio.slivkaclient.SlivkaClient; import uk.ac.dundee.compbio.slivkaclient.SlivkaService; 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<>(Job.Status.class); { stateMap.put(Job.Status.PENDING, WsJob.JobState.QUEUED); stateMap.put(Job.Status.REJECTED, WsJob.JobState.INVALID); stateMap.put(Job.Status.ACCEPTED, WsJob.JobState.QUEUED); stateMap.put(Job.Status.QUEUED, WsJob.JobState.QUEUED); stateMap.put(Job.Status.RUNNING, WsJob.JobState.RUNNING); stateMap.put(Job.Status.COMPLETED, WsJob.JobState.FINISHED); stateMap.put(Job.Status.INTERRUPTED, WsJob.JobState.CANCELLED); stateMap.put(Job.Status.DELETED, WsJob.JobState.CANCELLED); stateMap.put(Job.Status.FAILED, WsJob.JobState.FAILED); stateMap.put(Job.Status.ERROR, WsJob.JobState.SERVERERROR); stateMap.put(Job.Status.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(action, action, service.getName(), "Slivka", client.getUrl().toString()); this.client = client; this.service = service; } protected final JobId submit(List sequences, WsParamSetI preset, List args) throws Throwable { var parameters = service.getParameters(); var request = new JobRequest(); for (Parameter param : parameters) { if (param instanceof Parameter.FileParameter) { FormatAdapter fa = new FormatAdapter(); fa.setNewlineString("\r\n"); Parameter.FileParameter fileParam = (Parameter.FileParameter) param; FileFormat format; switch (fileParam.getMediaType()) { case "application/pfam": format = FileFormat.Pfam; break; case "application/stockholm": format = FileFormat.Stockholm; break; default: case "application/fasta": format = FileFormat.Fasta; break; } // we avoid any use of Jalview's user facing export routines here InputStream stream = new ByteArrayInputStream(format.getWriter(null) .print(sequences.toArray(new SequenceI[0]), false) .getBytes()); request.addFile(param.getId(), stream); } } if (args != null) { for (ArgumentI arg : args) { // multiple choice field names are name$number to avoid duplications // the number is stripped here String paramId = arg.getName().split("\\$", 2)[0]; Parameter param = service.getParameter(paramId); if (param instanceof Parameter.FlagParameter) { if (arg.getValue() != null && !arg.getValue().isBlank()) request.addData(paramId, true); else request.addData(paramId, false); } else { request.addData(paramId, arg.getValue()); } } } var job = service.submitJob(request); return new JobId(service.getName(), service.getName(), job.getId()); } @Override public final void updateStatus(WsJob job) { try { var slivkaJob = client.getJob(job.getJobId()); job.setState(stateMap.get(slivkaJob.getStatus())); } catch (IOException e) { throw new IOError(e); } } @Override public final boolean updateJobProgress(WsJob job) throws IOException { var slivkaJob = client.getJob(job.getJobId()); Collection files = slivkaJob.getResults(); RemoteFile logFile=null; for (RemoteFile f : files) { if (f.getLabel().equals("log")) { logFile = f; break; } } boolean newContent = false; if (logFile!=null) { ByteArrayOutputStream output = new ByteArrayOutputStream(); logFile.writeTo(output); if (output.size() > job.getNextChunk()) { newContent = true; job.setStatus(output.toString("UTF-8")); job.setnextChunk(output.size()); } } if (failedStates.contains(job.getJobState())) { RemoteFile errLogFile = null; for (RemoteFile f : files) { if (f.getLabel().equals("error-log")) { errLogFile = f; break; } } if (errLogFile!=null) { ByteArrayOutputStream output = new ByteArrayOutputStream(); errLogFile.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 ClientProtocolException) { j.setState(WsJob.JobState.INVALID); j.setStatus(_lex.getMessage()); 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) { store = new SlivkaDatastore(service); } } @Override public boolean hasParameters() { return true; } @Override public final ParamDatastoreI getParamStore() { if (store == null) { initParamStore(null); } return store; } public static AlignmentI readAlignment(RemoteFile f) throws IOException { final var mimetype = f.getMediaType(); FileFormat format; if (mimetype.equals("application/clustal")) format = FileFormat.Clustal; else if (mimetype.equals("application/fasta")) format = FileFormat.Fasta; else return null; return new FormatAdapter().readFile(f.getContentUrl().toString(), DataSourceType.URL, format); } }