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