2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
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;
48 * Copyright: Copyright (c) 2004
52 * Company: Dundee University
55 * @author not attributable
58 class MsaWSThread extends AWS2Thread implements WSClientI
60 boolean submitGaps = false; // pass sequences including gaps to alignment
64 boolean preserveOrder = true; // and always store and recover sequence
68 class MsaWSJob extends JWs2Job
72 List<Argument> arguments = null;
76 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
80 compbio.data.sequence.Alignment alignment;
81 // set if the job didn't get run - then the input is simply returned to the user
82 private boolean returnInput=false;
92 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
95 if (!prepareInput(inSeqs, 2))
98 subjobComplete = true;
104 Hashtable<String,Map> SeqNames = new Hashtable();
106 Vector<String[]> emptySeqs = new Vector();
109 * prepare input sequences for MsaWS service
112 * jalview sequences to be prepared
114 * minimum number of residues required for this MsaWS service
115 * @return true if seqs contains sequences to be submitted to service.
117 // TODO: return compbio.seqs list or nothing to indicate validity.
118 private boolean prepareInput(SequenceI[] seqs, int minlen)
124 "Implementation error: minlen must be zero or more.");
126 for (int i = 0; i < seqs.length; i++)
128 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
133 boolean valid = nseqs > 1; // need at least two seqs
134 compbio.data.sequence.FastaSequence seq;
135 for (int i = 0, n = 0; i < seqs.length; i++)
138 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
142 SeqNames.put(newname, jalview.analysis.SeqsetUtils
143 .SeqCharacterHash(seqs[i]));
144 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
146 // make new input sequence with or without gaps
147 seq = new compbio.data.sequence.FastaSequence(newname,
148 (submitGaps) ? seqs[i].getSequenceAsString()
149 : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
150 seqs[i].getSequenceAsString()));
156 if (seqs[i].getEnd() >= seqs[i].getStart())
158 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
159 .extractGaps(jalview.util.Comparison.GapChars, seqs[i]
160 .getSequenceAsString());
162 emptySeqs.add(new String[]
171 * @return true if getAlignment will return a valid alignment result.
173 public boolean hasResults()
175 if (subjobComplete && isFinished() && (alignment!=null || (emptySeqs!=null && emptySeqs.size()>0)))
184 * get the alignment including any empty sequences in the original order with original ids. Caller must access the alignment.getMetadata() object to annotate the final result passsed to the user.
185 * @return { SequenceI[], AlignmentOrder }
187 public Object[] getAlignment()
189 // is this a generic subjob or a Jws2 specific Object[] return signature
192 SequenceI[] alseqs = null;
193 char alseq_gapchar = '-';
195 if (alignment.getSequences().size()>0)
197 alseqs = new SequenceI[alignment.getSequences().size()];
198 for (compbio.data.sequence.FastaSequence seq: alignment.getSequences())
200 alseqs[alseq_l++] = new Sequence(seq.getId(),seq.getSequence());
202 alseq_gapchar = alignment.getMetadata().getGapchar();
205 // add in the empty seqs.
206 if (emptySeqs.size() > 0)
208 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
213 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
215 if (w < alseqs[i].getLength())
217 w = alseqs[i].getLength();
219 t_alseqs[i] = alseqs[i];
223 // check that aligned width is at least as wide as emptySeqs width.
225 for (i = 0, w = emptySeqs.size(); i < w; i++)
227 String[] es = (String[]) emptySeqs.get(i);
228 if (es != null && es[1] != null)
230 int sw = es[1].length();
237 // make a gapped string.
238 StringBuffer insbuff = new StringBuffer(w);
239 for (i = 0; i < nw; i++)
241 insbuff.append(alseq_gapchar);
245 for (i = 0; i < alseq_l; i++)
247 int sw = t_alseqs[i].getLength();
251 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
252 + insbuff.substring(0, sw - nw));
256 for (i = 0, w = emptySeqs.size(); i < w; i++)
258 String[] es = (String[]) emptySeqs.get(i);
261 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
262 insbuff.toString(), 1, 0);
266 if (es[1].length() < nw)
268 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
270 es[1] + insbuff.substring(0, nw - es[1].length()),
271 1, 1 + es[1].length());
275 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
282 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
283 // always recover the order - makes parseResult()'s life easier.
284 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
285 // account for any missing sequences
286 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
288 { alseqs, msaorder };
294 * mark subjob as cancelled and set result object appropriatly
299 subjobComplete = true;
305 * @return boolean true if job can be submitted.
307 public boolean hasValidInput()
309 // TODO: get attributes for this MsaWS instance to check if it can do two sequence alignment.
310 if (seqs != null && seqs.size()>=2) // two or more sequences is valid ?
316 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()
344 * @param lastChunk the lastChunk to set
346 public void setLastChunk(long lastChunk)
348 this.lastChunk = lastChunk;
351 String alignmentProgram=null;
352 public String getAlignmentProgram()
354 return alignmentProgram;
359 String alTitle; // name which will be used to form new alignment window.
361 Alignment dataset; // dataset to which the new alignment will be
365 @SuppressWarnings("unchecked")
369 * set basic options for this (group) of Msa jobs
376 MsaWSThread(MsaWS server, String wsUrl,
377 WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
378 AlignmentView alview, String wsname, boolean subgaps,
381 super(alFrame, wsinfo, alview, wsname, wsUrl);
382 this.server = server;
383 this.submitGaps = subgaps;
384 this.preserveOrder = presorder;
388 * create one or more Msa jobs to align visible seuqences in _msa
401 MsaWSThread(MsaWS server2, Preset preset, List<Argument> paramset, String wsUrl,
402 WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
403 String wsname, String title, AlignmentView _msa, boolean subgaps,
404 boolean presorder, Alignment seqset)
406 this(server2,wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
407 OutputHeader = wsInfo.getProgressText();
411 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
414 int njobs = conmsa.length;
415 jobs = new MsaWSJob[njobs];
416 for (int j = 0; j < njobs; j++)
420 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
424 jobs[j] = new MsaWSJob(0, conmsa[j]);
426 ((MsaWSJob) jobs[j]).preset = preset;
427 ((MsaWSJob) jobs[j]).arguments = paramset;
428 ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
432 .setProgressName("region " + jobs[j].getJobnum(),
433 jobs[j].getJobnum());
435 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
440 public boolean isCancellable()
445 public void cancelJob()
447 if (!jobComplete && jobs != null)
449 boolean cancelled = true;
450 for (int job = 0; job < jobs.length; job++)
452 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
454 String cancelledMessage = "";
457 boolean cancelledJob = server
458 .cancelJob(jobs[job].getJobId());
459 if (cancelledJob || true)
462 // if the Jaba server indicates the job can't be cancelled, its because its running on the server's local execution engine
463 // so we just close the window anyway.
464 cancelledMessage = "Job cancelled.";
465 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this ugliness -
466 wsInfo.setStatus(jobs[job].getJobnum(),
467 WebserviceInfo.STATE_CANCELLED_OK);
471 // VALID UNSTOPPABLE JOB
472 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
474 // wsInfo.setStatus(jobs[job].jobnum,
475 // WebserviceInfo.STATE_RUNNING);
477 } catch (Exception exc)
479 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
482 "Exception whilst cancelling " + jobs[job].getJobId(), exc);
484 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
485 + cancelledMessage + "\n");
490 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
493 this.interrupt(); // kick thread to update job states.
500 .setProgressText(OutputHeader
501 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
506 public void pollJob(AWsJob job) throws Exception
508 // TODO: investigate if we still need to cast here in J1.6
509 MsaWSJob j=((MsaWSJob) job);
510 // this is standard code, but since the interface doesn't comprise of a basic one that implements (getJobStatus, pullExecStatistics) we have to repeat the code for all jw2s services.
511 j.setjobStatus(server.getJobStatus(job.getJobId()));
512 updateJobProgress(j);
514 protected void updateJobProgress(MsaWSJob j) throws Exception {
515 StringBuffer response = j.jobProgress;
516 long lastchunk = j.getLastChunk();
518 j.setLastChunk(lastchunk);
519 ChunkHolder chunk = server.pullExecStatistics(j.getJobId(), lastchunk);
521 response.append(chunk.getChunk());
522 lastchunk = chunk.getNextPosition();
524 } while (lastchunk>=0 && j.getLastChunk()!=lastchunk);
527 public void StartJob(AWsJob job)
529 // boiler plate template
530 if (!(job instanceof MsaWSJob))
532 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
535 MsaWSJob j = (MsaWSJob) job;
538 if (Cache.log.isDebugEnabled())
540 Cache.log.debug("Tried to submit an already submitted job "
547 if (j.seqs == null || j.seqs.size()==0)
549 // special case - selection consisted entirely of empty sequences...
550 j.setjobStatus(JobStatus.FINISHED);
551 j.setStatus("Empty Alignment Job");
555 // TODO: get the parameters (if any) for this job and submit the job
556 if (j.arguments!=null && j.arguments.size()>0)
558 StringBuffer pset = new StringBuffer();
559 for (Argument arg: j.arguments) {
560 pset.append(arg.getName()+" "+arg.getDefaultValue()+"\n");
562 j.setStatus("Custom Parameters:\n"+pset.toString()+"\nJob Output:\n");
563 j.setJobId(server.customAlign(j.seqs, j.arguments));
567 j.setJobId(server.presetAlign(j.seqs, j.preset));
569 j.setJobId(server.align(j.seqs));
572 if (j.getJobId()!= null)
574 j.setSubmitted(true);
575 j.setSubjobComplete(false);
576 // System.out.println(WsURL + " Job Id '" + jobId + "'");
583 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
585 } catch (Exception e)
587 // Boilerplate code here
588 // TODO: JBPNote catch timeout or other fault types explicitly
589 // For unexpected errors
591 .println(WebServiceName
592 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
593 + "When contacting Server:" + WsUrl + "\n");
594 e.printStackTrace(System.err);
595 j.setAllowedServerExceptions(0);
596 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
597 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
601 "Failed to submit sequences for alignment.\n"
602 + "It is most likely that there is a problem with the server.\n"
603 + "Just close the window\n");
605 // e.printStackTrace(); // TODO: JBPNote DEBUG
610 public void parseResult()
612 int results = 0; // number of result sets received
613 JobStateSummary finalState = new JobStateSummary();
616 for (int j = 0; j < jobs.length; j++)
618 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
619 if (jobs[j].isFinished() && msjob.alignment==null)
622 updateJobProgress(msjob);
623 } catch (Exception e)
625 Cache.log.warn("Exception when retrieving remaining Job progress data for job "+msjob.getJobId()+" on server "+WsUrl);
628 if (Cache.log.isDebugEnabled())
630 System.out.println("Job Execution file for job: "+msjob.getJobId()+" on server "+WsUrl);
631 System.out.println(msjob.getStatus());
632 System.out.println("*** End of status");
636 msjob.alignment = server.getResult(msjob.getJobId());
640 Cache.log.error("Couldn't get Alignment for job.",e);
643 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
644 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
645 && jobs[j].hasResults())
648 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
649 if (alignment != null)
651 // server.close(jobs[j].getJobnum());
652 // wsInfo.appendProgressText(jobs[j].getJobnum(),
653 // "\nAlignment Object Method Notes\n");
654 // wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
655 // JBPNote The returned files from a webservice could be
656 // hidden behind icons in the monitor window that,
657 // when clicked, pop up their corresponding data
661 } catch (Exception ex)
664 Cache.log.error("Unexpected exception when processing results for "
666 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
670 wsInfo.showResultsNewFrame
671 .addActionListener(new java.awt.event.ActionListener()
673 public void actionPerformed(java.awt.event.ActionEvent evt)
675 displayResults(true);
679 .addActionListener(new java.awt.event.ActionListener()
681 public void actionPerformed(java.awt.event.ActionEvent evt)
683 displayResults(false);
686 wsInfo.setResultsReady();
690 wsInfo.setFinishedNoResults();
694 void displayResults(boolean newFrame)
696 // view input or result data for each block
697 Vector alorders = new Vector();
698 SequenceI[][] results = new SequenceI[jobs.length][];
699 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
700 String lastProgram = null;
702 for (int j = 0; j < jobs.length; j++)
704 if (jobs[j].hasResults())
706 msjob = (MsaWSJob)jobs[j];
707 Object[] res = msjob.getAlignment();
708 lastProgram = msjob.getAlignmentProgram();
709 alorders.add(res[1]);
710 results[j] = (SequenceI[]) res[0];
711 orders[j] = (AlignmentOrder) res[1];
713 // SequenceI[] alignment = input.getUpdated
720 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
721 // trash references to original result data
722 for (int j = 0; j < jobs.length; j++)
727 SequenceI[] alignment = (SequenceI[]) newview[0];
728 ColumnSelection columnselection = (ColumnSelection) newview[1];
729 Alignment al = new Alignment(alignment);
730 // TODO: add 'provenance' property to alignment from the method notes
731 if (lastProgram!=null) {
732 al.setProperty("Alignment Program", lastProgram);
734 // accompanying each subjob
737 al.setDataset(dataset);
740 propagateDatasetMappings(al);
741 // JBNote- TODO: warn user if a block is input rather than aligned data ?
745 AlignFrame af = new AlignFrame(al, columnselection,
746 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
748 // initialise with same renderer settings as in parent alignframe.
749 af.getFeatureRenderer().transferSettings(this.featureSettings);
751 if (alorders.size() > 0)
753 if (alorders.size() == 1)
755 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
756 (AlignmentOrder) alorders.get(0));
760 // construct a non-redundant ordering set
761 Vector names = new Vector();
762 for (int i = 0, l = alorders.size(); i < l; i++)
764 String orderName = new String(" Region " + i);
769 if (((AlignmentOrder) alorders.get(i))
770 .equals(((AlignmentOrder) alorders.get(j))))
774 orderName += "," + j;
782 if (i == 0 && j == 1)
784 names.add(new String(""));
788 names.add(orderName);
791 for (int i = 0, l = alorders.size(); i < l; i++)
793 af.addSortByOrderMenuItem(WebServiceName
794 + ((String) names.get(i)) + " Ordering",
795 (AlignmentOrder) alorders.get(i));
800 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
801 AlignFrame.DEFAULT_HEIGHT);
806 System.out.println("MERGE WITH OLD FRAME");
807 // TODO: modify alignment in original frame, replacing old for new
808 // alignment using the commands.EditCommand model to ensure the update can
813 public boolean canMergeResults()