2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3 * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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/>.
18 package jalview.ws.jws2;
22 import compbio.data.msa.MsaWS;
23 import compbio.data.sequence.AlignmentMetadata;
24 import compbio.data.sequence.Program;
25 import compbio.metadata.Argument;
26 import compbio.metadata.ChunkHolder;
27 import compbio.metadata.JobStatus;
28 import compbio.metadata.Preset;
30 import jalview.analysis.*;
32 import jalview.datamodel.*;
34 import jalview.ws.AWsJob;
35 import jalview.ws.WSClientI;
36 import jalview.ws.JobStateSummary;
37 import jalview.ws.jws2.dm.JabaWsParamSet;
38 import jalview.ws.params.WsParamSetI;
50 * Copyright: Copyright (c) 2004
54 * Company: Dundee University
57 * @author not attributable
60 class MsaWSThread extends AWS2Thread implements WSClientI
62 boolean submitGaps = false; // pass sequences including gaps to alignment
66 boolean preserveOrder = true; // and always store and recover sequence
70 class MsaWSJob extends JWs2Job
74 WsParamSetI preset = null;
76 List<Argument> arguments = null;
81 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
86 compbio.data.sequence.Alignment alignment;
88 // set if the job didn't get run - then the input is simply returned to the
90 private boolean returnInput = false;
100 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
102 this.jobnum = jobNum;
103 if (!prepareInput(inSeqs, 2))
106 subjobComplete = true;
112 Hashtable<String, Map> SeqNames = new Hashtable();
114 Vector<String[]> emptySeqs = new Vector();
117 * prepare input sequences for MsaWS service
120 * jalview sequences to be prepared
122 * minimum number of residues required for this MsaWS service
123 * @return true if seqs contains sequences to be submitted to service.
125 // TODO: return compbio.seqs list or nothing to indicate validity.
126 private boolean prepareInput(SequenceI[] seqs, int minlen)
132 "Implementation error: minlen must be zero or more.");
134 for (int i = 0; i < seqs.length; i++)
136 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
141 boolean valid = nseqs > 1; // need at least two seqs
142 compbio.data.sequence.FastaSequence seq;
143 for (int i = 0, n = 0; i < seqs.length; i++)
146 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
150 SeqNames.put(newname,
151 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
152 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
154 // make new input sequence with or without gaps
155 seq = new compbio.data.sequence.FastaSequence(newname,
156 (submitGaps) ? seqs[i].getSequenceAsString()
157 : AlignSeq.extractGaps(
158 jalview.util.Comparison.GapChars,
159 seqs[i].getSequenceAsString()));
165 if (seqs[i].getEnd() >= seqs[i].getStart())
167 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
168 .extractGaps(jalview.util.Comparison.GapChars,
169 seqs[i].getSequenceAsString());
171 emptySeqs.add(new String[]
180 * @return true if getAlignment will return a valid alignment result.
182 public boolean hasResults()
186 && (alignment != null || (emptySeqs != null && emptySeqs
196 * get the alignment including any empty sequences in the original order
197 * with original ids. Caller must access the alignment.getMetadata() object
198 * to annotate the final result passsed to the user.
200 * @return { SequenceI[], AlignmentOrder }
202 public Object[] getAlignment()
204 // is this a generic subjob or a Jws2 specific Object[] return signature
207 SequenceI[] alseqs = null;
208 char alseq_gapchar = '-';
210 if (alignment.getSequences().size() > 0)
212 alseqs = new SequenceI[alignment.getSequences().size()];
213 for (compbio.data.sequence.FastaSequence seq : alignment
216 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
218 alseq_gapchar = alignment.getMetadata().getGapchar();
221 // add in the empty seqs.
222 if (emptySeqs.size() > 0)
224 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
229 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
231 if (w < alseqs[i].getLength())
233 w = alseqs[i].getLength();
235 t_alseqs[i] = alseqs[i];
239 // check that aligned width is at least as wide as emptySeqs width.
241 for (i = 0, w = emptySeqs.size(); i < w; i++)
243 String[] es = (String[]) emptySeqs.get(i);
244 if (es != null && es[1] != null)
246 int sw = es[1].length();
253 // make a gapped string.
254 StringBuffer insbuff = new StringBuffer(w);
255 for (i = 0; i < nw; i++)
257 insbuff.append(alseq_gapchar);
261 for (i = 0; i < alseq_l; i++)
263 int sw = t_alseqs[i].getLength();
267 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
268 + insbuff.substring(0, sw - nw));
272 for (i = 0, w = emptySeqs.size(); i < w; i++)
274 String[] es = (String[]) emptySeqs.get(i);
277 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
278 insbuff.toString(), 1, 0);
282 if (es[1].length() < nw)
284 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
286 es[1] + insbuff.substring(0, nw - es[1].length()),
287 1, 1 + es[1].length());
291 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
298 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
299 // always recover the order - makes parseResult()'s life easier.
300 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
301 // account for any missing sequences
302 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
304 { alseqs, msaorder };
310 * mark subjob as cancelled and set result object appropriatly
315 subjobComplete = true;
321 * @return boolean true if job can be submitted.
323 public boolean hasValidInput()
325 // TODO: get attributes for this MsaWS instance to check if it can do two
326 // sequence alignment.
327 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
334 StringBuffer jobProgress = new StringBuffer();
336 public void setStatus(String string)
338 jobProgress.setLength(0);
339 jobProgress.append(string);
343 public String getStatus()
345 return jobProgress.toString();
349 public boolean hasStatus()
351 return jobProgress != null;
355 * @return the lastChunk
357 public long getLastChunk()
364 * the lastChunk to set
366 public void setLastChunk(long lastChunk)
368 this.lastChunk = lastChunk;
371 String alignmentProgram = null;
373 public String getAlignmentProgram()
375 return alignmentProgram;
378 public boolean hasArguments()
380 return (arguments != null && arguments.size() > 0)
381 || (preset != null && preset instanceof JabaWsParamSet);
384 public List<Argument> getJabaArguments()
386 List<Argument> newargs = new ArrayList<Argument>();
387 if (preset != null && preset instanceof JabaWsParamSet)
389 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
391 if (arguments != null && arguments.size() > 0)
393 newargs.addAll(arguments);
399 * add a progess header to status string containing presets/args used
401 public void addInitialStatus()
405 jobProgress.append("Using "
406 + (preset instanceof JabaPreset ? "Server" : "User")
407 + "Preset: " + preset.getName());
408 if (preset instanceof JabaWsParamSet)
410 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
412 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
417 if (arguments != null && arguments.size() > 0)
419 jobProgress.append("With custom parameters : \n");
420 // merge arguments with preset's own arguments.
421 for (Argument opt : arguments)
423 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
427 jobProgress.append("\nJob Output:\n");
430 public boolean isPresetJob()
432 return preset != null && preset instanceof JabaPreset;
435 public Preset getServerPreset()
437 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
441 String alTitle; // name which will be used to form new alignment window.
443 Alignment dataset; // dataset to which the new alignment will be
447 @SuppressWarnings("unchecked")
451 * set basic options for this (group) of Msa jobs
458 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
459 jalview.gui.AlignFrame alFrame, AlignmentView alview,
460 String wsname, boolean subgaps, boolean presorder)
462 super(alFrame, wsinfo, alview, wsname, wsUrl);
463 this.server = server;
464 this.submitGaps = subgaps;
465 this.preserveOrder = presorder;
469 * create one or more Msa jobs to align visible seuqences in _msa
482 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
483 String wsUrl, WebserviceInfo wsinfo,
484 jalview.gui.AlignFrame alFrame, String wsname, String title,
485 AlignmentView _msa, boolean subgaps, boolean presorder,
488 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
489 OutputHeader = wsInfo.getProgressText();
493 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
496 int njobs = conmsa.length;
497 jobs = new MsaWSJob[njobs];
498 for (int j = 0; j < njobs; j++)
502 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
506 jobs[j] = new MsaWSJob(0, conmsa[j]);
508 ((MsaWSJob) jobs[j]).preset = preset;
509 ((MsaWSJob) jobs[j]).arguments = paramset;
510 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
513 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
514 jobs[j].getJobnum());
516 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
521 public boolean isCancellable()
526 public void cancelJob()
528 if (!jobComplete && jobs != null)
530 boolean cancelled = true;
531 for (int job = 0; job < jobs.length; job++)
533 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
535 String cancelledMessage = "";
538 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
539 if (cancelledJob || true)
542 // if the Jaba server indicates the job can't be cancelled, its
543 // because its running on the server's local execution engine
544 // so we just close the window anyway.
545 cancelledMessage = "Job cancelled.";
546 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
548 wsInfo.setStatus(jobs[job].getJobnum(),
549 WebserviceInfo.STATE_CANCELLED_OK);
553 // VALID UNSTOPPABLE JOB
554 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
556 // wsInfo.setStatus(jobs[job].jobnum,
557 // WebserviceInfo.STATE_RUNNING);
559 } catch (Exception exc)
561 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
564 "Exception whilst cancelling " + jobs[job].getJobId(),
567 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
568 + cancelledMessage + "\n");
573 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
576 this.interrupt(); // kick thread to update job states.
582 wsInfo.setProgressText(OutputHeader
583 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
588 public void pollJob(AWsJob job) throws Exception
590 // TODO: investigate if we still need to cast here in J1.6
591 MsaWSJob j = ((MsaWSJob) job);
592 // this is standard code, but since the interface doesn't comprise of a
593 // basic one that implements (getJobStatus, pullExecStatistics) we have to
594 // repeat the code for all jw2s services.
595 j.setjobStatus(server.getJobStatus(job.getJobId()));
596 updateJobProgress(j);
599 protected void updateJobProgress(MsaWSJob j) throws Exception
601 StringBuffer response = j.jobProgress;
602 long lastchunk = j.getLastChunk();
605 j.setLastChunk(lastchunk);
606 ChunkHolder chunk = server
607 .pullExecStatistics(j.getJobId(), lastchunk);
610 response.append(chunk.getChunk());
611 lastchunk = chunk.getNextPosition();
614 } 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 int results = 0; // number of result sets received
741 JobStateSummary finalState = new JobStateSummary();
744 for (int j = 0; j < jobs.length; j++)
746 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
747 if (jobs[j].isFinished() && msjob.alignment == null)
751 updateJobProgress(msjob);
752 } catch (Exception e)
755 .warn("Exception when retrieving remaining Job progress data for job "
756 + msjob.getJobId() + " on server " + WsUrl);
759 if (Cache.log.isDebugEnabled())
761 System.out.println("Job Execution file for job: "
762 + msjob.getJobId() + " on server " + WsUrl);
763 System.out.println(msjob.getStatus());
764 System.out.println("*** End of status");
769 msjob.alignment = server.getResult(msjob.getJobId());
770 } catch (compbio.metadata.ResultNotAvailableException e)
772 // job has failed for some reason - probably due to invalid
775 .debug("Results not available for finished job - marking as broken job.",
777 msjob.setjobStatus(JobStatus.FAILED);
778 } catch (Exception e)
780 Cache.log.error("Couldn't get Alignment for job.", e);
781 // TODO: Increment count and retry ?
782 msjob.setjobStatus(JobStatus.UNDEFINED);
785 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
786 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
787 && jobs[j].hasResults())
790 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
791 if (alignment != null)
793 // server.close(jobs[j].getJobnum());
794 // wsInfo.appendProgressText(jobs[j].getJobnum(),
795 // "\nAlignment Object Method Notes\n");
796 // wsInfo.appendProgressText(jobs[j].getJobnum(),
797 // "Calculated with "+alignment.getMetadata().getProgram().toString());
798 // JBPNote The returned files from a webservice could be
799 // hidden behind icons in the monitor window that,
800 // when clicked, pop up their corresponding data
804 } catch (Exception ex)
807 Cache.log.error("Unexpected exception when processing results for "
809 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
813 wsInfo.showResultsNewFrame
814 .addActionListener(new java.awt.event.ActionListener()
816 public void actionPerformed(java.awt.event.ActionEvent evt)
818 displayResults(true);
822 .addActionListener(new java.awt.event.ActionListener()
824 public void actionPerformed(java.awt.event.ActionEvent evt)
826 displayResults(false);
829 wsInfo.setResultsReady();
833 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
834 wsInfo.setFinishedNoResults();
838 void displayResults(boolean newFrame)
840 // view input or result data for each block
841 Vector alorders = new Vector();
842 SequenceI[][] results = new SequenceI[jobs.length][];
843 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
844 String lastProgram = null;
846 for (int j = 0; j < jobs.length; j++)
848 if (jobs[j].hasResults())
850 msjob = (MsaWSJob) jobs[j];
851 Object[] res = msjob.getAlignment();
852 lastProgram = msjob.getAlignmentProgram();
853 alorders.add(res[1]);
854 results[j] = (SequenceI[]) res[0];
855 orders[j] = (AlignmentOrder) res[1];
857 // SequenceI[] alignment = input.getUpdated
864 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
865 // trash references to original result data
866 for (int j = 0; j < jobs.length; j++)
871 SequenceI[] alignment = (SequenceI[]) newview[0];
872 ColumnSelection columnselection = (ColumnSelection) newview[1];
873 Alignment al = new Alignment(alignment);
874 // TODO: add 'provenance' property to alignment from the method notes
875 if (lastProgram != null)
877 al.setProperty("Alignment Program", lastProgram);
879 // accompanying each subjob
882 al.setDataset(dataset);
885 propagateDatasetMappings(al);
886 // JBNote- TODO: warn user if a block is input rather than aligned data ?
890 AlignFrame af = new AlignFrame(al, columnselection,
891 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
893 // initialise with same renderer settings as in parent alignframe.
894 af.getFeatureRenderer().transferSettings(this.featureSettings);
896 if (alorders.size() > 0)
898 if (alorders.size() == 1)
900 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
901 (AlignmentOrder) alorders.get(0));
905 // construct a non-redundant ordering set
906 Vector names = new Vector();
907 for (int i = 0, l = alorders.size(); i < l; i++)
909 String orderName = new String(" Region " + i);
914 if (((AlignmentOrder) alorders.get(i))
915 .equals(((AlignmentOrder) alorders.get(j))))
919 orderName += "," + j;
927 if (i == 0 && j == 1)
929 names.add(new String(""));
933 names.add(orderName);
936 for (int i = 0, l = alorders.size(); i < l; i++)
938 af.addSortByOrderMenuItem(
939 WebServiceName + ((String) names.get(i)) + " Ordering",
940 (AlignmentOrder) alorders.get(i));
945 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
946 AlignFrame.DEFAULT_HEIGHT);
951 System.out.println("MERGE WITH OLD FRAME");
952 // TODO: modify alignment in original frame, replacing old for new
953 // alignment using the commands.EditCommand model to ensure the update can
958 public boolean canMergeResults()