2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.1)
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 of the License, or (at your option) any later version.
11 * Jalview is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
17 * The Jalview Authors are detailed in the 'AUTHORS' file.
19 package jalview.ws.jws2;
23 import compbio.data.msa.MsaWS;
24 import compbio.metadata.Argument;
25 import compbio.metadata.ChunkHolder;
26 import compbio.metadata.JobStatus;
27 import compbio.metadata.Preset;
29 import jalview.analysis.*;
31 import jalview.datamodel.*;
33 import jalview.ws.AWsJob;
34 import jalview.ws.WSClientI;
35 import jalview.ws.JobStateSummary;
36 import jalview.ws.jws2.dm.JabaWsParamSet;
37 import jalview.ws.params.WsParamSetI;
39 class MsaWSThread extends AWS2Thread implements WSClientI
41 boolean submitGaps = false; // pass sequences including gaps to alignment
45 boolean preserveOrder = true; // and always store and recover sequence
49 class MsaWSJob extends JWs2Job
53 WsParamSetI preset = null;
55 List<Argument> arguments = null;
60 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
65 compbio.data.sequence.Alignment alignment;
67 // set if the job didn't get run - then the input is simply returned to the
69 private boolean returnInput = false;
79 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
82 if (!prepareInput(inSeqs, 2))
85 subjobComplete = true;
91 Hashtable<String, Map> SeqNames = new Hashtable();
93 Vector<String[]> emptySeqs = new Vector();
96 * prepare input sequences for MsaWS service
99 * jalview sequences to be prepared
101 * minimum number of residues required for this MsaWS service
102 * @return true if seqs contains sequences to be submitted to service.
104 // TODO: return compbio.seqs list or nothing to indicate validity.
105 private boolean prepareInput(SequenceI[] seqs, int minlen)
111 "Implementation error: minlen must be zero or more.");
113 for (int i = 0; i < seqs.length; i++)
115 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
120 boolean valid = nseqs > 1; // need at least two seqs
121 compbio.data.sequence.FastaSequence seq;
122 for (int i = 0, n = 0; i < seqs.length; i++)
125 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
129 SeqNames.put(newname,
130 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
131 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
133 // make new input sequence with or without gaps
134 seq = new compbio.data.sequence.FastaSequence(newname,
135 (submitGaps) ? seqs[i].getSequenceAsString()
136 : AlignSeq.extractGaps(
137 jalview.util.Comparison.GapChars,
138 seqs[i].getSequenceAsString()));
144 if (seqs[i].getEnd() >= seqs[i].getStart())
146 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
147 .extractGaps(jalview.util.Comparison.GapChars,
148 seqs[i].getSequenceAsString());
150 emptySeqs.add(new String[]
159 * @return true if getAlignment will return a valid alignment result.
161 public boolean hasResults()
165 && (alignment != null || (emptySeqs != null && emptySeqs
175 * get the alignment including any empty sequences in the original order
176 * with original ids. Caller must access the alignment.getMetadata() object
177 * to annotate the final result passsed to the user.
179 * @return { SequenceI[], AlignmentOrder }
181 public Object[] getAlignment()
183 // is this a generic subjob or a Jws2 specific Object[] return signature
186 SequenceI[] alseqs = null;
187 char alseq_gapchar = '-';
189 if (alignment.getSequences().size() > 0)
191 alseqs = new SequenceI[alignment.getSequences().size()];
192 for (compbio.data.sequence.FastaSequence seq : alignment
195 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
197 alseq_gapchar = alignment.getMetadata().getGapchar();
200 // add in the empty seqs.
201 if (emptySeqs.size() > 0)
203 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
208 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
210 if (w < alseqs[i].getLength())
212 w = alseqs[i].getLength();
214 t_alseqs[i] = alseqs[i];
218 // check that aligned width is at least as wide as emptySeqs width.
220 for (i = 0, w = emptySeqs.size(); i < w; i++)
222 String[] es = (String[]) emptySeqs.get(i);
223 if (es != null && es[1] != null)
225 int sw = es[1].length();
232 // make a gapped string.
233 StringBuffer insbuff = new StringBuffer(w);
234 for (i = 0; i < nw; i++)
236 insbuff.append(alseq_gapchar);
240 for (i = 0; i < alseq_l; i++)
242 int sw = t_alseqs[i].getLength();
246 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
247 + insbuff.substring(0, sw - nw));
251 for (i = 0, w = emptySeqs.size(); i < w; i++)
253 String[] es = (String[]) emptySeqs.get(i);
256 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
257 insbuff.toString(), 1, 0);
261 if (es[1].length() < nw)
263 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
265 es[1] + insbuff.substring(0, nw - es[1].length()),
266 1, 1 + es[1].length());
270 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
277 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
278 // always recover the order - makes parseResult()'s life easier.
279 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
280 // account for any missing sequences
281 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
283 { alseqs, msaorder };
289 * mark subjob as cancelled and set result object appropriatly
294 subjobComplete = true;
300 * @return boolean true if job can be submitted.
302 public boolean hasValidInput()
304 // TODO: get attributes for this MsaWS instance to check if it can do two
305 // sequence alignment.
306 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
313 StringBuffer jobProgress = new StringBuffer();
315 public void setStatus(String string)
317 jobProgress.setLength(0);
318 jobProgress.append(string);
322 public String getStatus()
324 return jobProgress.toString();
328 public boolean hasStatus()
330 return jobProgress != null;
334 * @return the lastChunk
336 public long getLastChunk()
343 * the lastChunk to set
345 public void setLastChunk(long lastChunk)
347 this.lastChunk = lastChunk;
350 String alignmentProgram = null;
352 public String getAlignmentProgram()
354 return alignmentProgram;
357 public boolean hasArguments()
359 return (arguments != null && arguments.size() > 0)
360 || (preset != null && preset instanceof JabaWsParamSet);
363 public List<Argument> getJabaArguments()
365 List<Argument> newargs = new ArrayList<Argument>();
366 if (preset != null && preset instanceof JabaWsParamSet)
368 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
370 if (arguments != null && arguments.size() > 0)
372 newargs.addAll(arguments);
378 * add a progess header to status string containing presets/args used
380 public void addInitialStatus()
384 jobProgress.append("Using "
385 + (preset instanceof JabaPreset ? "Server" : "User")
386 + "Preset: " + preset.getName());
387 if (preset instanceof JabaWsParamSet)
389 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
391 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
396 if (arguments != null && arguments.size() > 0)
398 jobProgress.append("With custom parameters : \n");
399 // merge arguments with preset's own arguments.
400 for (Argument opt : arguments)
402 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
406 jobProgress.append("\nJob Output:\n");
409 public boolean isPresetJob()
411 return preset != null && preset instanceof JabaPreset;
414 public Preset getServerPreset()
416 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
420 String alTitle; // name which will be used to form new alignment window.
422 Alignment dataset; // dataset to which the new alignment will be
426 @SuppressWarnings("unchecked")
430 * set basic options for this (group) of Msa jobs
437 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
438 jalview.gui.AlignFrame alFrame, AlignmentView alview,
439 String wsname, boolean subgaps, boolean presorder)
441 super(alFrame, wsinfo, alview, wsname, wsUrl);
442 this.server = server;
443 this.submitGaps = subgaps;
444 this.preserveOrder = presorder;
448 * create one or more Msa jobs to align visible seuqences in _msa
461 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
462 String wsUrl, WebserviceInfo wsinfo,
463 jalview.gui.AlignFrame alFrame, String wsname, String title,
464 AlignmentView _msa, boolean subgaps, boolean presorder,
467 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
468 OutputHeader = wsInfo.getProgressText();
472 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
475 int njobs = conmsa.length;
476 jobs = new MsaWSJob[njobs];
477 for (int j = 0; j < njobs; j++)
481 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
485 jobs[j] = new MsaWSJob(0, conmsa[j]);
487 ((MsaWSJob) jobs[j]).preset = preset;
488 ((MsaWSJob) jobs[j]).arguments = paramset;
489 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
492 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
493 jobs[j].getJobnum());
495 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
500 public boolean isCancellable()
505 public void cancelJob()
507 if (!jobComplete && jobs != null)
509 boolean cancelled = true;
510 for (int job = 0; job < jobs.length; job++)
512 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
514 String cancelledMessage = "";
517 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
518 if (true) // cancelledJob || true)
521 // if the Jaba server indicates the job can't be cancelled, its
522 // because its running on the server's local execution engine
523 // so we just close the window anyway.
524 cancelledMessage = "Job cancelled.";
525 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
527 wsInfo.setStatus(jobs[job].getJobnum(),
528 WebserviceInfo.STATE_CANCELLED_OK);
532 // VALID UNSTOPPABLE JOB
533 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
535 // wsInfo.setStatus(jobs[job].jobnum,
536 // WebserviceInfo.STATE_RUNNING);
538 } catch (Exception exc)
540 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
543 "Exception whilst cancelling " + jobs[job].getJobId(),
546 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
547 + cancelledMessage + "\n");
549 // if we hadn't submitted then just mark the job as cancelled.
550 jobs[job].setSubjobComplete(true);
551 wsInfo.setStatus(jobs[job].getJobnum(), WebserviceInfo.STATE_CANCELLED_OK);
557 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
560 this.interrupt(); // kick thread to update job states.
566 wsInfo.setProgressText(OutputHeader
567 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
572 public void pollJob(AWsJob job) throws Exception
574 // TODO: investigate if we still need to cast here in J1.6
575 MsaWSJob j = ((MsaWSJob) job);
576 // this is standard code, but since the interface doesn't comprise of a
577 // basic one that implements (getJobStatus, pullExecStatistics) we have to
578 // repeat the code for all jw2s services.
579 j.setjobStatus(server.getJobStatus(job.getJobId()));
580 updateJobProgress(j);
586 * @return true if more job progress data was available
589 protected boolean updateJobProgress(MsaWSJob j) throws Exception
591 StringBuffer response = j.jobProgress;
592 long lastchunk = j.getLastChunk();
593 boolean changed = false;
596 j.setLastChunk(lastchunk);
597 ChunkHolder chunk = server
598 .pullExecStatistics(j.getJobId(), lastchunk);
601 changed |= chunk.getChunk().length() > 0;
602 response.append(chunk.getChunk());
603 lastchunk = chunk.getNextPosition();
607 } catch (InterruptedException x)
613 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
617 public void StartJob(AWsJob job)
619 Exception lex = null;
620 // boiler plate template
621 if (!(job instanceof MsaWSJob))
623 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
626 MsaWSJob j = (MsaWSJob) job;
629 if (Cache.log.isDebugEnabled())
631 Cache.log.debug("Tried to submit an already submitted job "
638 if (j.seqs == null || j.seqs.size() == 0)
640 // special case - selection consisted entirely of empty sequences...
641 j.setjobStatus(JobStatus.FINISHED);
642 j.setStatus("Empty Alignment Job");
646 j.addInitialStatus(); // list the presets/parameters used for the job in
650 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
652 else if (j.hasArguments())
654 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
658 j.setJobId(server.align(j.seqs));
661 if (j.getJobId() != null)
663 j.setSubmitted(true);
664 j.setSubjobComplete(false);
665 // System.out.println(WsURL + " Job Id '" + jobId + "'");
673 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
675 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
678 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
679 + _lex.getMessage());
680 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
681 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
682 wsInfo.setStatus(j.getJobnum(),
683 WebserviceInfo.STATE_STOPPED_SERVERERROR);
684 } catch (compbio.metadata.LimitExceededException _lex)
687 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
688 + _lex.getMessage());
689 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
690 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
691 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
692 } catch (compbio.metadata.WrongParameterException _lex)
695 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
696 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
698 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
699 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
700 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
703 // For unexpected errors
705 .println(WebServiceName
706 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
707 + "When contacting Server:" + WsUrl + "\n");
708 e.printStackTrace(System.err);
709 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
710 wsInfo.setStatus(j.getJobnum(),
711 WebserviceInfo.STATE_STOPPED_SERVERERROR);
712 } catch (Exception e)
714 // For unexpected errors
716 .println(WebServiceName
717 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
718 + "When contacting Server:" + WsUrl + "\n");
719 e.printStackTrace(System.err);
720 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
721 wsInfo.setStatus(j.getJobnum(),
722 WebserviceInfo.STATE_STOPPED_SERVERERROR);
725 if (!j.isSubmitted())
727 // Boilerplate code here
728 // TODO: JBPNote catch timeout or other fault types explicitly
730 j.setAllowedServerExceptions(0);
731 wsInfo.appendProgressText(j.getJobnum(),
732 "Failed to submit sequences for alignment.\n"
733 + "Just close the window\n");
738 public void parseResult()
740 long progbar = System.currentTimeMillis();
741 wsInfo.setProgressBar("Collecting job results.", progbar);
742 int results = 0; // number of result sets received
743 JobStateSummary finalState = new JobStateSummary();
746 for (int j = 0; j < jobs.length; j++)
748 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
749 if (jobs[j].isFinished() && msjob.alignment == null)
751 int nunchanged = 3, nexcept = 3;
752 boolean jpchanged = false, jpex = false;
757 jpchanged = updateJobProgress(msjob);
763 } catch (Exception e)
767 .warn("Exception when retrieving remaining Job progress data for job "
768 + msjob.getJobId() + " on server " + WsUrl);
772 // set flag remember that we've had an exception.
780 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
781 // experienced an exception.
782 } catch (Exception ex)
788 } while (nunchanged > 0 && nexcept > 0);
790 if (Cache.log.isDebugEnabled())
792 System.out.println("Job Execution file for job: "
793 + msjob.getJobId() + " on server " + WsUrl);
794 System.out.println(msjob.getStatus());
795 System.out.println("*** End of status");
800 msjob.alignment = server.getResult(msjob.getJobId());
801 } catch (compbio.metadata.ResultNotAvailableException e)
803 // job has failed for some reason - probably due to invalid
806 .debug("Results not available for finished job - marking as broken job.",
809 .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
810 + e.getLocalizedMessage());
811 msjob.setjobStatus(JobStatus.FAILED);
812 } catch (Exception e)
814 Cache.log.error("Couldn't get Alignment for job.", e);
815 // TODO: Increment count and retry ?
816 msjob.setjobStatus(JobStatus.UNDEFINED);
819 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
820 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
821 && jobs[j].hasResults())
824 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
825 if (alignment != null)
827 // server.close(jobs[j].getJobnum());
828 // wsInfo.appendProgressText(jobs[j].getJobnum(),
829 // "\nAlignment Object Method Notes\n");
830 // wsInfo.appendProgressText(jobs[j].getJobnum(),
831 // "Calculated with "+alignment.getMetadata().getProgram().toString());
832 // JBPNote The returned files from a webservice could be
833 // hidden behind icons in the monitor window that,
834 // when clicked, pop up their corresponding data
838 } catch (Exception ex)
841 Cache.log.error("Unexpected exception when processing results for "
843 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
847 wsInfo.showResultsNewFrame
848 .addActionListener(new java.awt.event.ActionListener()
850 public void actionPerformed(java.awt.event.ActionEvent evt)
852 displayResults(true);
856 .addActionListener(new java.awt.event.ActionListener()
858 public void actionPerformed(java.awt.event.ActionEvent evt)
860 displayResults(false);
863 wsInfo.setResultsReady();
867 wsInfo.setFinishedNoResults();
869 updateGlobalStatus(finalState);
870 wsInfo.setProgressBar(null, progbar);
873 void displayResults(boolean newFrame)
875 // view input or result data for each block
876 Vector alorders = new Vector();
877 SequenceI[][] results = new SequenceI[jobs.length][];
878 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
879 String lastProgram = null;
881 for (int j = 0; j < jobs.length; j++)
883 if (jobs[j].hasResults())
885 msjob = (MsaWSJob) jobs[j];
886 Object[] res = msjob.getAlignment();
887 lastProgram = msjob.getAlignmentProgram();
888 alorders.add(res[1]);
889 results[j] = (SequenceI[]) res[0];
890 orders[j] = (AlignmentOrder) res[1];
892 // SequenceI[] alignment = input.getUpdated
899 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
900 // trash references to original result data
901 for (int j = 0; j < jobs.length; j++)
906 SequenceI[] alignment = (SequenceI[]) newview[0];
907 ColumnSelection columnselection = (ColumnSelection) newview[1];
908 Alignment al = new Alignment(alignment);
909 // TODO: add 'provenance' property to alignment from the method notes
910 if (lastProgram != null)
912 al.setProperty("Alignment Program", lastProgram);
914 // accompanying each subjob
917 al.setDataset(dataset);
920 propagateDatasetMappings(al);
921 // JBNote- TODO: warn user if a block is input rather than aligned data ?
925 AlignFrame af = new AlignFrame(al, columnselection,
926 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
928 // initialise with same renderer settings as in parent alignframe.
929 af.getFeatureRenderer().transferSettings(this.featureSettings);
931 if (alorders.size() > 0)
933 if (alorders.size() == 1)
935 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
936 (AlignmentOrder) alorders.get(0));
940 // construct a non-redundant ordering set
941 Vector names = new Vector();
942 for (int i = 0, l = alorders.size(); i < l; i++)
944 String orderName = new String(" Region " + i);
949 if (((AlignmentOrder) alorders.get(i))
950 .equals(((AlignmentOrder) alorders.get(j))))
954 orderName += "," + j;
962 if (i == 0 && j == 1)
964 names.add(new String(""));
968 names.add(orderName);
971 for (int i = 0, l = alorders.size(); i < l; i++)
973 af.addSortByOrderMenuItem(
974 WebServiceName + ((String) names.get(i)) + " Ordering",
975 (AlignmentOrder) alorders.get(i));
980 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
981 AlignFrame.DEFAULT_HEIGHT);
986 System.out.println("MERGE WITH OLD FRAME");
987 // TODO: modify alignment in original frame, replacing old for new
988 // alignment using the commands.EditCommand model to ensure the update can
993 public boolean canMergeResults()