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<T> 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);
210 setStatus(JobStatus.SERVER_ERROR);
216 * Cancel all running jobs. Used in case of task failure to cleanup the
217 * resources or when the task has been cancelled.
220 public final void cancel()
227 setStatus(JobStatus.CANCELLED);
230 private final void cancelJob(T job)
232 if (!job.isCompleted())
236 if (job.getServerJob() != null)
237 webClient.cancel(job.getServerJob());
238 job.setStatus(JobStatus.CANCELLED);
239 } catch (IOException e)
241 Console.error(format("failed to cancel job %s", job.getServerJob()), e);
246 protected final void setStatus(JobStatus status)
248 Objects.requireNonNull(status);
249 if (this.status != status)
251 this.status = status;
252 eventHandler.fireTaskStatusChanged(status);
256 protected abstract List<T> prepareJobs() throws ServiceInputInvalidException;
258 protected abstract R collectResult(List<T> jobs) throws IOException;
261 * Update task status according to the overall status of its jobs. The rules
262 * of setting the status are following:
264 * <li>task is invalid if all jobs are invalid</li>
265 * <li>task is completed if all but invalid jobs are completed</li>
266 * <li>task is ready, submitted or queued if at least one job is ready,
267 * submitted or queued an none proceeded to the next stage excluding
269 * <li>task is running if at least one job is running and none are failed or
271 * <li>task is cancelled if at least one job is cancelled and none failed</li>
272 * <li>task is failed or server error if at least one job is failed or server
276 protected final void updateGlobalStatus()
279 for (BaseJob job : jobs)
281 JobStatus status = job.getStatus();
282 int jobPrecedence = ArrayUtils.indexOf(JobStatus.statusPrecedence, status);
283 if (precedence < jobPrecedence)
284 precedence = jobPrecedence;
288 setStatus(JobStatus.statusPrecedence[precedence]);
293 * Set the action that will be run when the {@link #cancel()} method is
294 * invoked. The action should typically stop the executor polling the task and
295 * release resources and threads running the task.
298 * runnable to be executed when the task is cancelled
300 public void setCancelAction(Runnable action)
302 Objects.requireNonNull(action);
303 this.cancelAction = action;
307 public String toString()
309 var statusName = status != null ? status.name() : "UNSET";
310 return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, statusName);