2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3 * Copyright (C) 2014 The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ws.jws2;
25 import compbio.data.msa.MsaWS;
26 import compbio.metadata.Argument;
27 import compbio.metadata.ChunkHolder;
28 import compbio.metadata.JobStatus;
29 import compbio.metadata.Preset;
31 import jalview.analysis.*;
33 import jalview.datamodel.*;
35 import jalview.ws.AWsJob;
36 import jalview.ws.WSClientI;
37 import jalview.ws.JobStateSummary;
38 import jalview.ws.jws2.dm.JabaWsParamSet;
39 import jalview.ws.params.WsParamSetI;
41 class MsaWSThread extends AWS2Thread implements WSClientI
43 boolean submitGaps = false; // pass sequences including gaps to alignment
47 boolean preserveOrder = true; // and always store and recover sequence
51 class MsaWSJob extends JWs2Job
55 WsParamSetI preset = null;
57 List<Argument> arguments = null;
62 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
67 compbio.data.sequence.Alignment alignment;
69 // set if the job didn't get run - then the input is simply returned to the
71 private boolean returnInput = false;
81 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
84 if (!prepareInput(inSeqs, 2))
87 subjobComplete = true;
93 Hashtable<String, Map> SeqNames = new Hashtable();
95 Vector<String[]> emptySeqs = new Vector();
98 * prepare input sequences for MsaWS service
101 * jalview sequences to be prepared
103 * minimum number of residues required for this MsaWS service
104 * @return true if seqs contains sequences to be submitted to service.
106 // TODO: return compbio.seqs list or nothing to indicate validity.
107 private boolean prepareInput(SequenceI[] seqs, int minlen)
113 "Implementation error: minlen must be zero or more.");
115 for (int i = 0; i < seqs.length; i++)
117 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
122 boolean valid = nseqs > 1; // need at least two seqs
123 compbio.data.sequence.FastaSequence seq;
124 for (int i = 0, n = 0; i < seqs.length; i++)
127 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
131 SeqNames.put(newname,
132 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
133 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
135 // make new input sequence with or without gaps
136 seq = new compbio.data.sequence.FastaSequence(newname,
137 (submitGaps) ? seqs[i].getSequenceAsString()
138 : AlignSeq.extractGaps(
139 jalview.util.Comparison.GapChars,
140 seqs[i].getSequenceAsString()));
146 if (seqs[i].getEnd() >= seqs[i].getStart())
148 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
149 .extractGaps(jalview.util.Comparison.GapChars,
150 seqs[i].getSequenceAsString());
152 emptySeqs.add(new String[]
161 * @return true if getAlignment will return a valid alignment result.
163 public boolean hasResults()
167 && (alignment != null || (emptySeqs != null && emptySeqs
177 * get the alignment including any empty sequences in the original order
178 * with original ids. Caller must access the alignment.getMetadata() object
179 * to annotate the final result passsed to the user.
181 * @return { SequenceI[], AlignmentOrder }
183 public Object[] getAlignment()
185 // is this a generic subjob or a Jws2 specific Object[] return signature
188 SequenceI[] alseqs = null;
189 char alseq_gapchar = '-';
191 if (alignment.getSequences().size() > 0)
193 alseqs = new SequenceI[alignment.getSequences().size()];
194 for (compbio.data.sequence.FastaSequence seq : alignment
197 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
199 alseq_gapchar = alignment.getMetadata().getGapchar();
202 // add in the empty seqs.
203 if (emptySeqs.size() > 0)
205 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
210 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
212 if (w < alseqs[i].getLength())
214 w = alseqs[i].getLength();
216 t_alseqs[i] = alseqs[i];
220 // check that aligned width is at least as wide as emptySeqs width.
222 for (i = 0, w = emptySeqs.size(); i < w; i++)
224 String[] es = (String[]) emptySeqs.get(i);
225 if (es != null && es[1] != null)
227 int sw = es[1].length();
234 // make a gapped string.
235 StringBuffer insbuff = new StringBuffer(w);
236 for (i = 0; i < nw; i++)
238 insbuff.append(alseq_gapchar);
242 for (i = 0; i < alseq_l; i++)
244 int sw = t_alseqs[i].getLength();
248 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
249 + insbuff.substring(0, sw - nw));
253 for (i = 0, w = emptySeqs.size(); i < w; i++)
255 String[] es = (String[]) emptySeqs.get(i);
258 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
259 insbuff.toString(), 1, 0);
263 if (es[1].length() < nw)
265 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
267 es[1] + insbuff.substring(0, nw - es[1].length()),
268 1, 1 + es[1].length());
272 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
279 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
280 // always recover the order - makes parseResult()'s life easier.
281 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
282 // account for any missing sequences
283 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
285 { alseqs, msaorder };
291 * mark subjob as cancelled and set result object appropriatly
296 subjobComplete = true;
302 * @return boolean true if job can be submitted.
304 public boolean hasValidInput()
306 // TODO: get attributes for this MsaWS instance to check if it can do two
307 // sequence alignment.
308 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
315 StringBuffer jobProgress = new StringBuffer();
317 public void setStatus(String string)
319 jobProgress.setLength(0);
320 jobProgress.append(string);
324 public String getStatus()
326 return jobProgress.toString();
330 public boolean hasStatus()
332 return jobProgress != null;
336 * @return the lastChunk
338 public long getLastChunk()
345 * the lastChunk to set
347 public void setLastChunk(long lastChunk)
349 this.lastChunk = lastChunk;
352 String alignmentProgram = null;
354 public String getAlignmentProgram()
356 return alignmentProgram;
359 public boolean hasArguments()
361 return (arguments != null && arguments.size() > 0)
362 || (preset != null && preset instanceof JabaWsParamSet);
365 public List<Argument> getJabaArguments()
367 List<Argument> newargs = new ArrayList<Argument>();
368 if (preset != null && preset instanceof JabaWsParamSet)
370 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
372 if (arguments != null && arguments.size() > 0)
374 newargs.addAll(arguments);
380 * add a progess header to status string containing presets/args used
382 public void addInitialStatus()
386 jobProgress.append("Using "
387 + (preset instanceof JabaPreset ? "Server" : "User")
388 + "Preset: " + preset.getName());
389 if (preset instanceof JabaWsParamSet)
391 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
393 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
398 if (arguments != null && arguments.size() > 0)
400 jobProgress.append("With custom parameters : \n");
401 // merge arguments with preset's own arguments.
402 for (Argument opt : arguments)
404 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
408 jobProgress.append("\nJob Output:\n");
411 public boolean isPresetJob()
413 return preset != null && preset instanceof JabaPreset;
416 public Preset getServerPreset()
418 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
422 String alTitle; // name which will be used to form new alignment window.
424 Alignment dataset; // dataset to which the new alignment will be
428 @SuppressWarnings("unchecked")
432 * set basic options for this (group) of Msa jobs
439 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
440 jalview.gui.AlignFrame alFrame, AlignmentView alview,
441 String wsname, boolean subgaps, boolean presorder)
443 super(alFrame, wsinfo, alview, wsname, wsUrl);
444 this.server = server;
445 this.submitGaps = subgaps;
446 this.preserveOrder = presorder;
450 * create one or more Msa jobs to align visible seuqences in _msa
463 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
464 String wsUrl, WebserviceInfo wsinfo,
465 jalview.gui.AlignFrame alFrame, String wsname, String title,
466 AlignmentView _msa, boolean subgaps, boolean presorder,
469 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
470 OutputHeader = wsInfo.getProgressText();
474 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
477 int njobs = conmsa.length;
478 jobs = new MsaWSJob[njobs];
479 for (int j = 0; j < njobs; j++)
483 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
487 jobs[j] = new MsaWSJob(0, conmsa[j]);
489 ((MsaWSJob) jobs[j]).preset = preset;
490 ((MsaWSJob) jobs[j]).arguments = paramset;
491 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
494 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
495 jobs[j].getJobnum());
497 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
502 public boolean isCancellable()
507 public void cancelJob()
509 if (!jobComplete && jobs != null)
511 boolean cancelled = true;
512 for (int job = 0; job < jobs.length; job++)
514 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
516 String cancelledMessage = "";
519 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
520 if (true) // cancelledJob || true)
523 // if the Jaba server indicates the job can't be cancelled, its
524 // because its running on the server's local execution engine
525 // so we just close the window anyway.
526 cancelledMessage = "Job cancelled.";
527 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
529 wsInfo.setStatus(jobs[job].getJobnum(),
530 WebserviceInfo.STATE_CANCELLED_OK);
534 // VALID UNSTOPPABLE JOB
535 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
537 // wsInfo.setStatus(jobs[job].jobnum,
538 // WebserviceInfo.STATE_RUNNING);
540 } catch (Exception exc)
542 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
545 "Exception whilst cancelling " + jobs[job].getJobId(),
548 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
549 + cancelledMessage + "\n");
553 // if we hadn't submitted then just mark the job as cancelled.
554 jobs[job].setSubjobComplete(true);
555 wsInfo.setStatus(jobs[job].getJobnum(),
556 WebserviceInfo.STATE_CANCELLED_OK);
562 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
565 this.interrupt(); // kick thread to update job states.
571 wsInfo.setProgressText(OutputHeader
572 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
577 public void pollJob(AWsJob job) throws Exception
579 // TODO: investigate if we still need to cast here in J1.6
580 MsaWSJob j = ((MsaWSJob) job);
581 // this is standard code, but since the interface doesn't comprise of a
582 // basic one that implements (getJobStatus, pullExecStatistics) we have to
583 // repeat the code for all jw2s services.
584 j.setjobStatus(server.getJobStatus(job.getJobId()));
585 updateJobProgress(j);
591 * @return true if more job progress data was available
594 protected boolean updateJobProgress(MsaWSJob j) throws Exception
596 StringBuffer response = j.jobProgress;
597 long lastchunk = j.getLastChunk();
598 boolean changed = false;
601 j.setLastChunk(lastchunk);
602 ChunkHolder chunk = server
603 .pullExecStatistics(j.getJobId(), lastchunk);
606 changed |= chunk.getChunk().length() > 0;
607 response.append(chunk.getChunk());
608 lastchunk = chunk.getNextPosition();
612 } catch (InterruptedException x)
618 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
622 public void StartJob(AWsJob job)
624 Exception lex = null;
625 // boiler plate template
626 if (!(job instanceof MsaWSJob))
628 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
631 MsaWSJob j = (MsaWSJob) job;
634 if (Cache.log.isDebugEnabled())
636 Cache.log.debug("Tried to submit an already submitted job "
643 if (j.seqs == null || j.seqs.size() == 0)
645 // special case - selection consisted entirely of empty sequences...
646 j.setjobStatus(JobStatus.FINISHED);
647 j.setStatus("Empty Alignment Job");
651 j.addInitialStatus(); // list the presets/parameters used for the job in
655 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
657 else if (j.hasArguments())
659 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
663 j.setJobId(server.align(j.seqs));
666 if (j.getJobId() != null)
668 j.setSubmitted(true);
669 j.setSubjobComplete(false);
670 // System.out.println(WsURL + " Job Id '" + jobId + "'");
678 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
680 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
683 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
684 + _lex.getMessage());
685 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
686 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
687 wsInfo.setStatus(j.getJobnum(),
688 WebserviceInfo.STATE_STOPPED_SERVERERROR);
689 } catch (compbio.metadata.LimitExceededException _lex)
692 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
693 + _lex.getMessage());
694 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
695 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
696 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
697 } catch (compbio.metadata.WrongParameterException _lex)
700 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
701 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
703 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
704 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
705 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
708 // For unexpected errors
710 .println(WebServiceName
711 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
712 + "When contacting Server:" + WsUrl + "\n");
713 e.printStackTrace(System.err);
714 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
715 wsInfo.setStatus(j.getJobnum(),
716 WebserviceInfo.STATE_STOPPED_SERVERERROR);
717 } catch (Exception e)
719 // For unexpected errors
721 .println(WebServiceName
722 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
723 + "When contacting Server:" + WsUrl + "\n");
724 e.printStackTrace(System.err);
725 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
726 wsInfo.setStatus(j.getJobnum(),
727 WebserviceInfo.STATE_STOPPED_SERVERERROR);
730 if (!j.isSubmitted())
732 // Boilerplate code here
733 // TODO: JBPNote catch timeout or other fault types explicitly
735 j.setAllowedServerExceptions(0);
736 wsInfo.appendProgressText(j.getJobnum(),
737 "Failed to submit sequences for alignment.\n"
738 + "Just close the window\n");
743 public void parseResult()
745 long progbar = System.currentTimeMillis();
746 wsInfo.setProgressBar("Collecting job results.", progbar);
747 int results = 0; // number of result sets received
748 JobStateSummary finalState = new JobStateSummary();
751 for (int j = 0; j < jobs.length; j++)
753 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
754 if (jobs[j].isFinished() && msjob.alignment == null)
756 int nunchanged = 3, nexcept = 3;
757 boolean jpchanged = false, jpex = false;
762 jpchanged = updateJobProgress(msjob);
768 } catch (Exception e)
772 .warn("Exception when retrieving remaining Job progress data for job "
773 + msjob.getJobId() + " on server " + WsUrl);
777 // set flag remember that we've had an exception.
785 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
786 // experienced an exception.
787 } catch (Exception ex)
793 } while (nunchanged > 0 && nexcept > 0);
795 if (Cache.log.isDebugEnabled())
797 System.out.println("Job Execution file for job: "
798 + msjob.getJobId() + " on server " + WsUrl);
799 System.out.println(msjob.getStatus());
800 System.out.println("*** End of status");
805 msjob.alignment = server.getResult(msjob.getJobId());
806 } catch (compbio.metadata.ResultNotAvailableException e)
808 // job has failed for some reason - probably due to invalid
811 .debug("Results not available for finished job - marking as broken job.",
814 .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
815 + e.getLocalizedMessage());
816 msjob.setjobStatus(JobStatus.FAILED);
817 } catch (Exception e)
819 Cache.log.error("Couldn't get Alignment for job.", e);
820 // TODO: Increment count and retry ?
821 msjob.setjobStatus(JobStatus.UNDEFINED);
824 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
825 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
826 && jobs[j].hasResults())
829 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
830 if (alignment != null)
832 // server.close(jobs[j].getJobnum());
833 // wsInfo.appendProgressText(jobs[j].getJobnum(),
834 // "\nAlignment Object Method Notes\n");
835 // wsInfo.appendProgressText(jobs[j].getJobnum(),
836 // "Calculated with "+alignment.getMetadata().getProgram().toString());
837 // JBPNote The returned files from a webservice could be
838 // hidden behind icons in the monitor window that,
839 // when clicked, pop up their corresponding data
843 } catch (Exception ex)
846 Cache.log.error("Unexpected exception when processing results for "
848 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
852 wsInfo.showResultsNewFrame
853 .addActionListener(new java.awt.event.ActionListener()
855 public void actionPerformed(java.awt.event.ActionEvent evt)
857 displayResults(true);
861 .addActionListener(new java.awt.event.ActionListener()
863 public void actionPerformed(java.awt.event.ActionEvent evt)
865 displayResults(false);
868 wsInfo.setResultsReady();
872 wsInfo.setFinishedNoResults();
874 updateGlobalStatus(finalState);
875 wsInfo.setProgressBar(null, progbar);
878 void displayResults(boolean newFrame)
880 // view input or result data for each block
881 Vector alorders = new Vector();
882 SequenceI[][] results = new SequenceI[jobs.length][];
883 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
884 String lastProgram = null;
886 for (int j = 0; j < jobs.length; j++)
888 if (jobs[j].hasResults())
890 msjob = (MsaWSJob) jobs[j];
891 Object[] res = msjob.getAlignment();
892 lastProgram = msjob.getAlignmentProgram();
893 alorders.add(res[1]);
894 results[j] = (SequenceI[]) res[0];
895 orders[j] = (AlignmentOrder) res[1];
897 // SequenceI[] alignment = input.getUpdated
904 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
905 // trash references to original result data
906 for (int j = 0; j < jobs.length; j++)
911 SequenceI[] alignment = (SequenceI[]) newview[0];
912 ColumnSelection columnselection = (ColumnSelection) newview[1];
913 Alignment al = new Alignment(alignment);
914 // TODO: add 'provenance' property to alignment from the method notes
915 if (lastProgram != null)
917 al.setProperty("Alignment Program", lastProgram);
919 // accompanying each subjob
922 al.setDataset(dataset);
925 propagateDatasetMappings(al);
926 // JBNote- TODO: warn user if a block is input rather than aligned data ?
930 AlignFrame af = new AlignFrame(al, columnselection,
931 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
933 // initialise with same renderer settings as in parent alignframe.
934 af.getFeatureRenderer().transferSettings(this.featureSettings);
936 if (alorders.size() > 0)
938 if (alorders.size() == 1)
940 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
941 (AlignmentOrder) alorders.get(0));
945 // construct a non-redundant ordering set
946 Vector names = new Vector();
947 for (int i = 0, l = alorders.size(); i < l; i++)
949 String orderName = new String(" Region " + i);
954 if (((AlignmentOrder) alorders.get(i))
955 .equals(((AlignmentOrder) alorders.get(j))))
959 orderName += "," + j;
967 if (i == 0 && j == 1)
969 names.add(new String(""));
973 names.add(orderName);
976 for (int i = 0, l = alorders.size(); i < l; i++)
978 af.addSortByOrderMenuItem(
979 WebServiceName + ((String) names.get(i)) + " Ordering",
980 (AlignmentOrder) alorders.get(i));
985 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
986 AlignFrame.DEFAULT_HEIGHT);
991 System.out.println("MERGE WITH OLD FRAME");
992 // TODO: modify alignment in original frame, replacing old for new
993 // alignment using the commands.EditCommand model to ensure the update can
998 public boolean canMergeResults()