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