JAL-1432 updated copyright notices
[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         }
549       }
550       if (cancelled)
551       {
552         wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
553         jobComplete = true;
554       }
555       this.interrupt(); // kick thread to update job states.
556     }
557     else
558     {
559       if (!jobComplete)
560       {
561         wsInfo.setProgressText(OutputHeader
562                 + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
563       }
564     }
565   }
566
567   public void pollJob(AWsJob job) throws Exception
568   {
569     // TODO: investigate if we still need to cast here in J1.6
570     MsaWSJob j = ((MsaWSJob) job);
571     // this is standard code, but since the interface doesn't comprise of a
572     // basic one that implements (getJobStatus, pullExecStatistics) we have to
573     // repeat the code for all jw2s services.
574     j.setjobStatus(server.getJobStatus(job.getJobId()));
575     updateJobProgress(j);
576   }
577
578   /**
579    * 
580    * @param j
581    * @return true if more job progress data was available
582    * @throws Exception
583    */
584   protected boolean updateJobProgress(MsaWSJob j) throws Exception
585   {
586     StringBuffer response = j.jobProgress;
587     long lastchunk = j.getLastChunk();
588     boolean changed = false;
589     do
590     {
591       j.setLastChunk(lastchunk);
592       ChunkHolder chunk = server
593               .pullExecStatistics(j.getJobId(), lastchunk);
594       if (chunk != null)
595       {
596         changed |= chunk.getChunk().length() > 0;
597         response.append(chunk.getChunk());
598         lastchunk = chunk.getNextPosition();
599         try
600         {
601           Thread.sleep(50);
602         } catch (InterruptedException x)
603         {
604         }
605         ;
606       }
607       ;
608     } while (lastchunk >= 0 && j.getLastChunk() != lastchunk);
609     return changed;
610   }
611
612   public void StartJob(AWsJob job)
613   {
614     Exception lex = null;
615     // boiler plate template
616     if (!(job instanceof MsaWSJob))
617     {
618       throw new Error("StartJob(MsaWSJob) called on a WSJobInstance "
619               + job.getClass());
620     }
621     MsaWSJob j = (MsaWSJob) job;
622     if (j.isSubmitted())
623     {
624       if (Cache.log.isDebugEnabled())
625       {
626         Cache.log.debug("Tried to submit an already submitted job "
627                 + j.getJobId());
628       }
629       return;
630     }
631     // end boilerplate
632
633     if (j.seqs == null || j.seqs.size() == 0)
634     {
635       // special case - selection consisted entirely of empty sequences...
636       j.setjobStatus(JobStatus.FINISHED);
637       j.setStatus("Empty Alignment Job");
638     }
639     try
640     {
641       j.addInitialStatus(); // list the presets/parameters used for the job in
642                             // status
643       if (j.isPresetJob())
644       {
645         j.setJobId(server.presetAlign(j.seqs, j.getServerPreset()));
646       }
647       else if (j.hasArguments())
648       {
649         j.setJobId(server.customAlign(j.seqs, j.getJabaArguments()));
650       }
651       else
652       {
653         j.setJobId(server.align(j.seqs));
654       }
655
656       if (j.getJobId() != null)
657       {
658         j.setSubmitted(true);
659         j.setSubjobComplete(false);
660         // System.out.println(WsURL + " Job Id '" + jobId + "'");
661         return;
662       }
663       else
664       {
665         throw new Exception(
666                 "Server at "
667                         + WsUrl
668                         + " returned null string for job id, it probably cannot be contacted. Try again later ?");
669       }
670     } catch (compbio.metadata.UnsupportedRuntimeException _lex)
671     {
672       lex = _lex;
673       wsInfo.appendProgressText("Job could not be run because the server doesn't support this program.\n"
674               + _lex.getMessage());
675       wsInfo.warnUser(_lex.getMessage(), "Service not supported!");
676       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
677       wsInfo.setStatus(j.getJobnum(),
678               WebserviceInfo.STATE_STOPPED_SERVERERROR);
679     } catch (compbio.metadata.LimitExceededException _lex)
680     {
681       lex = _lex;
682       wsInfo.appendProgressText("Job could not be run because it exceeded a hard limit on the server.\n"
683               + _lex.getMessage());
684       wsInfo.warnUser(_lex.getMessage(), "Input is too big!");
685       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
686       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
687     } catch (compbio.metadata.WrongParameterException _lex)
688     {
689       lex = _lex;
690       wsInfo.warnUser(_lex.getMessage(), "Invalid job parameter set!");
691       wsInfo.appendProgressText("Job could not be run because some of the parameter settings are not supported by the server.\n"
692               + _lex.getMessage()
693               + "\nPlease check to make sure you have used the correct parameter set for this service!\n");
694       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
695       wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
696     } catch (Error e)
697     {
698       // For unexpected errors
699       System.err
700               .println(WebServiceName
701                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
702                       + "When contacting Server:" + WsUrl + "\n");
703       e.printStackTrace(System.err);
704       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
705       wsInfo.setStatus(j.getJobnum(),
706               WebserviceInfo.STATE_STOPPED_SERVERERROR);
707     } catch (Exception e)
708     {
709       // For unexpected errors
710       System.err
711               .println(WebServiceName
712                       + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
713                       + "When contacting Server:" + WsUrl + "\n");
714       e.printStackTrace(System.err);
715       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
716       wsInfo.setStatus(j.getJobnum(),
717               WebserviceInfo.STATE_STOPPED_SERVERERROR);
718     } finally
719     {
720       if (!j.isSubmitted())
721       {
722         // Boilerplate code here
723         // TODO: JBPNote catch timeout or other fault types explicitly
724
725         j.setAllowedServerExceptions(0);
726         wsInfo.appendProgressText(j.getJobnum(),
727                 "Failed to submit sequences for alignment.\n"
728                         + "Just close the window\n");
729       }
730     }
731   }
732
733   public void parseResult()
734   {
735     long progbar = System.currentTimeMillis();
736     wsInfo.setProgressBar("Collecting job results.", progbar);
737     int results = 0; // number of result sets received
738     JobStateSummary finalState = new JobStateSummary();
739     try
740     {
741       for (int j = 0; j < jobs.length; j++)
742       {
743         MsaWSJob msjob = ((MsaWSJob) jobs[j]);
744         if (jobs[j].isFinished() && msjob.alignment == null)
745         {
746           int nunchanged = 3, nexcept = 3;
747           boolean jpchanged = false, jpex = false;
748           do
749           {
750             try
751             {
752               jpchanged = updateJobProgress(msjob);
753               jpex = false;
754               if (jpchanged)
755               {
756                 nexcept = 3;
757               }
758             } catch (Exception e)
759             {
760
761               Cache.log
762                       .warn("Exception when retrieving remaining Job progress data for job "
763                               + msjob.getJobId() + " on server " + WsUrl);
764               e.printStackTrace();
765               nexcept--;
766               nunchanged = 3;
767               // set flag remember that we've had an exception.
768               jpex = true;
769               jpchanged = false;
770             }
771             if (!jpchanged)
772             {
773               try
774               {
775                 Thread.sleep(jpex ? 2400 : 1200); // wait a bit longer if we
776                                                   // experienced an exception.
777               } catch (Exception ex)
778               {
779               }
780               ;
781               nunchanged--;
782             }
783           } while (nunchanged > 0 && nexcept > 0);
784
785           if (Cache.log.isDebugEnabled())
786           {
787             System.out.println("Job Execution file for job: "
788                     + msjob.getJobId() + " on server " + WsUrl);
789             System.out.println(msjob.getStatus());
790             System.out.println("*** End of status");
791
792           }
793           try
794           {
795             msjob.alignment = server.getResult(msjob.getJobId());
796           } catch (compbio.metadata.ResultNotAvailableException e)
797           {
798             // job has failed for some reason - probably due to invalid
799             // parameters
800             Cache.log
801                     .debug("Results not available for finished job - marking as broken job.",
802                             e);
803             msjob.jobProgress
804                     .append("\nResult not available. Probably due to invalid input or parameter settings. Server error message below:\n\n"
805                             + e.getLocalizedMessage());
806             msjob.setjobStatus(JobStatus.FAILED);
807           } catch (Exception e)
808           {
809             Cache.log.error("Couldn't get Alignment for job.", e);
810             // TODO: Increment count and retry ?
811             msjob.setjobStatus(JobStatus.UNDEFINED);
812           }
813         }
814         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
815         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
816                 && jobs[j].hasResults())
817         {
818           results++;
819           compbio.data.sequence.Alignment alignment = ((MsaWSJob) jobs[j]).alignment;
820           if (alignment != null)
821           {
822             // server.close(jobs[j].getJobnum());
823             // wsInfo.appendProgressText(jobs[j].getJobnum(),
824             // "\nAlignment Object Method Notes\n");
825             // wsInfo.appendProgressText(jobs[j].getJobnum(),
826             // "Calculated with "+alignment.getMetadata().getProgram().toString());
827             // JBPNote The returned files from a webservice could be
828             // hidden behind icons in the monitor window that,
829             // when clicked, pop up their corresponding data
830           }
831         }
832       }
833     } catch (Exception ex)
834     {
835
836       Cache.log.error("Unexpected exception when processing results for "
837               + alTitle, ex);
838       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
839     }
840     if (results > 0)
841     {
842       wsInfo.showResultsNewFrame
843               .addActionListener(new java.awt.event.ActionListener()
844               {
845                 public void actionPerformed(java.awt.event.ActionEvent evt)
846                 {
847                   displayResults(true);
848                 }
849               });
850       wsInfo.mergeResults
851               .addActionListener(new java.awt.event.ActionListener()
852               {
853                 public void actionPerformed(java.awt.event.ActionEvent evt)
854                 {
855                   displayResults(false);
856                 }
857               });
858       wsInfo.setResultsReady();
859     }
860     else
861     {
862       wsInfo.setFinishedNoResults();
863     }
864     updateGlobalStatus(finalState);
865     wsInfo.setProgressBar(null, progbar);
866   }
867
868   void displayResults(boolean newFrame)
869   {
870     // view input or result data for each block
871     Vector alorders = new Vector();
872     SequenceI[][] results = new SequenceI[jobs.length][];
873     AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
874     String lastProgram = null;
875     MsaWSJob msjob;
876     for (int j = 0; j < jobs.length; j++)
877     {
878       if (jobs[j].hasResults())
879       {
880         msjob = (MsaWSJob) jobs[j];
881         Object[] res = msjob.getAlignment();
882         lastProgram = msjob.getAlignmentProgram();
883         alorders.add(res[1]);
884         results[j] = (SequenceI[]) res[0];
885         orders[j] = (AlignmentOrder) res[1];
886
887         // SequenceI[] alignment = input.getUpdated
888       }
889       else
890       {
891         results[j] = null;
892       }
893     }
894     Object[] newview = input.getUpdatedView(results, orders, getGapChar());
895     // trash references to original result data
896     for (int j = 0; j < jobs.length; j++)
897     {
898       results[j] = null;
899       orders[j] = null;
900     }
901     SequenceI[] alignment = (SequenceI[]) newview[0];
902     ColumnSelection columnselection = (ColumnSelection) newview[1];
903     Alignment al = new Alignment(alignment);
904     // TODO: add 'provenance' property to alignment from the method notes
905     if (lastProgram != null)
906     {
907       al.setProperty("Alignment Program", lastProgram);
908     }
909     // accompanying each subjob
910     if (dataset != null)
911     {
912       al.setDataset(dataset);
913     }
914
915     propagateDatasetMappings(al);
916     // JBNote- TODO: warn user if a block is input rather than aligned data ?
917
918     if (newFrame)
919     {
920       AlignFrame af = new AlignFrame(al, columnselection,
921               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
922
923       // initialise with same renderer settings as in parent alignframe.
924       af.getFeatureRenderer().transferSettings(this.featureSettings);
925       // update orders
926       if (alorders.size() > 0)
927       {
928         if (alorders.size() == 1)
929         {
930           af.addSortByOrderMenuItem(WebServiceName + " Ordering",
931                   (AlignmentOrder) alorders.get(0));
932         }
933         else
934         {
935           // construct a non-redundant ordering set
936           Vector names = new Vector();
937           for (int i = 0, l = alorders.size(); i < l; i++)
938           {
939             String orderName = new String(" Region " + i);
940             int j = i + 1;
941
942             while (j < l)
943             {
944               if (((AlignmentOrder) alorders.get(i))
945                       .equals(((AlignmentOrder) alorders.get(j))))
946               {
947                 alorders.remove(j);
948                 l--;
949                 orderName += "," + j;
950               }
951               else
952               {
953                 j++;
954               }
955             }
956
957             if (i == 0 && j == 1)
958             {
959               names.add(new String(""));
960             }
961             else
962             {
963               names.add(orderName);
964             }
965           }
966           for (int i = 0, l = alorders.size(); i < l; i++)
967           {
968             af.addSortByOrderMenuItem(
969                     WebServiceName + ((String) names.get(i)) + " Ordering",
970                     (AlignmentOrder) alorders.get(i));
971           }
972         }
973       }
974
975       Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
976               AlignFrame.DEFAULT_HEIGHT);
977
978     }
979     else
980     {
981       System.out.println("MERGE WITH OLD FRAME");
982       // TODO: modify alignment in original frame, replacing old for new
983       // alignment using the commands.EditCommand model to ensure the update can
984       // be undone
985     }
986   }
987
988   public boolean canMergeResults()
989   {
990     return false;
991   }
992 }