2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
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
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ws.jws2;
25 import compbio.data.msa.MsaWS;
26 import compbio.metadata.Argument;
27 import compbio.metadata.ChunkHolder;
28 import compbio.metadata.JobStatus;
29 import compbio.metadata.Preset;
31 import jalview.analysis.*;
33 import jalview.datamodel.*;
35 import jalview.ws.AWsJob;
36 import jalview.ws.WSClientI;
37 import jalview.ws.JobStateSummary;
38 import jalview.ws.jws2.dm.JabaWsParamSet;
39 import jalview.ws.params.WsParamSetI;
41 class MsaWSThread extends AWS2Thread implements WSClientI
43 boolean submitGaps = false; // pass sequences including gaps to alignment
47 boolean preserveOrder = true; // and always store and recover sequence
51 class MsaWSJob extends JWs2Job
55 WsParamSetI preset = null;
57 List<Argument> arguments = null;
62 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
67 compbio.data.sequence.Alignment alignment;
69 // set if the job didn't get run - then the input is simply returned to the
71 private boolean returnInput = false;
81 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
84 if (!prepareInput(inSeqs, 2))
87 subjobComplete = true;
93 Hashtable<String, Map> SeqNames = new Hashtable();
95 Vector<String[]> emptySeqs = new Vector();
98 * prepare input sequences for MsaWS service
101 * jalview sequences to be prepared
103 * minimum number of residues required for this MsaWS service
104 * @return true if seqs contains sequences to be submitted to service.
106 // TODO: return compbio.seqs list or nothing to indicate validity.
107 private boolean prepareInput(SequenceI[] seqs, int minlen)
113 "Implementation error: minlen must be zero or more.");
115 for (int i = 0; i < seqs.length; i++)
117 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
122 boolean valid = nseqs > 1; // need at least two seqs
123 compbio.data.sequence.FastaSequence seq;
124 for (int i = 0, n = 0; i < seqs.length; i++)
127 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
131 SeqNames.put(newname,
132 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
133 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
135 // make new input sequence with or without gaps
136 seq = new compbio.data.sequence.FastaSequence(newname,
137 (submitGaps) ? seqs[i].getSequenceAsString()
138 : AlignSeq.extractGaps(
139 jalview.util.Comparison.GapChars,
140 seqs[i].getSequenceAsString()));
146 if (seqs[i].getEnd() >= seqs[i].getStart())
148 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
149 .extractGaps(jalview.util.Comparison.GapChars,
150 seqs[i].getSequenceAsString());
152 emptySeqs.add(new String[]
161 * @return true if getAlignment will return a valid alignment result.
163 public boolean hasResults()
167 && (alignment != null || (emptySeqs != null && emptySeqs
177 * get the alignment including any empty sequences in the original order
178 * with original ids. Caller must access the alignment.getMetadata() object
179 * to annotate the final result passsed to the user.
181 * @return { SequenceI[], AlignmentOrder }
183 public Object[] getAlignment()
185 // is this a generic subjob or a Jws2 specific Object[] return signature
188 SequenceI[] alseqs = null;
189 char alseq_gapchar = '-';
191 if (alignment.getSequences().size() > 0)
193 alseqs = new SequenceI[alignment.getSequences().size()];
194 for (compbio.data.sequence.FastaSequence seq : alignment
197 alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
199 alseq_gapchar = alignment.getMetadata().getGapchar();
202 // add in the empty seqs.
203 if (emptySeqs.size() > 0)
205 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
210 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
212 if (w < alseqs[i].getLength())
214 w = alseqs[i].getLength();
216 t_alseqs[i] = alseqs[i];
220 // check that aligned width is at least as wide as emptySeqs width.
222 for (i = 0, w = emptySeqs.size(); i < w; i++)
224 String[] es = (String[]) emptySeqs.get(i);
225 if (es != null && es[1] != null)
227 int sw = es[1].length();
234 // make a gapped string.
235 StringBuffer insbuff = new StringBuffer(w);
236 for (i = 0; i < nw; i++)
238 insbuff.append(alseq_gapchar);
242 for (i = 0; i < alseq_l; i++)
244 int sw = t_alseqs[i].getLength();
248 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
249 + insbuff.substring(0, sw - nw));
253 for (i = 0, w = emptySeqs.size(); i < w; i++)
255 String[] es = (String[]) emptySeqs.get(i);
258 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
259 insbuff.toString(), 1, 0);
263 if (es[1].length() < nw)
265 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
267 es[1] + insbuff.substring(0, nw - es[1].length()),
268 1, 1 + es[1].length());
272 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
279 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
280 // always recover the order - makes parseResult()'s life easier.
281 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
282 // account for any missing sequences
283 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
285 { alseqs, msaorder };
291 * mark subjob as cancelled and set result object appropriatly
296 subjobComplete = true;
302 * @return boolean true if job can be submitted.
304 public boolean hasValidInput()
306 // TODO: get attributes for this MsaWS instance to check if it can do two
307 // sequence alignment.
308 if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
315 StringBuffer jobProgress = new StringBuffer();
317 public void setStatus(String string)
319 jobProgress.setLength(0);
320 jobProgress.append(string);
324 public String getStatus()
326 return jobProgress.toString();
330 public boolean hasStatus()
332 return jobProgress != null;
336 * @return the lastChunk
338 public long getLastChunk()
345 * the lastChunk to set
347 public void setLastChunk(long lastChunk)
349 this.lastChunk = lastChunk;
352 String alignmentProgram = null;
354 public String getAlignmentProgram()
356 return alignmentProgram;
359 public boolean hasArguments()
361 return (arguments != null && arguments.size() > 0)
362 || (preset != null && preset instanceof JabaWsParamSet);
365 public List<Argument> getJabaArguments()
367 List<Argument> newargs = new ArrayList<Argument>();
368 if (preset != null && preset instanceof JabaWsParamSet)
370 newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
372 if (arguments != null && arguments.size() > 0)
374 newargs.addAll(arguments);
380 * add a progess header to status string containing presets/args used
382 public void addInitialStatus()
386 jobProgress.append("Using "
387 + (preset instanceof JabaPreset ? "Server" : "User")
388 + "Preset: " + preset.getName());
389 if (preset instanceof JabaWsParamSet)
391 for (Argument opt : ((JabaWsParamSet) preset).getjabaArguments())
393 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
398 if (arguments != null && arguments.size() > 0)
400 jobProgress.append("With custom parameters : \n");
401 // merge arguments with preset's own arguments.
402 for (Argument opt : arguments)
404 jobProgress.append(opt.getName() + " " + opt.getDefaultValue()
408 jobProgress.append("\nJob Output:\n");
411 public boolean isPresetJob()
413 return preset != null && preset instanceof JabaPreset;
416 public Preset getServerPreset()
418 return (isPresetJob()) ? ((JabaPreset) preset).p : null;
422 String alTitle; // name which will be used to form new alignment window.
424 Alignment dataset; // dataset to which the new alignment will be
428 @SuppressWarnings("unchecked")
432 * set basic options for this (group) of Msa jobs
439 MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
440 jalview.gui.AlignFrame alFrame, AlignmentView alview,
441 String wsname, boolean subgaps, boolean presorder)
443 super(alFrame, wsinfo, alview, wsname, wsUrl);
444 this.server = server;
445 this.submitGaps = subgaps;
446 this.preserveOrder = presorder;
450 * create one or more Msa jobs to align visible seuqences in _msa
463 MsaWSThread(MsaWS server2, WsParamSetI preset, List<Argument> paramset,
464 String wsUrl, WebserviceInfo wsinfo,
465 jalview.gui.AlignFrame alFrame, String wsname, String title,
466 AlignmentView _msa, boolean subgaps, boolean presorder,
469 this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
470 OutputHeader = wsInfo.getProgressText();
474 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
477 int njobs = conmsa.length;
478 jobs = new MsaWSJob[njobs];
479 for (int j = 0; j < njobs; j++)
483 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
487 jobs[j] = new MsaWSJob(0, conmsa[j]);
489 ((MsaWSJob) jobs[j]).preset = preset;
490 ((MsaWSJob) jobs[j]).arguments = paramset;
491 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
494 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
495 jobs[j].getJobnum());
497 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
502 public boolean isCancellable()
507 public void cancelJob()
509 if (!jobComplete && jobs != null)
511 boolean cancelled = true;
512 for (int job = 0; job < jobs.length; job++)
514 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
516 String cancelledMessage = "";
519 boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
520 if (true) // cancelledJob || true)
523 // if the Jaba server indicates the job can't be cancelled, its
524 // because its running on the server's local execution engine
525 // so we just close the window anyway.
526 cancelledMessage = "Job cancelled.";
527 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
529 wsInfo.setStatus(jobs[job].getJobnum(),
530 WebserviceInfo.STATE_CANCELLED_OK);
534 // VALID UNSTOPPABLE JOB
535 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
537 // wsInfo.setStatus(jobs[job].jobnum,
538 // WebserviceInfo.STATE_RUNNING);
540 } catch (Exception exc)
542 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
545 "Exception whilst cancelling " + jobs[job].getJobId(),
548 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
549 + cancelledMessage + "\n");
551 // if we hadn't submitted then just mark the job as cancelled.
552 jobs[job].setSubjobComplete(true);
553 wsInfo.setStatus(jobs[job].getJobnum(), WebserviceInfo.STATE_CANCELLED_OK);
559 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
562 this.interrupt(); // kick thread to update job states.
568 wsInfo.setProgressText(OutputHeader
569 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
574 public void pollJob(AWsJob job) throws Exception
576 // TODO: investigate if we still need to cast here in J1.6
577 MsaWSJob j = ((MsaWSJob) job);
578 // this is standard code, but since the interface doesn't comprise of a
579 // basic one that implements (getJobStatus, pullExecStatistics) we have to
580 // repeat the code for all jw2s services.
581 j.setjobStatus(server.getJobStatus(job.getJobId()));
582 updateJobProgress(j);
588 * @return true if more job progress data was available
591 protected boolean updateJobProgress(MsaWSJob j) throws Exception
593 StringBuffer response = j.jobProgress;
594 long lastchunk = j.getLastChunk();
595 boolean changed = false;
598 j.setLastChunk(lastchunk);
599 ChunkHolder chunk = server
600 .pullExecStatistics(j.getJobId(), lastchunk);
603 changed |= chunk.getChunk().length() > 0;
604 response.append(chunk.getChunk());
605 lastchunk = chunk.getNextPosition();
609 } catch (InterruptedException x)
615 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
619 public void StartJob(AWsJob job)
621 Exception lex = null;
622 // boiler plate template
623 if (!(job instanceof MsaWSJob))
625 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
628 MsaWSJob j = (MsaWSJob) job;
631 if (Cache.log.isDebugEnabled())
633 Cache.log.debug("Tried to submit an already submitted job "
640 if (j.seqs == null || j.seqs.size() == 0)
642 // special case - selection consisted entirely of empty sequences...
643 j.setjobStatus(JobStatus.FINISHED);
644 j.setStatus("Empty Alignment Job");
648 j.addInitialStatus(); // list the presets/parameters used for the job in
652 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
654 else if (j.hasArguments())
656 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
660 j.setJobId(server.align(j.seqs));
663 if (j.getJobId() != null)
665 j.setSubmitted(true);
666 j.setSubjobComplete(false);
667 // System.out.println(WsURL + " Job Id '" + jobId + "'");
675 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
677 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
680 wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
681 + _lex.getMessage());
682 wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
683 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
684 wsInfo.setStatus(j.getJobnum(),
685 WebserviceInfo.STATE_STOPPED_SERVERERROR);
686 } catch (compbio.metadata.LimitExceededException _lex)
689 wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
690 + _lex.getMessage());
691 wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
692 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
693 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
694 } catch (compbio.metadata.WrongParameterException _lex)
697 wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
698 wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
700 + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
701 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
702 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
705 // For unexpected errors
707 .println(WebServiceName
708 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
709 + "When contacting Server:" + WsUrl + "\n");
710 e.printStackTrace(System.err);
711 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
712 wsInfo.setStatus(j.getJobnum(),
713 WebserviceInfo.STATE_STOPPED_SERVERERROR);
714 } catch (Exception e)
716 // For unexpected errors
718 .println(WebServiceName
719 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
720 + "When contacting Server:" + WsUrl + "\n");
721 e.printStackTrace(System.err);
722 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
723 wsInfo.setStatus(j.getJobnum(),
724 WebserviceInfo.STATE_STOPPED_SERVERERROR);
727 if (!j.isSubmitted())
729 // Boilerplate code here
730 // TODO: JBPNote catch timeout or other fault types explicitly
732 j.setAllowedServerExceptions(0);
733 wsInfo.appendProgressText(j.getJobnum(),
734 "Failed to submit sequences for alignment.\n"
735 + "Just close the window\n");
740 public void parseResult()
742 long progbar = System.currentTimeMillis();
743 wsInfo.setProgressBar("Collecting job results.", progbar);
744 int results = 0; // number of result sets received
745 JobStateSummary finalState = new JobStateSummary();
748 for (int j = 0; j < jobs.length; j++)
750 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
751 if (jobs[j].isFinished() && msjob.alignment == null)
753 int nunchanged = 3, nexcept = 3;
754 boolean jpchanged = false, jpex = false;
759 jpchanged = updateJobProgress(msjob);
765 } catch (Exception e)
769 .warn("Exception when retrieving remaining Job progress data for job "
770 + msjob.getJobId() + " on server " + WsUrl);
774 // set flag remember that we've had an exception.
782 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
783 // experienced an exception.
784 } catch (Exception ex)
790 } while (nunchanged > 0 && nexcept > 0);
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.",
811 .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
812 + e.getLocalizedMessage());
813 msjob.setjobStatus(JobStatus.FAILED);
814 } catch (Exception e)
816 Cache.log.error("Couldn't get Alignment for job.", e);
817 // TODO: Increment count and retry ?
818 msjob.setjobStatus(JobStatus.UNDEFINED);
821 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
822 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
823 && jobs[j].hasResults())
826 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
827 if (alignment != null)
829 // server.close(jobs[j].getJobnum());
830 // wsInfo.appendProgressText(jobs[j].getJobnum(),
831 // "\nAlignment Object Method Notes\n");
832 // wsInfo.appendProgressText(jobs[j].getJobnum(),
833 // "Calculated with "+alignment.getMetadata().getProgram().toString());
834 // JBPNote The returned files from a webservice could be
835 // hidden behind icons in the monitor window that,
836 // when clicked, pop up their corresponding data
840 } catch (Exception ex)
843 Cache.log.error("Unexpected exception when processing results for "
845 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
849 wsInfo.showResultsNewFrame
850 .addActionListener(new java.awt.event.ActionListener()
852 public void actionPerformed(java.awt.event.ActionEvent evt)
854 displayResults(true);
858 .addActionListener(new java.awt.event.ActionListener()
860 public void actionPerformed(java.awt.event.ActionEvent evt)
862 displayResults(false);
865 wsInfo.setResultsReady();
869 wsInfo.setFinishedNoResults();
871 updateGlobalStatus(finalState);
872 wsInfo.setProgressBar(null, progbar);
875 void displayResults(boolean newFrame)
877 // view input or result data for each block
878 Vector alorders = new Vector();
879 SequenceI[][] results = new SequenceI[jobs.length][];
880 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
881 String lastProgram = null;
883 for (int j = 0; j < jobs.length; j++)
885 if (jobs[j].hasResults())
887 msjob = (MsaWSJob) jobs[j];
888 Object[] res = msjob.getAlignment();
889 lastProgram = msjob.getAlignmentProgram();
890 alorders.add(res[1]);
891 results[j] = (SequenceI[]) res[0];
892 orders[j] = (AlignmentOrder) res[1];
894 // SequenceI[] alignment = input.getUpdated
901 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
902 // trash references to original result data
903 for (int j = 0; j < jobs.length; j++)
908 SequenceI[] alignment = (SequenceI[]) newview[0];
909 ColumnSelection columnselection = (ColumnSelection) newview[1];
910 Alignment al = new Alignment(alignment);
911 // TODO: add 'provenance' property to alignment from the method notes
912 if (lastProgram != null)
914 al.setProperty("Alignment Program", lastProgram);
916 // accompanying each subjob
919 al.setDataset(dataset);
922 propagateDatasetMappings(al);
923 // JBNote- TODO: warn user if a block is input rather than aligned data ?
927 AlignFrame af = new AlignFrame(al, columnselection,
928 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
930 // initialise with same renderer settings as in parent alignframe.
931 af.getFeatureRenderer().transferSettings(this.featureSettings);
933 if (alorders.size() > 0)
935 if (alorders.size() == 1)
937 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
938 (AlignmentOrder) alorders.get(0));
942 // construct a non-redundant ordering set
943 Vector names = new Vector();
944 for (int i = 0, l = alorders.size(); i < l; i++)
946 String orderName = new String(" Region " + i);
951 if (((AlignmentOrder) alorders.get(i))
952 .equals(((AlignmentOrder) alorders.get(j))))
956 orderName += "," + j;
964 if (i == 0 && j == 1)
966 names.add(new String(""));
970 names.add(orderName);
973 for (int i = 0, l = alorders.size(); i < l; i++)
975 af.addSortByOrderMenuItem(
976 WebServiceName + ((String) names.get(i)) + " Ordering",
977 (AlignmentOrder) alorders.get(i));
982 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
983 AlignFrame.DEFAULT_HEIGHT);
988 System.out.println("MERGE WITH OLD FRAME");
989 // TODO: modify alignment in original frame, replacing old for new
990 // alignment using the commands.EditCommand model to ensure the update can
995 public boolean canMergeResults()