return gcd(b, a % b);
}
+
+ private static int uidCounter = (int)(Math.random() * 0xffffffff);
+ /**
+ * Generates a unique 64-bit identifier.
+ */
+ public static long getUID()
+ {
+ long uid = 0L;
+ uid |= ((System.currentTimeMillis() >> 10) & 0xfffffffL) << 36;
+ uid |= (long)(Math.random() * 0xfL) << 32;
+ uid |= ++uidCounter & 0xffffffff;
+ return uid;
+ }
}
--- /dev/null
+package jalview.ws2;
+
+import java.io.IOException;
+import java.util.List;
+
+import jalview.datamodel.SequenceI;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
+import jalview.ws.params.WsParamSetI;
+
+/**
+ * Provides information about the web service and sub-routines
+ * to submit and track the jobs running on the server as well as
+ * retrieve the results.
+ * The instances should not depend on any other jalview components, especially
+ * must be oblivious to the existence of any UI.
+ * They are used by other classes such as WebServiceWorkers rather than
+ * manipulate data themselves.
+ *
+ * @author mmwarowny
+ *
+ * @param <R>
+ */
+public interface JalviewWebServiceI<R>
+{
+ public static final int PROTEIN_SERVICE = 0x01;
+ public static final int NUCLEOTIDE_SERVICE = 0x02;
+ public static final int ALIGNMENT_ANALYSIS = 0x04;
+
+ public String getHostName();
+ public String getName();
+ public String getDescription();
+ public String getOperationType();
+ public int getTypeFlags();
+ public boolean canSubmitGaps();
+ public int getMinSequences();
+ public int getMaxSequences();
+ public boolean hasParameters();
+ public ParamDatastoreI getParamStore();
+
+ public default boolean isProteinService() {
+ return (getTypeFlags() & PROTEIN_SERVICE) > 0;
+ }
+ public default boolean isNucleotideService() {
+ return (getTypeFlags() & NUCLEOTIDE_SERVICE) > 0;
+ }
+ public default boolean isAlignmentAnalysis() {
+ return (getTypeFlags() & ALIGNMENT_ANALYSIS) > 0;
+ }
+
+ public WSJobID submit(List<SequenceI> sequences, WsParamSetI preset,
+ List<ArgumentI> parameters) throws IOException;
+
+ public void updateProgress(WSJobID id, WSJobTrackerI tracker)
+ throws IOException;
+
+ public R getResult(WSJobID id) throws IOException;
+
+ public void cancel(WSJobID id) throws IOException;
+
+ public boolean handleSubmissionError(WSJobID id, Throwable th,
+ WSJobTrackerI tracker);
+
+ public boolean handleCollectionError(WSJobID id, Throwable th,
+ WSJobTrackerI tracker);
+
+}
--- /dev/null
+package jalview.ws2;
+
+import javax.swing.JMenu;
+
+import jalview.gui.AlignFrame;
+
+@FunctionalInterface
+public interface MenuEntryProviderI
+{
+ public void buildMenu(JMenu parent, JalviewWebServiceI<?> service,
+ AlignFrame frame);
+}
--- /dev/null
+package jalview.ws2;
+
+import java.util.List;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Collections;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.ToolTipManager;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.JvSwingUtils;
+import jalview.gui.WsJobParameters;
+import jalview.util.MathUtils;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws.params.WsParamSetI;
+import static java.lang.String.format;
+
+
+class MsaWSWorker implements WebServiceWorkerI
+{
+ private long uid = MathUtils.getUID();
+
+ JalviewWebServiceI<List<SequenceI>> service;
+
+ private final AlignmentView msa;
+
+ private final AlignmentI seqdataset;
+
+ private boolean submitGaps = false;
+
+ private boolean preserveOrder = false;
+
+ private List<ArgumentI> parameters = Collections.emptyList();
+
+ MsaWSWorker(JalviewWebServiceI<List<SequenceI>> service, AlignmentView msa,
+ boolean submitGaps, boolean preserveOrder, AlignmentI seqdataset)
+ {
+ this.service = service;
+ this.msa = msa;
+ this.seqdataset = seqdataset;
+ this.submitGaps = submitGaps;
+ this.preserveOrder = preserveOrder;
+ }
+
+ @Override public long getUID() {
+ return uid;
+ };
+
+ void setParameters(List<ArgumentI> parameters)
+ {
+ this.parameters = parameters;
+ }
+
+ @Override
+ public WSJobID startJob(WSJob job) {
+ return new WSJobID(service.getName(), service.getClass().toString(), "0");
+ }
+
+ @Override
+ public List<WSJob> getJobs() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean pollJob(WSJob job) {
+ return false;
+ }
+
+}
+
+public class MsaMenuEntryProvider implements MenuEntryProviderI
+{
+ WebServiceExecutor executor;
+
+ public MsaMenuEntryProvider(WebServiceExecutor executor)
+ {
+ this.executor = executor;
+ }
+
+ @Override
+ public void buildMenu(JMenu parent, JalviewWebServiceI service, AlignFrame frame)
+ {
+ if (service.canSubmitGaps())
+ {
+ var alignSubmenu = new JMenu(service.getName());
+ buildMenu(alignSubmenu, service, frame, false);
+ parent.add(alignSubmenu);
+ var realignSubmenu = new JMenu(MessageManager.formatMessage(
+ "label.realign_with_params", service.getName()));
+ realignSubmenu.setToolTipText(MessageManager
+ .getString("label.align_sequences_to_existing_alignment"));
+ buildMenu(realignSubmenu, service, frame, true);
+ parent.add(realignSubmenu);
+ }
+ else
+ {
+ buildMenu(parent, service, frame, false);
+ }
+ }
+
+ private void buildMenu(JMenu parent, JalviewWebServiceI<List<SequenceI>> service,
+ AlignFrame frame, boolean submitGaps)
+ {
+ final String action = submitGaps ? "Align" : "Realign";
+ final var calcName = service.getName();
+
+ {
+ var item = new JMenuItem(MessageManager.formatMessage(
+ "label.calcname_with_default_settings", calcName));
+ item.setToolTipText(MessageManager.formatMessage(
+ "label.action_with_default_settings", action));
+ item.addActionListener((event) -> {
+ AlignmentView msa = frame.gatherSequencesForAlignment();
+ AlignmentI dataset = frame.getViewport().getAlignment().getDataset();
+ if (msa != null)
+ {
+ executor.submit(new MsaWSWorker(service, msa, submitGaps, true, dataset));
+ }
+ });
+ 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_alignment"));
+ item.addActionListener((event) -> {
+ AlignmentView msa = frame.gatherSequencesForAlignment();
+ AlignmentI dataset = frame.getViewport().getAlignment().getDataset();
+ if (msa != null)
+ {
+ var parameters = openEditParamsDialog(service, null, null);
+ if (parameters != null)
+ {
+ var thread = new MsaWSWorker(service, msa, submitGaps, true, dataset);
+ thread.setParameters(parameters);
+ executor.submit(thread);
+ }
+ }
+ });
+ parent.add(item);
+ }
+
+ var presets = service.getParamStore().getPresets();
+ if (presets != null && presets.size() > 0)
+ {
+ final var presetList = new JMenu(MessageManager.formatMessage(
+ "label.run_with_preset_params", calcName));
+ final var showToolTipFor = ToolTipManager.sharedInstance().getDismissDelay();
+ for (final var preset : presets)
+ {
+ var item = new JMenuItem(preset.getName());
+ final int QUICK_TOOLTIP = 1500;
+ item.addMouseListener(new MouseAdapter()
+ {
+ @Override public void mouseEntered(MouseEvent e)
+ {
+ ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
+ }
+ @Override public void mouseExited(MouseEvent e)
+ {
+ ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
+ }
+ });
+ String tooltip = JvSwingUtils.wrapTooltip(true, format(
+ "<strong>%s</strong><br/>%s",
+ MessageManager.getString(preset.isModifiable() ?
+ "label.user_preset" : "label.service_preset"),
+ preset.getDescription()));
+ item.setToolTipText(tooltip);
+ item.addActionListener((event) -> {
+ AlignmentView msa = frame.gatherSequencesForAlignment();
+ AlignmentI dataset = frame.getViewport().getAlignment().getDataset();
+ if (msa != null)
+ {
+ var thread = new MsaWSWorker(service, msa, submitGaps, true, dataset);
+ thread.setParameters(preset.getArguments());
+ executor.submit(thread);
+ }
+ });
+ presetList.add(item);
+ }
+ parent.add(presetList);
+ }
+
+ }
+
+ private List<ArgumentI> openEditParamsDialog(JalviewWebServiceI 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 (!jobParams.showRunDialog())
+ // cancelled
+ return null;
+ if (jobParams.getPreset() == null)
+ return jobParams.getJobParams();
+ else
+ return jobParams.getPreset().getArguments();
+ }
+}
--- /dev/null
+package jalview.ws2;
+
+import java.io.Serializable;
+import java.util.Date;
+import static java.lang.String.format;
+
+public final class WSJobID implements Serializable
+{
+ private static final long serialVersionUID = -4600214977954333787L;
+ private String serviceType = "";
+ private String serviceImpl = "";
+ private String jobID = "";
+ private Date creationTime = new Date();
+
+ public WSJobID() {}
+
+ public WSJobID(String serviceType, String serviceImpl, String jobID) {
+ this.serviceType = serviceType;
+ this.serviceImpl = serviceImpl;
+ this.jobID = jobID;
+ }
+
+ @Override
+ public String toString() {
+ return format("%s:%s [%s] Created %s",
+ serviceType, serviceImpl, jobID, creationTime);
+ }
+
+ public String getServiceType()
+ {
+ return serviceType;
+ }
+
+ public void setServiceType(String serviceType)
+ {
+ this.serviceType = serviceType;
+ }
+
+ public String getServiceImpl()
+ {
+ return serviceImpl;
+ }
+
+ public void setServiceImpl(String serviceImpl)
+ {
+ this.serviceImpl = serviceImpl;
+ }
+
+ public String getJobID()
+ {
+ return jobID;
+ }
+
+ public void setJobID(String jobID)
+ {
+ this.jobID = jobID;
+ }
+
+ public Date getCreationTime()
+ {
+ return creationTime;
+ }
+
+ public void setCreationTime(Date creationTime)
+ {
+ this.creationTime = creationTime;
+ }
+
+}
--- /dev/null
+package jalview.ws2;
+
+
+public enum WSJobState
+{
+ INVALID, READY, SUBMITTED, QUEUED, RUNNING, FINISHED, BROKEN, FAILED,
+ UNKNOWN, SERVER_ERROR, CANCELLED;
+
+ public boolean isSubmitted()
+ {
+ switch (this)
+ {
+ case INVALID:
+ case READY:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ public boolean isCancelled()
+ {
+ return this == WSJobState.CANCELLED;
+ }
+
+ public boolean isDone()
+ {
+ switch (this)
+ {
+ case INVALID:
+ case READY:
+ case SUBMITTED:
+ case QUEUED:
+ case RUNNING:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ public boolean isRunning()
+ {
+ switch (this)
+ {
+ case QUEUED:
+ case RUNNING:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isQueued()
+ {
+ return this == WSJobState.SUBMITTED;
+ }
+}
\ No newline at end of file
--- /dev/null
+package jalview.ws2;
+
+public interface WSJobTrackerI {
+ public void setState(WSJobState state);
+ public int getLogSize();
+ public void appendLog(String log);
+ public int getErrorLogSize();
+ public void appendErrorLog(String log);
+}
\ No newline at end of file
--- /dev/null
+package jalview.ws2;
+
+import java.io.IOException;
+import java.util.EventObject;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import jalview.bin.Cache;
+import jalview.ws2.WebServiceWorkerI.WSJob;
+
+public class WebServiceExecutor
+{
+ private ScheduledExecutorService executor =
+ Executors.newSingleThreadScheduledExecutor();
+
+ public void submit(final WebServiceWorkerI worker)
+ {
+ for (var job : worker.getJobs()) {
+ executor.submit(() -> submitJob(worker, job));
+ executor.schedule(() -> pollJob(worker, job), 1, TimeUnit.SECONDS);
+ }
+ }
+
+ private void submitJob(WebServiceWorkerI worker, WSJob job) {
+ try {
+ job.setJobID(worker.startJob(job).getJobID());
+ job.resetAllowedExceptions();
+ executor.schedule(() -> pollJob(worker, job), 1, TimeUnit.SECONDS);
+ }
+ catch (IOException e) {
+ Cache.log.error("Exception occurred during job submission", e);
+ if (!job.deductAllowedExceptions()) {
+ job.setState(WSJobState.SERVER_ERROR);
+ }
+ }
+ if (!job.getState().isSubmitted()) {
+ executor.schedule(() -> submitJob(worker, job), 5, TimeUnit.SECONDS);
+ }
+ }
+
+ private void pollJob(WebServiceWorkerI worker, WSJob job) {
+ try {
+ worker.pollJob(job);
+ job.resetAllowedExceptions();
+ }
+ catch (IOException e) {
+ Cache.log.error("Exception occurred duringn job pollign", e);
+ if (!job.deductAllowedExceptions()) {
+ job.setState(WSJobState.SERVER_ERROR);
+ }
+ }
+ if (!job.getState().isDone()) {
+ executor.schedule(() -> pollJob(worker, job), 1, TimeUnit.SECONDS);
+ }
+ }
+
+ public static interface WebServiceThreadListenerI
+ {
+ public void threadSubmitted(WebServiceWorkerI thread);
+ public void threadStarted(WebServiceWorkerI thread);
+ public void stateChanged(WebServiceWorkerI thread, WSJobState oldState,
+ WSJobState newState);
+ public void logAppended(WebServiceWorkerI thread, String text);
+ public void errorLogAppended(WebServiceWorkerI thread, String text);
+ public void cancelled(WebServiceWorkerI thread);
+ }
+
+
+ List<WebServiceThreadListenerI> listeners = new CopyOnWriteArrayList<>();
+
+ public void addServiceListener(WebServiceThreadListenerI listener)
+ {
+ if (!listeners.contains(listener))
+ listeners.add(listener);
+ }
+
+ public void removeServiceListener(WebServiceThreadListenerI listener)
+ {
+ listeners.remove(listener);
+ }
+}
--- /dev/null
+package jalview.ws2;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.print.attribute.standard.JobState;
+
+import jalview.util.MathUtils;
+
+public interface WebServiceWorkerI
+{
+ public class WSJob
+ {
+ public final long uid = MathUtils.getUID();
+
+ protected WSJobState state = WSJobState.UNKNOWN;
+
+ protected String jobID = "";
+
+ protected int jobNum = 0;
+
+ protected int allowedExceptions = 3;
+
+ public long getUID() {
+ return uid;
+ }
+
+ public WSJobState getState()
+ {
+ return state;
+ }
+
+ public void setState(WSJobState state)
+ {
+ this.state = state;
+ }
+
+ public String getJobID()
+ {
+ return jobID;
+ }
+
+ public void setJobID(String jobID) {
+ this.jobID = jobID;
+ }
+
+ public int getJobNum()
+ {
+ return jobNum;
+ }
+
+ public int getAllowedExceptions()
+ {
+ return allowedExceptions;
+ }
+
+ public boolean deductAllowedExceptions() {
+ return allowedExceptions-- > 0;
+ }
+
+ public void resetAllowedExceptions() {
+ allowedExceptions = 3;
+ }
+ }
+
+ public long getUID();
+
+ public List<WSJob> getJobs();
+
+ public WSJobID startJob(WSJob job) throws IOException;
+
+ public boolean pollJob(WSJob job) throws IOException;
+}