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 // 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.
497 j.setjobStatus(server.getJobStatus(job.getJobId()));
498 updateJobProgress(j);
500 protected void updateJobProgress(MsaWSJob j) throws Exception {
501 StringBuffer response = j.jobProgress;
502 long lastchunk = j.getLastChunk();
504 j.setLastChunk(lastchunk);
505 ChunkHolder chunk = server.pullExecStatistics(j.getJobId(), lastchunk);
507 response.append(chunk.getChunk());
508 lastchunk = chunk.getNextPosition();
510 } while (lastchunk>=0 && j.getLastChunk()!=lastchunk);
513 public void StartJob(AWsJob job)
515 // boiler plate template
516 if (!(job instanceof MsaWSJob))
518 throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
521 MsaWSJob j = (MsaWSJob) job;
524 if (Cache.log.isDebugEnabled())
526 Cache.log.debug("Tried to submit an already submitted job "
533 if (j.seqs == null || j.seqs.size()==0)
535 // special case - selection consisted entirely of empty sequences...
536 j.setjobStatus(JobStatus.FINISHED);
537 j.setStatus("Empty Alignment Job");
541 // TODO: get the parameters (if any) for this job and submit the job
542 j.setJobId(server.align(j.seqs));
544 if (j.getJobId()!= null)
546 j.setSubmitted(true);
547 j.setSubjobComplete(false);
548 // System.out.println(WsURL + " Job Id '" + jobId + "'");
555 + " returned null string for job id, it probably cannot be contacted. Try again later ?");
557 } catch (Exception e)
559 // Boilerplate code here
560 // TODO: JBPNote catch timeout or other fault types explicitly
561 // For unexpected errors
563 .println(WebServiceName
564 + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
565 + "When contacting Server:" + WsUrl + "\n"
566 + e.toString() + "\n");
567 j.setAllowedServerExceptions(0);
568 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
569 wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_SERVERERROR);
573 "Failed to submit sequences for alignment.\n"
574 + "It is most likely that there is a problem with the server.\n"
575 + "Just close the window\n");
577 // e.printStackTrace(); // TODO: JBPNote DEBUG
582 public void parseResult()
584 int results = 0; // number of result sets received
585 JobStateSummary finalState = new JobStateSummary();
588 for (int j = 0; j < jobs.length; j++)
590 MsaWSJob msjob = ((MsaWSJob) jobs[j]);
591 if (jobs[j].isFinished() && msjob.alignment==null)
594 updateJobProgress(msjob);
595 } catch (Exception e)
597 Cache.log.warn("Exception when retrieving remaining Job progress data for job "+msjob.getJobId()+" on server "+WsUrl);
600 if (Cache.log.isDebugEnabled())
602 System.out.println("Job Execution file for job: "+msjob.getJobId()+" on server "+WsUrl);
603 System.out.println(msjob.getStatus());
604 System.out.println("*** End of status");
608 msjob.alignment = server.getResult(msjob.getJobId());
612 Cache.log.error("Couldn't get Alignment for job.",e);
615 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
616 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
617 && jobs[j].hasResults())
620 compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
621 if (alignment != null)
623 wsInfo.appendProgressText(jobs[j].getJobnum(),
624 "\nAlignment Object Method Notes\n");
625 wsInfo.appendProgressText(jobs[j].getJobnum(), "Calculated with "+alignment.getMetadata().getProgram().toString());
626 // JBPNote The returned files from a webservice could be
627 // hidden behind icons in the monitor window that,
628 // when clicked, pop up their corresponding data
632 } catch (Exception ex)
635 Cache.log.error("Unexpected exception when processing results for "
637 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
641 wsInfo.showResultsNewFrame
642 .addActionListener(new java.awt.event.ActionListener()
644 public void actionPerformed(java.awt.event.ActionEvent evt)
646 displayResults(true);
650 .addActionListener(new java.awt.event.ActionListener()
652 public void actionPerformed(java.awt.event.ActionEvent evt)
654 displayResults(false);
657 wsInfo.setResultsReady();
661 wsInfo.setFinishedNoResults();
665 void displayResults(boolean newFrame)
667 // view input or result data for each block
668 Vector alorders = new Vector();
669 SequenceI[][] results = new SequenceI[jobs.length][];
670 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
671 String lastProgram = null;
673 for (int j = 0; j < jobs.length; j++)
675 if (jobs[j].hasResults())
677 msjob = (MsaWSJob)jobs[j];
678 Object[] res = msjob.getAlignment();
679 lastProgram = msjob.alignment.getMetadata().getProgram().name();
680 alorders.add(res[1]);
681 results[j] = (SequenceI[]) res[0];
682 orders[j] = (AlignmentOrder) res[1];
684 // SequenceI[] alignment = input.getUpdated
691 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
692 // trash references to original result data
693 for (int j = 0; j < jobs.length; j++)
698 SequenceI[] alignment = (SequenceI[]) newview[0];
699 ColumnSelection columnselection = (ColumnSelection) newview[1];
700 Alignment al = new Alignment(alignment);
701 // TODO: add 'provenance' property to alignment from the method notes
702 if (lastProgram!=null) {
703 al.setProperty("Alignment Program", lastProgram);
705 // accompanying each subjob
708 al.setDataset(dataset);
711 propagateDatasetMappings(al);
712 // JBNote- TODO: warn user if a block is input rather than aligned data ?
716 AlignFrame af = new AlignFrame(al, columnselection,
717 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
719 // initialise with same renderer settings as in parent alignframe.
720 af.getFeatureRenderer().transferSettings(this.featureSettings);
722 if (alorders.size() > 0)
724 if (alorders.size() == 1)
726 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
727 (AlignmentOrder) alorders.get(0));
731 // construct a non-redundant ordering set
732 Vector names = new Vector();
733 for (int i = 0, l = alorders.size(); i < l; i++)
735 String orderName = new String(" Region " + i);
740 if (((AlignmentOrder) alorders.get(i))
741 .equals(((AlignmentOrder) alorders.get(j))))
745 orderName += "," + j;
753 if (i == 0 && j == 1)
755 names.add(new String(""));
759 names.add(orderName);
762 for (int i = 0, l = alorders.size(); i < l; i++)
764 af.addSortByOrderMenuItem(WebServiceName
765 + ((String) names.get(i)) + " Ordering",
766 (AlignmentOrder) alorders.get(i));
771 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
772 AlignFrame.DEFAULT_HEIGHT);
777 System.out.println("MERGE WITH OLD FRAME");
778 // TODO: modify alignment in original frame, replacing old for new
779 // alignment using the commands.EditCommand model to ensure the update can
784 public boolean canMergeResults()