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.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 (true) // 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();
603 } catch (InterruptedException x)
609 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
613 public void StartJob(AWsJob job)
615 Exception lex = null;
616 // boiler plate template
617 if (!(job instanceof MsaWSJob))
619 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
622 MsaWSJob j = (MsaWSJob) job;
625 if (Cache.log.isDebugEnabled())
627 Cache.log.debug("Tried to submit an already submitted job "
634 if (j.seqs == null || j.seqs.size() == 0)
636 // special case - selection consisted entirely of empty sequences...
637 j.setjobStatus(JobStatus.FINISHED);
638 j.setStatus("Empty Alignment Job");
642 j.addInitialStatus(); // list the presets/parameters used for the job in
646 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
648 else if (j.hasArguments())
650 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
654 j.setJobId(server.align(j.seqs));
657 if (j.getJobId() != null)
659 j.setSubmitted(true);
660 j.setSubjobComplete(false);
661 // System.out.println(WsURL + " Job Id '" + jobId + "'");
669 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
671 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
674 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
675 + _lex.getMessage());
676 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
677 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
678 wsInfo.setStatus(j.getJobnum(),
679 WebserviceInfo.STATE_STOPPED_SERVERERROR);
680 } catch (compbio.metadata.LimitExceededException _lex)
683 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
684 + _lex.getMessage());
685 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
686 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
687 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
688 } catch (compbio.metadata.WrongParameterException _lex)
691 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
692 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
694 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
695 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
696 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
699 // For unexpected errors
701 .println(WebServiceName
702 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
703 + "When contacting Server:" + WsUrl + "\n");
704 e.printStackTrace(System.err);
705 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
706 wsInfo.setStatus(j.getJobnum(),
707 WebserviceInfo.STATE_STOPPED_SERVERERROR);
708 } catch (Exception e)
710 // For unexpected errors
712 .println(WebServiceName
713 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
714 + "When contacting Server:" + WsUrl + "\n");
715 e.printStackTrace(System.err);
716 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
717 wsInfo.setStatus(j.getJobnum(),
718 WebserviceInfo.STATE_STOPPED_SERVERERROR);
721 if (!j.isSubmitted())
723 // Boilerplate code here
724 // TODO: JBPNote catch timeout or other fault types explicitly
726 j.setAllowedServerExceptions(0);
727 wsInfo.appendProgressText(j.getJobnum(),
728 "Failed to submit sequences for alignment.\n"
729 + "Just close the window\n");
734 public void parseResult()
736 long progbar = System.currentTimeMillis();
737 wsInfo.setProgressBar("Collecting job results.", progbar);
738 int results = 0; // number of result sets received
739 JobStateSummary finalState = new JobStateSummary();
742 for (int j = 0; j < jobs.length; j++)
744 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
745 if (jobs[j].isFinished() && msjob.alignment == null)
747 int nunchanged = 3, nexcept = 3;
748 boolean jpchanged = false, jpex = false;
753 jpchanged = updateJobProgress(msjob);
759 } catch (Exception e)
763 .warn("Exception when retrieving remaining Job progress data for job "
764 + msjob.getJobId() + " on server " + WsUrl);
768 // set flag remember that we've had an exception.
776 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
777 // experienced an exception.
778 } catch (Exception ex)
784 } while (nunchanged > 0 && nexcept > 0);
786 if (Cache.log.isDebugEnabled())
788 System.out.println("Job Execution file for job: "
789 + msjob.getJobId() + " on server " + WsUrl);
790 System.out.println(msjob.getStatus());
791 System.out.println("*** End of status");
796 msjob.alignment = server.getResult(msjob.getJobId());
797 } catch (compbio.metadata.ResultNotAvailableException e)
799 // job has failed for some reason - probably due to invalid
802 .debug("Results not available for finished job - marking as broken job.",
805 .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
806 + e.getLocalizedMessage());
807 msjob.setjobStatus(JobStatus.FAILED);
808 } catch (Exception e)
810 Cache.log.error("Couldn't get Alignment for job.", e);
811 // TODO: Increment count and retry ?
812 msjob.setjobStatus(JobStatus.UNDEFINED);
815 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
816 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
817 && jobs[j].hasResults())
820 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
821 if (alignment != null)
823 // server.close(jobs[j].getJobnum());
824 // wsInfo.appendProgressText(jobs[j].getJobnum(),
825 // "\nAlignment Object Method Notes\n");
826 // wsInfo.appendProgressText(jobs[j].getJobnum(),
827 // "Calculated with "+alignment.getMetadata().getProgram().toString());
828 // JBPNote The returned files from a webservice could be
829 // hidden behind icons in the monitor window that,
830 // when clicked, pop up their corresponding data
834 } catch (Exception ex)
837 Cache.log.error("Unexpected exception when processing results for "
839 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
843 wsInfo.showResultsNewFrame
844 .addActionListener(new java.awt.event.ActionListener()
846 public void actionPerformed(java.awt.event.ActionEvent evt)
848 displayResults(true);
852 .addActionListener(new java.awt.event.ActionListener()
854 public void actionPerformed(java.awt.event.ActionEvent evt)
856 displayResults(false);
859 wsInfo.setResultsReady();
863 wsInfo.setFinishedNoResults();
865 updateGlobalStatus(finalState);
866 wsInfo.setProgressBar(null, progbar);
869 void displayResults(boolean newFrame)
871 // view input or result data for each block
872 Vector alorders = new Vector();
873 SequenceI[][] results = new SequenceI[jobs.length][];
874 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
875 String lastProgram = null;
877 for (int j = 0; j < jobs.length; j++)
879 if (jobs[j].hasResults())
881 msjob = (MsaWSJob) jobs[j];
882 Object[] res = msjob.getAlignment();
883 lastProgram = msjob.getAlignmentProgram();
884 alorders.add(res[1]);
885 results[j] = (SequenceI[]) res[0];
886 orders[j] = (AlignmentOrder) res[1];
888 // SequenceI[] alignment = input.getUpdated
895 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
896 // trash references to original result data
897 for (int j = 0; j < jobs.length; j++)
902 SequenceI[] alignment = (SequenceI[]) newview[0];
903 ColumnSelection columnselection = (ColumnSelection) newview[1];
904 Alignment al = new Alignment(alignment);
905 // TODO: add 'provenance' property to alignment from the method notes
906 if (lastProgram != null)
908 al.setProperty("Alignment Program", lastProgram);
910 // accompanying each subjob
913 al.setDataset(dataset);
916 propagateDatasetMappings(al);
917 // JBNote- TODO: warn user if a block is input rather than aligned data ?
921 AlignFrame af = new AlignFrame(al, columnselection,
922 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
924 // initialise with same renderer settings as in parent alignframe.
925 af.getFeatureRenderer().transferSettings(this.featureSettings);
927 if (alorders.size() > 0)
929 if (alorders.size() == 1)
931 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
932 (AlignmentOrder) alorders.get(0));
936 // construct a non-redundant ordering set
937 Vector names = new Vector();
938 for (int i = 0, l = alorders.size(); i < l; i++)
940 String orderName = new String(" Region " + i);
945 if (((AlignmentOrder) alorders.get(i))
946 .equals(((AlignmentOrder) alorders.get(j))))
950 orderName += "," + j;
958 if (i == 0 && j == 1)
960 names.add(new String(""));
964 names.add(orderName);
967 for (int i = 0, l = alorders.size(); i < l; i++)
969 af.addSortByOrderMenuItem(
970 WebServiceName + ((String) names.get(i)) + " Ordering",
971 (AlignmentOrder) alorders.get(i));
976 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
977 AlignFrame.DEFAULT_HEIGHT);
982 System.out.println("MERGE WITH OLD FRAME");
983 // TODO: modify alignment in original frame, replacing old for new
984 // alignment using the commands.EditCommand model to ensure the update can
989 public boolean canMergeResults()