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