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