Jalview 2.6 source licence
[jalview.git] / src / jalview / ws / jws2 / MsaWSThread.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
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.
10  * 
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.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ws.jws2;
19
20 import java.util.*;
21
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;
29
30 import jalview.analysis.*;
31 import jalview.bin.*;
32 import jalview.datamodel.*;
33 import jalview.gui.*;
34 import jalview.ws.AWsJob;
35 import jalview.ws.WSClientI;
36 import jalview.ws.JobStateSummary;
37
38 /**
39  * <p>
40  * Title:
41  * </p>
42  * 
43  * <p>
44  * Description:
45  * </p>
46  * 
47  * <p>
48  * Copyright: Copyright (c) 2004
49  * </p>
50  * 
51  * <p>
52  * Company: Dundee University
53  * </p>
54  * 
55  * @author not attributable
56  * @version 1.0
57  */
58 class MsaWSThread extends AWS2Thread implements WSClientI
59 {
60   boolean submitGaps = false; // pass sequences including gaps to alignment
61
62   // service
63
64   boolean preserveOrder = true; // and always store and recover sequence
65
66   // order
67
68   class MsaWSJob extends JWs2Job
69   {
70     long lastChunk = 0;
71
72     Preset preset = null;
73
74     List<Argument> arguments = null;
75
76     /**
77      * input
78      */
79     ArrayList<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
80
81     /**
82      * output
83      */
84     compbio.data.sequence.Alignment alignment;
85
86     // set if the job didn't get run - then the input is simply returned to the
87     // user
88     private boolean returnInput = false;
89
90     /**
91      * MsaWSJob
92      * 
93      * @param jobNum
94      *          int
95      * @param jobId
96      *          String
97      */
98     public MsaWSJob(int jobNum, SequenceI[] inSeqs)
99     {
100       this.jobnum = jobNum;
101       if (!prepareInput(inSeqs, 2))
102       {
103         submitted = true;
104         subjobComplete = true;
105         returnInput = true;
106       }
107
108     }
109
110     Hashtable<String, Map> SeqNames = new Hashtable();
111
112     Vector<String[]> emptySeqs = new Vector();
113
114     /**
115      * prepare input sequences for MsaWS service
116      * 
117      * @param seqs
118      *          jalview sequences to be prepared
119      * @param minlen
120      *          minimum number of residues required for this MsaWS service
121      * @return true if seqs contains sequences to be submitted to service.
122      */
123     // TODO: return compbio.seqs list or nothing to indicate validity.
124     private boolean prepareInput(SequenceI[] seqs, int minlen)
125     {
126       int nseqs = 0;
127       if (minlen < 0)
128       {
129         throw new Error(
130                 "Implementation error: minlen must be zero or more.");
131       }
132       for (int i = 0; i < seqs.length; i++)
133       {
134         if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
135         {
136           nseqs++;
137         }
138       }
139       boolean valid = nseqs > 1; // need at least two seqs
140       compbio.data.sequence.FastaSequence seq;
141       for (int i = 0, n = 0; i < seqs.length; i++)
142       {
143
144         String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
145         // for
146         // any
147         // subjob
148         SeqNames.put(newname,
149                 jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
150         if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
151         {
152           // make new input sequence with or without gaps
153           seq = new compbio.data.sequence.FastaSequence(newname,
154                   (submitGaps) ? seqs[i].getSequenceAsString()
155                           : AlignSeq.extractGaps(
156                                   jalview.util.Comparison.GapChars,
157                                   seqs[i].getSequenceAsString()));
158           this.seqs.add(seq);
159         }
160         else
161         {
162           String empty = null;
163           if (seqs[i].getEnd() >= seqs[i].getStart())
164           {
165             empty = (submitGaps) ? seqs[i].getSequenceAsString() : AlignSeq
166                     .extractGaps(jalview.util.Comparison.GapChars,
167                             seqs[i].getSequenceAsString());
168           }
169           emptySeqs.add(new String[]
170           { newname, empty });
171         }
172       }
173       return valid;
174     }
175
176     /**
177      * 
178      * @return true if getAlignment will return a valid alignment result.
179      */
180     public boolean hasResults()
181     {
182       if (subjobComplete
183               && isFinished()
184               && (alignment != null || (emptySeqs != null && emptySeqs
185                       .size() > 0)))
186       {
187         return true;
188       }
189       return false;
190     }
191
192     /**
193      * 
194      * get the alignment including any empty sequences in the original order
195      * with original ids. Caller must access the alignment.getMetadata() object
196      * to annotate the final result passsed to the user.
197      * 
198      * @return { SequenceI[], AlignmentOrder }
199      */
200     public Object[] getAlignment()
201     {
202       // is this a generic subjob or a Jws2 specific Object[] return signature
203       if (hasResults())
204       {
205         SequenceI[] alseqs = null;
206         char alseq_gapchar = '-';
207         int alseq_l = 0;
208         if (alignment.getSequences().size() > 0)
209         {
210           alseqs = new SequenceI[alignment.getSequences().size()];
211           for (compbio.data.sequence.FastaSequence seq : alignment
212                   .getSequences())
213           {
214             alseqs[alseq_l++] = new Sequence(seq.getId(), seq.getSequence());
215           }
216           alseq_gapchar = alignment.getMetadata().getGapchar();
217
218         }
219         // add in the empty seqs.
220         if (emptySeqs.size() > 0)
221         {
222           SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
223           // get width
224           int i, w = 0;
225           if (alseq_l > 0)
226           {
227             for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
228             {
229               if (w < alseqs[i].getLength())
230               {
231                 w = alseqs[i].getLength();
232               }
233               t_alseqs[i] = alseqs[i];
234               alseqs[i] = null;
235             }
236           }
237           // check that aligned width is at least as wide as emptySeqs width.
238           int ow = w, nw = w;
239           for (i = 0, w = emptySeqs.size(); i < w; i++)
240           {
241             String[] es = (String[]) emptySeqs.get(i);
242             if (es != null && es[1] != null)
243             {
244               int sw = es[1].length();
245               if (nw < sw)
246               {
247                 nw = sw;
248               }
249             }
250           }
251           // make a gapped string.
252           StringBuffer insbuff = new StringBuffer(w);
253           for (i = 0; i < nw; i++)
254           {
255             insbuff.append(alseq_gapchar);
256           }
257           if (ow < nw)
258           {
259             for (i = 0; i < alseq_l; i++)
260             {
261               int sw = t_alseqs[i].getLength();
262               if (nw > sw)
263               {
264                 // pad at end
265                 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
266                         + insbuff.substring(0, sw - nw));
267               }
268             }
269           }
270           for (i = 0, w = emptySeqs.size(); i < w; i++)
271           {
272             String[] es = (String[]) emptySeqs.get(i);
273             if (es[1] == null)
274             {
275               t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
276                       insbuff.toString(), 1, 0);
277             }
278             else
279             {
280               if (es[1].length() < nw)
281               {
282                 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
283                         es[0],
284                         es[1] + insbuff.substring(0, nw - es[1].length()),
285                         1, 1 + es[1].length());
286               }
287               else
288               {
289                 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
290                         es[0], es[1]);
291               }
292             }
293           }
294           alseqs = t_alseqs;
295         }
296         AlignmentOrder msaorder = new AlignmentOrder(alseqs);
297         // always recover the order - makes parseResult()'s life easier.
298         jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
299         // account for any missing sequences
300         jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
301         return new Object[]
302         { alseqs, msaorder };
303       }
304       return null;
305     }
306
307     /**
308      * mark subjob as cancelled and set result object appropriatly
309      */
310     void cancel()
311     {
312       cancelled = true;
313       subjobComplete = true;
314       alignment = null;
315     }
316
317     /**
318      * 
319      * @return boolean true if job can be submitted.
320      */
321     public boolean hasValidInput()
322     {
323       // TODO: get attributes for this MsaWS instance to check if it can do two
324       // sequence alignment.
325       if (seqs != null && seqs.size() >= 2) // two or more sequences is valid ?
326       {
327         return true;
328       }
329       return false;
330     }
331
332     StringBuffer jobProgress = new StringBuffer();
333
334     public void setStatus(String string)
335     {
336       jobProgress.setLength(0);
337       jobProgress.append(string);
338     }
339
340     @Override
341     public String getStatus()
342     {
343       return jobProgress.toString();
344     }
345
346     @Override
347     public boolean hasStatus()
348     {
349       return jobProgress != null;
350     }
351
352     /**
353      * @return the lastChunk
354      */
355     public long getLastChunk()
356     {
357       return lastChunk;
358     }
359
360     /**
361      * @param lastChunk
362      *          the lastChunk to set
363      */
364     public void setLastChunk(long lastChunk)
365     {
366       this.lastChunk = lastChunk;
367     }
368
369     String alignmentProgram = null;
370
371     public String getAlignmentProgram()
372     {
373       return alignmentProgram;
374     }
375
376   }
377
378   String alTitle; // name which will be used to form new alignment window.
379
380   Alignment dataset; // dataset to which the new alignment will be
381
382   // associated.
383
384   @SuppressWarnings("unchecked")
385   MsaWS server = null;
386
387   /**
388    * set basic options for this (group) of Msa jobs
389    * 
390    * @param subgaps
391    *          boolean
392    * @param presorder
393    *          boolean
394    */
395   MsaWSThread(MsaWS server, String wsUrl, WebserviceInfo wsinfo,
396           jalview.gui.AlignFrame alFrame, AlignmentView alview,
397           String wsname, boolean subgaps, boolean presorder)
398   {
399     super(alFrame, wsinfo, alview, wsname, wsUrl);
400     this.server = server;
401     this.submitGaps = subgaps;
402     this.preserveOrder = presorder;
403   }
404
405   /**
406    * create one or more Msa jobs to align visible seuqences in _msa
407    * 
408    * @param title
409    *          String
410    * @param _msa
411    *          AlignmentView
412    * @param subgaps
413    *          boolean
414    * @param presorder
415    *          boolean
416    * @param seqset
417    *          Alignment
418    */
419   MsaWSThread(MsaWS server2, Preset preset, List<Argument> paramset,
420           String wsUrl, WebserviceInfo wsinfo,
421           jalview.gui.AlignFrame alFrame, String wsname, String title,
422           AlignmentView _msa, boolean subgaps, boolean presorder,
423           Alignment seqset)
424   {
425     this(server2, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
426     OutputHeader = wsInfo.getProgressText();
427     alTitle = title;
428     dataset = seqset;
429
430     SequenceI[][] conmsa = _msa.getVisibleContigs('-');
431     if (conmsa != null)
432     {
433       int njobs = conmsa.length;
434       jobs = new MsaWSJob[njobs];
435       for (int j = 0; j < njobs; j++)
436       {
437         if (j != 0)
438         {
439           jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
440         }
441         else
442         {
443           jobs[j] = new MsaWSJob(0, conmsa[j]);
444         }
445         ((MsaWSJob) jobs[j]).preset = preset;
446         ((MsaWSJob) jobs[j]).arguments = paramset;
447         ((MsaWSJob) jobs[j]).alignmentProgram = wsname;
448         if (njobs > 0)
449         {
450           wsinfo.setProgressName("region " + jobs[j].getJobnum(),
451                   jobs[j].getJobnum());
452         }
453         wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
454       }
455     }
456   }
457
458   public boolean isCancellable()
459   {
460     return true;
461   }
462
463   public void cancelJob()
464   {
465     if (!jobComplete && jobs != null)
466     {
467       boolean cancelled = true;
468       for (int job = 0; job < jobs.length; job++)
469       {
470         if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
471         {
472           String cancelledMessage = "";
473           try
474           {
475             boolean cancelledJob = server.cancelJob(jobs[job].getJobId());
476             if (cancelledJob || true)
477             {
478               // CANCELLED_JOB
479               // if the Jaba server indicates the job can't be cancelled, its
480               // because its running on the server's local execution engine
481               // so we just close the window anyway.
482               cancelledMessage = "Job cancelled.";
483               ((MsaWSJob) jobs[job]).cancel(); // TODO: refactor to avoid this
484                                                // ugliness -
485               wsInfo.setStatus(jobs[job].getJobnum(),
486                       WebserviceInfo.STATE_CANCELLED_OK);
487             }
488             else
489             {
490               // VALID UNSTOPPABLE JOB
491               cancelledMessage += "Server cannot cancel this job. just close the window.\n";
492               cancelled = false;
493               // wsInfo.setStatus(jobs[job].jobnum,
494               // WebserviceInfo.STATE_RUNNING);
495             }
496           } catch (Exception exc)
497           {
498             cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
499                     + exc + "\n");
500             Cache.log.warn(
501                     "Exception whilst cancelling " + jobs[job].getJobId(),
502                     exc);
503           }
504           wsInfo.setProgressText(jobs[job].getJobnum(), OutputHeader
505                   + cancelledMessage + "\n");
506         }
507       }
508       if (cancelled)
509       {
510         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
511         jobComplete = true;
512       }
513       this.interrupt(); // kick thread to update job states.
514     }
515     else
516     {
517       if (!jobComplete)
518       {
519         wsInfo.setProgressText(OutputHeader
520                 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
521       }
522     }
523   }
524
525   public void pollJob(AWsJob job) throws Exception
526   {
527     // TODO: investigate if we still need to cast here in J1.6
528     MsaWSJob j = ((MsaWSJob) job);
529     // this is standard code, but since the interface doesn't comprise of a
530     // basic one that implements (getJobStatus, pullExecStatistics) we have to
531     // repeat the code for all jw2s services.
532     j.setjobStatus(server.getJobStatus(job.getJobId()));
533     updateJobProgress(j);
534   }
535
536   protected void updateJobProgress(MsaWSJob j) throws Exception
537   {
538     StringBuffer response = j.jobProgress;
539     long lastchunk = j.getLastChunk();
540     do
541     {
542       j.setLastChunk(lastchunk);
543       ChunkHolder chunk = server
544               .pullExecStatistics(j.getJobId(), lastchunk);
545       if (chunk != null)
546       {
547         response.append(chunk.getChunk());
548         lastchunk = chunk.getNextPosition();
549       }
550       ;
551     } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
552   }
553
554   public void StartJob(AWsJob job)
555   {
556     Exception lex = null;
557     // boiler plate template
558     if (!(job instanceof MsaWSJob))
559     {
560       throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
561               + job.getClass());
562     }
563     MsaWSJob j = (MsaWSJob) job;
564     if (j.isSubmitted())
565     {
566       if (Cache.log.isDebugEnabled())
567       {
568         Cache.log.debug("Tried to submit an already submitted job "
569                 + j.getJobId());
570       }
571       return;
572     }
573     // end boilerplate
574
575     if (j.seqs == null || j.seqs.size() == 0)
576     {
577       // special case - selection consisted entirely of empty sequences...
578       j.setjobStatus(JobStatus.FINISHED);
579       j.setStatus("Empty Alignment Job");
580     }
581     try
582     {
583       // TODO: get the parameters (if any) for this job and submit the job
584       if (j.arguments != null && j.arguments.size() > 0)
585       {
586         StringBuffer pset = new StringBuffer();
587         for (Argument arg : j.arguments)
588         {
589           pset.append(arg.getName() + " " + arg.getDefaultValue() + "\n");
590         }
591         j.setStatus("Custom Parameters:\n" + pset.toString()
592                 + "\nJob Output:\n");
593         j.setJobId(server.customAlign(j.seqs, j.arguments));
594       }
595       else if (j.preset != null)
596       {
597         j.setJobId(server.presetAlign(j.seqs, j.preset));
598       }
599       else
600       {
601         j.setJobId(server.align(j.seqs));
602       }
603
604       if (j.getJobId() != null)
605       {
606         j.setSubmitted(true);
607         j.setSubjobComplete(false);
608         // System.out.println(WsURL + " Job Id '" + jobId + "'");
609         return;
610       }
611       else
612       {
613         throw new Exception(
614                 "Server at "
615                         + WsUrl
616                         + " returned null string for job id, it probably cannot be contacted. Try again later ?");
617       }
618     } catch (compbio.metadata.UnsupportedRuntimeException _lex)
619     {
620       lex = _lex;
621       wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
622               + _lex.getMessage());
623       wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
624       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
625       wsInfo.setStatus(j.getJobnum(),
626               WebserviceInfo.STATE_STOPPED_SERVERERROR);
627     } catch (compbio.metadata.LimitExceededException _lex)
628     {
629       lex = _lex;
630       wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
631               + _lex.getMessage());
632       wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
633       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
634       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
635     } catch (compbio.metadata.WrongParameterException _lex)
636     {
637       lex = _lex;
638       wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
639       wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
640               + _lex.getMessage()
641               + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
642       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
643       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
644     } catch (Error e)
645     {
646       // For unexpected errors
647       System.err
648               .println(WebServiceName
649                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
650                       + "When contacting Server:" + WsUrl + "\n");
651       e.printStackTrace(System.err);
652       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
653       wsInfo.setStatus(j.getJobnum(),
654               WebserviceInfo.STATE_STOPPED_SERVERERROR);
655     } catch (Exception e)
656     {
657       // For unexpected errors
658       System.err
659               .println(WebServiceName
660                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
661                       + "When contacting Server:" + WsUrl + "\n");
662       e.printStackTrace(System.err);
663       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
664       wsInfo.setStatus(j.getJobnum(),
665               WebserviceInfo.STATE_STOPPED_SERVERERROR);
666     } finally
667     {
668       if (!j.isSubmitted())
669       {
670         // Boilerplate code here
671         // TODO: JBPNote catch timeout or other fault types explicitly
672
673         j.setAllowedServerExceptions(0);
674         wsInfo.appendProgressText(j.getJobnum(),
675                 "Failed to submit sequences for alignment.\n"
676                         + "Just close the window\n");
677       }
678     }
679   }
680
681   public void parseResult()
682   {
683     int results = 0; // number of result sets received
684     JobStateSummary finalState = new JobStateSummary();
685     try
686     {
687       for (int j = 0; j < jobs.length; j++)
688       {
689         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
690         if (jobs[j].isFinished() && msjob.alignment == null)
691         {
692           try
693           {
694             updateJobProgress(msjob);
695           } catch (Exception e)
696           {
697             Cache.log
698                     .warn("Exception when retrieving remaining Job progress data for job "
699                             + msjob.getJobId() + " on server " + WsUrl);
700             e.printStackTrace();
701           }
702           if (Cache.log.isDebugEnabled())
703           {
704             System.out.println("Job Execution file for job: "
705                     + msjob.getJobId() + " on server " + WsUrl);
706             System.out.println(msjob.getStatus());
707             System.out.println("*** End of status");
708
709           }
710           try
711           {
712             msjob.alignment = server.getResult(msjob.getJobId());
713           } catch (compbio.metadata.ResultNotAvailableException e)
714           {
715             // job has failed for some reason - probably due to invalid
716             // parameters
717             Cache.log
718                     .debug("Results not available for finished job - marking as broken job.",
719                             e);
720             msjob.setjobStatus(JobStatus.FAILED);
721           } catch (Exception e)
722           {
723             Cache.log.error("Couldn't get Alignment for job.", e);
724             // TODO: Increment count and retry ?
725             msjob.setjobStatus(JobStatus.UNDEFINED);
726           }
727         }
728         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
729         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
730                 && jobs[j].hasResults())
731         {
732           results++;
733           compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
734           if (alignment != null)
735           {
736             // server.close(jobs[j].getJobnum());
737             // wsInfo.appendProgressText(jobs[j].getJobnum(),
738             // "\nAlignment Object Method Notes\n");
739             // wsInfo.appendProgressText(jobs[j].getJobnum(),
740             // "Calculated with "+alignment.getMetadata().getProgram().toString());
741             // JBPNote The returned files from a webservice could be
742             // hidden behind icons in the monitor window that,
743             // when clicked, pop up their corresponding data
744           }
745         }
746       }
747     } catch (Exception ex)
748     {
749
750       Cache.log.error("Unexpected exception when processing results for "
751               + alTitle, ex);
752       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
753     }
754     if (results > 0)
755     {
756       wsInfo.showResultsNewFrame
757               .addActionListener(new java.awt.event.ActionListener()
758               {
759                 public void actionPerformed(java.awt.event.ActionEvent evt)
760                 {
761                   displayResults(true);
762                 }
763               });
764       wsInfo.mergeResults
765               .addActionListener(new java.awt.event.ActionListener()
766               {
767                 public void actionPerformed(java.awt.event.ActionEvent evt)
768                 {
769                   displayResults(false);
770                 }
771               });
772       wsInfo.setResultsReady();
773     }
774     else
775     {
776       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
777       wsInfo.setFinishedNoResults();
778     }
779   }
780
781   void displayResults(boolean newFrame)
782   {
783     // view input or result data for each block
784     Vector alorders = new Vector();
785     SequenceI[][] results = new SequenceI[jobs.length][];
786     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
787     String lastProgram = null;
788     MsaWSJob msjob;
789     for (int j = 0; j < jobs.length; j++)
790     {
791       if (jobs[j].hasResults())
792       {
793         msjob = (MsaWSJob) jobs[j];
794         Object[] res = msjob.getAlignment();
795         lastProgram = msjob.getAlignmentProgram();
796         alorders.add(res[1]);
797         results[j] = (SequenceI[]) res[0];
798         orders[j] = (AlignmentOrder) res[1];
799
800         // SequenceI[] alignment = input.getUpdated
801       }
802       else
803       {
804         results[j] = null;
805       }
806     }
807     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
808     // trash references to original result data
809     for (int j = 0; j < jobs.length; j++)
810     {
811       results[j] = null;
812       orders[j] = null;
813     }
814     SequenceI[] alignment = (SequenceI[]) newview[0];
815     ColumnSelection columnselection = (ColumnSelection) newview[1];
816     Alignment al = new Alignment(alignment);
817     // TODO: add 'provenance' property to alignment from the method notes
818     if (lastProgram != null)
819     {
820       al.setProperty("Alignment Program", lastProgram);
821     }
822     // accompanying each subjob
823     if (dataset != null)
824     {
825       al.setDataset(dataset);
826     }
827
828     propagateDatasetMappings(al);
829     // JBNote- TODO: warn user if a block is input rather than aligned data ?
830
831     if (newFrame)
832     {
833       AlignFrame af = new AlignFrame(al, columnselection,
834               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
835
836       // initialise with same renderer settings as in parent alignframe.
837       af.getFeatureRenderer().transferSettings(this.featureSettings);
838       // update orders
839       if (alorders.size() > 0)
840       {
841         if (alorders.size() == 1)
842         {
843           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
844                   (AlignmentOrder) alorders.get(0));
845         }
846         else
847         {
848           // construct a non-redundant ordering set
849           Vector names = new Vector();
850           for (int i = 0, l = alorders.size(); i < l; i++)
851           {
852             String orderName = new String(" Region " + i);
853             int j = i + 1;
854
855             while (j < l)
856             {
857               if (((AlignmentOrder) alorders.get(i))
858                       .equals(((AlignmentOrder) alorders.get(j))))
859               {
860                 alorders.remove(j);
861                 l--;
862                 orderName += "," + j;
863               }
864               else
865               {
866                 j++;
867               }
868             }
869
870             if (i == 0 && j == 1)
871             {
872               names.add(new String(""));
873             }
874             else
875             {
876               names.add(orderName);
877             }
878           }
879           for (int i = 0, l = alorders.size(); i < l; i++)
880           {
881             af.addSortByOrderMenuItem(
882                     WebServiceName + ((String) names.get(i)) + " Ordering",
883                     (AlignmentOrder) alorders.get(i));
884           }
885         }
886       }
887
888       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
889               AlignFrame.DEFAULT_HEIGHT);
890
891     }
892     else
893     {
894       System.out.println("MERGE WITH OLD FRAME");
895       // TODO: modify alignment in original frame, replacing old for new
896       // alignment using the commands.EditCommand model to ensure the update can
897       // be undone
898     }
899   }
900
901   public boolean canMergeResults()
902   {
903     return false;
904   }
905 }