import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.ToolTipManager;
+import org.eclipse.jetty.http.HttpGenerator.Result;
+
import jalview.analysis.AlignSeq;
import jalview.analysis.AlignmentSorter;
import jalview.analysis.SeqsetUtils;
import jalview.util.MathUtils;
import jalview.util.MessageManager;
import jalview.ws.params.ArgumentI;
+import jalview.ws.params.ParamDatastoreI;
import jalview.ws.params.WsParamSetI;
import jalview.ws2.MenuEntryProviderI;
import jalview.ws2.ResultSupplier;
import jalview.ws2.WebServiceI;
import jalview.ws2.WebServiceInfoUpdater;
import jalview.ws2.WebServiceWorkerI;
+import jalview.ws2.WebServiceWorkerListener;
+import jalview.ws2.WebServiceWorkerListenersList;
+import jalview.ws2.gui.AlignmentMenuBuilder;
import jalview.ws2.utils.WSJobList;
/**
+ * Implementation of the {@link Operation} for multiple sequence alignment jobs.
*
* @author mmwarowny
*
*/
public class AlignmentOperation implements Operation
{
- final WebServiceI service;
+ private final WebServiceI service;
- final ResultSupplier<AlignmentI> supplier;
+ private final ResultSupplier<AlignmentI> supplier;
- public AlignmentOperation(WebServiceI service,
- ResultSupplier<AlignmentI> supplier)
+ public AlignmentOperation(
+ WebServiceI service,
+ ResultSupplier<AlignmentI> supplier)
{
this.service = service;
this.supplier = supplier;
}
@Override
+ public String getDescription()
+ {
+ return service.getDescription();
+ }
+
+ @Override
public String getTypeName()
{
return "Multiple Sequence Alignment";
}
@Override
+ public boolean hasParameters()
+ {
+ return service.hasParameters();
+ }
+
+ @Override
+ public ParamDatastoreI getParamStore()
+ {
+ return service.getParamStore();
+ }
+
+ @Override
public int getMinSequences()
{
return 2;
}
@Override
+ public boolean isAlignmentAnalysis()
+ {
+ return false;
+ }
+
+ @Override
public boolean canSubmitGaps()
{
// hack copied from original jabaws code, don't blame me
- return service.getName().contains("lustal");
+ return getName().contains("lustal");
}
@Override
}
@Override
- public MenuEntryProviderI getMenuBuilder()
- {
- return this::buildMenu;
- }
-
- protected void buildMenu(JMenu parent, AlignFrame frame)
+ public boolean getFilterNonStandardSymbols()
{
- if (canSubmitGaps())
- {
- var alignSubmenu = new JMenu(service.getName());
- buildMenu(alignSubmenu, 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, frame, true);
- parent.add(realignSubmenu);
- }
- else
- {
- buildMenu(parent, frame, false);
- }
+ return true;
}
- protected void buildMenu(JMenu parent, AlignFrame frame,
- boolean submitGaps)
+ @Override
+ public boolean getNeedsAlignedSequences()
{
- final String action = submitGaps ? "Align" : "Realign";
- final var calcName = service.getName();
-
- String title = frame.getTitle();
- PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
- {
- 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) -> {
- final AlignmentView msa = frame.gatherSequencesForAlignment();
- final AlignViewport viewport = frame.getViewport();
- final AlignmentI alignment = frame.getViewport().getAlignment();
- if (msa != null)
- {
- WebServiceWorkerI worker = new AlignmentWorker(msa,
- Collections.emptyList(), title, submitGaps, true,
- alignment, viewport);
- executor.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_alignment"));
- item.addActionListener((event) -> {
- final AlignmentView msa = frame.gatherSequencesForAlignment();
- final AlignViewport viewport = frame.getViewport();
- final AlignmentI alignment = frame.getViewport().getAlignment();
- if (msa != null)
- {
- openEditParamsDialog(service, null, null)
- .thenAcceptAsync((arguments) -> {
- if (arguments != null)
- {
- WebServiceWorkerI worker = new AlignmentWorker(msa,
- arguments, title, submitGaps, true, alignment,
- viewport);
- executor.submit(worker);
- }
- });
- }
- });
- 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) -> {
- final AlignmentView msa = frame.gatherSequencesForAlignment();
- final AlignViewport viewport = frame.getViewport();
- final AlignmentI alignment = frame.getViewport().getAlignment();
- if (msa != null)
- {
- WebServiceWorkerI worker = new AlignmentWorker(msa,
- preset.getArguments(), title, submitGaps, true,
- alignment, viewport);
- executor.submit(worker);
- }
- });
- presetList.add(item);
- }
- parent.add(presetList);
- }
+ return false;
}
- private CompletionStage<List<ArgumentI>> openEditParamsDialog(
- WebServiceI service, WsParamSetI preset,
- List<ArgumentI> arguments)
+ @Override
+ public MenuEntryProviderI getMenuBuilder()
{
- 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);
- 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;
- }
- });
+ return new AlignmentMenuBuilder(this);
}
/**
* @author mmwarowny
*
*/
- private class AlignmentWorker implements WebServiceWorkerI
+ public class AlignmentWorker implements WebServiceWorkerI
{
private long uid = MathUtils.getUID();
private Map<Long, JobInput> inputs = new LinkedHashMap<>();
- private WebserviceInfo wsInfo;
-
private Map<Long, Integer> exceptionCount = new HashMap<>();
private final int MAX_RETRY = 5;
- AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
- String alnTitle, boolean submitGaps, boolean preserveOrder,
- AlignmentI alignment, AlignViewport viewport)
+ public AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
+ String alnTitle, boolean submitGaps, boolean preserveOrder,
+ AlignViewport viewport)
{
this.msa = msa;
- this.dataset = alignment.getDataset();
+ this.dataset = viewport.getAlignment().getDataset();
List<AlignedCodonFrame> cf = Objects.requireNonNullElse(
- alignment.getCodonFrames(), Collections.emptyList());
+ viewport.getAlignment().getCodonFrames(), Collections.emptyList());
this.codonFrame.addAll(cf);
this.args = args;
this.alnTitle = alnTitle;
this.preserveOrder = preserveOrder;
this.viewport = viewport;
this.gapCharacter = viewport.getGapCharacter();
-
- String panelInfo = String.format("%s using service hosted at %s%n%s",
- service.getName(), service.getHostName(),
- Objects.requireNonNullElse(service.getDescription(), ""));
- wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
}
@Override
}
@Override
- public List<WSJob> getJobs()
+ public WSJobList getJobs()
{
- return Collections.unmodifiableList(jobs);
+ return jobs;
}
@Override
public void start() throws IOException
{
Cache.log.info(format("Starting new %s job.", service.getName()));
- String outputHeader = String.format("%s of %s%nJob details%n",
- submitGaps ? "Re-alignment" : "Alignment", alnTitle);
SequenceI[][] conmsa = msa.getVisibleContigs('-');
if (conmsa == null)
{
return;
}
- WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
- updater.setOutputHeader(outputHeader);
int numValid = 0;
for (int i = 0; i < conmsa.length; i++)
{
JobInput input = JobInput.create(conmsa[i], 2, submitGaps);
WSJob job = new WSJob(service.getProviderName(), service.getName(),
- service.getHostName());
- job.setJobNum(wsInfo.addJobPane());
- if (conmsa.length > 1)
- {
- wsInfo.setProgressName(String.format("region %d", i),
- job.getJobNum());
- }
- wsInfo.setProgressText(job.getJobNum(), outputHeader);
- job.addPropertyChangeListener(updater);
+ service.getHostName());
+ job.setJobNum(i);
inputs.put(job.getUid(), input);
jobs.add(job);
+ listeners.fireJobCreated(job);
if (input.isInputValid())
{
int count;
{
job.setStatus(WSJobStatus.INVALID);
job.setErrorLog(
- MessageManager.getString("label.empty_alignment_job"));
+ MessageManager.getString("label.empty_alignment_job"));
}
}
if (numValid > 0)
{
- // wsInfo.setThisService() should happen here
- wsInfo.setVisible(true);
+ listeners.fireWorkerStarted();
}
else
{
- wsInfo.setVisible(false);
- // TODO show notification dialog.
- // JvOptionPane.showMessageDialog(frame,
- // MessageManager.getString("info.invalid_msa_input_mininfo"),
- // MessageManager.getString("info.invalid_msa_notenough"),
- // JvOptionPane.INFORMATION_MESSAGE);
+ listeners.fireWorkerNotStarted();
}
}
} catch (IOException e)
{
Cache.log.error(format("Polling job %s failed.", job), e);
- wsInfo.appendProgressText(job.getJobNum(),
- MessageManager.formatMessage("info.server_exception",
- service.getName(), e.getMessage()));
+ listeners.firePollException(job, e);
int count = exceptionCount.getOrDefault(job.getUid(),
- MAX_RETRY);
+ MAX_RETRY);
if (--count <= 0)
{
job.setStatus(WSJobStatus.SERVER_ERROR);
Cache.log.warn(format(
- "Attempts limit exceeded. Droping job %s.", job));
+ "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);
+ format("Out of memory when retrieving job %s", job), e);
}
Cache.log.debug(
- format("Job %s status is %s", job, job.getStatus()));
+ format("Job %s status is %s", job, job.getStatus()));
}
done &= job.getStatus().isDone() || job.getStatus().isFailed();
}
- updateWSInfoGlobalStatus();
return done;
}
- private void updateWSInfoGlobalStatus()
- {
- if (jobs.countRunning() > 0)
- {
- wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
- }
- else if (jobs.countQueuing() > 0
- || jobs.countSubmitted() < jobs.size())
- {
- wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
- }
- else
- {
- if (jobs.countSuccessful() > 0)
- {
- wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
- }
- else if (jobs.countCancelled() > 0)
- {
- wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
- }
- else if (jobs.countFailed() > 0)
- {
- wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
- }
- }
- }
-
@Override
public void done()
{
- long progbarId = MathUtils.getUID();
- wsInfo.setProgressBar(
- MessageManager.getString("status.collecting_job_results"),
- progbarId);
+ listeners.fireWorkerCompleting();
Map<Long, AlignmentI> results = new LinkedHashMap<>();
for (WSJob job : getJobs())
{
continue;
try
{
- AlignmentI alignment = supplier.getResult(job, dataset.getSequences(), viewport);
+ AlignmentI alignment = supplier.getResult(job,
+ dataset.getSequences(), viewport);
if (alignment != null)
{
results.put(job.getUid(), alignment);
}
}
}
- updateWSInfoGlobalStatus();
if (results.size() > 0)
{
- OutputWrapper out = prepareOutput(results);
- wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
- new Alignment(out.aln), out.alorders, out.hidden));
- wsInfo.setResultsReady();
+ AlignmentResult out = prepareResult(results);
+ resultConsumer.accept(out);
}
else
{
- wsInfo.setFinishedNoResults();
- }
- wsInfo.removeProgressBar(progbarId);
- }
-
- private class OutputWrapper
- {
- AlignmentI aln;
-
- List<AlignmentOrder> alorders;
-
- HiddenColumns hidden;
-
- OutputWrapper(AlignmentI aln, List<AlignmentOrder> alorders,
- HiddenColumns hidden)
- {
- this.aln = aln;
- this.alorders = alorders;
- this.hidden = hidden;
+ resultConsumer.accept(null);
}
+ listeners.fireWorkerCompleted();
}
- private OutputWrapper prepareOutput(Map<Long, AlignmentI> alignments)
+ private AlignmentResult prepareResult(Map<Long, AlignmentI> alignments)
{
List<AlignmentOrder> alorders = new ArrayList<>();
SequenceI[][] results = new SequenceI[jobs.size()][];
width = Integer.max(width, emptySeq.getLength());
// pad shorter sequences with gaps
String gapSeq = String.join("",
- Collections.nCopies(width, Character.toString(gapChar)));
+ Collections.nCopies(width, Character.toString(gapChar)));
List<SequenceI> seqs = new ArrayList<>(
- alnSeqs.size() + emptySeqs.size());
+ alnSeqs.size() + emptySeqs.size());
seqs.addAll(alnSeqs);
seqs.addAll(emptySeqs);
for (var seq : seqs)
{
if (seq.getLength() < width)
seq.setSequence(seq.getSequenceAsString()
- + gapSeq.substring(seq.getLength()));
+ + gapSeq.substring(seq.getLength()));
}
SequenceI[] result = seqs.toArray(new SequenceI[0]);
AlignmentOrder msaOrder = new AlignmentOrder(result);
aln.setDataset(dataset);
propagateDatasetMappings(aln);
- return new OutputWrapper(aln, alorders, hidden);
+ return new AlignmentResult(aln, alorders, hidden);
// displayNewFrame(aln, alorders, hidden);
}
}
}
- private void displayNewFrame(AlignmentI aln,
- List<AlignmentOrder> alorders, HiddenColumns hidden)
+ private Consumer<AlignmentResult> resultConsumer;
+
+ public void setResultConsumer(Consumer<AlignmentResult> consumer)
{
- AlignFrame frame = new AlignFrame(aln, hidden,
- AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
- // TODO store feature renderer settings in worker object
- // frame.getFeatureRenderer().transferSettings(featureSettings);
- var regions = sortOrders(alorders);
- if (alorders.size() == 1)
- {
- frame.addSortByOrderMenuItem(
- format("%s Ordering", service.getName()), alorders.get(0));
- }
- else
- {
- for (int i = 0; i < alorders.size(); i++)
- {
- final int j = i;
- Iterable<String> iter = () -> regions.get(j).stream()
- .map(it -> Integer.toString(it)).iterator();
- var orderName = format("%s Region %s Ordering", service.getName(),
- String.join(",", iter));
- frame.addSortByOrderMenuItem(orderName, alorders.get(i));
- }
- }
+ this.resultConsumer = consumer;
+ }
- /* TODO
- * If alignment was requested from one half of a SplitFrame, show in a
- * SplitFrame with the other pane similarly aligned.
- */
+ private WebServiceWorkerListenersList listeners = new WebServiceWorkerListenersList(this);
- Desktop.addInternalFrame(frame, alnTitle, AlignFrame.DEFAULT_WIDTH,
- AlignFrame.DEFAULT_HEIGHT);
+ @Override
+ public void addListener(WebServiceWorkerListener listener)
+ {
+ listeners.addListener(listener);
}
+ }
+
+ public class AlignmentResult
+ {
+ AlignmentI aln;
+
+ List<AlignmentOrder> alorders;
- private List<List<Integer>> sortOrders(List<?> alorders)
+ HiddenColumns hidden;
+
+ AlignmentResult(AlignmentI aln, List<AlignmentOrder> alorders,
+ HiddenColumns hidden)
{
- List<List<Integer>> regions = new ArrayList<>();
- for (int i = 0; i < alorders.size(); i++)
- {
- List<Integer> regs = new ArrayList<>();
- regs.add(i);
- int j = i + 1;
- while (j < alorders.size())
- {
- if (alorders.get(i).equals(alorders.get(j)))
- {
- alorders.remove(j);
- regs.add(j);
- }
- else
- {
- j++;
- }
- }
- regions.add(regs);
- }
- return regions;
+ this.aln = aln;
+ this.alorders = alorders;
+ this.hidden = hidden;
+ }
+
+ public AlignmentI getAln()
+ {
+ return aln;
+ }
+
+ public List<AlignmentOrder> getAlorders()
+ {
+ return alorders;
+ }
+
+ public HiddenColumns getHidden()
+ {
+ return hidden;
}
}
final Map<String, ? extends Map> sequenceNames;
private JobInput(int numSequences, List<SequenceI> inputSequences,
- List<SequenceI> emptySequences,
- @SuppressWarnings("rawtypes") Map<String, ? extends Map> names)
+ List<SequenceI> emptySequences,
+ @SuppressWarnings("rawtypes") Map<String, ? extends Map> names)
{
this.inputSequences = Collections.unmodifiableList(inputSequences);
this.emptySequences = Collections.unmodifiableList(emptySequences);
}
static JobInput create(SequenceI[] sequences, int minLength,
- boolean submitGaps)
+ boolean submitGaps)
{
assert minLength >= 0 : MessageManager.getString(
- "error.implementation_error_minlen_must_be_greater_zero");
+ "error.implementation_error_minlen_must_be_greater_zero");
int numSeq = 0;
for (SequenceI seq : sequences)
{
if (!submitGaps)
{
seqString = AlignSeq.extractGaps(
- jalview.util.Comparison.GapChars, seqString);
+ jalview.util.Comparison.GapChars, seqString);
}
inputSequences.add(new Sequence(newName, seqString));
}
else
{
String seqString = "";
- if (seq.getEnd() >= seq.getStart()) // true if gaps only
+ if (seq.getEnd() >= seq.getStart()) // true if gaps only
{
seqString = seq.getSequenceAsString();
if (!submitGaps)
{
seqString = AlignSeq.extractGaps(
- jalview.util.Comparison.GapChars, seqString);
+ jalview.util.Comparison.GapChars, seqString);
}
}
emptySequences.add(new Sequence(newName, seqString));