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.ChunkHolder;
26 import compbio.metadata.JobStatus;
28 import jalview.analysis.*;
30 import jalview.datamodel.*;
32 import jalview.ws.AWsJob;
33 import jalview.ws.WSClientI;
34 import jalview.ws.JobStateSummary;
46 * Copyright: Copyright (c) 2004
50 * Company: Dundee University
53 * @author not attributable
56 class MsaWSThread extends AWS2Thread implements WSClientI
58 boolean submitGaps = false; // pass sequences including gaps to alignment
62 boolean preserveOrder = true; // and always store and recover sequence
66 class MsaWSJob extends JWs2Job
73 ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
77 compbio.data.sequence.Alignment alignment;
78 // set if the job didn't get run - then the input is simply returned to the user
79 private boolean returnInput=false;
89 public MsaWSJob(int jobNum, SequenceI[] inSeqs)
92 if (!prepareInput(inSeqs, 2))
95 subjobComplete = true;
101 Hashtable<String,Map> SeqNames = new Hashtable();
103 Vector<String[]> emptySeqs = new Vector();
106 * prepare input sequences for MsaWS service
109 * jalview sequences to be prepared
111 * minimum number of residues required for this MsaWS service
112 * @return true if seqs contains sequences to be submitted to service.
114 // TODO: return compbio.seqs list or nothing to indicate validity.
115 private boolean prepareInput(SequenceI[] seqs, int minlen)
121 "Implementation error: minlen must be zero or more.");
123 for (int i = 0; i < seqs.length; i++)
125 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
130 boolean valid = nseqs > 1; // need at least two seqs
131 compbio.data.sequence.FastaSequence seq;
132 for (int i = 0, n = 0; i < seqs.length; i++)
135 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
139 SeqNames.put(newname, jalview.analysis.SeqsetUtils
140 .SeqCharacterHash(seqs[i]));
141 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
143 // make new input sequence with or without gaps
144 seq = new compbio.data.sequence.FastaSequence(newname,
145 (submitGaps) ? seqs[i].getSequenceAsString()
146 : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
147 seqs[i].getSequenceAsString()));
153 if (seqs[i].getEnd() >= seqs[i].getStart())
155 empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
156 .extractGaps(jalview.util.Comparison.GapChars, seqs[i]
157 .getSequenceAsString());
159 emptySeqs.add(new String[]
168 * @return true if getAlignment will return a valid alignment result.
170 public boolean hasResults()
172 if (subjobComplete && isFinished() && (alignment!=null || (emptySeqs!=null && emptySeqs.size()>0)))
181 * 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.
182 * @return { SequenceI[], AlignmentOrder }
184 public Object[] getAlignment()
186 // is this a generic subjob or a Jws2 specific Object[] return signature
189 SequenceI[] alseqs = null;
190 char alseq_gapchar = '-';
192 if (alignment.getSequences().size()>0)
194 alseqs = new SequenceI[alignment.getSequences().size()];
195 for (compbio.data.sequence.FastaSequence seq: alignment.getSequences())
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 sequence alignment.
307 if (seqs != null && seqs.size()>=2) // two or more sequences is valid ?
313 StringBuffer jobProgress=new StringBuffer();
314 public void setStatus(String string)
316 jobProgress.setLength(0);
317 jobProgress.append(string);
321 public String getStatus()
323 return jobProgress.toString();
327 public boolean hasStatus()
329 return jobProgress!=null;
333 * @return the lastChunk
335 public long getLastChunk()
341 * @param lastChunk the lastChunk to set
343 public void setLastChunk(long lastChunk)
345 this.lastChunk = lastChunk;
350 String alTitle; // name which will be used to form new alignment window.
352 Alignment dataset; // dataset to which the new alignment will be
356 @SuppressWarnings("unchecked")
360 * set basic options for this (group) of Msa jobs
367 MsaWSThread(MsaWS server, String wsUrl,
368 WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
369 AlignmentView alview, String wsname, boolean subgaps,
372 super(alFrame, wsinfo, alview, wsname, wsUrl);
373 this.server = server;
374 this.submitGaps = subgaps;
375 this.preserveOrder = presorder;
379 * create one or more Msa jobs to align visible seuqences in _msa
392 MsaWSThread(MsaWS server2, String wsUrl,
393 WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
394 String wsname, String title, AlignmentView _msa, boolean subgaps,
395 boolean presorder, Alignment seqset)
397 this(server2,wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
398 OutputHeader = wsInfo.getProgressText();
402 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
405 int njobs = conmsa.length;
406 jobs = new MsaWSJob[njobs];
407 for (int j = 0; j < njobs; j++)
411 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
415 jobs[j] = new MsaWSJob(0, conmsa[j]);
420 .setProgressName("region " + jobs[j].getJobnum(),
421 jobs[j].getJobnum());
423 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
428 public boolean isCancellable()
433 public void cancelJob()
435 if (!jobComplete && jobs != null)
437 boolean cancelled = true;
438 for (int job = 0; job < jobs.length; job++)
440 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
442 String cancelledMessage = "";
445 boolean cancelledJob = server
446 .cancelJob(jobs[job].getJobId());
450 cancelledMessage = "Job cancelled.";
451 ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this ugliness -
452 wsInfo.setStatus(jobs[job].getJobnum(),
453 WebserviceInfo.STATE_CANCELLED_OK);
457 // VALID UNSTOPPABLE JOB
458 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
460 // wsInfo.setStatus(jobs[job].jobnum,
461 // WebserviceInfo.STATE_RUNNING);
463 } catch (Exception exc)
465 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
468 "Exception whilst cancelling " + jobs[job].getJobId(), exc);
470 wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
471 + cancelledMessage + "\n");
476 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
479 this.interrupt(); // kick thread to update job states.
486 .setProgressText(OutputHeader
487 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
492 public void pollJob(AWsJob job) throws Exception
494 // TODO: investigate if we still need to cast here in J1.6
495 MsaWSJob j=((MsaWSJob) job);
496 j.setjobStatus(server.getJobStatus(job.getJobId()));
497 StringBuffer response = j.jobProgress;
498 ChunkHolder chunk = server.pullExecStatistics(job.getJobId(), j.getLastChunk());
499 response.append(chunk.getChunk());
500 j.setLastChunk(chunk.getNextPosition());
504 public void StartJob(AWsJob job)
506 // boiler plate template
507 if (!(job instanceof MsaWSJob))
509 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
512 MsaWSJob j = (MsaWSJob) job;
515 if (Cache.log.isDebugEnabled())
517 Cache.log.debug("Tried to submit an already submitted job "
524 if (j.seqs == null || j.seqs.size()==0)
526 // special case - selection consisted entirely of empty sequences...
527 j.setjobStatus(JobStatus.FINISHED);
528 j.setStatus("Empty Alignment Job");
532 // TODO: get the parameters (if any) for this job and submit the job
533 j.setJobId(server.align(j.seqs));
535 if (j.getJobId()!= null)
537 j.setSubmitted(true);
538 j.setSubjobComplete(false);
539 // System.out.println(WsURL + " Job Id '" + jobId + "'");
546 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
548 } catch (Exception e)
550 // Boilerplate code here
551 // TODO: JBPNote catch timeout or other fault types explicitly
552 // For unexpected errors
554 .println(WebServiceName
555 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
556 + "When contacting Server:" + WsUrl + "\n"
557 + e.toString() + "\n");
558 j.setAllowedServerExceptions(0);
559 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
560 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
564 "Failed to submit sequences for alignment.\n"
565 + "It is most likely that there is a problem with the server.\n"
566 + "Just close the window\n");
568 // e.printStackTrace(); // TODO: JBPNote DEBUG
573 public void parseResult()
575 int results = 0; // number of result sets received
576 JobStateSummary finalState = new JobStateSummary();
579 for (int j = 0; j < jobs.length; j++)
581 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
582 if (jobs[j].isFinished() && msjob.alignment==null)
585 msjob.alignment = server.getResult(msjob.getJobId());
589 Cache.log.error("Couldn't get Alignment for job.",e);
592 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
593 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
594 && jobs[j].hasResults())
597 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
598 if (alignment != null)
600 wsInfo.appendProgressText(jobs[j].getJobnum(),
601 "\nAlignment Object Method Notes\n");
602 wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
603 // JBPNote The returned files from a webservice could be
604 // hidden behind icons in the monitor window that,
605 // when clicked, pop up their corresponding data
609 } catch (Exception ex)
612 Cache.log.error("Unexpected exception when processing results for "
614 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
618 wsInfo.showResultsNewFrame
619 .addActionListener(new java.awt.event.ActionListener()
621 public void actionPerformed(java.awt.event.ActionEvent evt)
623 displayResults(true);
627 .addActionListener(new java.awt.event.ActionListener()
629 public void actionPerformed(java.awt.event.ActionEvent evt)
631 displayResults(false);
634 wsInfo.setResultsReady();
638 wsInfo.setFinishedNoResults();
642 void displayResults(boolean newFrame)
644 // view input or result data for each block
645 Vector alorders = new Vector();
646 SequenceI[][] results = new SequenceI[jobs.length][];
647 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
648 String lastProgram = null;
650 for (int j = 0; j < jobs.length; j++)
652 if (jobs[j].hasResults())
654 msjob = (MsaWSJob)jobs[j];
655 Object[] res = msjob.getAlignment();
656 lastProgram = msjob.alignment.getMetadata().getProgram().name();
657 alorders.add(res[1]);
658 results[j] = (SequenceI[]) res[0];
659 orders[j] = (AlignmentOrder) res[1];
661 // SequenceI[] alignment = input.getUpdated
668 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
669 // trash references to original result data
670 for (int j = 0; j < jobs.length; j++)
675 SequenceI[] alignment = (SequenceI[]) newview[0];
676 ColumnSelection columnselection = (ColumnSelection) newview[1];
677 Alignment al = new Alignment(alignment);
678 // TODO: add 'provenance' property to alignment from the method notes
679 if (lastProgram!=null) {
680 al.setProperty("Alignment Program", lastProgram);
682 // accompanying each subjob
685 al.setDataset(dataset);
688 propagateDatasetMappings(al);
689 // JBNote- TODO: warn user if a block is input rather than aligned data ?
693 AlignFrame af = new AlignFrame(al, columnselection,
694 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
696 // initialise with same renderer settings as in parent alignframe.
697 af.getFeatureRenderer().transferSettings(this.featureSettings);
699 if (alorders.size() > 0)
701 if (alorders.size() == 1)
703 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
704 (AlignmentOrder) alorders.get(0));
708 // construct a non-redundant ordering set
709 Vector names = new Vector();
710 for (int i = 0, l = alorders.size(); i < l; i++)
712 String orderName = new String(" Region " + i);
717 if (((AlignmentOrder) alorders.get(i))
718 .equals(((AlignmentOrder) alorders.get(j))))
722 orderName += "," + j;
730 if (i == 0 && j == 1)
732 names.add(new String(""));
736 names.add(orderName);
739 for (int i = 0, l = alorders.size(); i < l; i++)
741 af.addSortByOrderMenuItem(WebServiceName
742 + ((String) names.get(i)) + " Ordering",
743 (AlignmentOrder) alorders.get(i));
748 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
749 AlignFrame.DEFAULT_HEIGHT);
754 System.out.println("MERGE WITH OLD FRAME");
755 // TODO: modify alignment in original frame, replacing old for new
756 // alignment using the commands.EditCommand model to ensure the update can
761 public boolean canMergeResults()