From 67b44948b38b6d226d8ea2efe8eda3992c788a4e Mon Sep 17 00:00:00 2001 From: Mateusz Warowny Date: Wed, 3 Feb 2021 16:14:50 +0100 Subject: [PATCH] JAL-3878 Initial preparation. --- src/jalview/util/MathUtils.java | 13 ++ src/jalview/ws2/JalviewWebServiceI.java | 67 +++++++++ src/jalview/ws2/MenuEntryProviderI.java | 12 ++ src/jalview/ws2/MsaMenuEntryProvider.java | 211 +++++++++++++++++++++++++++++ src/jalview/ws2/WSJobID.java | 69 ++++++++++ src/jalview/ws2/WSJobState.java | 57 ++++++++ src/jalview/ws2/WSJobTrackerI.java | 9 ++ src/jalview/ws2/WebServiceExecutor.java | 85 ++++++++++++ src/jalview/ws2/WebServiceWorkerI.java | 73 ++++++++++ 9 files changed, 596 insertions(+) create mode 100755 src/jalview/ws2/JalviewWebServiceI.java create mode 100755 src/jalview/ws2/MenuEntryProviderI.java create mode 100755 src/jalview/ws2/MsaMenuEntryProvider.java create mode 100755 src/jalview/ws2/WSJobID.java create mode 100755 src/jalview/ws2/WSJobState.java create mode 100755 src/jalview/ws2/WSJobTrackerI.java create mode 100644 src/jalview/ws2/WebServiceExecutor.java create mode 100644 src/jalview/ws2/WebServiceWorkerI.java diff --git a/src/jalview/util/MathUtils.java b/src/jalview/util/MathUtils.java index ecbb6e1..819d17f 100644 --- a/src/jalview/util/MathUtils.java +++ b/src/jalview/util/MathUtils.java @@ -39,4 +39,17 @@ public class MathUtils 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; + } } diff --git a/src/jalview/ws2/JalviewWebServiceI.java b/src/jalview/ws2/JalviewWebServiceI.java new file mode 100755 index 0000000..6e65dd0 --- /dev/null +++ b/src/jalview/ws2/JalviewWebServiceI.java @@ -0,0 +1,67 @@ +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 + */ +public interface JalviewWebServiceI +{ + 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 sequences, WsParamSetI preset, + List 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); + +} diff --git a/src/jalview/ws2/MenuEntryProviderI.java b/src/jalview/ws2/MenuEntryProviderI.java new file mode 100755 index 0000000..ac08c49 --- /dev/null +++ b/src/jalview/ws2/MenuEntryProviderI.java @@ -0,0 +1,12 @@ +package jalview.ws2; + +import javax.swing.JMenu; + +import jalview.gui.AlignFrame; + +@FunctionalInterface +public interface MenuEntryProviderI +{ + public void buildMenu(JMenu parent, JalviewWebServiceI service, + AlignFrame frame); +} diff --git a/src/jalview/ws2/MsaMenuEntryProvider.java b/src/jalview/ws2/MsaMenuEntryProvider.java new file mode 100755 index 0000000..d29f638 --- /dev/null +++ b/src/jalview/ws2/MsaMenuEntryProvider.java @@ -0,0 +1,211 @@ +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> service; + + private final AlignmentView msa; + + private final AlignmentI seqdataset; + + private boolean submitGaps = false; + + private boolean preserveOrder = false; + + private List parameters = Collections.emptyList(); + + MsaWSWorker(JalviewWebServiceI> 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 parameters) + { + this.parameters = parameters; + } + + @Override + public WSJobID startJob(WSJob job) { + return new WSJobID(service.getName(), service.getClass().toString(), "0"); + } + + @Override + public List 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> 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( + "%s
%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 openEditParamsDialog(JalviewWebServiceI service, + WsParamSetI preset, List 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(); + } +} diff --git a/src/jalview/ws2/WSJobID.java b/src/jalview/ws2/WSJobID.java new file mode 100755 index 0000000..97a1f1a --- /dev/null +++ b/src/jalview/ws2/WSJobID.java @@ -0,0 +1,69 @@ +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; + } + +} diff --git a/src/jalview/ws2/WSJobState.java b/src/jalview/ws2/WSJobState.java new file mode 100755 index 0000000..719fdbd --- /dev/null +++ b/src/jalview/ws2/WSJobState.java @@ -0,0 +1,57 @@ +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 diff --git a/src/jalview/ws2/WSJobTrackerI.java b/src/jalview/ws2/WSJobTrackerI.java new file mode 100755 index 0000000..671610a --- /dev/null +++ b/src/jalview/ws2/WSJobTrackerI.java @@ -0,0 +1,9 @@ +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 diff --git a/src/jalview/ws2/WebServiceExecutor.java b/src/jalview/ws2/WebServiceExecutor.java new file mode 100644 index 0000000..8959dd6 --- /dev/null +++ b/src/jalview/ws2/WebServiceExecutor.java @@ -0,0 +1,85 @@ +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 listeners = new CopyOnWriteArrayList<>(); + + public void addServiceListener(WebServiceThreadListenerI listener) + { + if (!listeners.contains(listener)) + listeners.add(listener); + } + + public void removeServiceListener(WebServiceThreadListenerI listener) + { + listeners.remove(listener); + } +} diff --git a/src/jalview/ws2/WebServiceWorkerI.java b/src/jalview/ws2/WebServiceWorkerI.java new file mode 100644 index 0000000..1c40eb8 --- /dev/null +++ b/src/jalview/ws2/WebServiceWorkerI.java @@ -0,0 +1,73 @@ +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 getJobs(); + + public WSJobID startJob(WSJob job) throws IOException; + + public boolean pollJob(WSJob job) throws IOException; +} -- 1.7.10.2