info.server_exception = \n{0} Server exception\!\n{1}
info.invalid_msa_input_mininfo = Need at least two sequences with at least 3 residues each, with no hidden regions between them.
info.invalid_msa_notenough = Not enough sequence data to align
+info.invalid_search_input = Invalid input for sequence search.
status.processing_commandline_args = Processing commandline arguments...
status.das_features_being_retrived = DAS features being retrieved...
status.searching_for_sequences_from = Searching for sequences from {0}
* @return
*/
boolean isDeletable();
+
+ /**
+ * Returns the name of this calculation.
+ */
+ public default String getCalcName()
+ {
+ return null;
+ }
}
*/
AnnotatedCollectionI getContext();
-
+ /**
+ * Returns the number of the highest annotation graph group attached to this
+ * alignment.
+ */
+ default int getLastGraphGroup()
+ {
+ var annots = getAlignmentAnnotation();
+ if (annots == null)
+ return 1;
+ int graphGroup = 1;
+ for (AlignmentAnnotation ala : annots)
+ {
+ graphGroup = Math.max(graphGroup, ala.graphGroup);
+ }
+ return graphGroup;
+ }
+
}
*/
package jalview.datamodel;
+import java.util.Collection;
+
public interface ContiguousI
{
int getBegin(); // todo want long for genomic positions?
int getEnd();
+
+ public static int[] toStartEndArray(Collection<? extends ContiguousI> ranges)
+ {
+ int[] startend = new int[ranges.size() * 2];
+ int i = 0;
+ for (var range : ranges)
+ {
+ startend[i++] = range.getBegin();
+ startend[i++] = range.getEnd();
+ }
+ return startend;
+ }
}
public SequenceI getDatasetSequence();
/**
+ * Returns the top grandparent in the dataset sequences hierarchy.
+ */
+ public default SequenceI getRootDatasetSequence()
+ {
+ var sequence = this;
+ while (sequence.getDatasetSequence() != null)
+ {
+ sequence = sequence.getDatasetSequence();
+ }
+ return sequence;
+ }
+
+ /**
* Returns a new array containing this sequence's annotations, or null.
*/
public AlignmentAnnotation[] getAnnotation();
import jalview.ws.params.WsParamSetI;
import jalview.ws.seqfetcher.DbSourceProxy;
import jalview.ws2.client.api.WebServiceDiscovererI;
+import jalview.ws2.client.ebi.JobDispatcherWSDiscoverer;
import jalview.ws2.client.slivka.SlivkaWSDiscoverer;
import jalview.ws2.gui.WebServicesMenuManager;
menu.setNoServices(services.isEmpty() && discoverer.isDone());
};
+ private WebServiceDiscovererI.ServicesChangeListener ebiServiceChangeListener =
+ (discoverer, services) -> {
+ // run when ebi services change
+ var menu = AlignFrame.this.ebiMenu;
+ menu.setServices(discoverer);
+ menu.setInProgress(discoverer.isRunning());
+ menu.setNoServices(services.isEmpty() && discoverer.isDone());
+ };
+
/* Set up intrinsic listeners for dynamically generated GUI bits. */
private void addServiceListeners()
{
WebServiceDiscovererI discoverer = SlivkaWSDiscoverer.getInstance();
discoverer.addServicesChangeListener(slivkaServiceChangeListener);
}
+ if (Cache.getDefault("SHOW_EBI_SERVICES", true))
+ {
+ JobDispatcherWSDiscoverer.getInstance().addServicesChangeListener(ebiServiceChangeListener);
+ }
if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
{
WSDiscovererI discoverer = Jws2Discoverer.getInstance();
}
private WebServicesMenuManager slivkaMenu = new WebServicesMenuManager("slivka", this);
+ private WebServicesMenuManager ebiMenu = new WebServicesMenuManager("job dispatcher", this);
/**
* Schedule the web services menu rebuild to the event dispatch thread.
slivkaMenu.setNoServices(discoverer.isDone() && !discoverer.hasServices());
webService.add(slivkaMenu.getMenu());
}
+ if (Cache.getDefault("SHOW_EBI_SERVICES", true))
+ {
+ Console.info("Building web services menu for jobs dispatcher");
+ JobDispatcherWSDiscoverer discoverer = JobDispatcherWSDiscoverer.getInstance();
+ ebiMenu.setServices(discoverer);
+ ebiMenu.setInProgress(discoverer.isRunning());
+ ebiMenu.setNoServices(discoverer.isDone() && !discoverer.hasServices());
+ webService.add(ebiMenu.getMenu());
+ }
if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
{
WSDiscovererI jws2servs = Jws2Discoverer.getInstance();
tasks.add(jalview.ws2.client.slivka.SlivkaWSDiscoverer
.getInstance().startDiscoverer());
}
+ if (Cache.getDefault("SHOW_EBI_SERVICES", true))
+ {
+ tasks.add(jalview.ws2.client.ebi.JobDispatcherWSDiscoverer
+ .getInstance().startDiscoverer());
+ }
if (blocking)
{
for (Future<?> task : tasks) {
import java.util.Arrays;
import java.util.List;
+import jalview.ws.params.ArgumentI;
+
public class BooleanOption extends Option
{
public static class Builder extends Option.Builder
{
this(name, description, label, isrequired, defValue, String.valueOf(true), link);
}
+
+ public static Boolean parseBoolean(ArgumentI argument)
+ {
+ return argument.getValue() != null && !argument.getValue().isEmpty() ?
+ true : false;
+ }
}
package jalview.ws.params.simple;
+import jalview.ws.params.ArgumentI;
import jalview.ws.params.ParameterI;
import jalview.ws.params.ValueConstrainI;
{
return new DoubleParameter(this);
}
+
+ /**
+ * Return argument value as double or null if string value is null or empty.
+ *
+ * @param arg argument to extract value form
+ * @return argument value as double
+ */
+ public static Double parseDouble(ArgumentI arg)
+ {
+ return arg.getValue() != null && !arg.getValue().isEmpty() ?
+ Double.parseDouble(arg.getValue()) : null;
+ }
+
+ /**
+ * Return argument value as float or null if string value is null or empty.
+ *
+ * @param arg argument to extract value from
+ * @return value as float
+ */
+ public static Float parseFloat(ArgumentI arg)
+ {
+ return arg.getValue() != null && !arg.getValue().isEmpty() ?
+ Float.parseFloat(arg.getValue()) : null;
+ }
}
*/
package jalview.ws.params.simple;
+import jalview.ws.params.ArgumentI;
import jalview.ws.params.ParameterI;
import jalview.ws.params.ValueConstrainI;
return new IntegerParameter(this);
}
+ /**
+ * Return argument value as int or null if string value is null or empty.
+ *
+ * @param arg argument to extract value from
+ * @return value as int
+ */
+ public static Integer parseInt(ArgumentI arg)
+ {
+ return arg.getValue() != null && !arg.getValue().isEmpty() ?
+ Integer.parseInt(arg.getValue()) : null;
+ }
+
}
+++ /dev/null
-package jalview.ws2.actions;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import jalview.bin.Cache;
-import jalview.bin.Console;
-import jalview.util.ArrayUtils;
-import jalview.util.MathUtils;
-import jalview.ws.params.ArgumentI;
-import jalview.ws2.actions.api.TaskEventListener;
-import jalview.ws2.actions.api.TaskI;
-import jalview.ws2.api.Credentials;
-import jalview.ws2.api.JobStatus;
-import jalview.ws2.api.WebServiceJobHandle;
-import jalview.ws2.client.api.WebServiceClientI;
-import jalview.ws2.helpers.DelegateJobEventListener;
-import jalview.ws2.helpers.TaskEventSupport;
-import static java.lang.String.format;
-
-/**
- * An abstract base class for non-interactive tasks which implements common
- * tasks methods. Additionally, it manages task execution in a polling loop.
- * Subclasses are only required to implement {@link #prepare()} and
- * {@link #done()} methods.
- *
- * @author mmwarowny
- *
- * @param <T>
- * the type of jobs managed by the task
- * @param <R>
- * the type of result provided by the task
- */
-public abstract class AbstractPollableTask<T extends BaseJob, R> implements TaskI<R>
-{
- private final long uid = MathUtils.getUID();
-
- protected final WebServiceClientI client;
-
- protected final List<ArgumentI> args;
-
- protected final Credentials credentials;
-
- private final TaskEventSupport<R> eventHandler;
-
- protected JobStatus taskStatus = JobStatus.CREATED;
-
- private Future<?> future = null;
-
- protected List<T> jobs = Collections.emptyList();
-
- protected R result;
-
- protected AbstractPollableTask(WebServiceClientI client, List<ArgumentI> args,
- Credentials credentials, TaskEventListener<R> eventListener)
- {
- this.client = client;
- this.args = args;
- this.credentials = credentials;
- this.eventHandler = new TaskEventSupport<R>(this, eventListener);
- }
-
- public long getUid()
- {
- return uid;
- }
-
- /**
- * Start the task using provided scheduled executor service. It creates a
- * polling loop running at set intervals.
- *
- * @param executor
- * executor to run the polling loop with
- */
- public void start(ScheduledExecutorService executor)
- {
- if (future != null)
- throw new IllegalStateException("task already started");
- var runnable = new Runnable()
- {
- private int stage = STAGE_PREPARE;
-
- private static final int STAGE_PREPARE = 0;
-
- private static final int STAGE_START = 1;
-
- private static final int STAGE_POLL = 2;
-
- private static final int STAGE_FINALIZE = 3;
-
- private static final int STAGE_DONE = 4;
-
- private int retryCount = 0;
-
- private static final int MAX_RETRY = 5;
-
- /**
- * A polling loop run periodically which carries the task through its
- * consecutive execution stages.
- */
- @Override
- public void run()
- {
- if (stage == STAGE_PREPARE)
- {
- // first stage - the input data is collected and the jobs are created
- try
- {
- jobs = prepare();
- } catch (ServiceInputInvalidException e)
- {
- stage = STAGE_DONE;
- setStatus(JobStatus.INVALID);
- eventHandler.fireTaskException(e);
- throw new CompletionException(e);
- }
- stage = STAGE_START;
- setStatus(JobStatus.READY);
- eventHandler.fireTaskStarted(jobs);
- var jobListener = new DelegateJobEventListener<>(eventHandler);
- for (var job : jobs)
- {
- job.addPropertyChagneListener(jobListener);
- }
- }
- try
- {
- if (stage == STAGE_START)
- {
- // second stage - jobs are submitted to the server
- startJobs();
- stage = STAGE_POLL;
- setStatus(JobStatus.SUBMITTED);
- }
- if (stage == STAGE_POLL)
- {
- // third stage - jobs are poolled until all of them are completed
- if (pollJobs())
- {
- stage = STAGE_FINALIZE;
- }
- updateGlobalStatus();
- }
- if (stage == STAGE_FINALIZE)
- {
- // final stage - results are collected and stored
- result = done();
- eventHandler.fireTaskCompleted(result);
- stage = STAGE_DONE;
- }
- retryCount = 0;
- } catch (IOException e)
- {
- eventHandler.fireTaskException(e);
- if (++retryCount > MAX_RETRY)
- {
- stage = STAGE_DONE;
- cancelJobs();
- setStatus(JobStatus.SERVER_ERROR);
- throw new CompletionException(e);
- }
- }
- if (stage == STAGE_DONE)
- {
- // finalization - terminating the future task
- throw new CancellationException("task terminated");
- }
- }
- };
- if (taskStatus != JobStatus.CANCELLED)
- future = executor.scheduleWithFixedDelay(runnable, 0, 2, TimeUnit.SECONDS);
- }
-
- @Override
- public JobStatus getStatus()
- {
- return taskStatus;
- }
-
- /**
- * Set the status of the task and notify the event handler.
- *
- * @param status
- * new task status
- */
- protected void setStatus(JobStatus status)
- {
- if (this.taskStatus != status)
- {
- this.taskStatus = status;
- eventHandler.fireTaskStatusChanged(status);
- }
- }
-
- /**
- * Update task status according to the overall status of its jobs. The rules
- * of setting the status are following:
- * <ul>
- * <li>task is invalid if all jobs are invalid</li>
- * <li>task is completed if all but invalid jobs are completed</li>
- * <li>task is ready, submitted or queued if at least one job is ready,
- * submitted or queued an none proceeded to the next stage excluding
- * completed.</li>
- * <li>task is running if at least one job is running and none are failed or
- * cancelled</li>
- * <li>task is cancelled if at least one job is cancelled and none failed</li>
- * <li>task is failed or server error if at least one job is failed or server
- * error</li>
- * </ul>
- */
- private void updateGlobalStatus()
- {
- int precedence = -1;
- for (BaseJob job : jobs)
- {
- JobStatus status = job.getStatus();
- int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status);
- if (precedence < jobPrecedence)
- precedence = jobPrecedence;
- }
- if (precedence >= 0)
- {
- setStatus(JobStatus.statusPrecedence[precedence]);
- }
- }
-
- @Override
- public void cancel()
- {
- setStatus(JobStatus.CANCELLED);
- if (future != null)
- future.cancel(false);
- cancelJobs();
- }
-
- @Override
- public List<? extends BaseJob> getSubJobs()
- {
- return jobs;
- }
-
- /**
- * Collect and process input sequences for submission and return the list of
- * jobs to be submitted.
- *
- * @return list of jobs to be submitted
- * @throws ServiceInputInvalidException
- * input is invalid and the task should not be started
- */
- protected abstract List<T> prepare() throws ServiceInputInvalidException;
-
- /**
- * Submit all valid jobs to the server and store their job handles.
- *
- * @throws IOException
- * if server error occurred
- */
- protected void startJobs() throws IOException
- {
- for (BaseJob job : jobs)
- {
- if (job.isInputValid() && job.getStatus() == JobStatus.READY)
- {
- WebServiceJobHandle serverJob = client.submit(job.getInputSequences(),
- args, credentials);
- job.setServerJob(serverJob);
- job.setStatus(JobStatus.SUBMITTED);
- }
- }
- }
-
- /**
- * Poll all running jobs and update their status and logs. Polling is repeated
- * periodically until this method return true when all jobs are done.
- *
- * @return {@code true] if all jobs are done @throws IOException if server
- * error occurred
- */
- protected boolean pollJobs() throws IOException
- {
- boolean allDone = true;
- for (BaseJob job : jobs)
- {
- if (job.isInputValid() && !job.getStatus().isDone())
- {
- WebServiceJobHandle serverJob = job.getServerJob();
- job.setStatus(client.getStatus(serverJob));
- job.setLog(client.getLog(serverJob));
- job.setErrorLog(client.getErrorLog(serverJob));
- }
- allDone &= job.isCompleted();
- }
- return allDone;
- }
-
- /**
- * Fetch and process the outputs produced by jobs and return the final result
- * of the task. The method is called once all jobs have finished execution. If
- * this method raises {@link IOException} it will be called again after a
- * delay. All IO operations should happen before data processing, so
- * potentially expensive computation is avoided in case of an error.
- *
- * @return final result of the computation
- * @throws IOException
- * if server error occurred
- */
- protected abstract R done() throws IOException;
-
- /**
- * Cancel all running jobs. Used in case of task failure to cleanup the
- * resources or when the task has been cancelled.
- */
- protected void cancelJobs()
- {
- for (BaseJob job : jobs)
- {
- if (!job.isCompleted())
- {
- try
- {
- if (job.getServerJob() != null)
- {
- client.cancel(job.getServerJob());
- }
- job.setStatus(JobStatus.CANCELLED);
- } catch (IOException e)
- {
- Console.error(format("failed to cancel job %s", job.getServerJob()), e);
- }
- }
- }
- }
-
- @Override
- public R getResult()
- {
- return result;
- }
-
- @Override
- public String toString()
- {
- var status = taskStatus != null ? taskStatus.name() : "UNSET";
- return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status);
- }
-}
*
* @author mmwarowny
*/
+// TODO: make class non-abstract by removing isInputValid()
public abstract class BaseJob implements JobI
{
protected final long internalId = MathUtils.getUID();
*
* @return {@code true} if the input is valid.
*/
+ // FIXME: method not necessary, may incorporate into task#prepare()
public abstract boolean isInputValid();
/**
* @param listener
* property change listener
*/
- public final void addPropertyChagneListener(PropertyChangeListener listener)
+ public final void addPropertyChangeListener(PropertyChangeListener listener)
{
pcs.addPropertyChangeListener(listener);
}
--- /dev/null
+package jalview.ws2.actions;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import jalview.bin.Console;
+import jalview.util.ArrayUtils;
+import jalview.util.MathUtils;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.actions.api.TaskEventListener;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.Credentials;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.client.api.WebServiceClientI;
+import jalview.ws2.helpers.DelegateJobEventListener;
+import jalview.ws2.helpers.TaskEventSupport;
+
+import static java.lang.String.format;
+
+public abstract class BaseTask<T extends BaseJob, R> implements TaskI<R>
+{
+ protected final long uid = MathUtils.getUID();
+
+ protected final WebServiceClientI webClient;
+
+ protected final List<ArgumentI> args;
+
+ protected final Credentials credentials;
+
+ private final TaskEventSupport<R> eventHandler;
+
+ protected JobStatus status = JobStatus.CREATED;
+
+ protected List<T> jobs = Collections.emptyList();
+
+ protected R result = null;
+
+ protected Runnable cancelAction = () -> {
+ };
+
+ protected BaseTask(WebServiceClientI webClient, List<ArgumentI> args,
+ Credentials credentials)
+ {
+ this.webClient = webClient;
+ this.args = args;
+ this.credentials = credentials;
+ this.eventHandler = new TaskEventSupport<>(this);
+ }
+
+ @Override
+ public final long getUid()
+ {
+ return uid;
+ }
+
+ @Override
+ public final JobStatus getStatus()
+ {
+ return status;
+ }
+
+ @Override
+ public final List<? extends BaseJob> getSubJobs()
+ {
+ return jobs;
+ }
+
+ @Override
+ public final void addTaskEventListener(TaskEventListener<R> listener)
+ {
+ eventHandler.addListener(listener);
+ }
+
+ @Override
+ public final void removeTaskEventListener(TaskEventListener<R> listener)
+ {
+ eventHandler.addListener(listener);
+ }
+
+ @Override
+ public final R getResult()
+ {
+ return result;
+ }
+
+ @Override
+ public final void init() throws Exception
+ {
+ try
+ {
+ jobs = prepareJobs();
+ } catch (ServiceInputInvalidException e)
+ {
+ setStatus(JobStatus.INVALID);
+ eventHandler.fireTaskException(e);
+ throw e;
+ }
+ setStatus(JobStatus.READY);
+ eventHandler.fireTaskStarted(jobs);
+ var jobListener = new DelegateJobEventListener<>(eventHandler);
+ for (var job : jobs)
+ job.addPropertyChangeListener(jobListener);
+ submitJobs(jobs);
+ }
+
+ static final int MAX_SUBMIT_RETRY = 5;
+
+ protected final void submitJobs(List<T> jobs) throws IOException
+ {
+ var retryCounter = 0;
+ while (true)
+ {
+ try
+ {
+ submitJobs0(jobs);
+ setStatus(JobStatus.SUBMITTED);
+ break;
+ } catch (IOException e)
+ {
+ eventHandler.fireTaskException(e);
+ if (++retryCounter > MAX_SUBMIT_RETRY)
+ {
+ cancel();
+ setStatus(JobStatus.SERVER_ERROR);
+ throw e;
+ }
+ }
+ }
+ }
+
+ private final void submitJobs0(List<T> jobs) throws IOException
+ {
+ IOException exception = null;
+ for (BaseJob job : jobs)
+ {
+ if (job.getStatus() != JobStatus.READY || !job.isInputValid())
+ continue;
+ try
+ {
+ var jobRef = webClient.submit(job.getInputSequences(), args, credentials);
+ job.setServerJob(jobRef);
+ job.setStatus(JobStatus.SUBMITTED);
+ } catch (IOException e)
+ {
+ exception = e;
+ }
+ }
+ if (exception != null)
+ throw exception;
+ }
+
+ /**
+ * Poll all running jobs and update their status and logs. Polling is repeated
+ * periodically until this method return true when all jobs are done.
+ *
+ * @return {@code true] if all jobs are done @throws IOException if server
+ * error occurred
+ */
+ @Override
+ public final boolean poll() throws IOException
+ {
+ boolean allDone = true;
+ IOException exception = null;
+ for (BaseJob job : jobs)
+ {
+ if (job.isInputValid() && !job.getStatus().isDone())
+ {
+ var serverJob = job.getServerJob();
+ try
+ {
+ job.setStatus(webClient.getStatus(serverJob));
+ job.setLog(webClient.getLog(serverJob));
+ job.setErrorLog(webClient.getErrorLog(serverJob));
+ } catch (IOException e)
+ {
+ exception = e;
+ }
+ }
+ allDone &= job.isCompleted();
+ }
+ updateGlobalStatus();
+ if (exception != null)
+ throw exception;
+ return allDone;
+ }
+
+ @Override
+ public final void complete() throws IOException
+ {
+ for (var job : jobs)
+ {
+ if (!job.isCompleted())
+ {
+ // a fallback in case the executor decides to finish prematurely
+ cancelJob(job);
+ job.setStatus(JobStatus.SERVER_ERROR);
+ }
+ }
+ updateGlobalStatus();
+ try {
+ result = collectResult(jobs);
+ eventHandler.fireTaskCompleted(result);
+ }
+ catch (Exception e)
+ {
+ eventHandler.fireTaskException(e);
+ throw e;
+ }
+ }
+
+ /**
+ * Cancel all running jobs. Used in case of task failure to cleanup the
+ * resources or when the task has been cancelled.
+ */
+ @Override
+ public final void cancel()
+ {
+ cancelAction.run();
+ for (T job : jobs)
+ {
+ cancelJob(job);
+ }
+ setStatus(JobStatus.CANCELLED);
+ }
+
+ private final void cancelJob(T job)
+ {
+ if (!job.isCompleted())
+ {
+ try
+ {
+ if (job.getServerJob() != null)
+ webClient.cancel(job.getServerJob());
+ job.setStatus(JobStatus.CANCELLED);
+ } catch (IOException e)
+ {
+ Console.error(format("failed to cancel job %s", job.getServerJob()), e);
+ }
+ }
+ }
+
+ protected final void setStatus(JobStatus status)
+ {
+ Objects.requireNonNull(status);
+ if (this.status != status)
+ {
+ this.status = status;
+ eventHandler.fireTaskStatusChanged(status);
+ }
+ }
+
+ protected abstract List<T> prepareJobs() throws ServiceInputInvalidException;
+
+ protected abstract R collectResult(List<T> jobs) throws IOException;
+
+ /**
+ * Update task status according to the overall status of its jobs. The rules
+ * of setting the status are following:
+ * <ul>
+ * <li>task is invalid if all jobs are invalid</li>
+ * <li>task is completed if all but invalid jobs are completed</li>
+ * <li>task is ready, submitted or queued if at least one job is ready,
+ * submitted or queued an none proceeded to the next stage excluding
+ * completed.</li>
+ * <li>task is running if at least one job is running and none are failed or
+ * cancelled</li>
+ * <li>task is cancelled if at least one job is cancelled and none failed</li>
+ * <li>task is failed or server error if at least one job is failed or server
+ * error</li>
+ * </ul>
+ */
+ protected final void updateGlobalStatus()
+ {
+ int precedence = -1;
+ for (BaseJob job : jobs)
+ {
+ JobStatus status = job.getStatus();
+ int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status);
+ if (precedence < jobPrecedence)
+ precedence = jobPrecedence;
+ }
+ if (precedence >= 0)
+ {
+ setStatus(JobStatus.statusPrecedence[precedence]);
+ }
+ }
+
+ /**
+ * Set the action that will be run when the {@link #cancel()} method is
+ * invoked. The action should typically stop the executor polling the task and
+ * release resources and threads running the task.
+ *
+ * @param action
+ * runnable to be executed when the task is cancelled
+ */
+ public void setCancelAction(Runnable action)
+ {
+ Objects.requireNonNull(action);
+ this.cancelAction = action;
+ }
+
+ @Override
+ public String toString()
+ {
+ var statusName = status != null ? status.name() : "UNSET";
+ return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, statusName);
+ }
+}
--- /dev/null
+package jalview.ws2.actions;
+
+import java.util.List;
+
+import jalview.api.AlignViewportI;
+import jalview.viewmodel.AlignmentViewport;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.actions.api.ActionI;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.Credentials;
+
+/**
+ * An empty implementation of the {@link ActionI} interface that does nothing.
+ * Use as a placeholder for testing purposes.
+ *
+ * @author mmwarowny
+ *
+ */
+public final class NullAction extends BaseAction<Void>
+{
+ public static final class Builder extends BaseAction.Builder<NullAction>
+ {
+ public NullAction build()
+ {
+ return new NullAction(this);
+ }
+ }
+
+ public static Builder newBuilder()
+ {
+ return new Builder();
+ }
+
+ protected NullAction(Builder builder)
+ {
+ super(builder);
+ }
+
+ @Override
+ public TaskI<Void> createTask(AlignViewportI viewport,
+ List<ArgumentI> args, Credentials credentials)
+ {
+ return new NullTask();
+ }
+
+ @Override
+ public boolean isActive(AlignmentViewport viewport)
+ {
+ return false;
+ }
+}
--- /dev/null
+package jalview.ws2.actions;
+
+import java.util.Collections;
+import java.util.List;
+
+import jalview.ws2.actions.api.JobI;
+import jalview.ws2.actions.api.TaskEventListener;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.JobStatus;
+
+/**
+ * An empty task returned by the {@link NullAction}. Use as a placeholder for
+ * testing purposes.
+ *
+ * @author mmwarowny
+ *
+ */
+class NullTask implements TaskI<Void>
+{
+ @Override
+ public long getUid()
+ {
+ return 0;
+ }
+
+ @Override
+ public JobStatus getStatus()
+ {
+ return JobStatus.READY;
+ }
+
+ @Override
+ public List<? extends JobI> getSubJobs()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void init() throws Exception
+ {
+ }
+
+ @Override
+ public boolean poll() throws Exception
+ {
+ return true;
+ }
+
+ @Override
+ public void complete() throws Exception
+ {
+ }
+
+ @Override
+ public Void getResult()
+ {
+ return null;
+ }
+
+ @Override
+ public void cancel()
+ {
+ }
+
+ @Override
+ public void addTaskEventListener(TaskEventListener<Void> listener)
+ {
+ }
+
+ @Override
+ public void removeTaskEventListener(TaskEventListener<Void> listener)
+ {
+ }
+}
\ No newline at end of file
--- /dev/null
+package jalview.ws2.actions;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import jalview.ws2.actions.api.TaskI;
+
+public class PollingTaskExecutor
+{
+ private static final Map<ScheduledExecutorService, PollingTaskExecutor> executorPool =
+ Collections.synchronizedMap(new WeakHashMap<>());
+
+ public static PollingTaskExecutor fromPool(ScheduledExecutorService executor)
+ {
+ return executorPool.computeIfAbsent(executor, PollingTaskExecutor::new);
+ }
+
+ private final ScheduledExecutorService executor;
+
+ public PollingTaskExecutor(ScheduledExecutorService executor)
+ {
+ this.executor = executor;
+ }
+
+ public Future<?> submit(TaskI<?> task)
+ {
+ Objects.requireNonNull(task);
+ return executor.scheduleWithFixedDelay(
+ new TaskRunnable(task), 0, 2, TimeUnit.SECONDS);
+ }
+
+ private static class TaskRunnable implements Runnable
+ {
+ private final TaskI<?> task;
+
+ private static final int STAGE_INIT = 0;
+
+ private static final int STAGE_POLLING = 2;
+
+ private static final int STAGE_FINISHED = 3;
+
+ private static final int STAGE_STOPPED = 4;
+
+ private int stage = STAGE_INIT;
+
+ private static final int MAX_POLL_RETRY = 5;
+
+ private int pollRetryCount = 0;
+
+ private TaskRunnable(TaskI<?> task)
+ {
+ this.task = task;
+ }
+
+ @Override
+ public void run()
+ {
+ if (task.getStatus().isDone())
+ {
+ stage = STAGE_STOPPED;
+ }
+ if (stage == STAGE_INIT)
+ {
+ try
+ {
+ task.init();
+ stage = STAGE_POLLING;
+ } catch (Exception e)
+ {
+ stage = STAGE_STOPPED;
+ throw new CompletionException(e);
+ }
+ }
+ try
+ {
+ if (stage == STAGE_POLLING && task.poll())
+ {
+ stage = STAGE_FINISHED;
+ }
+ if (stage == STAGE_FINISHED)
+ {
+ task.complete();
+ stage = STAGE_STOPPED;
+ }
+ } catch (Exception e)
+ {
+ if (++pollRetryCount > MAX_POLL_RETRY || e instanceof RuntimeException)
+ {
+ task.cancel();
+ stage = STAGE_STOPPED;
+ throw new CompletionException(e);
+ }
+ }
+ if (stage == STAGE_STOPPED)
+ {
+ throw new CancellationException();
+ }
+ }
+ }
+}
import java.util.List;
import java.util.Objects;
+import jalview.api.AlignViewportI;
import jalview.viewmodel.AlignmentViewport;
import jalview.ws.params.ArgumentI;
import jalview.ws2.actions.BaseAction;
+import jalview.ws2.actions.BaseTask;
+import jalview.ws2.actions.PollingTaskExecutor;
import jalview.ws2.actions.api.TaskEventListener;
import jalview.ws2.actions.api.TaskI;
import jalview.ws2.api.Credentials;
}
@Override
- public TaskI<AlignmentResult> perform(AlignmentViewport viewport,
- List<ArgumentI> args, Credentials credentials,
- TaskEventListener<AlignmentResult> handler)
+ public AlignmentTask createTask(AlignViewportI viewport,
+ List<ArgumentI> args, Credentials credentials)
{
- var msa = viewport.getAlignmentView(true);
- var task = new AlignmentTask(
- client, this, args, credentials, msa, viewport, submitGaps, handler);
- task.start(viewport.getServiceExecutor());
- return task;
+ return new AlignmentTask(
+ client, this, args, credentials, viewport, submitGaps);
}
/**
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jalview.analysis.SeqsetUtils;
import jalview.analysis.SeqsetUtils.SequenceInfo;
import jalview.api.AlignViewportI;
-import jalview.bin.Cache;
import jalview.bin.Console;
import jalview.datamodel.AlignedCodonFrame;
import jalview.datamodel.Alignment;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceI;
import jalview.ws.params.ArgumentI;
-import jalview.ws2.actions.AbstractPollableTask;
+import jalview.ws2.actions.BaseTask;
import jalview.ws2.actions.ServiceInputInvalidException;
-import jalview.ws2.actions.api.TaskEventListener;
import jalview.ws2.api.Credentials;
import jalview.ws2.api.JobStatus;
import jalview.ws2.client.api.AlignmentWebServiceClientI;
* @author mmwarowny
*
*/
-class AlignmentTask extends AbstractPollableTask<AlignmentJob, AlignmentResult>
+class AlignmentTask extends BaseTask<AlignmentJob, AlignmentResult>
{
/* task parameters set in the constructor */
private final AlignmentWebServiceClientI client;
private final AlignmentView msa; // a.k.a. input
- private final AlignViewportI viewport;
-
private final boolean submitGaps;
private final AlignmentI currentView;
AlignmentTask(AlignmentWebServiceClientI client, AlignmentAction action,
List<ArgumentI> args, Credentials credentials,
- AlignmentView msa, AlignViewportI viewport, boolean submitGaps,
- TaskEventListener<AlignmentResult> eventListener)
+ AlignViewportI viewport, boolean submitGaps)
{
- super(client, args, credentials, eventListener);
+ super(client, args, credentials);
this.client = client;
this.action = action;
- this.msa = msa;
- this.viewport = viewport;
+ this.msa = viewport.getAlignmentView(true);
this.submitGaps = submitGaps;
this.currentView = viewport.getAlignment();
this.dataset = viewport.getAlignment().getDataset();
}
@Override
- protected List<AlignmentJob> prepare() throws ServiceInputInvalidException
+ protected List<AlignmentJob> prepareJobs() throws ServiceInputInvalidException
{
Console.info(format("starting alignment service %s:%s",
client.getClientName(), action.getName()));
}
@Override
- protected AlignmentResult done() throws IOException
+ protected AlignmentResult collectResult(List<AlignmentJob> jobs) throws IOException
{
IOException lastIOE = null;
for (AlignmentJob job : jobs)
--- /dev/null
+package jalview.ws2.actions.annotation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+import jalview.analysis.AlignmentAnnotationUtils;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.PollableAlignCalcWorkerI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.workers.AlignCalcWorker;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.actions.api.TaskEventListener;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.Credentials;
+
+public class AlignCalcWorkerAdapter extends AlignCalcWorker implements PollableAlignCalcWorkerI
+{
+ private static int calcCount = 0;
+
+ private final int calcNumber = calcCount++;
+
+ private final AnnotationAction action;
+
+ private final List<ArgumentI> args;
+
+ private final Credentials credentials;
+
+ private TaskI<AnnotationResult> currentTask = null;
+
+ private TaskEventListener<AnnotationResult> taskListener = new TaskEventListener<>()
+ {
+ @Override
+ public void taskCompleted(TaskI source, AnnotationResult result)
+ {
+ int graphGroup = alignViewport.getAlignment().getLastGraphGroup();
+ List<AlignmentAnnotation> annotations = new ArrayList<>();
+ for (AlignmentAnnotation ala : result.getAnnotations())
+ {
+ if (ala.graphGroup > 0)
+ ala.graphGroup += graphGroup;
+ var newAnnot = alignViewport.getAlignment()
+ .updateFromOrCopyAnnotation(ala);
+ if (ala.sequenceRef != null)
+ {
+ ala.sequenceRef.addAlignmentAnnotation(newAnnot);
+ newAnnot.adjustForAlignment();
+ AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
+ newAnnot, newAnnot.label, newAnnot.getCalcId());
+ }
+ annotations.add(newAnnot);
+ }
+ updateOurAnnots(annotations);
+ listener.workerHasResult(
+ AlignCalcWorkerAdapter.this,
+ new AnnotationResult(
+ annotations,
+ result.transferFeatures,
+ result.featureColours,
+ result.featureFilters));
+ }
+ };
+
+ public AlignCalcWorkerAdapter(AlignViewportI alignViewport, AlignmentViewPanel alignPanel, AnnotationAction action,
+ List<ArgumentI> args, Credentials credentials)
+ {
+ super(alignViewport, alignPanel);
+ this.action = action;
+ this.args = args;
+ this.credentials = credentials;
+ }
+
+ @Override
+ public String getCalcName()
+ {
+ return action.getWebService().getName();
+ }
+
+ @Override
+ public void updateAnnotation()
+ {
+
+ }
+
+ private void updateOurAnnots(List<AlignmentAnnotation> newAnnots)
+ {
+ List<AlignmentAnnotation> oldAnnots = ourAnnots != null ? ourAnnots : List.of();
+ AlignmentI alignment = alignViewport.getAlignment();
+ for (AlignmentAnnotation annotation : oldAnnots)
+ if (!newAnnots.contains(annotation))
+ alignment.deleteAnnotation(annotation);
+ for (AlignmentAnnotation annotation : newAnnots)
+ alignment.validateAnnotation(annotation);
+ ourAnnots = Collections.synchronizedList(newAnnots);
+ }
+
+ @Override
+ public synchronized void startUp() throws Throwable
+ {
+ if (alignViewport.isClosed())
+ {
+ calcMan.disableWorker(this);
+ abortAndDestroy();
+ throw new IllegalStateException("Starting calculation for closed viewport");
+ }
+ currentTask = action.createTask(alignViewport, args, credentials);
+ currentTask.addTaskEventListener(taskListener);
+ currentTask.init();
+ listener.workerStarted(this);
+ }
+
+ @Override
+ public boolean poll() throws Throwable
+ {
+ return currentTask.poll();
+ }
+
+ @Override
+ public synchronized void cancel()
+ {
+ try
+ {
+ currentTask.cancel();
+ } finally
+ {
+ currentTask.removeTaskEventListener(taskListener);
+ listener.workerStopped(this);
+ }
+ }
+
+ @Override
+ public synchronized void done()
+ {
+ try
+ {
+ currentTask.complete();
+ } catch (Exception e)
+ {
+ } finally
+ {
+ currentTask.removeTaskEventListener(taskListener);
+ listener.workerStopped(this);
+ }
+ }
+
+ @Override
+ public boolean isDeletable()
+ {
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "AlignCalcWorkerAdapter-" + calcNumber + " for " + getCalcName();
+ }
+
+ public interface WorkerListener extends java.util.EventListener
+ {
+ default void workerStarted(AlignCalcWorkerAdapter source)
+ {
+ };
+
+ default void workerStopped(AlignCalcWorkerAdapter source)
+ {
+ };
+
+ default void workerHasResult(AlignCalcWorkerAdapter source, AnnotationResult result)
+ {
+ };
+
+ static final WorkerListener NULL_LISTENER = new WorkerListener()
+ {
+ };
+ }
+
+ private WorkerListener listener = WorkerListener.NULL_LISTENER;
+
+ public void setWorkerListener(WorkerListener listener)
+ {
+ if (listener == null) listener = WorkerListener.NULL_LISTENER;
+ this.listener = listener;
+ }
+}
import java.util.List;
import java.util.Objects;
+import jalview.api.AlignViewportI;
import jalview.viewmodel.AlignmentViewport;
import jalview.ws.params.ArgumentI;
import jalview.ws2.actions.BaseAction;
requireAlignedSequences = builder.requireAlignedSequences;
filterSymbols = builder.filterSymbols;
}
-
+
@Override
- public TaskI<AnnotationResult> perform(AlignmentViewport viewport,
- List<ArgumentI> args, Credentials credentials,
- TaskEventListener<AnnotationResult> handler)
+ public AnnotationTask createTask(AlignViewportI viewport,
+ List<ArgumentI> args, Credentials credentials)
{
- var task = new AnnotationTask(client, this, args, credentials, viewport,
- handler);
- task.start(viewport.getCalcManager());
- return task;
+ return new AnnotationTask(client, this, args, credentials, viewport);
}
-
+
/**
* Return if this action is an alignment analysis service.
*/
* @param job
* web service job
* @param sequences
- * sequences the annotations will be added to
+ * features and alignment annotation added to these will be
+ * imported to the dataset for the alignment
* @param colours
* container for feature colours
* @param filters
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jalview.analysis.AlignmentAnnotationUtils;
-import jalview.api.AlignCalcManagerI2;
-import jalview.api.AlignCalcWorkerI;
import jalview.api.AlignViewportI;
import jalview.api.FeatureColourI;
-import jalview.api.PollableAlignCalcWorkerI;
-import jalview.bin.Cache;
-import jalview.bin.Console;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.AnnotatedCollectionI;
import jalview.datamodel.Mapping;
import jalview.datamodel.SequenceI;
import jalview.datamodel.features.FeatureMatcherSetI;
-import jalview.schemes.FeatureSettingsAdapter;
-import jalview.util.ArrayUtils;
import jalview.util.MapList;
-import jalview.util.MathUtils;
-import jalview.util.Pair;
-import jalview.workers.AlignCalcWorker;
import jalview.ws.params.ArgumentI;
-import jalview.ws2.actions.AbstractPollableTask;
-import jalview.ws2.actions.BaseJob;
+import jalview.ws2.actions.BaseTask;
import jalview.ws2.actions.ServiceInputInvalidException;
-import jalview.ws2.actions.api.JobI;
-import jalview.ws2.actions.api.TaskEventListener;
-import jalview.ws2.actions.api.TaskI;
import jalview.ws2.api.Credentials;
import jalview.ws2.api.JobStatus;
-import jalview.ws2.api.WebServiceJobHandle;
import jalview.ws2.client.api.AnnotationWebServiceClientI;
-import jalview.ws2.helpers.DelegateJobEventListener;
-import jalview.ws2.helpers.TaskEventSupport;
-public class AnnotationTask implements TaskI<AnnotationResult>
+public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
{
- private final long uid = MathUtils.getUID();
-
private AnnotationWebServiceClientI client;
private final AnnotationAction action;
- private final List<ArgumentI> args;
-
- private final Credentials credentials;
-
- private final AlignViewportI viewport;
-
- private final TaskEventSupport<AnnotationResult> eventHandler;
-
- private JobStatus taskStatus = null;
-
- private AlignCalcWorkerAdapter worker = null;
-
- private List<AnnotationJob> jobs = Collections.emptyList();
-
- private AnnotationResult result = null;
-
- private DelegateJobEventListener<AnnotationResult> jobEventHandler;
-
- private class AlignCalcWorkerAdapter extends AlignCalcWorker
- implements PollableAlignCalcWorkerI
- {
- private boolean restarting = false;
-
- AlignCalcWorkerAdapter(AlignCalcManagerI2 calcMan)
- {
- super(viewport, null);
- this.calcMan = calcMan;
- }
-
- String getServiceName()
- {
- return action.getWebService().getName();
- }
-
- @Override
- public void startUp() throws Throwable
- {
- if (alignViewport.isClosed())
- {
- stop();
- throw new IllegalStateException("Starting annotation for closed viewport");
- }
- if (restarting)
- eventHandler.fireTaskRestarted();
- else
- restarting = true;
- jobs = Collections.emptyList();
- try
- {
- jobs = prepare();
- } catch (ServiceInputInvalidException e)
- {
- setStatus(JobStatus.INVALID);
- eventHandler.fireTaskException(e);
- throw e;
- }
- setStatus(JobStatus.READY);
- eventHandler.fireTaskStarted(jobs);
- for (var job : jobs)
- {
- job.addPropertyChagneListener(jobEventHandler);
- }
- try
- {
- startJobs();
- } catch (IOException e)
- {
- eventHandler.fireTaskException(e);
- cancelJobs();
- setStatus(JobStatus.SERVER_ERROR);
- throw e;
- }
- setStatus(JobStatus.SUBMITTED);
- }
-
- @Override
- public boolean poll() throws Throwable
- {
- boolean done = AnnotationTask.this.poll();
- updateGlobalStatus();
- if (done)
- {
- retrieveAndProcessResult();
- eventHandler.fireTaskCompleted(result);
- }
- return done;
- }
-
- private void retrieveAndProcessResult() throws IOException
- {
- result = retrieveResult();
- updateOurAnnots(result.annotations);
- if (result.transferFeatures)
- {
- final var featureColours = result.featureColours;
- final var featureFilters = result.featureFilters;
- viewport.applyFeaturesStyle(new FeatureSettingsAdapter()
- {
- @Override
- public FeatureColourI getFeatureColour(String type)
- {
- return featureColours.get(type);
- }
-
- @Override
- public FeatureMatcherSetI getFeatureFilters(String type)
- {
- return featureFilters.get(type);
- }
-
- @Override
- public boolean isFeatureDisplayed(String type)
- {
- return featureColours.containsKey(type);
- }
- });
- }
- }
-
- @Override
- public void updateAnnotation()
- {
- var job = jobs.size() > 0 ? jobs.get(0) : null;
- if (!calcMan.isWorking(this) && job != null)
- {
- var ret = updateResultAnnotation(job, job.returnedAnnotations);
- updateOurAnnots(ret.get0());
- }
- }
-
- private void updateOurAnnots(List<AlignmentAnnotation> newAnnots)
- {
- List<AlignmentAnnotation> oldAnnots = ourAnnots != null ? ourAnnots : Collections.emptyList();
- ourAnnots = newAnnots;
- AlignmentI alignment = viewport.getAlignment();
- for (AlignmentAnnotation an : oldAnnots)
- {
- if (!newAnnots.contains(an))
- {
- alignment.deleteAnnotation(an);
- }
- }
- oldAnnots.clear();
- for (AlignmentAnnotation an : ourAnnots)
- {
- viewport.getAlignment().validateAnnotation(an);
- }
- }
-
- @Override
- public void cancel()
- {
- cancelJobs();
- }
-
- void stop()
- {
- calcMan.disableWorker(this);
- super.abortAndDestroy();
- }
+ private final AlignmentI alignment;
- @Override
- public void done()
- {
- for (var job : jobs)
- {
- if (job.isInputValid() && !job.isCompleted())
- {
- /* if done was called but job is not completed then it
- * must have been stopped by an exception */
- job.setStatus(JobStatus.SERVER_ERROR);
- }
- }
- updateGlobalStatus();
- // dispose of unfinished jobs just in case
- cancelJobs();
- }
-
- @Override
- public String toString()
- {
- return AnnotationTask.this.toString() + "$AlignCalcWorker@"
- + Integer.toHexString(hashCode());
- }
- }
+ private final AnnotatedCollectionI selectionGroup;
public AnnotationTask(AnnotationWebServiceClientI client,
AnnotationAction action, List<ArgumentI> args, Credentials credentials,
- AlignViewportI viewport,
- TaskEventListener<AnnotationResult> eventListener)
+ AlignViewportI viewport)
{
+ super(client, args, credentials);
this.client = client;
this.action = action;
- this.args = args;
- this.credentials = credentials;
- this.viewport = viewport;
- this.eventHandler = new TaskEventSupport<>(this, eventListener);
- this.jobEventHandler = new DelegateJobEventListener<>(this.eventHandler);
- }
-
- @Override
- public long getUid()
- {
- return uid;
- }
-
- public void start(AlignCalcManagerI2 calcManager)
- {
- if (this.worker != null)
- throw new IllegalStateException("task already started");
- this.worker = new AlignCalcWorkerAdapter(calcManager);
- if (taskStatus != JobStatus.CANCELLED)
- {
- List<AlignCalcWorkerI> oldWorkers = calcManager.getWorkersOfClass(
- AlignCalcWorkerAdapter.class);
- for (var worker : oldWorkers)
- {
- if (action.getWebService().getName().equalsIgnoreCase(
- ((AlignCalcWorkerAdapter) worker).getServiceName()))
- {
- // remove interactive workers for the same service.
- calcManager.removeWorker(worker);
- calcManager.cancelWorker(worker);
- }
- }
- if (action.getWebService().isInteractive())
- calcManager.registerWorker(worker);
- else
- calcManager.startWorker(worker);
- }
- }
-
- /*
- * The following methods are mostly copied from the {@link AbstractPollableTask}
- * TODO: move common functionality to a base class
- */
- @Override
- public JobStatus getStatus()
- {
- return taskStatus;
- }
-
- private void setStatus(JobStatus status)
- {
- if (this.taskStatus != status)
- {
- Console.debug(String.format("%s status change to %s", this, status.name()));
- this.taskStatus = status;
- eventHandler.fireTaskStatusChanged(status);
- }
- }
-
- private void updateGlobalStatus()
- {
- int precedence = -1;
- for (BaseJob job : jobs)
- {
- JobStatus status = job.getStatus();
- int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status);
- if (precedence < jobPrecedence)
- precedence = jobPrecedence;
- }
- if (precedence >= 0)
- {
- setStatus(JobStatus.statusPrecedence[precedence]);
- }
- }
-
- @Override
- public List<? extends JobI> getSubJobs()
- {
- return jobs;
+ this.alignment = viewport.getAlignment();
+ this.selectionGroup = viewport.getSelectionGroup();
}
/**
* @throws ServiceInputInvalidException
* input data is not valid
*/
- private List<AnnotationJob> prepare() throws ServiceInputInvalidException
+ @Override
+ public List<AnnotationJob> prepareJobs() throws ServiceInputInvalidException
{
- AlignmentI alignment = viewport.getAlignment();
if (alignment == null || alignment.getWidth() <= 0 ||
alignment.getSequences() == null)
throw new ServiceInputInvalidException("Alignment does not contain sequences");
throw new ServiceInputInvalidException(
action.getFullName() + " does not allow protein sequences");
boolean bySequence = !action.isAlignmentAnalysis();
- AnnotatedCollectionI inputSeqs = bySequence ? viewport.getSelectionGroup() : null;
+ AnnotatedCollectionI inputSeqs = bySequence ? selectionGroup : null;
if (inputSeqs == null || inputSeqs.getWidth() <= 0 ||
inputSeqs.getSequences() == null || inputSeqs.getSequences().size() < 1)
inputSeqs = alignment;
return List.of(job);
}
- private void startJobs() throws IOException
- {
- for (BaseJob job : jobs)
- {
- if (job.isInputValid() && job.getStatus() == JobStatus.READY)
- {
- var serverJob = client.submit(job.getInputSequences(),
- args, credentials);
- job.setServerJob(serverJob);
- job.setStatus(JobStatus.SUBMITTED);
- }
- }
- }
-
- private boolean poll() throws IOException
- {
- boolean allDone = true;
- for (BaseJob job : jobs)
- {
- if (job.isInputValid() && !job.getStatus().isDone())
- {
- WebServiceJobHandle serverJob = job.getServerJob();
- job.setStatus(client.getStatus(serverJob));
- job.setLog(client.getLog(serverJob));
- job.setErrorLog(client.getErrorLog(serverJob));
- }
- allDone &= job.isCompleted();
- }
- return allDone;
- }
-
- private AnnotationResult retrieveResult() throws IOException
+ @Override
+ protected AnnotationResult collectResult(List<AnnotationJob> jobs) throws IOException
{
final Map<String, FeatureColourI> featureColours = new HashMap<>();
final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
* sequence, excluding regions not annotated due to gapMap/column
* visibility */
- // update calcId if it is not already set on returned annotation
- for (AlignmentAnnotation annot : returnedAnnot)
- {
- if (annot.getCalcId() == null || annot.getCalcId().isEmpty())
- {
- annot.setCalcId(action.getFullName());
- }
- annot.autoCalculated = action.isAlignmentAnalysis() &&
- action.getWebService().isInteractive();
- }
- job.returnedAnnotations = returnedAnnot;
- job.featureColours = featureColours;
- job.featureFilters = featureFilters;
- var ret = updateResultAnnotation(job, returnedAnnot);
- var annotations = ret.get0();
- var transferFeatures = ret.get1();
- return new AnnotationResult(annotations, transferFeatures, featureColours,
- featureFilters);
- }
-
- private Pair<List<AlignmentAnnotation>, Boolean> updateResultAnnotation(
- AnnotationJob job, List<AlignmentAnnotation> annotations)
- {
- List<AlignmentAnnotation> newAnnots = new ArrayList<>();
- // update graphGroup for all annotation
- /* find a graphGroup greater than any existing one, could be moved
- * to Alignment#getNewGraphGroup() - returns next unused graph group */
- int graphGroup = 1;
- if (viewport.getAlignment().getAlignmentAnnotation() != null)
+ udpateCalcId(returnedAnnot);
+ for (AlignmentAnnotation ala : returnedAnnot)
{
- for (var ala : viewport.getAlignment().getAlignmentAnnotation())
- {
- graphGroup = Math.max(graphGroup, ala.graphGroup);
- }
- }
- // update graphGroup in the annotation rows returned form service'
- /* TODO: look at sequence annotation rows and update graph groups in the
- * case of reference annotation */
- for (AlignmentAnnotation ala : annotations)
- {
- if (ala.graphGroup > 0)
- ala.graphGroup += graphGroup;
SequenceI aseq = null;
- // transfer sequence refs and adjust gapMap
if (ala.sequenceRef != null)
{
- aseq = job.seqNames.get(ala.sequenceRef.getName());
+ SequenceI seq = job.seqNames.get(ala.sequenceRef.getName());
+ aseq = seq.getRootDatasetSequence();
}
ala.sequenceRef = aseq;
-
- Annotation[] resAnnot = ala.annotations;
- boolean[] gapMap = job.gapMap;
- Annotation[] gappedAnnot = new Annotation[Math.max(
- viewport.getAlignment().getWidth(), gapMap.length)];
- for (int p = 0, ap = job.start; ap < gappedAnnot.length; ap++)
- {
- if (gapMap.length > ap && !gapMap[ap])
- gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
- else if (p < resAnnot.length)
- gappedAnnot[ap] = resAnnot[p++];
- // is this loop exhaustive of resAnnot?
- }
- ala.annotations = gappedAnnot;
-
- AlignmentAnnotation newAnnot = viewport.getAlignment()
- .updateFromOrCopyAnnotation(ala);
- if (aseq != null)
- {
- aseq.addAlignmentAnnotation(newAnnot);
- newAnnot.adjustForAlignment();
- AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
- newAnnot, newAnnot.label, newAnnot.getCalcId());
- }
- newAnnots.add(newAnnot);
+ Annotation[] gappedAnnots = createGappedAnnotations(ala.annotations, job.start, job.gapMap);
+ ala.annotations = gappedAnnots;
}
- boolean transferFeatures = false;
+ boolean hasFeatures = false;
for (SequenceI sq : job.getInputSequences())
{
- if (!sq.getFeatures().hasFeatures() &&
- (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
+ if (!sq.getFeatures().hasFeatures() && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty()))
continue;
- transferFeatures = true;
+ hasFeatures = true;
SequenceI seq = job.seqNames.get(sq.getName());
- SequenceI dseq;
- int start = job.start, end = job.end;
- boolean[] gapMap = job.gapMap;
- ContiguousI seqRange = seq.findPositions(start, end);
- while ((dseq = seq).getDatasetSequence() != null)
- {
- seq = seq.getDatasetSequence();
- }
- List<ContiguousI> sourceRange = new ArrayList<>();
- if (gapMap.length >= end)
- {
- int lastcol = start, col = start;
- do
- {
- if (col == end || !gapMap[col])
- {
- if (lastcol <= (col - 1))
- {
- seqRange = seq.findPositions(lastcol, col);
- sourceRange.add(seqRange);
- }
- lastcol = col + 1;
- }
- } while (col++ < end);
- }
- else
- {
- sourceRange.add(seq.findPositions(start, end));
- }
-
- int i = 0;
- int sourceStartEnd[] = new int[sourceRange.size() * 2];
- for (ContiguousI range : sourceRange)
- {
- sourceStartEnd[i++] = range.getBegin();
- sourceStartEnd[i++] = range.getEnd();
- }
+ SequenceI datasetSeq = seq.getRootDatasetSequence();
+ List<ContiguousI> sourceRange = findContiguousRanges(datasetSeq, job.gapMap, job.start, job.end);
+ int[] sourceStartEnd = ContiguousI.toStartEndArray(sourceRange);
Mapping mp = new Mapping(new MapList(
sourceStartEnd, new int[]
- { seq.getStart(), seq.getEnd() }, 1, 1));
- dseq.transferAnnotation(sq, mp);
+ { datasetSeq.getStart(), datasetSeq.getEnd() }, 1, 1));
+ datasetSeq.transferAnnotation(sq, mp);
}
- return new Pair<>(newAnnots, transferFeatures);
- }
-
- @Override
- public AnnotationResult getResult()
- {
- return result;
+ return new AnnotationResult(returnedAnnot, hasFeatures, featureColours, featureFilters);
}
- @Override
- public void cancel()
+ /**
+ * Updates calcId on provided annotations if not already set.
+ */
+ public void udpateCalcId(Iterable<AlignmentAnnotation> annotations)
{
- setStatus(JobStatus.CANCELLED);
- if (worker != null)
+ for (var annotation : annotations)
{
- worker.stop();
+ if (annotation.getCalcId() == null || annotation.getCalcId().isEmpty())
+ {
+ annotation.setCalcId(action.getFullName());
+ }
+ annotation.autoCalculated = action.isAlignmentAnalysis() &&
+ action.getWebService().isInteractive();
}
- cancelJobs();
}
- public void cancelJobs()
+ private Annotation[] createGappedAnnotations(Annotation[] annotations, int start, boolean[] gapMap)
{
- for (BaseJob job : jobs)
+ var size = Math.max(alignment.getWidth(), gapMap.length);
+ Annotation[] gappedAnnotations = new Annotation[size];
+ for (int p = 0, ap = start; ap < size; ap++)
{
- if (!job.isCompleted())
+ if (gapMap != null && gapMap.length > ap && !gapMap[ap])
+ {
+ gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN);
+ }
+ else if (p < annotations.length)
{
- try
- {
- if (job.getServerJob() != null)
- {
- client.cancel(job.getServerJob());
- }
- job.setStatus(JobStatus.CANCELLED);
- } catch (IOException e)
- {
- Console.error(String.format(
- "failed to cancel job %s", job.getServerJob()), e);
- }
+ gappedAnnotations[ap] = annotations[p++];
}
}
+ return gappedAnnotations;
}
- @Override
- public String toString()
+ private List<ContiguousI> findContiguousRanges(SequenceI seq, boolean[] gapMap, int start, int end)
{
- var status = taskStatus != null ? taskStatus.name() : "UNSET";
- return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status);
+ if (gapMap == null || gapMap.length < end)
+ return List.of(seq.findPositions(start, end));
+ List<ContiguousI> ranges = new ArrayList<>();
+ int lastcol = start, col = start;
+ do
+ {
+ if (col == end || !gapMap[col])
+ {
+ if (lastcol < col)
+ ranges.add(seq.findPositions(lastcol, col));
+ lastcol = col + 1;
+ }
+ } while (++col <= end);
+ return ranges;
}
}
import javax.swing.Icon;
+import jalview.api.AlignViewportI;
import jalview.viewmodel.AlignmentViewport;
import jalview.ws.params.ArgumentI;
import jalview.ws2.api.CredentialType;
EnumSet<CredentialType> getRequiredCredentials();
/**
- * Run the action, create and start a new task with provided viewport,
- * arguments and credentials and attach the handler to the task. The
- * implementations of this method are responsible for starting the task using
- * execution method appropriate for the action class.
+ * Create a new task with provided viewport, arguments and credentials ready
+ * to be started.
*
* @param viewport
* current alignment viewport
* job parameters appropriate for the service
* @param credentials
* optional user credentials
- * @param handler
- * event handler attached to the new task
- * @return new running task
+ * @return new task
*/
- TaskI<R> perform(AlignmentViewport viewport, List<ArgumentI> args,
- Credentials credentials, TaskEventListener<R> handler);
+ TaskI<R> createTask(AlignViewportI viewport, List<ArgumentI> args,
+ Credentials credentials);
/**
* Return if the action is currently active for the given viewport. Active
import java.util.List;
+import jalview.bin.Console;
import jalview.ws2.api.JobStatus;
import jalview.ws2.api.WebServiceJobHandle;
* @param subJobs
* list of sub-jobs for this run
*/
- void taskStarted(TaskI<T> source, List<? extends JobI> subJobs);
+ default void taskStarted(TaskI<T> source, List<? extends JobI> subJobs) {};
/**
* Invoked when the global task status has changed.
* @param status
* new task status
*/
- void taskStatusChanged(TaskI<T> source, JobStatus status);
+ default void taskStatusChanged(TaskI<T> source, JobStatus status) {};
/**
* Invoked when the task has completed. If the task completed with a result,
* @param result
* computation result or null if result not present
*/
- void taskCompleted(TaskI<T> source, T result);
+ default void taskCompleted(TaskI<T> source, T result) {};
/**
* Invoked when an unhandled exception has occurred during task execution.
* @param e
* exception
*/
- void taskException(TaskI<T> source, Exception e);
-
- /**
- * Invoked when the task had been restarted. This event is only applicable to
- * restartable tasks and will precede each {@link #taskStarted} after the
- * first one.
- *
- * @param source
- * task this event originates from
- */
- void taskRestarted(TaskI<T> source);
+ default void taskException(TaskI<T> source, Exception e) {};
/**
* Invoked when the status of a sub-job has changed.
* @param status
* new job status
*/
- void subJobStatusChanged(TaskI<T> source, JobI job, JobStatus status);
+ default void subJobStatusChanged(TaskI<T> source, JobI job, JobStatus status) {};
/**
* Invoked when a log string of the sub-job has changed.
* @param log
* new log string
*/
- void subJobLogChanged(TaskI<T> source, JobI job, String log);
+ default void subJobLogChanged(TaskI<T> source, JobI job, String log) {};
/**
* Invoked when an error log string of the sub-job has changed.
* @param log
* new log string
*/
- void subJobErrorLogChanged(TaskI<T> source, JobI job, String log);
+ default void subJobErrorLogChanged(TaskI<T> source, JobI job, String log) {};
+
+ @SuppressWarnings("rawtypes")
+ static final TaskEventListener NULL_LISTENER = new TaskEventListener()
+ {
+ @Override
+ public void taskStarted(TaskI source, List subJobs)
+ {
+ Console.info("task started with " + subJobs.size() + " jobs");
+ }
+
+ @Override
+ public void taskStatusChanged(TaskI source, JobStatus status)
+ {
+ Console.info("task status " + status);
+ }
+
+ @Override
+ public void taskCompleted(TaskI source, Object result)
+ {
+ Console.info("task completed");
+ }
+
+ @Override
+ public void taskException(TaskI source, Exception e)
+ {
+ Console.info("task failed", e);
+ }
+
+ @Override
+ public void subJobStatusChanged(TaskI source, JobI job,
+ JobStatus status)
+ {
+ Console.info("sub-job " + job.getInternalId() + " status " + status);
+ }
+
+ @Override
+ public void subJobLogChanged(TaskI source, JobI job, String log)
+ {
+ }
+
+ @Override
+ public void subJobErrorLogChanged(TaskI source, JobI job, String log)
+ {
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ static <T> TaskEventListener<T> nullListener()
+ {
+ return (TaskEventListener<T>) NULL_LISTENER;
+ }
}
*/
List<? extends JobI> getSubJobs();
+ void addTaskEventListener(TaskEventListener<T> listener);
+
+ void removeTaskEventListener(TaskEventListener<T> listener);
+
/**
* Get the last result of the task or {@code null} if not present. Note that
* the result is subject to change for restartable tasks.
*/
T getResult();
+ public void init() throws Exception;
+
+ public boolean poll() throws Exception;
+
+ public void complete() throws Exception;
+
/**
* Cancel the task, stop all sub-jobs running on a server and stop all threads
* managing this task.
--- /dev/null
+package jalview.ws2.actions.hmmer;
+
+import java.util.List;
+import java.util.Objects;
+
+import jalview.api.AlignViewportI;
+import jalview.datamodel.AlignmentI;
+import jalview.viewmodel.AlignmentViewport;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.actions.BaseAction;
+import jalview.ws2.actions.PollingTaskExecutor;
+import jalview.ws2.actions.api.TaskEventListener;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.Credentials;
+import jalview.ws2.client.api.AlignmentWebServiceClientI;
+import jalview.ws2.client.api.WebServiceClientI;
+
+/**
+ * Implementation of the {@link BaseAction} for the phmmer client. This is NOT
+ * how you should implement it. The action should be more generic and cover
+ * range of similar services.
+ *
+ * @author mmwarowny
+ *
+ */
+// FIXME: Not an alignment action (temporary hack)
+public class PhmmerAction extends BaseAction<AlignmentI>
+{
+ public static class Builder extends BaseAction.Builder<PhmmerAction>
+ {
+ protected AlignmentWebServiceClientI client;
+
+ private Builder(AlignmentWebServiceClientI client)
+ {
+ super();
+ Objects.requireNonNull(client);
+ this.client = client;
+ }
+
+ public PhmmerAction build()
+ {
+ return new PhmmerAction(this);
+ }
+ }
+
+ public static Builder newBuilder(AlignmentWebServiceClientI client)
+ {
+ return new Builder(client);
+ }
+
+ protected final AlignmentWebServiceClientI client;
+
+ public PhmmerAction(Builder builder)
+ {
+ super(builder);
+ client = builder.client;
+ }
+
+ public TaskI<AlignmentI> perform(AlignmentViewport viewport,
+ List<ArgumentI> args, Credentials credentials,
+ TaskEventListener<AlignmentI> handler)
+ {
+ var task = createTask(viewport, args, credentials);
+ task.addTaskEventListener(handler);
+ var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
+ var future = executor.submit(task);
+ task.setCancelAction(() -> { future.cancel(true); });
+ return task;
+ }
+
+ public PhmmerTask createTask(AlignViewportI viewport,
+ List<ArgumentI> args, Credentials credentials)
+ {
+ return new PhmmerTask(client, args, credentials, viewport.getAlignmentView(true));
+ }
+
+ @Override
+ public boolean isActive(AlignmentViewport viewport)
+ {
+ return false;
+ }
+}
--- /dev/null
+package jalview.ws2.actions.hmmer;
+
+import static jalview.util.Comparison.GapChars;
+
+import java.io.IOException;
+import java.util.List;
+
+import jalview.analysis.AlignSeq;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.actions.BaseJob;
+import jalview.ws2.actions.BaseTask;
+import jalview.ws2.actions.ServiceInputInvalidException;
+import jalview.ws2.api.Credentials;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.client.api.AlignmentWebServiceClientI;
+
+class PhmmerTask extends BaseTask<BaseJob, AlignmentI>
+{
+ private final AlignmentWebServiceClientI client;
+ private final AlignmentView view;
+
+ PhmmerTask(AlignmentWebServiceClientI client, List<ArgumentI> args,
+ Credentials credentials, AlignmentView view)
+ {
+ super(client, args, credentials);
+ this.client = client;
+ this.view = view;
+ }
+
+ @Override
+ protected List<BaseJob> prepareJobs() throws ServiceInputInvalidException
+ {
+ Console.info("Preparing sequence for phmmer job");
+ var sequence = view.getVisibleAlignment('-').getSequenceAt(0);
+ var seq = new Sequence(sequence.getName(),
+ AlignSeq.extractGaps(GapChars, sequence.getSequenceAsString()));
+ var job = new BaseJob(List.of(seq))
+ {
+ @Override
+ public boolean isInputValid()
+ {
+ return true;
+ }
+ };
+ job.setStatus(JobStatus.READY);
+ return List.of(job);
+ }
+
+ @Override
+ protected AlignmentI collectResult(List<BaseJob> jobs) throws IOException
+ {
+ var job = jobs.get(0);
+ var status = job.getStatus();
+ Console.info(String.format("phmmer finished job \"%s\" with status %s",
+ job.getServerJob().getJobId(), status));
+ if (status != JobStatus.COMPLETED)
+ return null;
+ var outputAlignment = client.getAlignment(job.getServerJob());
+ var querySeq = job.getInputSequences().get(0).deriveSequence();
+ {
+ AlignmentAnnotation refpos = null;
+ for (var annot : outputAlignment.getAlignmentAnnotation())
+ {
+ if (annot.sequenceRef == null && annot.label.equals("Reference Positions"))
+ {
+ refpos = annot;
+ break;
+ }
+ }
+ if (refpos != null)
+ {
+ querySeq = alignQeuryToReferencePositions(querySeq, refpos);
+ }
+ }
+ outputAlignment.insertSequenceAt(0, querySeq);
+ return outputAlignment;
+ }
+
+ private SequenceI alignQeuryToReferencePositions(SequenceI query, AlignmentAnnotation refpos)
+ {
+ var sequenceBuilder = new StringBuilder();
+ var index = 0;
+ for (Annotation a : refpos.annotations)
+ {
+ // TODO: we assume that the number of "x" annotations is equal to the number
+ // of residues. may need a safeguard against invalid input
+ if (a != null && a.displayCharacter.equals("x"))
+ {
+ char c;
+ do
+ c = query.getCharAt(index++);
+ while (Comparison.isGap(c));
+ sequenceBuilder.append(c);
+ }
+ else
+ {
+ sequenceBuilder.append(Comparison.GAP_DASH);
+ }
+ }
+ query.setSequence(sequenceBuilder.toString());
+ return query;
+ }
+}
public final class Credentials
{
String username = null;
+
String email = null;
+
String password = null;
+
private static final Credentials EMPTY = new Credentials();
- private Credentials() {
+ private Credentials()
+ {
+ }
+
+ public String getUsername()
+ {
+ return username;
+ }
+
+ public String getEmail()
+ {
+ return email;
}
-
+
+ public String getPassword()
+ {
+ return password;
+ }
+
public static final Credentials empty()
{
return EMPTY;
}
- public static final Credentials usingEmail(String email) {
+ public static final Credentials usingEmail(String email)
+ {
Objects.requireNonNull(email);
if (email.isEmpty())
throw new IllegalArgumentException("empty email");
credentials.email = email;
return credentials;
}
-
- public static final Credentials usingEmail(String email, String password) {
+
+ public static final Credentials usingEmail(String email, String password)
+ {
Objects.requireNonNull(email);
Objects.requireNonNull(password);
if (email.isEmpty())
credentials.password = password;
return credentials;
}
-
- public static final Credentials usingUsername(String username, String password) {
+
+ public static final Credentials usingUsername(String username,
+ String password)
+ {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
if (username.isEmpty())
--- /dev/null
+package jalview.ws2.client.ebi;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import jalview.bin.Console;
+import jalview.ws.params.ParamManager;
+import jalview.ws2.actions.NullAction;
+import jalview.ws2.actions.api.ActionI;
+import jalview.ws2.actions.hmmer.PhmmerAction;
+import jalview.ws2.api.WebService;
+import jalview.ws2.client.api.AbstractWebServiceDiscoverer;
+import uk.ac.dundee.compbio.hmmerclient.PhmmerClient;
+
+public final class JobDispatcherWSDiscoverer extends AbstractWebServiceDiscoverer
+{
+
+ private static final URL DEFAULT_URL;
+ static
+ {
+ try
+ {
+ DEFAULT_URL = new URL("https://www.ebi.ac.uk/Tools/services/rest/hmmer3_phmmer/");
+ } catch (MalformedURLException e)
+ {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private static JobDispatcherWSDiscoverer instance = null;
+ private static ParamManager paramManager = null;
+
+ private JobDispatcherWSDiscoverer() {}
+
+ public static JobDispatcherWSDiscoverer getInstance() {
+ if (instance == null)
+ instance = new JobDispatcherWSDiscoverer();
+ return instance;
+ }
+
+ public static void setParamManager(ParamManager manager)
+ {
+ paramManager = manager;
+ }
+
+ @Override
+ public int getStatusForUrl(URL url)
+ {
+ try
+ {
+ return new PhmmerClient(url).testEndpoint() ? STATUS_OK : STATUS_INVALID;
+ } catch (URISyntaxException e)
+ {
+ Console.error(e.getMessage());
+ return STATUS_INVALID;
+ }
+ }
+
+ @Override
+ protected String getUrlsPropertyKey()
+ {
+ return null;
+ }
+
+ @Override
+ protected URL getDefaultUrl()
+ {
+ return DEFAULT_URL;
+ }
+
+ @Override
+ protected List<WebService<?>> fetchServices(URL url) throws IOException
+ {
+ PhmmerClient phmmerClient;
+ try {
+ phmmerClient = new PhmmerClient(url);
+ }
+ catch (URISyntaxException e) {
+ throw new MalformedURLException(e.getMessage());
+ }
+ if (!phmmerClient.testEndpoint())
+ throw new IOException(
+ "unable to reach dispatcher server at " + url);
+ var wsBuilder = WebService.<PhmmerAction> newBuilder();
+ wsBuilder.url(url);
+ wsBuilder.clientName("job dispatcher");
+ wsBuilder.category("Database search");
+ wsBuilder.name("pHMMER");
+ wsBuilder.description("Hmmer3 phmmer is used to search one or more query sequences against a sequence database.");
+ wsBuilder.interactive(false);
+ wsBuilder.paramDatastore(ParamStores.newPhmmerDatastore(url, paramManager));
+ wsBuilder.actionClass(PhmmerAction.class);
+ var webService = wsBuilder.build();
+
+ var client = new PhmmerWSClient(phmmerClient);
+ var actionBuilder = PhmmerAction.newBuilder(client);
+ actionBuilder.webService(webService);
+ actionBuilder.name("");
+ webService.addAction(actionBuilder.build());
+
+ return List.of(webService);
+ }
+
+}
--- /dev/null
+package jalview.ws2.client.ebi;
+
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
+import jalview.ws.params.ParamManager;
+import jalview.ws.params.simple.*;
+import jalview.ws2.params.SimpleParamDatastore;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class ParamStores
+{
+ static final List<ArgumentI> phmmerParameters;
+
+ /**
+ * Set up phmmer parameters list.
+ */
+ static
+ {
+ var parameters = new ArrayList<ArgumentI>();
+ URL baseURL;
+ try
+ {
+ baseURL = new URL( "https://www.ebi.ac.uk/Tools/services/rest/hmmer3_phmmer/");
+ } catch (MalformedURLException e)
+ {
+ throw new ExceptionInInitializerError(e);
+ }
+ {
+ var builder = StringParameter.newBuilder();
+ builder.setName("cut-offs");
+ builder.setLabel("Cut-offs");
+ builder.setDescription("Set the method of controlling which target sequences match your query.");
+ builder.setRequired(true);
+ builder.setValue("E");
+ builder.setDefaultValue("E");
+ builder.setPossibleValues(List.of("E", "T"));
+ builder.setDisplayValues(List.of("E-values", "Bit scores"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("incE");
+ builder.setLabel("Significance E-values[Sequence]");
+ builder.setDescription("Significance E-values[Sequence]");
+ builder.setRequired(false);
+ builder.setValue(0.01);
+ builder.setDefaultValue(0.01);
+ builder.setMin(0.);
+ builder.setMax(10.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/incE"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("incdomE");
+ builder.setLabel("Significance E-values[Hit]");
+ builder.setDescription("Significance E-values[Hit]");
+ builder.setRequired(false);
+ builder.setValue(0.03);
+ builder.setDefaultValue(0.03);
+ builder.setMin(0.);
+ builder.setMax(10.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/incdomE"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("E");
+ builder.setLabel("Report E-values[Sequence]");
+ builder.setDescription("Report E-values[Sequence]");
+ builder.setRequired(false);
+ builder.setValue(1.0);
+ builder.setDefaultValue(1.0);
+ builder.setMin(0.);
+ builder.setMax(10.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/E"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("domE");
+ builder.setLabel("Report E-values[Hit]");
+ builder.setDescription("Report E-values[Hit]");
+ builder.setRequired(false);
+ builder.setValue(1.0);
+ builder.setDefaultValue(1.0);
+ builder.setMin(0.);
+ builder.setMax(10.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/domE"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("incT");
+ builder.setLabel("Significance bit scores[Sequence]");
+ builder.setDescription("Significance bit scores[Sequence]");
+ builder.setRequired(false);
+ builder.setValue(25.0);
+ builder.setDefaultValue(25.0);
+ builder.setMin(0.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/incT"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("incdomT");
+ builder.setLabel("Significance bit scores[Hit]");
+ builder.setDescription("Significance bit scores[Hit]");
+ builder.setRequired(false);
+ builder.setValue(22.0);
+ builder.setDefaultValue(22.0);
+ builder.setMin(0.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/incdomT"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("T");
+ builder.setLabel("Report bit scores[Sequence]");
+ builder.setDescription("Report bit scores[Sequence]");
+ builder.setRequired(false);
+ builder.setValue(7.0);
+ builder.setDefaultValue(7.0);
+ builder.setMin(0.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/T"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("domT");
+ builder.setLabel("Report bit scores[Hit]");
+ builder.setDescription("Report bit scores[Hit]");
+ builder.setRequired(false);
+ builder.setValue(5.0);
+ builder.setDefaultValue(5.0);
+ builder.setMin(0.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/domT"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("popen");
+ builder.setLabel("Gap Penalties[open]");
+ builder.setDescription("Gap Penalties[open]");
+ builder.setRequired(false);
+ builder.setValue(0.02);
+ builder.setDefaultValue(0.02);
+ builder.setMin(0.);
+ builder.setMax(0.5);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/popen"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("pextend");
+ builder.setLabel("Gap Penalties[extend]");
+ builder.setDescription("Gap Penalties[extend]");
+ builder.setRequired(false);
+ builder.setValue(0.4);
+ builder.setDefaultValue(0.4);
+ builder.setMin(0.);
+ builder.setMax(1.);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/pextend"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = StringParameter.newBuilder();
+ builder.setName("mx");
+ builder.setLabel("Gap Penalties[Substitution scoring matrix]");
+ builder.setDescription("Gap Penalties[Substitution scoring matrix]");
+ builder.setRequired(false);
+ builder.setValue("BLOSUM62");
+ builder.setDefaultValue("BLOSUM62");
+ builder.setPossibleValues(List.of("BLOSUM45", "BLOSUM62", "BLOSUM90", "PAM30", "PAM70"));
+ builder.setDisplayValues(List.of("BLOSUM45", "BLOSUM62", "BLOSUM90", "PAM30", "PAM70"));
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/mx"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = BooleanOption.newBuilder();
+ builder.setName("nobias");
+ builder.setLabel("No bias filter");
+ builder.setDescription(
+ "The '--nobias' option turns off (bypasses) the biased composition filter which is on by default.");
+ builder.setRequired(false);
+ builder.setValue(false);
+ builder.setDefaultValue(false);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/nobias"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = BooleanOption.newBuilder();
+ builder.setName("compressedout");
+ builder.setLabel("Compressed Output");
+ builder.setDescription(
+ "By default it runs hmm2c plus post-processing (default output), whereas with compressedout, it gets compressed output only.");
+ builder.setRequired(false);
+ builder.setValue(false);
+ builder.setDefaultValue(false);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/compressedout"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = BooleanOption.newBuilder();
+ builder.setName("alignView");
+ builder.setLabel("Output Alignment");
+ builder.setDescription("Output alignment in result");
+ builder.setRequired(false);
+ builder.setValue(true);
+ builder.setDefaultValue(true);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/alignView"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = StringParameter.newBuilder();
+ builder.setName("database");
+ builder.setLabel("Sequence Database");
+ builder.setDescription("Sequence Database Selection");
+ builder.setRequired(true);
+ builder.setValue("uniprotkb");
+ builder.setDefaultValue("uniprotkb");
+ builder.setPossibleValues(List.of("swissprot", "uniprotrefprot", "uniprotkb", "pdb", "rp75", "rp55", "rp35", "rp15", "ensembl", "merops", "qfo", "chembl"));
+ builder.setDisplayValues(List.of("SwissProt", "Reference Proteomes", "UniProtKB", "PDB", "rp75", "rp55", "rp35", "rp15", "Ensembl", "MEROPS", "Quest for Orthologs", "ChEMBL"));
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/database"));
+ parameters.add(builder.build());
+ }
+ {
+ var builder = DoubleParameter.newBuilder();
+ builder.setName("evalue");
+ builder.setLabel("Expectation Value Threshold");
+ builder.setDescription(
+ "Expectation value cut-off for reporting target profiles in the per-target output.");
+ builder.setRequired(false);
+ builder.setValue(0.01);
+ builder.setDefaultValue(0.01);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/evalue"));
+ parameters.add(builder.build());
+ }
+// {
+// var builder = StringParameter.newBuilder();
+// builder.setName("sequence");
+// builder.setLabel("Input Sequence");
+// builder.setDescription(
+// "The input sequence can be entered directly into this form. The sequence can be be in FASTA or UniProtKB/Swiss-Prot format. A partially formatted sequence is not accepted. Adding a return to the end of the sequence may help certain applications understand the input. Note that directly using data from word processors may yield unpredictable results as hidden/control characters may be present.");
+// builder.setRequired(false);
+//
+// builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/sequence"));
+// parameters.add(builder.build());
+// }
+ {
+ var builder = IntegerParameter.newBuilder();
+ builder.setName("nhits");
+ builder.setLabel("Number of Hits Displayed");
+ builder.setDescription("Number of hits to be displayed.");
+ builder.setRequired(false);
+ builder.setValue(100);
+ builder.setDefaultValue(100);
+ builder.setDetailsUrl(resolveURL(baseURL, "parameterdetails/nhits"));
+ parameters.add(builder.build());
+ }
+ phmmerParameters = Collections.unmodifiableList(parameters);
+ }
+
+ private static URL resolveURL(URL base, String spec)
+ {
+ try
+ {
+ return new URL(base, spec);
+ } catch (MalformedURLException e)
+ {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ public static ParamDatastoreI newPhmmerDatastore(URL url, ParamManager manager)
+ {
+ return new SimpleParamDatastore(url, phmmerParameters,
+ Collections.emptyList(), manager);
+ }
+}
--- /dev/null
+package jalview.ws2.client.ebi;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.URI;
+import java.util.List;
+
+import jalview.bin.Console;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FileParse;
+import jalview.io.FormatAdapter;
+import jalview.io.StockholmFile;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.simple.BooleanOption;
+import jalview.ws.params.simple.DoubleParameter;
+import jalview.ws.params.simple.IntegerParameter;
+import jalview.ws2.api.Credentials;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.api.WebServiceJobHandle;
+import jalview.ws2.client.api.AlignmentWebServiceClientI;
+import jalview.ws2.client.api.WebServiceClientI;
+import uk.ac.dundee.compbio.hmmerclient.PhmmerClient;
+import uk.ac.dundee.compbio.hmmerclient.PhmmerRequest;
+import uk.ac.dundee.compbio.hmmerclient.PhmmerRequest.SequenceDatabase;
+import uk.ac.dundee.compbio.hmmerclient.PhmmerRequest.SubstitutionMatrix;
+
+public class PhmmerWSClient implements AlignmentWebServiceClientI
+{
+
+ final PhmmerClient client;
+
+ PhmmerWSClient(PhmmerClient client)
+ {
+ this.client = client;
+ }
+
+ @Override
+ public String getUrl()
+ {
+ return client.getURL().toString();
+ }
+
+ @Override
+ public String getClientName()
+ {
+ return "ebi-job-dispatcher";
+ }
+
+ @Override
+ public WebServiceJobHandle submit(List<SequenceI> sequences,
+ List<ArgumentI> args, Credentials credentials) throws IOException
+ {
+ var request = PhmmerRequest.newBuilder();
+ String sequence = FileFormat.Fasta.getWriter(null)
+ .print(new SequenceI[]{ sequences.get(0) }, false);
+ request.sequence(new StringReader(sequence));
+ populateRequestArguments(request, args);
+ var email = credentials.getEmail() != null ? credentials.getEmail() :
+ "nouser@jalview.org";
+ var jobId = client.submit(request.build(), email);
+ Console.debug("Phmmer client submitted new job with id " + jobId);
+ return new WebServiceJobHandle(
+ getClientName(), "phmmer", getUrl(), jobId);
+ }
+
+ private static void populateRequestArguments(PhmmerRequest.Builder request, List<ArgumentI> args)
+ {
+ boolean useBitScore = false;
+ boolean useEValue = false;
+ for (var arg : args)
+ {
+ if (arg.getName().equals("cut-offs"))
+ if (arg.getValue().equals("E"))
+ useEValue = true;
+ else if (arg.getValue().equals("T"))
+ useBitScore = true;
+ else
+ throw new IllegalArgumentException(
+ "cut-offs argument contains value other than \"E\" or \"T\": "
+ + arg.getValue());
+ }
+ assert (useBitScore || useEValue) && !(useBitScore && useEValue);
+ for (var arg : args)
+ {
+ switch (arg.getName())
+ {
+ case "incE":
+ request.incE(useEValue ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "incdomE":
+ request.incdomE(useEValue ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "E":
+ request.E(useEValue ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "domE":
+ request.domE(useEValue ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "incT":
+ request.incT(useBitScore ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "incdomT":
+ request.incdomT(useBitScore ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "T":
+ request.T(useBitScore ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "domT":
+ request.domT(useBitScore ? DoubleParameter.parseFloat(arg) : null);
+ break;
+ case "popen":
+ request.popen(DoubleParameter.parseFloat(arg));
+ break;
+ case "pextend":
+ request.pextend(DoubleParameter.parseFloat(arg));
+ break;
+ case "mx":
+ request.mx(parseSubstitutionMatrix(arg));
+ break;
+ case "nobias":
+ request.noBias(BooleanOption.parseBoolean(arg));
+ break;
+ case "compressedout":
+ request.compressedOut(BooleanOption.parseBoolean(arg));
+ break;
+ case "alignView":
+ request.compressedOut(BooleanOption.parseBoolean(arg));
+ break;
+ case "database":
+ request.database(parseSequenceDatabase(arg));
+ break;
+ case "evalue":
+ request.evalue(DoubleParameter.parseFloat(arg));
+ break;
+ case "nhits":
+ request.nhits(IntegerParameter.parseInt(arg));
+ break;
+ }
+ }
+ }
+
+ private static SubstitutionMatrix parseSubstitutionMatrix(ArgumentI arg)
+ {
+ if (arg.getValue() == null)
+ return null;
+ switch (arg.getValue())
+ {
+ case "BLOSUM45":
+ return SubstitutionMatrix.BLOSUM45;
+ case "BLOSUM62":
+ return SubstitutionMatrix.BLOSUM62;
+ case "BLOSUM90":
+ return SubstitutionMatrix.BLOSUM90;
+ case "PAM30":
+ return SubstitutionMatrix.PAM30;
+ case "PAM70":
+ return SubstitutionMatrix.PAM70;
+ default:
+ throw new IllegalArgumentException(
+ "invalid matrix " + arg.getValue());
+ }
+ }
+
+ private static SequenceDatabase parseSequenceDatabase(ArgumentI arg)
+ {
+ if (arg.getValue() == null)
+ return null;
+ switch (arg.getValue())
+ {
+ case "swissprot":
+ return SequenceDatabase.SWISS_PROT;
+ case "uniprotrefprot":
+ return SequenceDatabase.REFERENCE_PROTEOMES;
+ case "uniprotkb":
+ return SequenceDatabase.UNIPROTKB;
+ case "pdb":
+ return SequenceDatabase.PDB;
+ case "rp75":
+ return SequenceDatabase.RP75;
+ case "rp55":
+ return SequenceDatabase.RP55;
+ case "rp35":
+ return SequenceDatabase.RP35;
+ case "rp15":
+ return SequenceDatabase.RP15;
+ case "ensembl":
+ return SequenceDatabase.ENSEMBL;
+ case "merops":
+ return SequenceDatabase.MEROPS;
+ case "qfo":
+ return SequenceDatabase.QUEST_FOR_ORTHOLOGS;
+ case "chembl":
+ return SequenceDatabase.CHEMBL;
+ default:
+ throw new IllegalArgumentException(
+ "invalid database " + arg.getValue());
+ }
+ }
+
+ @Override
+ public JobStatus getStatus(WebServiceJobHandle job) throws IOException
+ {
+ var status = client.getStatus(job.getJobId());
+ switch (status)
+ {
+ case PENDING: return JobStatus.SUBMITTED;
+ case QUEUED: return JobStatus.QUEUED;
+ case RUNNING: return JobStatus.RUNNING;
+ case FINISHED: return JobStatus.COMPLETED;
+ case FAILURE: return JobStatus.FAILED;
+ case ERROR: return JobStatus.SERVER_ERROR;
+ case NOT_FOUND: return JobStatus.SERVER_ERROR;
+ case UNDEFINED: return JobStatus.UNKNOWN;
+ }
+ return JobStatus.UNKNOWN;
+ }
+
+ @Override
+ public String getLog(WebServiceJobHandle job) throws IOException
+ {
+ return "";
+ }
+
+ @Override
+ public String getErrorLog(WebServiceJobHandle job) throws IOException
+ {
+ if (getStatus(job) != JobStatus.FAILED)
+ return "";
+ try(InputStream stream = client.getResultStream(job.getJobId(), "error"))
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ stream.transferTo(out);
+ return out.toString();
+ }
+ }
+
+ @Override
+ public void cancel(WebServiceJobHandle job)
+ throws IOException, UnsupportedOperationException
+ {
+ throw new UnsupportedOperationException(
+ "ebi job dispatcher does not support job cancellation");
+ }
+
+ /**
+ * FIXME: Temporary hack
+ */
+ @Override
+ public AlignmentI getAlignment(WebServiceJobHandle job) throws IOException
+ {
+ URI url = client.getResultURL(job.getJobId(), "sto");
+ try(InputStream stream = client.getResultStream(job.getJobId(), "sto"))
+ {
+ StockholmFile file = new StockholmFile(new FileParse(
+ new BufferedReader(new InputStreamReader(stream)),
+ url.toString(), DataSourceType.URL));
+ var aln = new Alignment(file.getSeqsAsArray());
+ for (var annotation : file.getAnnotations())
+ aln.addAnnotation(annotation);
+ return aln;
+ }
+ }
+}
{
FeaturesFile ff = new FeaturesFile(f.getContentUrl().toString(),
DataSourceType.URL);
- featPresent = ff.parse(aln, colours, true);
+ // TODO: determine if relaxed id matching is T/F
+ featPresent = ff.parse(aln, colours, filters, false, true);
if (featPresent)
Console.debug(format("loaded features for %s", service.getId()));
}
}
@Override
- public void taskRestarted(TaskI<AlignmentResult> source)
- {
- // alignment services are not restartable
- }
-
- @Override
public void subJobStatusChanged(TaskI<AlignmentResult> source, JobI job, JobStatus status)
{
int i = ArrayUtils.indexOf(jobs, job);
package jalview.ws2.gui;
-import java.util.List;
-
+import jalview.api.FeatureColourI;
+import jalview.datamodel.features.FeatureMatcherSetI;
import jalview.gui.AlignFrame;
import jalview.gui.AlignmentPanel;
import jalview.gui.IProgressIndicator;
import jalview.gui.IProgressIndicatorHandler;
+import jalview.schemes.FeatureSettingsAdapter;
+import jalview.util.MathUtils;
+import jalview.ws2.actions.annotation.AlignCalcWorkerAdapter;
import jalview.ws2.actions.annotation.AnnotationAction;
import jalview.ws2.actions.annotation.AnnotationResult;
-import jalview.ws2.actions.api.JobI;
-import jalview.ws2.actions.api.TaskEventListener;
-import jalview.ws2.actions.api.TaskI;
-import jalview.ws2.api.JobStatus;
public class AnnotationServiceGuiHandler
- implements TaskEventListener<AnnotationResult>
+ implements AlignCalcWorkerAdapter.WorkerListener
{
+ private final long progressId = MathUtils.getUID();
+
private final AlignFrame alignFrame;
private final AlignmentPanel alignPanel;
}
@Override
- public void taskStarted(TaskI<AnnotationResult> source, List<? extends JobI> subJobs)
+ public void workerStarted(AlignCalcWorkerAdapter source)
{
- progressIndicator.registerHandler(source.getUid(),
+ progressIndicator.addProgressBar(progressId, action.getFullName());
+ progressIndicator.registerHandler(progressId,
new IProgressIndicatorHandler()
{
@Override
}
@Override
- public void taskStatusChanged(TaskI<AnnotationResult> source, JobStatus status)
+ public void workerStopped(AlignCalcWorkerAdapter source)
{
- switch (status)
- {
- case INVALID:
- case COMPLETED:
- case CANCELLED:
- case FAILED:
- case SERVER_ERROR:
- progressIndicator.removeProgressBar(source.getUid());
- break;
- case READY:
- case SUBMITTED:
- case QUEUED:
- case RUNNING:
- case UNKNOWN:
- progressIndicator.addProgressBar(source.getUid(), action.getFullName());
- break;
- }
+ progressIndicator.removeProgressBar(progressId);
}
@Override
- public void taskCompleted(TaskI<AnnotationResult> source, AnnotationResult result)
+ public void workerHasResult(AlignCalcWorkerAdapter source, final AnnotationResult result)
{
if (result == null)
return;
- if (result.getTransferFeatures() && alignFrame.alignPanel == alignPanel)
+ if (result.getTransferFeatures())
{
- alignFrame.getViewport().setShowSequenceFeatures(true);
- alignFrame.setMenusForViewport();
- }
- alignPanel.adjustAnnotationHeight();
- }
-
- @Override
- public void taskException(TaskI<AnnotationResult> source, Exception e)
- {
-
- }
-
- @Override
- public void taskRestarted(TaskI<AnnotationResult> source)
- {
-
- }
-
- @Override
- public void subJobStatusChanged(TaskI<AnnotationResult> source, JobI job, JobStatus status)
- {
-
- }
-
- @Override
- public void subJobLogChanged(TaskI<AnnotationResult> source, JobI job, String log)
- {
-
- }
+ alignFrame.getViewport().applyFeaturesStyle(new FeatureSettingsAdapter()
+ {
+ @Override
+ public FeatureColourI getFeatureColour(String type)
+ {
+ return result.getFeatureColours().get(type);
+ }
- @Override
- public void subJobErrorLogChanged(TaskI<AnnotationResult> source, JobI job, String log)
- {
+ @Override
+ public FeatureMatcherSetI getFeatureFilters(String type)
+ {
+ return result.getFeatureFilters().get(type);
+ }
+ @Override
+ public boolean isFeatureDisplayed(String type)
+ {
+ return result.getFeatureColours().containsKey(type);
+ }
+ });
+ if (alignFrame.alignPanel == alignPanel)
+ {
+ alignFrame.getViewport().setShowSequenceFeatures(true);
+ alignFrame.setMenusForViewport();
+ }
+ }
+ alignPanel.adjustAnnotationHeight();
}
}
--- /dev/null
+package jalview.ws2.gui;
+
+import java.util.List;
+
+import javax.swing.SwingUtilities;
+
+import jalview.bin.Console;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
+import jalview.gui.WebserviceInfo;
+import jalview.util.ArrayUtils;
+import jalview.util.MessageManager;
+import jalview.ws2.actions.api.ActionI;
+import jalview.ws2.actions.api.JobI;
+import jalview.ws2.actions.api.TaskEventListener;
+import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.api.WebService;
+import jalview.ws2.helpers.WSClientTaskWrapper;
+
+import static java.lang.String.format;
+
+class SearchServiceGuiHandler implements TaskEventListener<AlignmentI>
+{
+ private final AlignFrame parentFrame;
+
+ private final ActionI<?> action;
+
+ private final WebService<?> service;
+
+ private WebserviceInfo infoPanel;
+
+ private JobI[] jobs = new JobI[0];
+
+ private int[] tabs = new int[0];
+
+ private int[] logOffset = new int[0];
+
+ private int[] errLogOffset = new int[0];
+
+ public SearchServiceGuiHandler(ActionI<?> action, AlignFrame parentFrame)
+ {
+ this.parentFrame = parentFrame;
+ this.action = action;
+ this.service = action.getWebService();
+ var info = String.format("%s search using service at %s%n%s",
+ service.getName(), service.getUrl(), service.getDescription());
+ this.infoPanel = new WebserviceInfo(service.getName(), info, false);
+ }
+
+ @Override
+ public void taskStarted(TaskI<AlignmentI> source,
+ List<? extends JobI> subJobs)
+ {
+ Console.debug(format("task %s#%x started with %d sub-jobs",
+ service.getName(), source.getUid(), subJobs.size()));
+ jobs = subJobs.toArray(new JobI[subJobs.size()]);
+ tabs = new int[subJobs.size()];
+ logOffset = new int[subJobs.size()];
+ errLogOffset = new int[subJobs.size()];
+ for (int i = 0; i < subJobs.size(); i++)
+ {
+ JobI job = jobs[i];
+ int tabIndex = infoPanel.addJobPane();
+ tabs[i] = tabIndex;
+ infoPanel.setProgressName(format("region %d", i), tabIndex);
+ infoPanel.setProgressText(tabIndex, "Job details:\n");
+ // jobs should not have states other than invalid or ready at this point
+ if (job.getStatus() == JobStatus.INVALID)
+ infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_STOPPED_OK);
+ else if (job.getStatus() == JobStatus.READY)
+ infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_QUEUING);
+ }
+ }
+
+ @Override
+ public void taskStatusChanged(TaskI<AlignmentI> source, JobStatus status)
+ {
+ Console.debug(format("task %s#%x status changed to %s",
+ service.getName(), source.getUid(), status));
+ switch (status)
+ {
+ case INVALID:
+ infoPanel.setVisible(false);
+ JvOptionPane.showMessageDialog(parentFrame,
+ MessageManager.getString("info.invalid_search_input"),
+ MessageManager.getString("info.invalid_search_input"),
+ JvOptionPane.INFORMATION_MESSAGE);
+ break;
+ case READY:
+ infoPanel.setthisService(new WSClientTaskWrapper(source));
+ infoPanel.setVisible(true);
+ // intentional no break
+ case SUBMITTED:
+ case QUEUED:
+ infoPanel.setStatus(WebserviceInfo.STATE_QUEUING);
+ break;
+ case RUNNING:
+ case UNKNOWN: // unsure what to do with unknown
+ infoPanel.setStatus(WebserviceInfo.STATE_RUNNING);
+ break;
+ case COMPLETED:
+ infoPanel.setProgressBar(
+ MessageManager.getString("status.collecting_job_results"),
+ jobs[0].getInternalId());
+ infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_OK);
+ break;
+ case FAILED:
+ infoPanel.removeProgressBar(jobs[0].getInternalId());
+ infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+ break;
+ case CANCELLED:
+ infoPanel.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
+ break;
+ case SERVER_ERROR:
+ infoPanel.removeProgressBar(jobs[0].getInternalId());
+ infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
+ break;
+ }
+ }
+
+ @Override
+ public void taskCompleted(TaskI<AlignmentI> source, AlignmentI result)
+ {
+ Console.debug(format("task %s#%x completed", service.getName(),
+ source.getUid()));
+ SwingUtilities.invokeLater(
+ () -> infoPanel.removeProgressBar(jobs[0].getInternalId()));
+ if (result == null)
+ {
+ SwingUtilities.invokeLater(infoPanel::setFinishedNoResults);
+ return;
+ }
+ infoPanel.showResultsNewFrame.addActionListener(evt -> {
+ // copy alignment for each frame to have its own instance
+ var alnCpy = new Alignment(result);
+ alnCpy.setGapCharacter(result.getGapCharacter());
+ alnCpy.setDataset(result.getDataset());
+ for (AlignmentAnnotation annotation : result.getAlignmentAnnotation())
+ alnCpy.addAnnotation(new AlignmentAnnotation(annotation));
+ displayResultsNewFrame(alnCpy);
+ });
+ SwingUtilities.invokeLater(infoPanel::setResultsReady);
+ }
+
+ private void displayResultsNewFrame(AlignmentI aln)
+ {
+ AlignFrame frame = new AlignFrame(aln, AlignFrame.DEFAULT_WIDTH,
+ AlignFrame.DEFAULT_HEIGHT);
+ frame.getFeatureRenderer().transferSettings(
+ parentFrame.getFeatureRenderer().getSettings());
+ var actionName = action.getName() != null ? action.getName() : "Search";
+ var title = String.format("%s %s of %s", service.getName(), actionName,
+ parentFrame.getTitle());
+ Desktop.addInternalFrame(frame, title, AlignFrame.DEFAULT_WIDTH,
+ AlignFrame.DEFAULT_HEIGHT);
+ }
+
+ @Override
+ public void taskException(TaskI<AlignmentI> source, Exception e)
+ {
+ Console.error(format("Task %s#%x raised an exception.",
+ service.getName(), source.getUid()), e);
+ infoPanel.appendProgressText(e.getMessage());
+ }
+
+ @Override
+ public void subJobStatusChanged(TaskI<AlignmentI> source, JobI job,
+ JobStatus status)
+ {
+ Console.debug(format("sub-job %x status changed to %s",
+ job.getInternalId(), status));
+ int i = ArrayUtils.indexOf(jobs, job);
+ assert i >= 0 : "job does not exist";
+ if (i < 0)
+ // safeguard that should not happen irl
+ return;
+ int wsStatus;
+ switch (status)
+ {
+ case INVALID:
+ case COMPLETED:
+ wsStatus = WebserviceInfo.STATE_STOPPED_OK;
+ break;
+ case READY:
+ case SUBMITTED:
+ case QUEUED:
+ wsStatus = WebserviceInfo.STATE_QUEUING;
+ break;
+ case RUNNING:
+ case UNKNOWN:
+ wsStatus = WebserviceInfo.STATE_RUNNING;
+ break;
+ case FAILED:
+ wsStatus = WebserviceInfo.STATE_STOPPED_ERROR;
+ break;
+ case CANCELLED:
+ wsStatus = WebserviceInfo.STATE_CANCELLED_OK;
+ break;
+ case SERVER_ERROR:
+ wsStatus = WebserviceInfo.STATE_STOPPED_SERVERERROR;
+ break;
+ default:
+ throw new AssertionError("Non-exhaustive switch statement");
+ }
+ infoPanel.setStatus(tabs[i], wsStatus);
+ }
+
+ @Override
+ public void subJobLogChanged(TaskI<AlignmentI> source, JobI job,
+ String log)
+ {
+ int i = ArrayUtils.indexOf(jobs, job);
+ assert i >= 0 : "job does not exist";
+ if (i < 0)
+ // safeguard that should never happen
+ return;
+ infoPanel.appendProgressText(tabs[i], log.substring(logOffset[i]));
+ }
+
+ @Override
+ public void subJobErrorLogChanged(TaskI<AlignmentI> source, JobI job,
+ String log)
+ {
+ int i = ArrayUtils.indexOf(jobs, job);
+ assert i >= 0 : "job does not exist";
+ if (i < 0)
+ // safeguard that should never happen
+ return;
+ infoPanel.appendProgressText(tabs[i], log.substring(errLogOffset[i]));
+ }
+}
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
-import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.CompletionStage;
import javax.swing.ToolTipManager;
import javax.swing.border.EmptyBorder;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentI;
import jalview.gui.AlignFrame;
import jalview.gui.Desktop;
import jalview.gui.JvSwingUtils;
import jalview.ws.params.ArgumentI;
import jalview.ws.params.ParamDatastoreI;
import jalview.ws.params.WsParamSetI;
+import jalview.ws2.actions.BaseTask;
+import jalview.ws2.actions.PollingTaskExecutor;
import jalview.ws2.actions.alignment.AlignmentAction;
+import jalview.ws2.actions.alignment.AlignmentResult;
+import jalview.ws2.actions.annotation.AlignCalcWorkerAdapter;
import jalview.ws2.actions.annotation.AnnotationAction;
import jalview.ws2.actions.api.ActionI;
+import jalview.ws2.actions.api.TaskEventListener;
import jalview.ws2.actions.api.TaskI;
+import jalview.ws2.actions.hmmer.PhmmerAction;
import jalview.ws2.api.Credentials;
import jalview.ws2.api.WebService;
import jalview.ws2.client.api.WebServiceProviderI;
-import static java.lang.String.format;
-
public class WebServicesMenuManager
{
private final JMenu menu;
noServicesItem.setEnabled(false);
}
- private Map<String, WeakReference<TaskI<?>>> interactiveTasks = new HashMap<>();
-
public WebServicesMenuManager(String name, AlignFrame frame)
{
this.frame = frame;
// sort actions by name pulling nulls to the front
actions.sort(Comparator.comparing(
ActionI::getName, Comparator.nullsFirst(Comparator.naturalOrder())));
- for (int i = 0; i < actions.size(); i++) {
+ for (int i = 0; i < actions.size(); i++)
+ {
addEntriesForAction(actions.get(i), atMenu, atMenu == menu);
}
}
serviceItem.removeActionListener(l);
WebService<?> service = action.getWebService();
serviceItem.addActionListener(e -> {
- if (serviceItem.getState())
- {
- cancelAndRunInteractive(action, frame.getCurrentView(), arguments,
- Credentials.empty());
- }
- else
- {
- cancelInteractive(service.getName());
- }
+ runAction(action, frame.getCurrentView(), arguments,
+ Credentials.empty());
});
serviceItem.setSelected(true);
lastPreset[0] = null;
arguments.clear();
arguments.addAll(args);
- cancelAndRunInteractive(action, frame.getCurrentView(),
+ runAction(action, frame.getCurrentView(),
arguments, Credentials.empty());
}
});
var item = new JMenuItem(preset.getName());
item.addActionListener(e -> {
lastPreset[0] = preset;
- cancelAndRunInteractive(action, frame.getCurrentView(),
+ runAction(action, frame.getCurrentView(),
preset.getArguments(), Credentials.empty());
});
presetsMenu.add(item);
}
}
- cancelAndRunInteractive(action, frame.getCurrentView(), arguments,
+ runAction(action, frame.getCurrentView(), arguments,
Credentials.empty());
}
}
}
- private void cancelInteractive(String wsName)
- {
- var taskRef = interactiveTasks.get(wsName);
- if (taskRef != null && taskRef.get() != null)
- taskRef.get().cancel();
- interactiveTasks.put(wsName, null);
- }
-
- private void cancelAndRunInteractive(ActionI<?> action,
- AlignmentViewport viewport, List<ArgumentI> args, Credentials credentials)
- {
- var wsName = action.getWebService().getName();
- cancelInteractive(wsName);
- var task = runAction(action, viewport, args, credentials);
- interactiveTasks.put(wsName, new WeakReference<>(task));
- }
- private TaskI<?> runAction(ActionI<?> action, AlignmentViewport viewport,
+ private void runAction(ActionI<?> action, AlignmentViewport viewport,
List<ArgumentI> args, Credentials credentials)
{
// casting and instance checks can be avoided with some effort,
// TODO: test if selection contains enough sequences
var _action = (AlignmentAction) action;
var handler = new AlignmentServiceGuiHandler(_action, frame);
- return _action.perform(viewport, args, credentials, handler);
+ BaseTask<?, AlignmentResult> task = _action.createTask(viewport, args, credentials);
+ var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
+ task.addTaskEventListener(handler);
+ var future = executor.submit(task);
+ task.setCancelAction(() -> { future.cancel(true); });
+ return;
}
if (action instanceof AnnotationAction)
{
+ var calcManager = viewport.getCalcManager();
+
var _action = (AnnotationAction) action;
+ var worker = new AlignCalcWorkerAdapter(viewport, frame.alignPanel,
+ _action, args, credentials);
var handler = new AnnotationServiceGuiHandler(_action, frame);
- return _action.perform(viewport, args, credentials, handler);
+ worker.setWorkerListener(handler);
+ for (var w : calcManager.getWorkers())
+ {
+ if (worker.getCalcName() != null && worker.getCalcName().equals(w.getCalcName()))
+ {
+ calcManager.cancelWorker(w);
+ calcManager.removeWorker(w);
+ }
+ }
+ if (action.getWebService().isInteractive())
+ calcManager.registerWorker(worker);
+ else
+ calcManager.startWorker(worker);
+ return;
+ }
+ if (action instanceof PhmmerAction)
+ {
+ var _action = (PhmmerAction) action;
+ var handler = new SearchServiceGuiHandler(_action, frame);
+ TaskI<AlignmentI> task = _action.createTask(viewport, args, credentials);
+ var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
+ task.addTaskEventListener(handler);
+ _action.perform(viewport, args, credentials, handler);
+ return;
}
- throw new IllegalArgumentException(
- String.format("Illegal action type %s", action.getClass().getName()));
+ Console.warn(String.format(
+ "No known handler for action type %s. All output will be discarded.",
+ action.getClass().getName()));
+ var task = action.createTask(viewport, args, credentials);
+ task.addTaskEventListener(TaskEventListener.nullListener());
+ PollingTaskExecutor.fromPool(viewport.getServiceExecutor())
+ .submit(task);
}
private static CompletionStage<List<ArgumentI>> openEditParamsDialog(
package jalview.ws2.helpers;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import jalview.ws2.actions.api.JobI;
import jalview.ws2.actions.api.TaskEventListener;
public class TaskEventSupport<T>
{
- TaskI<T> source;
- TaskEventListener<T> handler;
-
- public TaskEventSupport(TaskI<T> source, TaskEventListener<T> handler)
+ private TaskI<T> source;
+
+ private List<TaskEventListener<T>> listeners = new CopyOnWriteArrayList<>();
+
+ public TaskEventSupport(TaskI<T> source)
{
this.source = source;
- this.handler = handler;
+ }
+
+ public TaskEventSupport(TaskI<T> source, TaskEventListener<T> handler)
+ {
+ this(source);
+ addListener(handler);
+ }
+
+ public void addListener(TaskEventListener<T> listener)
+ {
+ listeners.add(listener);
}
+ public void removeListener(TaskEventListener<T> listener)
+ {
+ listeners.remove(listener);
+ }
+
public void fireTaskStarted(List<? extends JobI> subJobs)
{
- handler.taskStarted(source, subJobs);
+ for (var listener : listeners)
+ listener.taskStarted(source, subJobs);
}
-
+
public void fireTaskStatusChanged(JobStatus status)
{
- handler.taskStatusChanged(source, status);
+ for (var listener : listeners)
+ listener.taskStatusChanged(source, status);
}
-
+
public void fireTaskCompleted(T result)
{
- handler.taskCompleted(source, result);
+ for (var listener : listeners)
+ listener.taskCompleted(source, result);
}
-
+
public void fireTaskException(Exception e)
{
- handler.taskException(source, e);
- }
-
- public void fireTaskRestarted()
- {
- handler.taskRestarted(source);
+ for (var listener : listeners)
+ listener.taskException(source, e);
}
-
+
public void fireSubJobStatusChanged(JobI job, JobStatus status)
{
- handler.subJobStatusChanged(source, job, status);
+ for (var listener : listeners)
+ listener.subJobStatusChanged(source, job, status);
}
-
+
public void fireSubJobLogChanged(JobI job, String log)
{
- handler.subJobLogChanged(source, job, log);
+ for (var listener : listeners)
+ listener.subJobLogChanged(source, job, log);
}
-
+
public void fireSubJobErrorLogChanged(JobI job, String log)
{
- handler.subJobErrorLogChanged(source, job, log);
+ for (var listener : listeners)
+ listener.subJobErrorLogChanged(source, job, log);
}
}
import jalview.gui.AlignViewport;
import jalview.viewmodel.AlignmentViewport;
import jalview.ws.params.ParamDatastoreI;
+import jalview.ws2.actions.PollingTaskExecutor;
import jalview.ws2.actions.api.JobI;
import jalview.ws2.actions.api.TaskEventListener;
import jalview.ws2.api.Credentials;
return null;
})
.when(listener).taskCompleted(any(), any());
- action.perform(viewport, List.of(), Credentials.empty(), listener);
+ var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
+ var task = action.createTask(viewport, List.of(), Credentials.empty());
+ task.addTaskEventListener(listener);
+ var cancellable = executor.submit(task);
try
{
latch.await(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e)
{
+ cancellable.cancel(true);
}
return listener;
}