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