--- /dev/null
+package jalview.ws2.operations;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletionStage;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import jalview.bin.Cache;
+import jalview.gui.AlignFrame;
+import jalview.gui.WsJobParameters;
+import jalview.io.AnnotationFile;
+import jalview.io.FeaturesFile;
+import jalview.util.MathUtils;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.WsParamSetI;
+import jalview.ws2.MenuEntryProviderI;
+import jalview.ws2.ResultSupplier;
+import jalview.ws2.WSJob;
+import jalview.ws2.WSJobStatus;
+import jalview.ws2.WebServiceExecutor;
+import jalview.ws2.WebServiceI;
+import jalview.ws2.WebServiceWorkerI;
+import jalview.ws2.utils.WSJobList;
+
+import static java.lang.String.format;
+
+/**
+ *
+ * @author mmwarowny
+ *
+ */
+public class AnnotationOperation implements Operation
+{
+ final WebServiceI service;
+
+ final String typeName;
+
+ final ResultSupplier<AnnotationFile> annotationSupplier;
+
+ final ResultSupplier<FeaturesFile> featuresSupplier;
+
+ public AnnotationOperation(WebServiceI service,
+ ResultSupplier<AnnotationFile> annotSupplier,
+ ResultSupplier<FeaturesFile> featSupplier, String operationName)
+ {
+ this.service = service;
+ this.annotationSupplier = annotSupplier;
+ this.featuresSupplier = featSupplier;
+ this.typeName = operationName;
+ }
+
+ @Override
+ public String getName()
+ {
+ return service.getName();
+ }
+
+ @Override
+ public String getTypeName()
+ {
+ return typeName;
+ }
+
+ @Override
+ public String getHostName()
+ {
+ return service.getHostName();
+ }
+
+ @Override
+ public int getMinSequences()
+ {
+ return 0;
+ }
+
+ @Override
+ public int getMaxSequences()
+ {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public boolean canSubmitGaps()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isProteinOperation()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isNucleotideOperation()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+ @Override
+ public MenuEntryProviderI getMenuBuilder()
+ {
+ return this::buildMenu;
+ }
+
+ protected void buildMenu(JMenu parent, AlignFrame frame)
+ {
+ final var calcName = service.getName();
+ WebServiceExecutor wsExecutor = frame.getViewport().getWSExecutor();
+ {
+ var item = new JMenuItem(MessageManager.formatMessage(
+ "label.calcname_with_default_settings", calcName));
+ item.addActionListener((event) -> {
+ WebServiceWorkerI worker = new AnnotationWorker();
+ wsExecutor.submit(worker);
+ });
+ parent.add(item);
+ }
+ if (service.hasParameters())
+ {
+ var item = new JMenuItem(
+ MessageManager.getString("label.edit_settings_and_run"));
+ item.setToolTipText(MessageManager.getString(
+ "label.view_and_change_parameters_before_running_calculation"));
+ item.addActionListener((event) -> {
+ openEditParamsDialog(service, null, null)
+ .thenAcceptAsync((arguments) -> {
+ if (arguments != null)
+ {
+
+ }
+ });
+ });
+ }
+ }
+
+ private CompletionStage<List<ArgumentI>> openEditParamsDialog(
+ WebServiceI service, WsParamSetI preset, List<ArgumentI> arguments)
+ {
+ WsJobParameters jobParams;
+ if (preset == null && arguments != null && arguments.size() > 0)
+ jobParams = new WsJobParameters(service.getParamStore(), preset,
+ arguments);
+ else
+ jobParams = new WsJobParameters(service.getParamStore(), preset,
+ null);
+ if (preset != null)
+ {
+ jobParams.setName(MessageManager.getString(
+ "label.adjusting_parameters_for_calculation"));
+ }
+ var stage = jobParams.showRunDialog();
+ return stage.thenApply((startJob) -> {
+ if (startJob)
+ {
+ if (jobParams.getPreset() == null)
+ return jobParams.getJobParams();
+ else
+ return jobParams.getPreset().getArguments();
+ }
+ else
+ {
+ return null;
+ }
+ });
+ }
+
+ private class AnnotationWorker implements WebServiceWorkerI
+ {
+ private long uid = MathUtils.getUID();
+
+ private WSJobList jobs = new WSJobList();
+
+ private HashMap<Long, Integer> exceptionCount = new HashMap<>();
+
+ private static final int MAX_RETRY = 5;
+
+ @Override
+ public long getUID()
+ {
+ return uid;
+ }
+
+ @Override
+ public WebServiceI getWebService()
+ {
+ return service;
+ }
+
+ @Override
+ public List<WSJob> getJobs()
+ {
+ return Collections.unmodifiableList(jobs);
+ }
+
+ @Override
+ public void startJobs() throws IOException
+ {
+
+ }
+
+ @Override
+ public boolean pollJobs() throws IOException
+ {
+ boolean done = true;
+ for (WSJob job : getJobs())
+ {
+ if (!job.getStatus().isDone() && !job.getStatus().isFailed())
+ {
+ Cache.log.debug(format("Polling job %s", job));
+ try
+ {
+ service.updateProgress(job);
+ exceptionCount.remove(job.getUid());
+ } catch (IOException e)
+ {
+ Cache.log.error(format("Polling job %s failed.", job), e);
+ int count = exceptionCount.getOrDefault(job.getUid(),
+ MAX_RETRY);
+ if (--count <= 0)
+ {
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ Cache.log.warn(format(
+ "Attempts limit exceeded. Droping job %s.", job));
+ }
+ exceptionCount.put(job.getUid(), count);
+ } catch (OutOfMemoryError e)
+ {
+ job.setStatus(WSJobStatus.BROKEN);
+ Cache.log.error(
+ format("Out of memory when retrieving job %s", job), e);
+ }
+ Cache.log.debug(
+ format("Job %s status is %s", job, job.getStatus()));
+ }
+ done &= job.getStatus().isDone() || job.getStatus().isFailed();
+ }
+ return done;
+ }
+
+ @Override
+ public void done()
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ }
+}