2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3 * Copyright (C) 2011 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;
40 class MsaWSThread extends AWS2Thread implements WSClientI
42 boolean submitGaps = false; // pass sequences including gaps to alignment
46 boolean preserveOrder = true; // and always store and recover sequence
50 class MsaWSJob extends JWs2Job
54 WsParamSetI preset = null;
56 List<Argument> arguments = null;
61 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
66 compbio.data.sequence.Alignment alignment;
68 // set if the job didn't get run - then the input is simply returned to the
70 private boolean returnInput = false;
80 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
83 if (!prepareInput(inSeqs, 2))
86 subjobComplete = true;
92 Hashtable<String, Map> SeqNames = new Hashtable();
94 Vector<String[]> emptySeqs = new Vector();
97 * prepare input sequences for MsaWS service
100 * jalview sequences to be prepared
102 * minimum number of residues required for this MsaWS service
103 * @return true if seqs contains sequences to be submitted to service.
105 // TODO: return compbio.seqs list or nothing to indicate validity.
106 private boolean prepareInput(SequenceI[] seqs, int minlen)
112 "Implementation error: minlen must be zero or more.");
114 for (int i = 0; i < seqs.length; i++)
116 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
121 boolean valid = nseqs > 1; // need at least two seqs
122 compbio.data.sequence.FastaSequence seq;
123 for (int i = 0, n = 0; i < seqs.length; i++)
126 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
130 SeqNames.put(newname,
131 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
132 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
134 // make new input sequence with or without gaps
135 seq = new compbio.data.sequence.FastaSequence(newname,
136 (submitGaps) ? seqs[i].getSequenceAsString()
137 : AlignSeq.extractGaps(
138 jalview.util.Comparison.GapChars,
139 seqs[i].getSequenceAsString()));
145 if (seqs[i].getEnd() >= seqs[i].getStart())
147 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
148 .extractGaps(jalview.util.Comparison.GapChars,
149 seqs[i].getSequenceAsString());
151 emptySeqs.add(new String[]
160 * @return true if getAlignment will return a valid alignment result.
162 public boolean hasResults()
166 && (alignment != null || (emptySeqs != null && emptySeqs
176 * get the alignment including any empty sequences in the original order
177 * with original ids. Caller must access the alignment.getMetadata() object
178 * to annotate the final result passsed to the user.
180 * @return { SequenceI[], AlignmentOrder }
182 public Object[] getAlignment()
184 // is this a generic subjob or a Jws2 specific Object[] return signature
187 SequenceI[] alseqs = null;
188 char alseq_gapchar = '-';
190 if (alignment.getSequences().size() > 0)
192 alseqs = new SequenceI[alignment.getSequences().size()];
193 for (compbio.data.sequence.FastaSequence seq : alignment
196 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
198 alseq_gapchar = alignment.getMetadata().getGapchar();
201 // add in the empty seqs.
202 if (emptySeqs.size() > 0)
204 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
209 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
211 if (w < alseqs[i].getLength())
213 w = alseqs[i].getLength();
215 t_alseqs[i] = alseqs[i];
219 // check that aligned width is at least as wide as emptySeqs width.
221 for (i = 0, w = emptySeqs.size(); i < w; i++)
223 String[] es = (String[]) emptySeqs.get(i);
224 if (es != null && es[1] != null)
226 int sw = es[1].length();
233 // make a gapped string.
234 StringBuffer insbuff = new StringBuffer(w);
235 for (i = 0; i < nw; i++)
237 insbuff.append(alseq_gapchar);
241 for (i = 0; i < alseq_l; i++)
243 int sw = t_alseqs[i].getLength();
247 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
248 + insbuff.substring(0, sw - nw));
252 for (i = 0, w = emptySeqs.size(); i < w; i++)
254 String[] es = (String[]) emptySeqs.get(i);
257 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
258 insbuff.toString(), 1, 0);
262 if (es[1].length() < nw)
264 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
266 es[1] + insbuff.substring(0, nw - es[1].length()),
267 1, 1 + es[1].length());
271 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
278 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
279 // always recover the order - makes parseResult()'s life easier.
280 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
281 // account for any missing sequences
282 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
284 { alseqs, msaorder };
290 * mark subjob as cancelled and set result object appropriatly
295 subjobComplete = true;
301 * @return boolean true if job can be submitted.
303 public boolean hasValidInput()
305 // TODO: get attributes for this MsaWS instance to check if it can do two
306 // sequence alignment.
307 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
314 StringBuffer jobProgress = new StringBuffer();
316 public void setStatus(String string)
318 jobProgress.setLength(0);
319 jobProgress.append(string);
323 public String getStatus()
325 return jobProgress.toString();
329 public boolean hasStatus()
331 return jobProgress != null;
335 * @return the lastChunk
337 public long getLastChunk()
344 * the lastChunk to set
346 public void setLastChunk(long lastChunk)
348 this.lastChunk = lastChunk;
351 String alignmentProgram = null;
353 public String getAlignmentProgram()
355 return alignmentProgram;
358 public boolean hasArguments()
360 return (arguments != null && arguments.size() > 0)
361 || (preset != null && preset instanceof JabaWsParamSet);
364 public List<Argument> getJabaArguments()
366 List<Argument> newargs = new ArrayList<Argument>();
367 if (preset != null && preset instanceof JabaWsParamSet)
369 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
371 if (arguments != null && arguments.size() > 0)
373 newargs.addAll(arguments);
379 * add a progess header to status string containing presets/args used
381 public void addInitialStatus()
385 jobProgress.append("Using "
386 + (preset instanceof JabaPreset ? "Server" : "User")
387 + "Preset: " + preset.getName());
388 if (preset instanceof JabaWsParamSet)
390 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
392 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
397 if (arguments != null && arguments.size() > 0)
399 jobProgress.append("With custom parameters : \n");
400 // merge arguments with preset's own arguments.
401 for (Argument opt : arguments)
403 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
407 jobProgress.append("\nJob Output:\n");
410 public boolean isPresetJob()
412 return preset != null && preset instanceof JabaPreset;
415 public Preset getServerPreset()
417 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
421 String alTitle; // name which will be used to form new alignment window.
423 Alignment dataset; // dataset to which the new alignment will be
427 @SuppressWarnings("unchecked")
431 * set basic options for this (group) of Msa jobs
438 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
439 jalview.gui.AlignFrame alFrame, AlignmentView alview,
440 String wsname, boolean subgaps, boolean presorder)
442 super(alFrame, wsinfo, alview, wsname, wsUrl);
443 this.server = server;
444 this.submitGaps = subgaps;
445 this.preserveOrder = presorder;
449 * create one or more Msa jobs to align visible seuqences in _msa
462 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
463 String wsUrl, WebserviceInfo wsinfo,
464 jalview.gui.AlignFrame alFrame, String wsname, String title,
465 AlignmentView _msa, boolean subgaps, boolean presorder,
468 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
469 OutputHeader = wsInfo.getProgressText();
473 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
476 int njobs = conmsa.length;
477 jobs = new MsaWSJob[njobs];
478 for (int j = 0; j < njobs; j++)
482 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
486 jobs[j] = new MsaWSJob(0, conmsa[j]);
488 ((MsaWSJob) jobs[j]).preset = preset;
489 ((MsaWSJob) jobs[j]).arguments = paramset;
490 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
493 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
494 jobs[j].getJobnum());
496 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
501 public boolean isCancellable()
506 public void cancelJob()
508 if (!jobComplete && jobs != null)
510 boolean cancelled = true;
511 for (int job = 0; job < jobs.length; job++)
513 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
515 String cancelledMessage = "";
518 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
519 if (cancelledJob || true)
522 // if the Jaba server indicates the job can't be cancelled, its
523 // because its running on the server's local execution engine
524 // so we just close the window anyway.
525 cancelledMessage = "Job cancelled.";
526 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
528 wsInfo.setStatus(jobs[job].getJobnum(),
529 WebserviceInfo.STATE_CANCELLED_OK);
533 // VALID UNSTOPPABLE JOB
534 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
536 // wsInfo.setStatus(jobs[job].jobnum,
537 // WebserviceInfo.STATE_RUNNING);
539 } catch (Exception exc)
541 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
544 "Exception whilst cancelling " + jobs[job].getJobId(),
547 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
548 + cancelledMessage + "\n");
553 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
556 this.interrupt(); // kick thread to update job states.
562 wsInfo.setProgressText(OutputHeader
563 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
568 public void pollJob(AWsJob job) throws Exception
570 // TODO: investigate if we still need to cast here in J1.6
571 MsaWSJob j = ((MsaWSJob) job);
572 // this is standard code, but since the interface doesn't comprise of a
573 // basic one that implements (getJobStatus, pullExecStatistics) we have to
574 // repeat the code for all jw2s services.
575 j.setjobStatus(server.getJobStatus(job.getJobId()));
576 updateJobProgress(j);
582 * @return true if more job progress data was available
585 protected boolean updateJobProgress(MsaWSJob j) throws Exception
587 StringBuffer response = j.jobProgress;
588 long lastchunk = j.getLastChunk();
589 boolean changed=false;
592 j.setLastChunk(lastchunk);
593 ChunkHolder chunk = server
594 .pullExecStatistics(j.getJobId(), lastchunk);
597 changed=chunk.getChunk().length()>0;
598 response.append(chunk.getChunk());
599 lastchunk = chunk.getNextPosition();
602 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
606 public void StartJob(AWsJob job)
608 Exception lex = null;
609 // boiler plate template
610 if (!(job instanceof MsaWSJob))
612 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
615 MsaWSJob j = (MsaWSJob) job;
618 if (Cache.log.isDebugEnabled())
620 Cache.log.debug("Tried to submit an already submitted job "
627 if (j.seqs == null || j.seqs.size() == 0)
629 // special case - selection consisted entirely of empty sequences...
630 j.setjobStatus(JobStatus.FINISHED);
631 j.setStatus("Empty Alignment Job");
635 j.addInitialStatus(); // list the presets/parameters used for the job in
639 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
641 else if (j.hasArguments())
643 j.setJobId(server.customAlign(j.seqs,j.getJabaArguments()));
647 j.setJobId(server.align(j.seqs));
650 if (j.getJobId() != null)
652 j.setSubmitted(true);
653 j.setSubjobComplete(false);
654 // System.out.println(WsURL + " Job Id '" + jobId + "'");
662 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
664 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
667 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
668 + _lex.getMessage());
669 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
670 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
671 wsInfo.setStatus(j.getJobnum(),
672 WebserviceInfo.STATE_STOPPED_SERVERERROR);
673 } catch (compbio.metadata.LimitExceededException _lex)
676 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
677 + _lex.getMessage());
678 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
679 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
680 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
681 } catch (compbio.metadata.WrongParameterException _lex)
684 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
685 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
687 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
688 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
689 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
692 // For unexpected errors
694 .println(WebServiceName
695 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
696 + "When contacting Server:" + WsUrl + "\n");
697 e.printStackTrace(System.err);
698 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
699 wsInfo.setStatus(j.getJobnum(),
700 WebserviceInfo.STATE_STOPPED_SERVERERROR);
701 } catch (Exception e)
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);
714 if (!j.isSubmitted())
716 // Boilerplate code here
717 // TODO: JBPNote catch timeout or other fault types explicitly
719 j.setAllowedServerExceptions(0);
720 wsInfo.appendProgressText(j.getJobnum(),
721 "Failed to submit sequences for alignment.\n"
722 + "Just close the window\n");
727 public void parseResult()
729 int results = 0; // number of result sets received
730 JobStateSummary finalState = new JobStateSummary();
733 for (int j = 0; j < jobs.length; j++)
735 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
736 if (jobs[j].isFinished() && msjob.alignment == null)
738 boolean jpchanged=false,jpex=false;
742 jpchanged = updateJobProgress(msjob);
744 } catch (Exception e)
748 .warn("Exception when retrieving remaining Job progress data for job "
749 + msjob.getJobId() + " on server " + WsUrl);
752 // give up polling after two consecutive exceptions
757 // set flag remember that we've had an exception.
764 Thread.sleep(jpex ? 400 : 200); // wait a bit longer if we experienced an exception.
765 } catch (Exception ex)
772 if (Cache.log.isDebugEnabled())
774 System.out.println("Job Execution file for job: "
775 + msjob.getJobId() + " on server " + WsUrl);
776 System.out.println(msjob.getStatus());
777 System.out.println("*** End of status");
782 msjob.alignment = server.getResult(msjob.getJobId());
783 } catch (compbio.metadata.ResultNotAvailableException e)
785 // job has failed for some reason - probably due to invalid
788 .debug("Results not available for finished job - marking as broken job.",
790 msjob.setjobStatus(JobStatus.FAILED);
791 } catch (Exception e)
793 Cache.log.error("Couldn't get Alignment for job.", e);
794 // TODO: Increment count and retry ?
795 msjob.setjobStatus(JobStatus.UNDEFINED);
798 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
799 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
800 && jobs[j].hasResults())
803 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
804 if (alignment != null)
806 // server.close(jobs[j].getJobnum());
807 // wsInfo.appendProgressText(jobs[j].getJobnum(),
808 // "\nAlignment Object Method Notes\n");
809 // wsInfo.appendProgressText(jobs[j].getJobnum(),
810 // "Calculated with "+alignment.getMetadata().getProgram().toString());
811 // JBPNote The returned files from a webservice could be
812 // hidden behind icons in the monitor window that,
813 // when clicked, pop up their corresponding data
817 } catch (Exception ex)
820 Cache.log.error("Unexpected exception when processing results for "
822 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
826 wsInfo.showResultsNewFrame
827 .addActionListener(new java.awt.event.ActionListener()
829 public void actionPerformed(java.awt.event.ActionEvent evt)
831 displayResults(true);
835 .addActionListener(new java.awt.event.ActionListener()
837 public void actionPerformed(java.awt.event.ActionEvent evt)
839 displayResults(false);
842 wsInfo.setResultsReady();
846 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
847 wsInfo.setFinishedNoResults();
851 void displayResults(boolean newFrame)
853 // view input or result data for each block
854 Vector alorders = new Vector();
855 SequenceI[][] results = new SequenceI[jobs.length][];
856 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
857 String lastProgram = null;
859 for (int j = 0; j < jobs.length; j++)
861 if (jobs[j].hasResults())
863 msjob = (MsaWSJob) jobs[j];
864 Object[] res = msjob.getAlignment();
865 lastProgram = msjob.getAlignmentProgram();
866 alorders.add(res[1]);
867 results[j] = (SequenceI[]) res[0];
868 orders[j] = (AlignmentOrder) res[1];
870 // SequenceI[] alignment = input.getUpdated
877 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
878 // trash references to original result data
879 for (int j = 0; j < jobs.length; j++)
884 SequenceI[] alignment = (SequenceI[]) newview[0];
885 ColumnSelection columnselection = (ColumnSelection) newview[1];
886 Alignment al = new Alignment(alignment);
887 // TODO: add 'provenance' property to alignment from the method notes
888 if (lastProgram != null)
890 al.setProperty("Alignment Program", lastProgram);
892 // accompanying each subjob
895 al.setDataset(dataset);
898 propagateDatasetMappings(al);
899 // JBNote- TODO: warn user if a block is input rather than aligned data ?
903 AlignFrame af = new AlignFrame(al, columnselection,
904 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
906 // initialise with same renderer settings as in parent alignframe.
907 af.getFeatureRenderer().transferSettings(this.featureSettings);
909 if (alorders.size() > 0)
911 if (alorders.size() == 1)
913 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
914 (AlignmentOrder) alorders.get(0));
918 // construct a non-redundant ordering set
919 Vector names = new Vector();
920 for (int i = 0, l = alorders.size(); i < l; i++)
922 String orderName = new String(" Region " + i);
927 if (((AlignmentOrder) alorders.get(i))
928 .equals(((AlignmentOrder) alorders.get(j))))
932 orderName += "," + j;
940 if (i == 0 && j == 1)
942 names.add(new String(""));
946 names.add(orderName);
949 for (int i = 0, l = alorders.size(); i < l; i++)
951 af.addSortByOrderMenuItem(
952 WebServiceName + ((String) names.get(i)) + " Ordering",
953 (AlignmentOrder) alorders.get(i));
958 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
959 AlignFrame.DEFAULT_HEIGHT);
964 System.out.println("MERGE WITH OLD FRAME");
965 // TODO: modify alignment in original frame, replacing old for new
966 // alignment using the commands.EditCommand model to ensure the update can
971 public boolean canMergeResults()