2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3 * Copyright (C) 2014 The Jalview Authors
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/>.
17 * The Jalview Authors are detailed in the 'AUTHORS' file.
19 package jalview.ws.jws2;
23 import compbio.data.msa.MsaWS;
24 import compbio.metadata.Argument;
25 import compbio.metadata.ChunkHolder;
26 import compbio.metadata.JobStatus;
27 import compbio.metadata.Preset;
29 import jalview.analysis.*;
31 import jalview.datamodel.*;
33 import jalview.ws.AWsJob;
34 import jalview.ws.WSClientI;
35 import jalview.ws.JobStateSummary;
36 import jalview.ws.jws2.dm.JabaWsParamSet;
37 import jalview.ws.params.WsParamSetI;
39 class MsaWSThread extends AWS2Thread implements WSClientI
41 boolean submitGaps = false; // pass sequences including gaps to alignment
45 boolean preserveOrder = true; // and always store and recover sequence
49 class MsaWSJob extends JWs2Job
53 WsParamSetI preset = null;
55 List<Argument> arguments = null;
60 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
65 compbio.data.sequence.Alignment alignment;
67 // set if the job didn't get run - then the input is simply returned to the
69 private boolean returnInput = false;
79 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
82 if (!prepareInput(inSeqs, 2))
85 subjobComplete = true;
91 Hashtable<String, Map> SeqNames = new Hashtable();
93 Vector<String[]> emptySeqs = new Vector();
96 * prepare input sequences for MsaWS service
99 * jalview sequences to be prepared
101 * minimum number of residues required for this MsaWS service
102 * @return true if seqs contains sequences to be submitted to service.
104 // TODO: return compbio.seqs list or nothing to indicate validity.
105 private boolean prepareInput(SequenceI[] seqs, int minlen)
111 "Implementation error: minlen must be zero or more.");
113 for (int i = 0; i < seqs.length; i++)
115 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
120 boolean valid = nseqs > 1; // need at least two seqs
121 compbio.data.sequence.FastaSequence seq;
122 for (int i = 0, n = 0; i < seqs.length; i++)
125 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
129 SeqNames.put(newname,
130 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
131 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
133 // make new input sequence with or without gaps
134 seq = new compbio.data.sequence.FastaSequence(newname,
135 (submitGaps) ? seqs[i].getSequenceAsString()
136 : AlignSeq.extractGaps(
137 jalview.util.Comparison.GapChars,
138 seqs[i].getSequenceAsString()));
144 if (seqs[i].getEnd() >= seqs[i].getStart())
146 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
147 .extractGaps(jalview.util.Comparison.GapChars,
148 seqs[i].getSequenceAsString());
150 emptySeqs.add(new String[]
159 * @return true if getAlignment will return a valid alignment result.
161 public boolean hasResults()
165 && (alignment != null || (emptySeqs != null && emptySeqs
175 * get the alignment including any empty sequences in the original order
176 * with original ids. Caller must access the alignment.getMetadata() object
177 * to annotate the final result passsed to the user.
179 * @return { SequenceI[], AlignmentOrder }
181 public Object[] getAlignment()
183 // is this a generic subjob or a Jws2 specific Object[] return signature
186 SequenceI[] alseqs = null;
187 char alseq_gapchar = '-';
189 if (alignment.getSequences().size() > 0)
191 alseqs = new SequenceI[alignment.getSequences().size()];
192 for (compbio.data.sequence.FastaSequence seq : alignment
195 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
197 alseq_gapchar = alignment.getMetadata().getGapchar();
200 // add in the empty seqs.
201 if (emptySeqs.size() > 0)
203 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
208 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
210 if (w < alseqs[i].getLength())
212 w = alseqs[i].getLength();
214 t_alseqs[i] = alseqs[i];
218 // check that aligned width is at least as wide as emptySeqs width.
220 for (i = 0, w = emptySeqs.size(); i < w; i++)
222 String[] es = (String[]) emptySeqs.get(i);
223 if (es != null && es[1] != null)
225 int sw = es[1].length();
232 // make a gapped string.
233 StringBuffer insbuff = new StringBuffer(w);
234 for (i = 0; i < nw; i++)
236 insbuff.append(alseq_gapchar);
240 for (i = 0; i < alseq_l; i++)
242 int sw = t_alseqs[i].getLength();
246 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
247 + insbuff.substring(0, sw - nw));
251 for (i = 0, w = emptySeqs.size(); i < w; i++)
253 String[] es = (String[]) emptySeqs.get(i);
256 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
257 insbuff.toString(), 1, 0);
261 if (es[1].length() < nw)
263 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
265 es[1] + insbuff.substring(0, nw - es[1].length()),
266 1, 1 + es[1].length());
270 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
277 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
278 // always recover the order - makes parseResult()'s life easier.
279 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
280 // account for any missing sequences
281 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
283 { alseqs, msaorder };
289 * mark subjob as cancelled and set result object appropriatly
294 subjobComplete = true;
300 * @return boolean true if job can be submitted.
302 public boolean hasValidInput()
304 // TODO: get attributes for this MsaWS instance to check if it can do two
305 // sequence alignment.
306 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
313 StringBuffer jobProgress = new StringBuffer();
315 public void setStatus(String string)
317 jobProgress.setLength(0);
318 jobProgress.append(string);
322 public String getStatus()
324 return jobProgress.toString();
328 public boolean hasStatus()
330 return jobProgress != null;
334 * @return the lastChunk
336 public long getLastChunk()
343 * the lastChunk to set
345 public void setLastChunk(long lastChunk)
347 this.lastChunk = lastChunk;
350 String alignmentProgram = null;
352 public String getAlignmentProgram()
354 return alignmentProgram;
357 public boolean hasArguments()
359 return (arguments != null && arguments.size() > 0)
360 || (preset != null && preset instanceof JabaWsParamSet);
363 public List<Argument> getJabaArguments()
365 List<Argument> newargs = new ArrayList<Argument>();
366 if (preset != null && preset instanceof JabaWsParamSet)
368 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
370 if (arguments != null && arguments.size() > 0)
372 newargs.addAll(arguments);
378 * add a progess header to status string containing presets/args used
380 public void addInitialStatus()
384 jobProgress.append("Using "
385 + (preset instanceof JabaPreset ? "Server" : "User")
386 + "Preset: " + preset.getName());
387 if (preset instanceof JabaWsParamSet)
389 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
391 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
396 if (arguments != null && arguments.size() > 0)
398 jobProgress.append("With custom parameters : \n");
399 // merge arguments with preset's own arguments.
400 for (Argument opt : arguments)
402 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
406 jobProgress.append("\nJob Output:\n");
409 public boolean isPresetJob()
411 return preset != null && preset instanceof JabaPreset;
414 public Preset getServerPreset()
416 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
420 String alTitle; // name which will be used to form new alignment window.
422 Alignment dataset; // dataset to which the new alignment will be
426 @SuppressWarnings("unchecked")
430 * set basic options for this (group) of Msa jobs
437 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
438 jalview.gui.AlignFrame alFrame, AlignmentView alview,
439 String wsname, boolean subgaps, boolean presorder)
441 super(alFrame, wsinfo, alview, wsname, wsUrl);
442 this.server = server;
443 this.submitGaps = subgaps;
444 this.preserveOrder = presorder;
448 * create one or more Msa jobs to align visible seuqences in _msa
461 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
462 String wsUrl, WebserviceInfo wsinfo,
463 jalview.gui.AlignFrame alFrame, String wsname, String title,
464 AlignmentView _msa, boolean subgaps, boolean presorder,
467 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
468 OutputHeader = wsInfo.getProgressText();
472 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
475 int njobs = conmsa.length;
476 jobs = new MsaWSJob[njobs];
477 for (int j = 0; j < njobs; j++)
481 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
485 jobs[j] = new MsaWSJob(0, conmsa[j]);
487 ((MsaWSJob) jobs[j]).preset = preset;
488 ((MsaWSJob) jobs[j]).arguments = paramset;
489 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
492 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
493 jobs[j].getJobnum());
495 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
500 public boolean isCancellable()
505 public void cancelJob()
507 if (!jobComplete && jobs != null)
509 boolean cancelled = true;
510 for (int job = 0; job < jobs.length; job++)
512 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
514 String cancelledMessage = "";
517 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
518 if (true) // cancelledJob || true)
521 // if the Jaba server indicates the job can't be cancelled, its
522 // because its running on the server's local execution engine
523 // so we just close the window anyway.
524 cancelledMessage = "Job cancelled.";
525 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
527 wsInfo.setStatus(jobs[job].getJobnum(),
528 WebserviceInfo.STATE_CANCELLED_OK);
532 // VALID UNSTOPPABLE JOB
533 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
535 // wsInfo.setStatus(jobs[job].jobnum,
536 // WebserviceInfo.STATE_RUNNING);
538 } catch (Exception exc)
540 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
543 "Exception whilst cancelling " + jobs[job].getJobId(),
546 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
547 + cancelledMessage + "\n");
552 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
555 this.interrupt(); // kick thread to update job states.
561 wsInfo.setProgressText(OutputHeader
562 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
567 public void pollJob(AWsJob job) throws Exception
569 // TODO: investigate if we still need to cast here in J1.6
570 MsaWSJob j = ((MsaWSJob) job);
571 // this is standard code, but since the interface doesn't comprise of a
572 // basic one that implements (getJobStatus, pullExecStatistics) we have to
573 // repeat the code for all jw2s services.
574 j.setjobStatus(server.getJobStatus(job.getJobId()));
575 updateJobProgress(j);
581 * @return true if more job progress data was available
584 protected boolean updateJobProgress(MsaWSJob j) throws Exception
586 StringBuffer response = j.jobProgress;
587 long lastchunk = j.getLastChunk();
588 boolean changed = false;
591 j.setLastChunk(lastchunk);
592 ChunkHolder chunk = server
593 .pullExecStatistics(j.getJobId(), lastchunk);
596 changed |= chunk.getChunk().length() > 0;
597 response.append(chunk.getChunk());
598 lastchunk = chunk.getNextPosition();
602 } catch (InterruptedException x)
608 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
612 public void StartJob(AWsJob job)
614 Exception lex = null;
615 // boiler plate template
616 if (!(job instanceof MsaWSJob))
618 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
621 MsaWSJob j = (MsaWSJob) job;
624 if (Cache.log.isDebugEnabled())
626 Cache.log.debug("Tried to submit an already submitted job "
633 if (j.seqs == null || j.seqs.size() == 0)
635 // special case - selection consisted entirely of empty sequences...
636 j.setjobStatus(JobStatus.FINISHED);
637 j.setStatus("Empty Alignment Job");
641 j.addInitialStatus(); // list the presets/parameters used for the job in
645 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
647 else if (j.hasArguments())
649 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
653 j.setJobId(server.align(j.seqs));
656 if (j.getJobId() != null)
658 j.setSubmitted(true);
659 j.setSubjobComplete(false);
660 // System.out.println(WsURL + " Job Id '" + jobId + "'");
668 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
670 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
673 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
674 + _lex.getMessage());
675 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
676 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
677 wsInfo.setStatus(j.getJobnum(),
678 WebserviceInfo.STATE_STOPPED_SERVERERROR);
679 } catch (compbio.metadata.LimitExceededException _lex)
682 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
683 + _lex.getMessage());
684 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
685 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
686 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
687 } catch (compbio.metadata.WrongParameterException _lex)
690 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
691 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
693 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
694 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
695 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
698 // For unexpected errors
700 .println(WebServiceName
701 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
702 + "When contacting Server:" + WsUrl + "\n");
703 e.printStackTrace(System.err);
704 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
705 wsInfo.setStatus(j.getJobnum(),
706 WebserviceInfo.STATE_STOPPED_SERVERERROR);
707 } catch (Exception e)
709 // For unexpected errors
711 .println(WebServiceName
712 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
713 + "When contacting Server:" + WsUrl + "\n");
714 e.printStackTrace(System.err);
715 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
716 wsInfo.setStatus(j.getJobnum(),
717 WebserviceInfo.STATE_STOPPED_SERVERERROR);
720 if (!j.isSubmitted())
722 // Boilerplate code here
723 // TODO: JBPNote catch timeout or other fault types explicitly
725 j.setAllowedServerExceptions(0);
726 wsInfo.appendProgressText(j.getJobnum(),
727 "Failed to submit sequences for alignment.\n"
728 + "Just close the window\n");
733 public void parseResult()
735 long progbar = System.currentTimeMillis();
736 wsInfo.setProgressBar("Collecting job results.", progbar);
737 int results = 0; // number of result sets received
738 JobStateSummary finalState = new JobStateSummary();
741 for (int j = 0; j < jobs.length; j++)
743 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
744 if (jobs[j].isFinished() && msjob.alignment == null)
746 int nunchanged = 3, nexcept = 3;
747 boolean jpchanged = false, jpex = false;
752 jpchanged = updateJobProgress(msjob);
758 } catch (Exception e)
762 .warn("Exception when retrieving remaining Job progress data for job "
763 + msjob.getJobId() + " on server " + WsUrl);
767 // set flag remember that we've had an exception.
775 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
776 // experienced an exception.
777 } catch (Exception ex)
783 } while (nunchanged > 0 && nexcept > 0);
785 if (Cache.log.isDebugEnabled())
787 System.out.println("Job Execution file for job: "
788 + msjob.getJobId() + " on server " + WsUrl);
789 System.out.println(msjob.getStatus());
790 System.out.println("*** End of status");
795 msjob.alignment = server.getResult(msjob.getJobId());
796 } catch (compbio.metadata.ResultNotAvailableException e)
798 // job has failed for some reason - probably due to invalid
801 .debug("Results not available for finished job - marking as broken job.",
804 .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
805 + e.getLocalizedMessage());
806 msjob.setjobStatus(JobStatus.FAILED);
807 } catch (Exception e)
809 Cache.log.error("Couldn't get Alignment for job.", e);
810 // TODO: Increment count and retry ?
811 msjob.setjobStatus(JobStatus.UNDEFINED);
814 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
815 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
816 && jobs[j].hasResults())
819 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
820 if (alignment != null)
822 // server.close(jobs[j].getJobnum());
823 // wsInfo.appendProgressText(jobs[j].getJobnum(),
824 // "\nAlignment Object Method Notes\n");
825 // wsInfo.appendProgressText(jobs[j].getJobnum(),
826 // "Calculated with "+alignment.getMetadata().getProgram().toString());
827 // JBPNote The returned files from a webservice could be
828 // hidden behind icons in the monitor window that,
829 // when clicked, pop up their corresponding data
833 } catch (Exception ex)
836 Cache.log.error("Unexpected exception when processing results for "
838 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
842 wsInfo.showResultsNewFrame
843 .addActionListener(new java.awt.event.ActionListener()
845 public void actionPerformed(java.awt.event.ActionEvent evt)
847 displayResults(true);
851 .addActionListener(new java.awt.event.ActionListener()
853 public void actionPerformed(java.awt.event.ActionEvent evt)
855 displayResults(false);
858 wsInfo.setResultsReady();
862 wsInfo.setFinishedNoResults();
864 updateGlobalStatus(finalState);
865 wsInfo.setProgressBar(null, progbar);
868 void displayResults(boolean newFrame)
870 // view input or result data for each block
871 Vector alorders = new Vector();
872 SequenceI[][] results = new SequenceI[jobs.length][];
873 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
874 String lastProgram = null;
876 for (int j = 0; j < jobs.length; j++)
878 if (jobs[j].hasResults())
880 msjob = (MsaWSJob) jobs[j];
881 Object[] res = msjob.getAlignment();
882 lastProgram = msjob.getAlignmentProgram();
883 alorders.add(res[1]);
884 results[j] = (SequenceI[]) res[0];
885 orders[j] = (AlignmentOrder) res[1];
887 // SequenceI[] alignment = input.getUpdated
894 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
895 // trash references to original result data
896 for (int j = 0; j < jobs.length; j++)
901 SequenceI[] alignment = (SequenceI[]) newview[0];
902 ColumnSelection columnselection = (ColumnSelection) newview[1];
903 Alignment al = new Alignment(alignment);
904 // TODO: add 'provenance' property to alignment from the method notes
905 if (lastProgram != null)
907 al.setProperty("Alignment Program", lastProgram);
909 // accompanying each subjob
912 al.setDataset(dataset);
915 propagateDatasetMappings(al);
916 // JBNote- TODO: warn user if a block is input rather than aligned data ?
920 AlignFrame af = new AlignFrame(al, columnselection,
921 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
923 // initialise with same renderer settings as in parent alignframe.
924 af.getFeatureRenderer().transferSettings(this.featureSettings);
926 if (alorders.size() > 0)
928 if (alorders.size() == 1)
930 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
931 (AlignmentOrder) alorders.get(0));
935 // construct a non-redundant ordering set
936 Vector names = new Vector();
937 for (int i = 0, l = alorders.size(); i < l; i++)
939 String orderName = new String(" Region " + i);
944 if (((AlignmentOrder) alorders.get(i))
945 .equals(((AlignmentOrder) alorders.get(j))))
949 orderName += "," + j;
957 if (i == 0 && j == 1)
959 names.add(new String(""));
963 names.add(orderName);
966 for (int i = 0, l = alorders.size(); i < l; i++)
968 af.addSortByOrderMenuItem(
969 WebServiceName + ((String) names.get(i)) + " Ordering",
970 (AlignmentOrder) alorders.get(i));
975 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
976 AlignFrame.DEFAULT_HEIGHT);
981 System.out.println("MERGE WITH OLD FRAME");
982 // TODO: modify alignment in original frame, replacing old for new
983 // alignment using the commands.EditCommand model to ensure the update can
988 public boolean canMergeResults()