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.util.MessageManager;
36 import jalview.ws.AWsJob;
37 import jalview.ws.WSClientI;
38 import jalview.ws.JobStateSummary;
39 import jalview.ws.jws2.dm.JabaWsParamSet;
40 import jalview.ws.params.WsParamSetI;
42 class MsaWSThread extends AWS2Thread implements WSClientI
44 boolean submitGaps = false; // pass sequences including gaps to alignment
48 boolean preserveOrder = true; // and always store and recover sequence
52 class MsaWSJob extends JWs2Job
56 WsParamSetI preset = null;
58 List<Argument> arguments = null;
63 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
68 compbio.data.sequence.Alignment alignment;
70 // set if the job didn't get run - then the input is simply returned to the
72 private boolean returnInput = false;
82 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
85 if (!prepareInput(inSeqs, 2))
88 subjobComplete = true;
94 Hashtable<String, Map> SeqNames = new Hashtable();
96 Vector<String[]> emptySeqs = new Vector();
99 * prepare input sequences for MsaWS service
102 * jalview sequences to be prepared
104 * minimum number of residues required for this MsaWS service
105 * @return true if seqs contains sequences to be submitted to service.
107 // TODO: return compbio.seqs list or nothing to indicate validity.
108 private boolean prepareInput(SequenceI[] seqs, int minlen)
113 throw new Error(MessageManager.getString("error.implementation_error_minlen_must_be_greater_zero"));
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");
553 // if we hadn't submitted then just mark the job as cancelled.
554 jobs[job].setSubjobComplete(true);
555 wsInfo.setStatus(jobs[job].getJobnum(),
556 WebserviceInfo.STATE_CANCELLED_OK);
562 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
565 this.interrupt(); // kick thread to update job states.
571 wsInfo.setProgressText(OutputHeader
572 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
577 public void pollJob(AWsJob job) throws Exception
579 // TODO: investigate if we still need to cast here in J1.6
580 MsaWSJob j = ((MsaWSJob) job);
581 // this is standard code, but since the interface doesn't comprise of a
582 // basic one that implements (getJobStatus, pullExecStatistics) we have to
583 // repeat the code for all jw2s services.
584 j.setjobStatus(server.getJobStatus(job.getJobId()));
585 updateJobProgress(j);
591 * @return true if more job progress data was available
594 protected boolean updateJobProgress(MsaWSJob j) throws Exception
596 StringBuffer response = j.jobProgress;
597 long lastchunk = j.getLastChunk();
598 boolean changed = false;
601 j.setLastChunk(lastchunk);
602 ChunkHolder chunk = server
603 .pullExecStatistics(j.getJobId(), lastchunk);
606 changed |= chunk.getChunk().length() > 0;
607 response.append(chunk.getChunk());
608 lastchunk = chunk.getNextPosition();
612 } catch (InterruptedException x)
618 } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
622 public void StartJob(AWsJob job)
624 Exception lex = null;
625 // boiler plate template
626 if (!(job instanceof MsaWSJob))
628 throw new Error(MessageManager.formatMessage("error.implementation_error_msawbjob_called", new String[]{job.getClass().toString()}));
630 MsaWSJob j = (MsaWSJob) job;
633 if (Cache.log.isDebugEnabled())
635 Cache.log.debug("Tried to submit an already submitted job "
642 if (j.seqs == null || j.seqs.size() == 0)
644 // special case - selection consisted entirely of empty sequences...
645 j.setjobStatus(JobStatus.FINISHED);
646 j.setStatus(MessageManager.getString("label.empty_alignment_job"));
650 j.addInitialStatus(); // list the presets/parameters used for the job in
654 j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
656 else if (j.hasArguments())
658 j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
662 j.setJobId(server.align(j.seqs));
665 if (j.getJobId() != null)
667 j.setSubmitted(true);
668 j.setSubjobComplete(false);
669 // System.out.println(WsURL + " Job Id '" + jobId + "'");
674 throw new Exception(MessageManager.formatMessage("exception.web_service_returned_null_try_later", new String[]{WsUrl}));
676 } catch (compbio.metadata.UnsupportedRuntimeException _lex)
679 wsInfo.appendProgressText(MessageManager.formatMessage("info.job_couldnt_be_run_server_doesnt_support_program", new String[]{_lex.getMessage()}));
680 wsInfo.warnUser(_lex.getMessage(), MessageManager.getString("warn.service_not_supported"));
681 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
682 wsInfo.setStatus(j.getJobnum(),
683 WebserviceInfo.STATE_STOPPED_SERVERERROR);
684 } catch (compbio.metadata.LimitExceededException _lex)
687 wsInfo.appendProgressText(MessageManager.formatMessage("info.job_couldnt_be_run_exceeded_hard_limit", new String[]{_lex.getMessage()}));
688 wsInfo.warnUser(_lex.getMessage(), MessageManager.getString("warn.input_is_too_big"));
689 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
690 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
691 } catch (compbio.metadata.WrongParameterException _lex)
694 wsInfo.warnUser(_lex.getMessage(), MessageManager.getString("warn.invalid_job_param_set"));
695 wsInfo.appendProgressText(MessageManager.formatMessage("info.job_couldnt_be_run_incorrect_param_setting", new String[]{_lex.getMessage()}));
696 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
697 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
700 // For unexpected errors
702 .println(WebServiceName
703 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
704 + "When contacting Server:" + WsUrl + "\n");
705 e.printStackTrace(System.err);
706 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
707 wsInfo.setStatus(j.getJobnum(),
708 WebserviceInfo.STATE_STOPPED_SERVERERROR);
709 } catch (Exception e)
711 // For unexpected errors
713 .println(WebServiceName
714 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
715 + "When contacting Server:" + WsUrl + "\n");
716 e.printStackTrace(System.err);
717 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
718 wsInfo.setStatus(j.getJobnum(),
719 WebserviceInfo.STATE_STOPPED_SERVERERROR);
722 if (!j.isSubmitted())
724 // Boilerplate code here
725 // TODO: JBPNote catch timeout or other fault types explicitly
727 j.setAllowedServerExceptions(0);
728 wsInfo.appendProgressText(j.getJobnum(),
729 MessageManager.getString("info.failed_to_submit_sequences_for_alignment"));
734 public void parseResult()
736 long progbar = System.currentTimeMillis();
737 wsInfo.setProgressBar(MessageManager.getString("status.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()