1 package jalview.ws2.actions;
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Collections;
7 import java.util.Objects;
9 import jalview.bin.Console;
10 import jalview.util.ArrayUtils;
11 import jalview.util.MathUtils;
12 import jalview.ws.params.ArgumentI;
13 import jalview.ws2.actions.api.TaskEventListener;
14 import jalview.ws2.actions.api.TaskI;
15 import jalview.ws2.api.Credentials;
16 import jalview.ws2.api.JobStatus;
17 import jalview.ws2.client.api.WebServiceClientI;
18 import jalview.ws2.helpers.DelegateJobEventListener;
19 import jalview.ws2.helpers.TaskEventSupport;
21 import static java.lang.String.format;
23 public abstract class BaseTask<T extends BaseJob, R> implements TaskI<R>
25 protected final long uid = MathUtils.getUID();
27 protected final WebServiceClientI webClient;
29 protected final List<ArgumentI> args;
31 protected final Credentials credentials;
33 private final TaskEventSupport<R> eventHandler;
35 protected JobStatus status = JobStatus.CREATED;
37 protected List<T> jobs = Collections.emptyList();
39 protected R result = null;
41 protected Runnable cancelAction = () -> {
44 protected BaseTask(WebServiceClientI webClient, List<ArgumentI> args,
45 Credentials credentials)
47 this.webClient = webClient;
49 this.credentials = credentials;
50 this.eventHandler = new TaskEventSupport<>(this);
54 public final long getUid()
60 public final JobStatus getStatus()
66 public final List<? extends BaseJob> getSubJobs()
72 public final void addTaskEventListener(TaskEventListener<R> listener)
74 eventHandler.addListener(listener);
78 public final void removeTaskEventListener(TaskEventListener<R> listener)
80 eventHandler.addListener(listener);
84 public final R getResult()
90 public final void init() throws Exception
95 } catch (ServiceInputInvalidException e)
97 setStatus(JobStatus.INVALID);
98 eventHandler.fireTaskException(e);
101 setStatus(JobStatus.READY);
102 eventHandler.fireTaskStarted(jobs);
103 var jobListener = new DelegateJobEventListener<>(eventHandler);
105 job.addPropertyChangeListener(jobListener);
109 static final int MAX_SUBMIT_RETRY = 5;
111 protected final void submitJobs(List<T> jobs) throws IOException
113 var retryCounter = 0;
119 setStatus(JobStatus.SUBMITTED);
121 } catch (IOException e)
123 eventHandler.fireTaskException(e);
124 if (++retryCounter > MAX_SUBMIT_RETRY)
127 setStatus(JobStatus.SERVER_ERROR);
134 private final void submitJobs0(List<T> jobs) throws IOException
136 IOException exception = null;
137 for (BaseJob job : jobs)
139 if (job.getStatus() != JobStatus.READY || !job.isInputValid())
143 var jobRef = webClient.submit(job.getInputSequences(), args, credentials);
144 job.setServerJob(jobRef);
145 job.setStatus(JobStatus.SUBMITTED);
146 } catch (IOException e)
151 if (exception != null)
156 * Poll all running jobs and update their status and logs. Polling is repeated
157 * periodically until this method return true when all jobs are done.
159 * @return {@code true] if all jobs are done @throws IOException if server
163 public final boolean poll() throws IOException
165 boolean allDone = true;
166 IOException exception = null;
167 for (BaseJob job : jobs)
169 if (job.isInputValid() && !job.getStatus().isDone())
171 var serverJob = job.getServerJob();
174 job.setStatus(webClient.getStatus(serverJob));
175 job.setLog(webClient.getLog(serverJob));
176 job.setErrorLog(webClient.getErrorLog(serverJob));
177 } catch (IOException e)
182 allDone &= job.isCompleted();
184 updateGlobalStatus();
185 if (exception != null)
191 public final void complete() throws IOException
195 if (!job.isCompleted())
197 // a fallback in case the executor decides to finish prematurely
199 job.setStatus(JobStatus.SERVER_ERROR);
202 updateGlobalStatus();
204 result = collectResult(jobs);
205 eventHandler.fireTaskCompleted(result);
209 eventHandler.fireTaskException(e);
215 * Cancel all running jobs. Used in case of task failure to cleanup the
216 * resources or when the task has been cancelled.
219 public final void cancel()
226 setStatus(JobStatus.CANCELLED);
229 private final void cancelJob(T job)
231 if (!job.isCompleted())
235 if (job.getServerJob() != null)
236 webClient.cancel(job.getServerJob());
237 job.setStatus(JobStatus.CANCELLED);
238 } catch (IOException e)
240 Console.error(format("failed to cancel job %s", job.getServerJob()), e);
245 protected final void setStatus(JobStatus status)
247 Objects.requireNonNull(status);
248 if (this.status != status)
250 this.status = status;
251 eventHandler.fireTaskStatusChanged(status);
255 protected abstract List<T> prepareJobs() throws ServiceInputInvalidException;
257 protected abstract R collectResult(List<T> jobs) throws IOException;
260 * Update task status according to the overall status of its jobs. The rules
261 * of setting the status are following:
263 * <li>task is invalid if all jobs are invalid</li>
264 * <li>task is completed if all but invalid jobs are completed</li>
265 * <li>task is ready, submitted or queued if at least one job is ready,
266 * submitted or queued an none proceeded to the next stage excluding
268 * <li>task is running if at least one job is running and none are failed or
270 * <li>task is cancelled if at least one job is cancelled and none failed</li>
271 * <li>task is failed or server error if at least one job is failed or server
275 protected final void updateGlobalStatus()
278 for (BaseJob job : jobs)
280 JobStatus status = job.getStatus();
281 int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status);
282 if (precedence < jobPrecedence)
283 precedence = jobPrecedence;
287 setStatus(JobStatus.statusPrecedence[precedence]);
292 * Set the action that will be run when the {@link #cancel()} method is
293 * invoked. The action should typically stop the executor polling the task and
294 * release resources and threads running the task.
297 * runnable to be executed when the task is cancelled
299 public void setCancelAction(Runnable action)
301 Objects.requireNonNull(action);
302 this.cancelAction = action;
306 public String toString()
308 var statusName = status != null ? status.name() : "UNSET";
309 return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, statusName);