2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3 * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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.metadata.Argument;
24 import compbio.metadata.ChunkHolder;
25 import compbio.metadata.JobStatus;
26 import compbio.metadata.Preset;
28 import jalview.analysis.*;
30 import jalview.datamodel.*;
32 import jalview.ws.AWsJob;
33 import jalview.ws.WSClientI;
34 import jalview.ws.JobStateSummary;
35 import jalview.ws.jws2.dm.JabaWsParamSet;
36 import jalview.ws.params.WsParamSetI;
38 class MsaWSThread extends AWS2Thread implements WSClientI
40 boolean submitGaps = false; // pass sequences including gaps to alignment
44 boolean preserveOrder = true; // and always store and recover sequence
48 class MsaWSJob extends JWs2Job
52 WsParamSetI preset = null;
54 List<Argument> arguments = null;
59 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
64 compbio.data.sequence.Alignment alignment;
66 // set if the job didn't get run - then the input is simply returned to the
68 private boolean returnInput = false;
78 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
81 if (!prepareInput(inSeqs, 2))
84 subjobComplete = true;
90 Hashtable<String, Map> SeqNames = new Hashtable();
92 Vector<String[]> emptySeqs = new Vector();
95 * prepare input sequences for MsaWS service
98 * jalview sequences to be prepared
100 * minimum number of residues required for this MsaWS service
101 * @return true if seqs contains sequences to be submitted to service.
103 // TODO: return compbio.seqs list or nothing to indicate validity.
104 private boolean prepareInput(SequenceI[] seqs, int minlen)
110 "Implementation error: minlen must be zero or more.");
112 for (int i = 0; i < seqs.length; i++)
114 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
119 boolean valid = nseqs > 1; // need at least two seqs
120 compbio.data.sequence.FastaSequence seq;
121 for (int i = 0, n = 0; i < seqs.length; i++)
124 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
128 SeqNames.put(newname,
129 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
130 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
132 // make new input sequence with or without gaps
133 seq = new compbio.data.sequence.FastaSequence(newname,
134 (submitGaps) ? seqs[i].getSequenceAsString()
135 : AlignSeq.extractGaps(
136 jalview.util.Comparison.GapChars,
137 seqs[i].getSequenceAsString()));
143 if (seqs[i].getEnd() >= seqs[i].getStart())
145 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
146 .extractGaps(jalview.util.Comparison.GapChars,
147 seqs[i].getSequenceAsString());
149 emptySeqs.add(new String[]
158 * @return true if getAlignment will return a valid alignment result.
160 public boolean hasResults()
164 && (alignment != null || (emptySeqs != null && emptySeqs
174 * get the alignment including any empty sequences in the original order
175 * with original ids. Caller must access the alignment.getMetadata() object
176 * to annotate the final result passsed to the user.
178 * @return { SequenceI[], AlignmentOrder }
180 public Object[] getAlignment()
182 // is this a generic subjob or a Jws2 specific Object[] return signature
185 SequenceI[] alseqs = null;
186 char alseq_gapchar = '-';
188 if (alignment.getSequences().size() > 0)
190 alseqs = new SequenceI[alignment.getSequences().size()];
191 for (compbio.data.sequence.FastaSequence seq : alignment
194 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
196 alseq_gapchar = alignment.getMetadata().getGapchar();
199 // add in the empty seqs.
200 if (emptySeqs.size() > 0)
202 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
207 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
209 if (w < alseqs[i].getLength())
211 w = alseqs[i].getLength();
213 t_alseqs[i] = alseqs[i];
217 // check that aligned width is at least as wide as emptySeqs width.
219 for (i = 0, w = emptySeqs.size(); i < w; i++)
221 String[] es = (String[]) emptySeqs.get(i);
222 if (es != null && es[1] != null)
224 int sw = es[1].length();
231 // make a gapped string.
232 StringBuffer insbuff = new StringBuffer(w);
233 for (i = 0; i < nw; i++)
235 insbuff.append(alseq_gapchar);
239 for (i = 0; i < alseq_l; i++)
241 int sw = t_alseqs[i].getLength();
245 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
246 + insbuff.substring(0, sw - nw));
250 for (i = 0, w = emptySeqs.size(); i < w; i++)
252 String[] es = (String[]) emptySeqs.get(i);
255 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
256 insbuff.toString(), 1, 0);
260 if (es[1].length() < nw)
262 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
264 es[1] + insbuff.substring(0, nw - es[1].length()),
265 1, 1 + es[1].length());
269 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
276 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
277 // always recover the order - makes parseResult()'s life easier.
278 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
279 // account for any missing sequences
280 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
282 { alseqs, msaorder };
288 * mark subjob as cancelled and set result object appropriatly
293 subjobComplete = true;
299 * @return boolean true if job can be submitted.
301 public boolean hasValidInput()
303 // TODO: get attributes for this MsaWS instance to check if it can do two
304 // sequence alignment.
305 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
312 StringBuffer jobProgress = new StringBuffer();
314 public void setStatus(String string)
316 jobProgress.setLength(0);
317 jobProgress.append(string);
321 public String getStatus()
323 return jobProgress.toString();
327 public boolean hasStatus()
329 return jobProgress != null;
333 * @return the lastChunk
335 public long getLastChunk()
342 * the lastChunk to set
344 public void setLastChunk(long lastChunk)
346 this.lastChunk = lastChunk;
349 String alignmentProgram = null;
351 public String getAlignmentProgram()
353 return alignmentProgram;
356 public boolean hasArguments()
358 return (arguments != null && arguments.size() > 0)
359 || (preset != null && preset instanceof JabaWsParamSet);
362 public List<Argument> getJabaArguments()
364 List<Argument> newargs = new ArrayList<Argument>();
365 if (preset != null && preset instanceof JabaWsParamSet)
367 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
369 if (arguments != null && arguments.size() > 0)
371 newargs.addAll(arguments);
377 * add a progess header to status string containing presets/args used
379 public void addInitialStatus()
383 jobProgress.append("Using "
384 + (preset instanceof JabaPreset ? "Server" : "User")
385 + "Preset: " + preset.getName());
386 if (preset instanceof JabaWsParamSet)
388 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
390 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
395 if (arguments != null && arguments.size() > 0)
397 jobProgress.append("With custom parameters : \n");
398 // merge arguments with preset's own arguments.
399 for (Argument opt : arguments)
401 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
405 jobProgress.append("\nJob Output:\n");
408 public boolean isPresetJob()
410 return preset != null && preset instanceof JabaPreset;
413 public Preset getServerPreset()
415 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
419 String alTitle; // name which will be used to form new alignment window.
421 Alignment dataset; // dataset to which the new alignment will be
425 @SuppressWarnings("unchecked")
429 * set basic options for this (group) of Msa jobs
436 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
437 jalview.gui.AlignFrame alFrame, AlignmentView alview,
438 String wsname, boolean subgaps, boolean presorder)
440 super(alFrame, wsinfo, alview, wsname, wsUrl);
441 this.server = server;
442 this.submitGaps = subgaps;
443 this.preserveOrder = presorder;
447 * create one or more Msa jobs to align visible seuqences in _msa
460 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
461 String wsUrl, WebserviceInfo wsinfo,
462 jalview.gui.AlignFrame alFrame, String wsname, String title,
463 AlignmentView _msa, boolean subgaps, boolean presorder,
466 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
467 OutputHeader = wsInfo.getProgressText();
471 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
474 int njobs = conmsa.length;
475 jobs = new MsaWSJob[njobs];
476 for (int j = 0; j < njobs; j++)
480 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
484 jobs[j] = new MsaWSJob(0, conmsa[j]);
486 ((MsaWSJob) jobs[j]).preset = preset;
487 ((MsaWSJob) jobs[j]).arguments = paramset;
488 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
491 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
492 jobs[j].getJobnum());
494 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
499 public boolean isCancellable()
504 public void cancelJob()
506 if (!jobComplete && jobs != null)
508 boolean cancelled = true;
509 for (int job = 0; job < jobs.length; job++)
511 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
513 String cancelledMessage = "";
516 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
517 if (true) // cancelledJob || true)
520 // if the Jaba server indicates the job can't be cancelled, its
521 // because its running on the server's local execution engine
522 // so we just close the window anyway.
523 cancelledMessage = "Job cancelled.";
524 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
526 wsInfo.setStatus(jobs[job].getJobnum(),
527 WebserviceInfo.STATE_CANCELLED_OK);
531 // VALID UNSTOPPABLE JOB
532 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
534 // wsInfo.setStatus(jobs[job].jobnum,
535 // WebserviceInfo.STATE_RUNNING);
537 } catch (Exception exc)
539 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
542 "Exception whilst cancelling " + jobs[job].getJobId(),
545 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
546 + cancelledMessage + "\n");
551 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
554 this.interrupt(); // kick thread to update job states.
560 wsInfo.setProgressText(OutputHeader
561 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
566 public void pollJob(AWsJob job) throws Exception
568 // TODO: investigate if we still need to cast here in J1.6
569 MsaWSJob j = ((MsaWSJob) job);
570 // this is standard code, but since the interface doesn't comprise of a
571 // basic one that implements (getJobStatus, pullExecStatistics) we have to
572 // repeat the code for all jw2s services.
573 j.setjobStatus(server.getJobStatus(job.getJobId()));
574 updateJobProgress(j);
580 * @return true if more job progress data was available
583 protected boolean updateJobProgress(MsaWSJob j) throws Exception
585 StringBuffer response = j.jobProgress;
586 long lastchunk = j.getLastChunk();
587 boolean changed = false;
590 j.setLastChunk(lastchunk);
591 ChunkHolder chunk = server
592 .pullExecStatistics(j.getJobId(), lastchunk);
595 changed |= chunk.getChunk().length() > 0;
596 response.append(chunk.getChunk());
597 lastchunk = chunk.getNextPosition();
601 } catch (InterruptedException x)
607 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
611 public void StartJob(AWsJob job)
613 Exception lex = null;
614 // boiler plate template
615 if (!(job instanceof MsaWSJob))
617 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
620 MsaWSJob j = (MsaWSJob) job;
623 if (Cache.log.isDebugEnabled())
625 Cache.log.debug("Tried to submit an already submitted job "
632 if (j.seqs == null || j.seqs.size() == 0)
634 // special case - selection consisted entirely of empty sequences...
635 j.setjobStatus(JobStatus.FINISHED);
636 j.setStatus("Empty Alignment Job");
640 j.addInitialStatus(); // list the presets/parameters used for the job in
644 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
646 else if (j.hasArguments())
648 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
652 j.setJobId(server.align(j.seqs));
655 if (j.getJobId() != null)
657 j.setSubmitted(true);
658 j.setSubjobComplete(false);
659 // System.out.println(WsURL + " Job Id '" + jobId + "'");
667 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
669 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
672 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
673 + _lex.getMessage());
674 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
675 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
676 wsInfo.setStatus(j.getJobnum(),
677 WebserviceInfo.STATE_STOPPED_SERVERERROR);
678 } catch (compbio.metadata.LimitExceededException _lex)
681 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
682 + _lex.getMessage());
683 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
684 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
685 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
686 } catch (compbio.metadata.WrongParameterException _lex)
689 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
690 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
692 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
693 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
694 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
697 // For unexpected errors
699 .println(WebServiceName
700 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
701 + "When contacting Server:" + WsUrl + "\n");
702 e.printStackTrace(System.err);
703 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
704 wsInfo.setStatus(j.getJobnum(),
705 WebserviceInfo.STATE_STOPPED_SERVERERROR);
706 } catch (Exception e)
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);
719 if (!j.isSubmitted())
721 // Boilerplate code here
722 // TODO: JBPNote catch timeout or other fault types explicitly
724 j.setAllowedServerExceptions(0);
725 wsInfo.appendProgressText(j.getJobnum(),
726 "Failed to submit sequences for alignment.\n"
727 + "Just close the window\n");
732 public void parseResult()
734 long progbar = System.currentTimeMillis();
735 wsInfo.setProgressBar("Collecting job results.", progbar);
736 int results = 0; // number of result sets received
737 JobStateSummary finalState = new JobStateSummary();
740 for (int j = 0; j < jobs.length; j++)
742 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
743 if (jobs[j].isFinished() && msjob.alignment == null)
745 int nunchanged = 3, nexcept = 3;
746 boolean jpchanged = false, jpex = false;
751 jpchanged = updateJobProgress(msjob);
757 } catch (Exception e)
761 .warn("Exception when retrieving remaining Job progress data for job "
762 + msjob.getJobId() + " on server " + WsUrl);
766 // set flag remember that we've had an exception.
774 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
775 // experienced an exception.
776 } catch (Exception ex)
782 } while (nunchanged > 0 && nexcept > 0);
784 if (Cache.log.isDebugEnabled())
786 System.out.println("Job Execution file for job: "
787 + msjob.getJobId() + " on server " + WsUrl);
788 System.out.println(msjob.getStatus());
789 System.out.println("*** End of status");
794 msjob.alignment = server.getResult(msjob.getJobId());
795 } catch (compbio.metadata.ResultNotAvailableException e)
797 // job has failed for some reason - probably due to invalid
800 .debug("Results not available for finished job - marking as broken job.",
803 .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
804 + e.getLocalizedMessage());
805 msjob.setjobStatus(JobStatus.FAILED);
806 } catch (Exception e)
808 Cache.log.error("Couldn't get Alignment for job.", e);
809 // TODO: Increment count and retry ?
810 msjob.setjobStatus(JobStatus.UNDEFINED);
813 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
814 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
815 && jobs[j].hasResults())
818 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
819 if (alignment != null)
821 // server.close(jobs[j].getJobnum());
822 // wsInfo.appendProgressText(jobs[j].getJobnum(),
823 // "\nAlignment Object Method Notes\n");
824 // wsInfo.appendProgressText(jobs[j].getJobnum(),
825 // "Calculated with "+alignment.getMetadata().getProgram().toString());
826 // JBPNote The returned files from a webservice could be
827 // hidden behind icons in the monitor window that,
828 // when clicked, pop up their corresponding data
832 } catch (Exception ex)
835 Cache.log.error("Unexpected exception when processing results for "
837 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
841 wsInfo.showResultsNewFrame
842 .addActionListener(new java.awt.event.ActionListener()
844 public void actionPerformed(java.awt.event.ActionEvent evt)
846 displayResults(true);
850 .addActionListener(new java.awt.event.ActionListener()
852 public void actionPerformed(java.awt.event.ActionEvent evt)
854 displayResults(false);
857 wsInfo.setResultsReady();
861 wsInfo.setFinishedNoResults();
863 updateGlobalStatus(finalState);
864 wsInfo.setProgressBar(null, progbar);
867 void displayResults(boolean newFrame)
869 // view input or result data for each block
870 Vector alorders = new Vector();
871 SequenceI[][] results = new SequenceI[jobs.length][];
872 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
873 String lastProgram = null;
875 for (int j = 0; j < jobs.length; j++)
877 if (jobs[j].hasResults())
879 msjob = (MsaWSJob) jobs[j];
880 Object[] res = msjob.getAlignment();
881 lastProgram = msjob.getAlignmentProgram();
882 alorders.add(res[1]);
883 results[j] = (SequenceI[]) res[0];
884 orders[j] = (AlignmentOrder) res[1];
886 // SequenceI[] alignment = input.getUpdated
893 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
894 // trash references to original result data
895 for (int j = 0; j < jobs.length; j++)
900 SequenceI[] alignment = (SequenceI[]) newview[0];
901 ColumnSelection columnselection = (ColumnSelection) newview[1];
902 Alignment al = new Alignment(alignment);
903 // TODO: add 'provenance' property to alignment from the method notes
904 if (lastProgram != null)
906 al.setProperty("Alignment Program", lastProgram);
908 // accompanying each subjob
911 al.setDataset(dataset);
914 propagateDatasetMappings(al);
915 // JBNote- TODO: warn user if a block is input rather than aligned data ?
919 AlignFrame af = new AlignFrame(al, columnselection,
920 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
922 // initialise with same renderer settings as in parent alignframe.
923 af.getFeatureRenderer().transferSettings(this.featureSettings);
925 if (alorders.size() > 0)
927 if (alorders.size() == 1)
929 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
930 (AlignmentOrder) alorders.get(0));
934 // construct a non-redundant ordering set
935 Vector names = new Vector();
936 for (int i = 0, l = alorders.size(); i < l; i++)
938 String orderName = new String(" Region " + i);
943 if (((AlignmentOrder) alorders.get(i))
944 .equals(((AlignmentOrder) alorders.get(j))))
948 orderName += "," + j;
956 if (i == 0 && j == 1)
958 names.add(new String(""));
962 names.add(orderName);
965 for (int i = 0, l = alorders.size(); i < l; i++)
967 af.addSortByOrderMenuItem(
968 WebServiceName + ((String) names.get(i)) + " Ordering",
969 (AlignmentOrder) alorders.get(i));
974 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
975 AlignFrame.DEFAULT_HEIGHT);
980 System.out.println("MERGE WITH OLD FRAME");
981 // TODO: modify alignment in original frame, replacing old for new
982 // alignment using the commands.EditCommand model to ensure the update can
987 public boolean canMergeResults()