1 package jalview.ws2.operations;
3 import static java.lang.String.format;
5 import java.awt.event.MouseAdapter;
6 import java.awt.event.MouseEvent;
7 import java.io.IOException;
8 import java.util.ArrayList;
9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.LinkedHashMap;
12 import java.util.List;
14 import java.util.Objects;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.concurrent.CompletionStage;
18 import javax.swing.JMenu;
19 import javax.swing.JMenuItem;
20 import javax.swing.ToolTipManager;
22 import org.codehaus.groovy.ast.GenericsType.GenericsTypeName;
24 import jalview.bin.Cache;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.AlignmentOrder;
27 import jalview.datamodel.AlignmentView;
28 import jalview.datamodel.SequenceI;
29 import jalview.gui.AlignFrame;
30 import jalview.gui.JvSwingUtils;
31 import jalview.gui.WebserviceInfo;
32 import jalview.gui.WsJobParameters;
33 import jalview.util.MathUtils;
34 import jalview.util.MessageManager;
35 import jalview.ws.params.ArgumentI;
36 import jalview.ws.params.WsParamSetI;
37 import jalview.ws2.MenuEntryProviderI;
38 import jalview.ws2.ResultSupplier;
39 import jalview.ws2.WSJob;
40 import jalview.ws2.WSJobStatus;
41 import jalview.ws2.WebServiceExecutor;
42 import jalview.ws2.WebServiceI;
43 import jalview.ws2.WebServiceInfoUpdater;
44 import jalview.ws2.WebServiceWorkerI;
45 import jalview.ws2.utils.WSJobList;
52 public class AlignmentOperation implements Operation
54 final WebServiceI service;
55 final ResultSupplier<AlignmentI> supplier;
57 public AlignmentOperation(WebServiceI service, ResultSupplier<AlignmentI> supplier) {
58 this.service = service;
59 this.supplier = supplier;
62 @Override public int getMinSequences() { return 2; }
63 @Override public int getMaxSequences() { return Integer.MAX_VALUE; }
64 @Override public boolean isProteinOperation() { return true; }
65 @Override public boolean isNucleotideOperation() { return true; }
66 @Override public boolean canSubmitGaps() {
67 // hack copied from original jabaws code, don't blame me
68 return service.getName().contains("lustal");
70 @Override public MenuEntryProviderI getMenuBuilder() { return this::buildMenu; }
72 protected void buildMenu(JMenu parent, AlignFrame frame) {
73 if (canSubmitGaps()) {
74 var alignSubmenu = new JMenu(service.getName());
75 buildMenu(alignSubmenu, frame, false);
76 parent.add(alignSubmenu);
77 var realignSubmenu = new JMenu(MessageManager.formatMessage(
78 "label.realign_with_params", service.getName()));
79 realignSubmenu.setToolTipText(MessageManager.getString(
80 "label.align_sequences_to_existing_alignment"));
81 buildMenu(realignSubmenu, frame, true);
82 parent.add(realignSubmenu);
85 buildMenu(parent, frame, false);
89 protected void buildMenu(JMenu parent, AlignFrame frame, boolean submitGaps) {
90 final String action = submitGaps ? "Align" : "Realign";
91 final var calcName = service.getName();
93 final AlignmentView msa = frame.gatherSequencesForAlignment();
94 final AlignmentI dataset = frame.getViewport().getAlignment().getDataset();
95 String title = frame.getTitle();
96 WebServiceExecutor executor = frame.getViewport().getWSExecutor();
98 var item = new JMenuItem(MessageManager.formatMessage(
99 "label.calcname_with_default_settings", calcName));
100 item.setToolTipText(MessageManager.formatMessage(
101 "label.action_with_default_settings", action));
102 item.addActionListener((event) -> {
105 WebServiceWorkerI worker = new AlignmentWorker(
106 msa, Collections.emptyList(), title, submitGaps, true, dataset);
107 executor.submit(worker);
113 if (service.hasParameters())
115 var item = new JMenuItem(MessageManager.getString("label.edit_settings_and_run"));
116 item.setToolTipText(MessageManager.getString(
117 "label.view_and_change_parameters_before_alignment"));
118 item.addActionListener((event) -> {
121 openEditParamsDialog(service, null, null).thenAcceptAsync((arguments) ->{
122 if (arguments != null)
124 WebServiceWorkerI worker = new AlignmentWorker(
125 msa, arguments, title, submitGaps, true, dataset);
126 executor.submit(worker);
134 var presets = service.getParamStore().getPresets();
135 if (presets != null && presets.size() > 0)
137 final var presetList = new JMenu(MessageManager.formatMessage(
138 "label.run_with_preset_params", calcName));
139 final var showToolTipFor = ToolTipManager.sharedInstance().getDismissDelay();
140 for (final var preset : presets)
142 var item = new JMenuItem(preset.getName());
143 final int QUICK_TOOLTIP = 1500;
144 item.addMouseListener(new MouseAdapter()
146 @Override public void mouseEntered(MouseEvent e)
148 ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
150 @Override public void mouseExited(MouseEvent e)
152 ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
155 String tooltip = JvSwingUtils.wrapTooltip(true, format(
156 "<strong>%s</strong><br/>%s",
157 MessageManager.getString(preset.isModifiable() ?
158 "label.user_preset" : "label.service_preset"),
159 preset.getDescription()));
160 item.setToolTipText(tooltip);
161 item.addActionListener((event) -> {
164 WebServiceWorkerI worker = new AlignmentWorker(
165 msa, preset.getArguments(), title, submitGaps, true, dataset);
166 executor.submit(worker);
169 presetList.add(item);
171 parent.add(presetList);
176 private CompletionStage<List<ArgumentI>> openEditParamsDialog(WebServiceI service,
177 WsParamSetI preset, List<ArgumentI> arguments)
179 WsJobParameters jobParams;
180 if (preset == null && arguments != null && arguments.size() > 0)
181 jobParams = new WsJobParameters(service.getParamStore(), preset, arguments);
183 jobParams = new WsJobParameters(service.getParamStore(), preset, null);
184 var stage = jobParams.showRunDialog();
185 return stage.thenApply((startJob) -> {
187 if (jobParams.getPreset() == null) {
188 return jobParams.getJobParams();
191 return jobParams.getPreset().getArguments();
201 * Implementation of the web service worker performing multiple sequence
207 private class AlignmentWorker implements WebServiceWorkerI {
209 private long uid = MathUtils.getUID();
210 private final AlignmentView msa;
211 private final AlignmentI seqdata;
212 private List<ArgumentI> args = Collections.emptyList();
213 private String alnTitle = "";
214 private boolean submitGaps = false;
215 private boolean preserveOrder = false;
216 private WSJobList jobs = new WSJobList();
217 private WebserviceInfo wsInfo;
218 private Map<Long, Integer> exceptionCount = new HashMap<>();
219 private final int MAX_RETRY = 5;
221 AlignmentWorker(AlignmentView msa, List<ArgumentI> args, String alnTitle,
222 boolean submitGaps, boolean preserveOrder, AlignmentI seqdata)
225 this.seqdata = seqdata;
227 this.alnTitle = alnTitle;
228 this.submitGaps = submitGaps;
229 this.preserveOrder = preserveOrder;
231 String panelInfo = String.format("%s using service hosted at %s%n%s",
232 service.getName(), service.getHostName(),
233 Objects.requireNonNullElse(service.getDescription(), ""));
234 wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
239 public long getUID() { return uid; }
242 public List<WSJob> getJobs()
244 return Collections.unmodifiableList(jobs);
248 public void startJobs() throws IOException
250 String outputHeader = String.format("%s of %s%nJob details%n",
251 submitGaps ? "Re-alignment" : "Alignment", alnTitle);
252 SequenceI[][] conmsa = msa.getVisibleContigs('-');
253 if (conmsa == null) {
256 WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
257 updater.setOutputHeader(outputHeader);
258 for (int i = 0; i < conmsa.length; i++) {
259 WSJob job = service.submit(List.of(conmsa[i]), args);
260 job.setJobNum(wsInfo.addJobPane());
261 job.addPropertyChangeListener(updater);
263 if (conmsa.length > 0) {
264 wsInfo.setProgressName(String.format("region %d", i), job.getJobNum());
266 wsInfo.setProgressText(job.getJobNum(), outputHeader);
271 public boolean pollJobs()
274 for (WSJob job : getJobs()) {
275 if (!job.getStatus().isDone() ) {
277 service.updateProgress(job);
278 exceptionCount.remove(job.getUid());
280 catch (IOException e) {
281 int count = exceptionCount.getOrDefault(job.getUid(), MAX_RETRY);
283 job.setStatus(WSJobStatus.SERVER_ERROR);
285 exceptionCount.put(job.getUid(), count);
288 done &= job.getStatus().isDone();
290 updateWSInfoGlobalStatus();
294 private void updateWSInfoGlobalStatus() {
295 if (jobs.countRunning() > 0) {
296 wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
298 else if (jobs.countQueuing() > 0 || jobs.countSubmitted() < jobs.size()) {
299 wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
302 if (jobs.countSuccessful() > 0) {
303 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
305 else if (jobs.countCancelled() > 0) {
306 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
308 else if (jobs.countFailed() > 0) {
309 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
316 long progbarId = MathUtils.getUID();
317 wsInfo.setProgressBar(
318 MessageManager.getString("status.collecting_job_results"),
320 Map<Long, AlignmentI> results = new LinkedHashMap<>();
321 for (WSJob job : getJobs()) {
323 AlignmentI alignment = supplier.getResult(job);
324 if (alignment != null) {
325 results.put(job.getUid(), alignment);
328 catch (Exception e) {
329 if (!service.handleCollectionError(job, e)) {
330 Cache.log.error("Couldn't get alignment for job.", e);
331 // TODO: Increment exception count and retry.
332 job.setStatus(WSJobStatus.SERVER_ERROR);
336 updateWSInfoGlobalStatus();
337 if (results.size() > 0) {
338 wsInfo.showResultsNewFrame.addActionListener(
339 evt -> displayResults(results));
340 wsInfo.setResultsReady();
343 wsInfo.setFinishedNoResults();
345 wsInfo.removeProgressBar(progbarId);
348 private void displayResults(Map<Long, AlignmentI> alignments)
350 List<AlignmentOrder> alorders = new ArrayList<>();
351 SequenceI[][] results = new SequenceI[jobs.size()][];
352 AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
353 for (int i = 0; i < jobs.size(); i++) {
354 WSJob job = jobs.get(i);
355 AlignmentI aln = alignments.get(job.getUid());
358 * Get the alignment including any empty sequences in the original
359 * order with original ids.
361 char gapChar = aln.getGapCharacter();
362 int alSeqLen = aln.getSequences().size();
363 SequenceI[] alSeqs = new SequenceI[alSeqLen];
364 alSeqs = aln.getSequences().toArray(alSeqs);
373 public WebServiceI getWebService()