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);
602 * @return true if more job progress data was available
605 protected boolean updateJobProgress(MsaWSJob j) throws Exception
607 StringBuffer response = j.jobProgress;
608 long lastchunk = j.getLastChunk();
609 boolean changed=false;
612 j.setLastChunk(lastchunk);
613 ChunkHolder chunk = server
614 .pullExecStatistics(j.getJobId(), lastchunk);
617 changed=chunk.getChunk().length()>0;
618 response.append(chunk.getChunk());
619 lastchunk = chunk.getNextPosition();
622 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
626 public void StartJob(AWsJob job)
628 Exception lex = null;
629 // boiler plate template
630 if (!(job instanceof MsaWSJob))
632 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
635 MsaWSJob j = (MsaWSJob) job;
638 if (Cache.log.isDebugEnabled())
640 Cache.log.debug("Tried to submit an already submitted job "
647 if (j.seqs == null || j.seqs.size() == 0)
649 // special case - selection consisted entirely of empty sequences...
650 j.setjobStatus(JobStatus.FINISHED);
651 j.setStatus("Empty Alignment Job");
655 j.addInitialStatus(); // list the presets/parameters used for the job in
659 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
661 else if (j.hasArguments())
663 j.setJobId(server.customAlign(j.seqs,j.getJabaArguments()));
667 j.setJobId(server.align(j.seqs));
670 if (j.getJobId() != null)
672 j.setSubmitted(true);
673 j.setSubjobComplete(false);
674 // System.out.println(WsURL + " Job Id '" + jobId + "'");
682 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
684 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
687 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
688 + _lex.getMessage());
689 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
690 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
691 wsInfo.setStatus(j.getJobnum(),
692 WebserviceInfo.STATE_STOPPED_SERVERERROR);
693 } catch (compbio.metadata.LimitExceededException _lex)
696 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
697 + _lex.getMessage());
698 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
699 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
700 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
701 } catch (compbio.metadata.WrongParameterException _lex)
704 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
705 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
707 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
708 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
709 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
712 // For unexpected errors
714 .println(WebServiceName
715 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
716 + "When contacting Server:" + WsUrl + "\n");
717 e.printStackTrace(System.err);
718 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
719 wsInfo.setStatus(j.getJobnum(),
720 WebserviceInfo.STATE_STOPPED_SERVERERROR);
721 } catch (Exception e)
723 // For unexpected errors
725 .println(WebServiceName
726 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
727 + "When contacting Server:" + WsUrl + "\n");
728 e.printStackTrace(System.err);
729 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
730 wsInfo.setStatus(j.getJobnum(),
731 WebserviceInfo.STATE_STOPPED_SERVERERROR);
734 if (!j.isSubmitted())
736 // Boilerplate code here
737 // TODO: JBPNote catch timeout or other fault types explicitly
739 j.setAllowedServerExceptions(0);
740 wsInfo.appendProgressText(j.getJobnum(),
741 "Failed to submit sequences for alignment.\n"
742 + "Just close the window\n");
747 public void parseResult()
749 int results = 0; // number of result sets received
750 JobStateSummary finalState = new JobStateSummary();
753 for (int j = 0; j < jobs.length; j++)
755 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
756 if (jobs[j].isFinished() && msjob.alignment == null)
758 boolean jpchanged=false,jpex=false;
762 jpchanged = updateJobProgress(msjob);
764 } catch (Exception e)
768 .warn("Exception when retrieving remaining Job progress data for job "
769 + msjob.getJobId() + " on server " + WsUrl);
772 // give up polling after two consecutive exceptions
777 // set flag remember that we've had an exception.
784 Thread.sleep(jpex ? 400 : 200); // wait a bit longer if we experienced an exception.
785 } catch (Exception ex)
792 if (Cache.log.isDebugEnabled())
794 System.out.println("Job Execution file for job: "
795 + msjob.getJobId() + " on server " + WsUrl);
796 System.out.println(msjob.getStatus());
797 System.out.println("*** End of status");
802 msjob.alignment = server.getResult(msjob.getJobId());
803 } catch (compbio.metadata.ResultNotAvailableException e)
805 // job has failed for some reason - probably due to invalid
808 .debug("Results not available for finished job - marking as broken job.",
810 msjob.setjobStatus(JobStatus.FAILED);
811 } catch (Exception e)
813 Cache.log.error("Couldn't get Alignment for job.", e);
814 // TODO: Increment count and retry ?
815 msjob.setjobStatus(JobStatus.UNDEFINED);
818 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
819 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
820 && jobs[j].hasResults())
823 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
824 if (alignment != null)
826 // server.close(jobs[j].getJobnum());
827 // wsInfo.appendProgressText(jobs[j].getJobnum(),
828 // "\nAlignment Object Method Notes\n");
829 // wsInfo.appendProgressText(jobs[j].getJobnum(),
830 // "Calculated with "+alignment.getMetadata().getProgram().toString());
831 // JBPNote The returned files from a webservice could be
832 // hidden behind icons in the monitor window that,
833 // when clicked, pop up their corresponding data
837 } catch (Exception ex)
840 Cache.log.error("Unexpected exception when processing results for "
842 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
846 wsInfo.showResultsNewFrame
847 .addActionListener(new java.awt.event.ActionListener()
849 public void actionPerformed(java.awt.event.ActionEvent evt)
851 displayResults(true);
855 .addActionListener(new java.awt.event.ActionListener()
857 public void actionPerformed(java.awt.event.ActionEvent evt)
859 displayResults(false);
862 wsInfo.setResultsReady();
866 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
867 wsInfo.setFinishedNoResults();
871 void displayResults(boolean newFrame)
873 // view input or result data for each block
874 Vector alorders = new Vector();
875 SequenceI[][] results = new SequenceI[jobs.length][];
876 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
877 String lastProgram = null;
879 for (int j = 0; j < jobs.length; j++)
881 if (jobs[j].hasResults())
883 msjob = (MsaWSJob) jobs[j];
884 Object[] res = msjob.getAlignment();
885 lastProgram = msjob.getAlignmentProgram();
886 alorders.add(res[1]);
887 results[j] = (SequenceI[]) res[0];
888 orders[j] = (AlignmentOrder) res[1];
890 // SequenceI[] alignment = input.getUpdated
897 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
898 // trash references to original result data
899 for (int j = 0; j < jobs.length; j++)
904 SequenceI[] alignment = (SequenceI[]) newview[0];
905 ColumnSelection columnselection = (ColumnSelection) newview[1];
906 Alignment al = new Alignment(alignment);
907 // TODO: add 'provenance' property to alignment from the method notes
908 if (lastProgram != null)
910 al.setProperty("Alignment Program", lastProgram);
912 // accompanying each subjob
915 al.setDataset(dataset);
918 propagateDatasetMappings(al);
919 // JBNote- TODO: warn user if a block is input rather than aligned data ?
923 AlignFrame af = new AlignFrame(al, columnselection,
924 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
926 // initialise with same renderer settings as in parent alignframe.
927 af.getFeatureRenderer().transferSettings(this.featureSettings);
929 if (alorders.size() > 0)
931 if (alorders.size() == 1)
933 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
934 (AlignmentOrder) alorders.get(0));
938 // construct a non-redundant ordering set
939 Vector names = new Vector();
940 for (int i = 0, l = alorders.size(); i < l; i++)
942 String orderName = new String(" Region " + i);
947 if (((AlignmentOrder) alorders.get(i))
948 .equals(((AlignmentOrder) alorders.get(j))))
952 orderName += "," + j;
960 if (i == 0 && j == 1)
962 names.add(new String(""));
966 names.add(orderName);
969 for (int i = 0, l = alorders.size(); i < l; i++)
971 af.addSortByOrderMenuItem(
972 WebServiceName + ((String) names.get(i)) + " Ordering",
973 (AlignmentOrder) alorders.get(i));
978 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
979 AlignFrame.DEFAULT_HEIGHT);
984 System.out.println("MERGE WITH OLD FRAME");
985 // TODO: modify alignment in original frame, replacing old for new
986 // alignment using the commands.EditCommand model to ensure the update can
991 public boolean canMergeResults()