JAL-715 - grammar for warning message.
[jalview.git] / src / jalview / ws / rest / RestJobThread.java
1 package jalview.ws.rest;
2
3 import jalview.bin.Cache;
4 import jalview.datamodel.Alignment;
5 import jalview.datamodel.AlignmentAnnotation;
6 import jalview.datamodel.AlignmentI;
7 import jalview.datamodel.AlignmentOrder;
8 import jalview.datamodel.Annotation;
9 import jalview.datamodel.ColumnSelection;
10 import jalview.datamodel.SequenceGroup;
11 import jalview.datamodel.SequenceI;
12 import jalview.gui.AlignFrame;
13 import jalview.gui.Desktop;
14 import jalview.gui.PaintRefresher;
15 import jalview.gui.WebserviceInfo;
16 import jalview.io.NewickFile;
17 import jalview.io.packed.JalviewDataset;
18 import jalview.io.packed.JalviewDataset.AlignmentSet;
19 import jalview.ws.AWSThread;
20 import jalview.ws.AWsJob;
21
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Enumeration;
27 import java.util.Hashtable;
28 import java.util.List;
29 import java.util.Map.Entry;
30 import java.util.Vector;
31
32 import org.apache.axis.transport.http.HTTPConstants;
33 import org.apache.http.Header;
34 import org.apache.http.HttpEntity;
35 import org.apache.http.HttpResponse;
36 import org.apache.http.client.ClientProtocolException;
37 import org.apache.http.client.methods.HttpGet;
38 import org.apache.http.client.methods.HttpPost;
39 import org.apache.http.client.methods.HttpRequestBase;
40 import org.apache.http.entity.mime.HttpMultipartMode;
41 import org.apache.http.entity.mime.MultipartEntity;
42 import org.apache.http.impl.client.DefaultHttpClient;
43 import org.apache.http.protocol.BasicHttpContext;
44 import org.apache.http.protocol.HttpContext;
45 import org.apache.http.util.EntityUtils;
46
47 public class RestJobThread extends AWSThread
48 {
49   enum Stage
50   {
51     SUBMIT, POLL
52   }
53
54   protected RestClient restClient;
55
56   public RestJobThread(RestClient restClient)
57   {
58     super(restClient.af, null, restClient._input,
59             restClient.service.postUrl);
60     this.restClient = restClient; // may not be needed
61     // Test Code
62     // minimal job - submit given input and parse result onto alignment as
63     // annotation/whatever
64
65     // look for tree data, etc.
66
67     // for moment do following job type only
68     // input=visiblealignment,groupsindex
69     // ie one subjob using groups defined on alignment.
70     if (!restClient.service.isHseparable())
71     {
72       jobs = new RestJob[1];
73       jobs[0] = new RestJob(0, this,
74               restClient._input.getVisibleAlignment(restClient.service
75                       .getGapCharacter()),
76               restClient._input.getVisibleContigs());
77       // need a function to get a range on a view/alignment and return both
78       // annotation, groups and selection subsetted to just that region.
79
80     }
81     else
82     {
83       int[] viscontig = restClient._input.getVisibleContigs();
84       AlignmentI[] viscontigals = restClient._input
85               .getVisibleContigAlignments(restClient.service
86                       .getGapCharacter());
87       if (viscontigals != null && viscontigals.length > 0)
88       {
89         jobs = new RestJob[viscontigals.length];
90         for (int j = 0; j < jobs.length; j++)
91         {
92           int[] visc = new int[]
93           { viscontig[j * 2], viscontig[j * 2 + 1] };
94           if (j != 0)
95           {
96             jobs[j] = new RestJob(j, this, viscontigals[j], visc);
97           }
98           else
99           {
100             jobs[j] = new RestJob(0, this, viscontigals[j], visc);
101           }
102         }
103       }
104     }
105     // end Test Code
106     /**
107      * subjob types row based: per sequence in alignment/selected region { input
108      * is one sequence or sequence ID } per alignment/selected region { input is
109      * set of sequences, alignment, one or more sets of sequence IDs,
110      */
111
112     if (!restClient.service.isHseparable())
113     {
114
115       // create a bunch of subjobs per visible contig to ensure result honours
116       // hidden boundaries
117       // TODO: determine if a 'soft' hSeperable property is needed - e.g. if
118       // user does SS-pred on sequence with big hidden regions, its going to be
119       // less reliable.
120     }
121     else
122     {
123       // create a single subjob for the visible/selected input
124
125     }
126     // TODO: decide if vSeperable exists: eg. column wide analysis where hidden
127     // rows do not affect output - generally no analysis that generates
128     // alignment annotation is vSeparable -
129   }
130
131   /**
132    * create gui components for monitoring jobs
133    * 
134    * @param webserviceInfo
135    */
136   public void setWebServiceInfo(WebserviceInfo webserviceInfo)
137   {
138     wsInfo = webserviceInfo;
139     for (int j = 0; j < jobs.length; j++)
140     {
141       wsInfo.addJobPane();
142       // Copy over any per job params
143       if (jobs.length > 1)
144       {
145         wsInfo.setProgressName("region " + jobs[j].getJobnum(),
146                 jobs[j].getJobnum());
147       }
148       else
149       {
150         wsInfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
151       }
152     }
153   }
154
155   private String getStage(Stage stg)
156   {
157     if (stg == Stage.SUBMIT)
158       return "submitting ";
159     if (stg == Stage.POLL)
160       return "checking status of ";
161
162     return (" being confused about ");
163   }
164
165   private void doPoll(RestJob rj) throws Exception
166   {
167     String postUrl = rj.getPollUrl();
168     doHttpReq(Stage.POLL, rj, postUrl);
169   }
170
171   /**
172    * construct the post and handle the response.
173    * 
174    * @throws Exception
175    */
176   public void doPost(RestJob rj) throws Exception
177   {
178     String postUrl = rj.getPostUrl();
179     doHttpReq(Stage.SUBMIT, rj, postUrl);
180     wsInfo.invalidate();
181   }
182
183   /**
184    * do the HTTP request - and respond/set statuses appropriate to the current
185    * stage.
186    * 
187    * @param stg
188    * @param rj
189    *          - provides any data needed for posting and used to record state
190    * @param postUrl
191    *          - actual URL to post/get from
192    * @throws Exception
193    */
194   protected void doHttpReq(Stage stg, RestJob rj, String postUrl)
195           throws Exception
196   {
197     StringBuffer respText = new StringBuffer();
198     // con.setContentHandlerFactory(new
199     // jalview.ws.io.mime.HttpContentHandler());
200     HttpRequestBase request = null;
201     String messages = "";
202     if (stg == Stage.SUBMIT)
203     {
204       // Got this from
205       // http://evgenyg.wordpress.com/2010/05/01/uploading-files-multipart-post-apache/
206
207       HttpPost htpost = new HttpPost(postUrl);
208       MultipartEntity postentity = new MultipartEntity(
209               HttpMultipartMode.STRICT);
210       for (Entry<String, InputType> input : rj.getInputParams())
211       {
212         if (input.getValue().validFor(rj))
213         {
214           postentity.addPart(input.getKey(), input.getValue()
215                   .formatForInput(rj));
216         }
217         else
218         {
219           messages += "Skipped an input (" + input.getKey()
220                   + ") - Couldn't generate it from available data.";
221         }
222       }
223       htpost.setEntity(postentity);
224       request = htpost;
225     }
226     else
227     {
228       request = new HttpGet(postUrl);
229     }
230     if (request != null)
231     {
232       DefaultHttpClient httpclient = new DefaultHttpClient();
233
234       HttpContext localContext = new BasicHttpContext();
235       HttpResponse response = null;
236       try
237       {
238         response = httpclient.execute(request);
239       } catch (ClientProtocolException he)
240       {
241         rj.statMessage = "Web Protocol Exception when "
242                 + getStage(stg) + "Job. <br>Problematic url was <a href=\""+request.getURI()+"\">"+request.getURI()+"</a><br>See Console output for details.";
243         rj.setAllowedServerExceptions(0);// unrecoverable;
244         rj.error = true;
245         Cache.log.fatal("Unexpected REST Job " + getStage(stg)
246                 + "exception for URL " + rj.rsd.postUrl);
247         throw (he);
248       } catch (IOException e)
249       {
250         rj.statMessage = "IO Exception when "
251                 + getStage(stg) + "Job. <br>Problematic url was <a href=\""+request.getURI()+"\">"+request.getURI()+"</a><br>See Console output for details.";
252         Cache.log.warn("IO Exception for REST Job " + getStage(stg)
253                 + "exception for URL " + rj.rsd.postUrl);
254
255         throw (e);
256       }
257       switch (response.getStatusLine().getStatusCode())
258       {
259       case 200:
260         rj.running = false;
261         Cache.log.debug("Processing result set.");
262         processResultSet(rj, response, request);
263         break;
264       case 202:
265         rj.statMessage = "<br>Job submitted successfully. Results available at this URL:\n"
266                 + "<a href="
267                 + rj.getJobId()
268                 + "\">"
269                 + rj.getJobId()
270                 + "</a><br>";
271         rj.running = true;
272         break;
273       case 302:
274         Header[] loc;
275         if (!rj.isSubmitted()
276                 && (loc = response
277                         .getHeaders(HTTPConstants.HEADER_LOCATION)) != null
278                 && loc.length > 0)
279         {
280           if (loc.length > 1)
281           {
282             Cache.log
283                     .warn("Ignoring additional "
284                             + (loc.length - 1)
285                             + " location(s) provided in response header ( next one is '"
286                             + loc[1].getValue() + "' )");
287           }
288           rj.setJobId(loc[0].getValue());
289           rj.setSubmitted(true);
290         }
291         completeStatus(rj, response);
292         break;
293       case 500:
294         // Failed.
295         rj.setSubmitted(true);
296         rj.setAllowedServerExceptions(0);
297         rj.setSubjobComplete(true);
298         rj.error = true;
299         rj.running = false;
300         completeStatus(rj, response, "" + getStage(stg)
301                 + "failed. Reason below:\n");
302         break;
303       default:
304         // Some other response. Probably need to pop up the content in a window.
305         // TODO: deal with all other HTTP response codes from server.
306         Cache.log.warn("Unhandled response status when " + getStage(stg)
307                 + "for " + postUrl + ": " + response.getStatusLine());
308         try
309         {
310           response.getEntity().consumeContent();
311         } catch (IOException e)
312         {
313           Cache.log.debug("IOException when consuming unhandled response",
314                   e);
315         }
316         ;
317       }
318     }
319   }
320
321   /**
322    * job has completed. Something valid should be available from con
323    * 
324    * @param rj
325    * @param con
326    * @param req
327    *          is a stateless request - expected to return the same data
328    *          regardless of how many times its called.
329    */
330   private void processResultSet(RestJob rj, HttpResponse con,
331           HttpRequestBase req)
332   {
333     if (rj.statMessage == null)
334     {
335       rj.statMessage = "";
336     }
337     rj.statMessage += "Job Complete.\n";
338     try
339     {
340       rj.resSet = new HttpResultSet(rj, con, req);
341       rj.gotresult = true;
342     } catch (IOException e)
343     {
344       rj.statMessage += "Couldn't parse results. Failed.";
345       rj.error = true;
346       rj.gotresult = false;
347     }
348   }
349
350   private void completeStatus(RestJob rj, HttpResponse con)
351           throws IOException
352   {
353     completeStatus(rj, con, null);
354
355   }
356
357   private void completeStatus(RestJob rj, HttpResponse con, String prefix)
358           throws IOException
359   {
360     StringBuffer sb = new StringBuffer();
361     if (prefix != null)
362     {
363       sb.append(prefix);
364     }
365     ;
366     if (rj.statMessage != null && rj.statMessage.length() > 0)
367     {
368       sb.append(rj.statMessage);
369     }
370     HttpEntity en = con.getEntity();
371     /*
372      * Just append the content as a string.
373      */
374     String f;
375     StringBuffer content = new StringBuffer(f = EntityUtils.toString(en));
376     f = f.toLowerCase();
377     int body = f.indexOf("<body");
378     if (body > -1)
379     {
380       content.delete(0, f.indexOf(">", body));
381     }
382     if (body > -1 && sb.length() > 0)
383     {
384       sb.append("\n");
385       content.insert(0, sb);
386       sb = null;
387     }
388     f = null;
389     rj.statMessage = content.toString();
390   }
391
392   @Override
393   public void pollJob(AWsJob job) throws Exception
394   {
395     assert (job instanceof RestJob);
396     System.err.println("Debug RestJob: Polling Job");
397     doPoll((RestJob) job);
398   }
399
400   @Override
401   public void StartJob(AWsJob job)
402   {
403     assert (job instanceof RestJob);
404     try
405     {
406       System.err.println("Debug RestJob: Posting Job");
407       doPost((RestJob) job);
408     }
409     catch (NoValidInputDataException erex)
410     {
411       job.setSubjobComplete(true);
412       job.setSubmitted(true);
413       ((RestJob)job).statMessage="<br>It looks like there was a problem with the data sent to the service :<br>"+erex.getMessage()+"\n";
414       ((RestJob)job).error=true;
415       
416     }
417     catch (Exception ex)
418     {
419       job.setSubjobComplete(true);
420       job.setAllowedServerExceptions(-1);
421       Cache.log.error("Exception when trying to start Rest Job.", ex);
422     }
423   }
424
425   @Override
426   public void parseResult()
427   {
428     // crazy users will see this message
429     // TODO: finish this! and remove the message below!
430     Cache.log.warn("Rest job result parser is currently INCOMPLETE!");
431     int validres = 0;
432     for (RestJob rj : (RestJob[]) jobs)
433     {
434       if (rj.hasResponse() && rj.resSet != null && rj.resSet.isValid())
435       {
436         String ln = null;
437         try
438         {
439           Cache.log.debug("Parsing data for job " + rj.getJobId());
440           rj.parseResultSet();
441           if (rj.hasResults())
442           {
443             validres++;
444           }
445           Cache.log.debug("Finished parsing data for job " + rj.getJobId());
446
447         } catch (Error ex)
448         {
449           Cache.log.warn("Failed to finish parsing data for job "
450                   + rj.getJobId());
451           ex.printStackTrace();
452         } catch (Exception ex)
453         {
454           Cache.log.warn("Failed to finish parsing data for job "
455                   + rj.getJobId());
456           ex.printStackTrace();
457         }
458       }
459     }
460     if (validres > 0)
461     {
462       // add listeners and activate result display gui elements
463       /**
464        * decisions based on job result content + state of alignFrame that
465        * originated the job:
466        */
467       /*
468        * 1. Can/Should this job automatically open a new window for results
469        */
470       if (true)
471       {
472         // preserver current jalview behaviour
473         wsInfo.setViewResultsImmediatly(true);
474       }
475       else
476       {
477         // realiseResults(true, true);
478       }
479       // otherwise, should automatically view results
480
481       // TODO: check if at least one or more contexts are valid - if so, enable
482       // gui
483       wsInfo.showResultsNewFrame.addActionListener(new ActionListener()
484       {
485
486         @Override
487         public void actionPerformed(ActionEvent e)
488         {
489           realiseResults(false);
490         }
491
492       });
493       wsInfo.mergeResults.addActionListener(new ActionListener()
494       {
495
496         @Override
497         public void actionPerformed(ActionEvent e)
498         {
499           realiseResults(true);
500         }
501
502       });
503
504       wsInfo.setResultsReady();
505     }
506     else
507     {
508       // tell the user nothing was returned.
509       wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
510       wsInfo.setFinishedNoResults();
511     }
512   }
513
514   /**
515    * instructions for whether to create new alignment view on current alignment
516    * set, add to current set, or create new alignFrame
517    */
518   private enum AddDataTo
519   {
520     /**
521      * add annotation, trees etc to current view
522      */
523     currentView,
524     /**
525      * create a new view derived from current view and add data to that
526      */
527     newView,
528     /**
529      * create a new alignment frame for the result set and add annotation to
530      * that.
531      */
532     newAlignment
533   };
534
535   public void realiseResults(boolean merge)
536   {
537     /*
538      * 2. Should the job modify the parent alignment frame/view(s) (if they
539      * still exist and the alignment hasn't been edited) in order to display new
540      * annotation/features.
541      */
542     /**
543      * alignment panels derived from each alignment set returned by service.
544      */
545     ArrayList<jalview.gui.AlignmentPanel> destPanels = new ArrayList<jalview.gui.AlignmentPanel>();
546     /**
547      * list of instructions for how to process each distinct alignment set
548      * returned by the job set
549      */
550     ArrayList<AddDataTo> resultDest = new ArrayList<AddDataTo>();
551     /**
552      * when false, zeroth pane is panel derived from input deta.
553      */
554     boolean newAlignment = false;
555     /**
556      * gap character to be used for alignment reconstruction
557      */
558     char gapCharacter = restClient.av.getGapCharacter();
559     // Now, iterate over all alignment sets returned from all jobs:
560     // first inspect jobs and collate results data in order to count alignments
561     // and other results
562     // then assemble results from decomposed (v followed by h-separated) jobs
563     // finally, new views and alignments will be created and displayed as
564     // necessary.
565     boolean hsepjobs = restClient.service.isHseparable();
566     boolean vsepjobs = restClient.service.isVseparable();
567     // total number of distinct alignment sets generated by job set.
568     int numAlSets = 0, als = 0;
569     List<AlignmentI> destAls = new ArrayList<AlignmentI>();
570     List<jalview.datamodel.ColumnSelection> destColsel = new ArrayList<jalview.datamodel.ColumnSelection>();
571     List<List<NewickFile>> trees = new ArrayList<List<NewickFile>>();
572
573     do
574     {
575       // Step 1.
576       // iterate over each alignment set returned from each subjob. Treating
577       // each one returned in parallel with others.
578       // Result collation arrays
579
580       /**
581        * mapping between index of sequence in alignment that was submitted to
582        * server and index of sequence in the input alignment
583        */
584       int[][] ordermap = new int[jobs.length][];
585       SequenceI[][] rseqs = new SequenceI[jobs.length][];
586       AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
587       AlignmentAnnotation[][] alan = new AlignmentAnnotation[jobs.length][];
588       SequenceGroup[][] sgrp = new SequenceGroup[jobs.length][];
589       // Now collect all data for alignment Set als from job array
590       for (int j = 0; j < jobs.length; j++)
591       {
592         RestJob rj = (RestJob) jobs[j];
593         if (rj.hasResults())
594         {
595           JalviewDataset rset = rj.context;
596           if (rset.hasAlignments())
597           {
598             if (numAlSets < rset.getAl().size())
599             {
600               numAlSets = rset.getAl().size();
601             }
602             if (als < rset.getAl().size()
603                     && rset.getAl().get(als).isModified())
604             {
605               // Collate result data
606               // TODO: decide if all alignmentI should be collected rather than
607               // specific alignment data containers
608               // for moment, we just extract content, but this means any
609               // alignment properties may be lost.
610               AlignmentSet alset = rset.getAl().get(als);
611               alan[j] = alset.al.getAlignmentAnnotation();
612               if (alset.al.getGroups() != null)
613               {
614                 sgrp[j] = new SequenceGroup[alset.al.getGroups().size()];
615                 alset.al.getGroups().toArray(sgrp[j]);
616               }
617               else
618               {
619                 sgrp[j] = null;
620               }
621               orders[j] = new AlignmentOrder(alset.al);
622               rseqs[j] = alset.al.getSequencesArray();
623               ordermap[j] = rj.getOrderMap();
624               // if (rj.isInputUniquified()) {
625               // jalview.analysis.AlignmentSorter.recoverOrder(rseqs[als]);
626               // }
627
628               if (alset.trees != null)
629               {
630                 trees.add(new ArrayList<NewickFile>(alset.trees));
631               }
632               else
633               {
634                 trees.add(new ArrayList<NewickFile>());
635               }
636             }
637           }
638         }
639       }
640       // Now aggregate and present results from this frame of alignment data.
641       int nvertsep = 0, nvertseps = 1;
642       if (vsepjobs)
643       {
644         // Jobs relate to different rows of input alignment.
645         // Jobs are subdivided by rows before columns,
646         // so there will now be a number of subjobs according tohsep for each
647         // vertsep
648         // TODO: get vertical separation intervals for each job and set
649         // nvertseps
650         // TODO: merge data from each group/sequence onto whole
651         // alignment
652       }
653       /**
654        * index into rest jobs subdivided vertically
655        */
656       int vrestjob = 0;
657       // Destination alignments for all result data.
658       ArrayList<SequenceGroup> visgrps = new ArrayList<SequenceGroup>();
659       Hashtable<String, SequenceGroup> groupNames = new Hashtable<String, SequenceGroup>();
660       ArrayList<AlignmentAnnotation> visAlAn = null;
661       for (nvertsep = 0; nvertsep < nvertseps; nvertsep++)
662       {
663         // TODO: set scope w.r.t. original alignment view for vertical
664         // separation.
665         {
666           // results for a job exclude hidden columns of input data, so map
667           // back on to all visible regions
668           /**
669            * rest job result we are working with
670            */
671           int nrj = vrestjob;
672
673           RestJob rj = (RestJob) jobs[nrj];
674           int contigs[] = input.getVisibleContigs();
675           AlignmentI destAl = null;
676           jalview.datamodel.ColumnSelection destCs = null;
677           // Resolve destAl for this data.
678           if (als == 0 && rj.isInputContextModified())
679           {
680             // special case: transfer features, annotation, groups, etc,
681             // from input
682             // context to align panel derived from input data
683             if (destAls.size() > als)
684             {
685               destAl = destAls.get(als);
686             }
687             else
688             {
689               if (!restClient.isAlignmentModified() && merge)
690               {
691                 destAl = restClient.av.getAlignment();
692                 destCs = restClient.av.getColumnSelection();
693                 resultDest
694                         .add(restClient.isShowResultsInNewView() ? AddDataTo.newView
695                                 : AddDataTo.currentView);
696                 destPanels.add(restClient.recoverAlignPanelForView());
697               }
698               else
699               {
700                 newAlignment = true;
701                 // recreate the input alignment data
702                 Object[] idat = input
703                         .getAlignmentAndColumnSelection(gapCharacter);
704                 destAl = new Alignment((SequenceI[]) idat[0]);
705                 destCs = (ColumnSelection) idat[1];
706                 resultDest.add(AddDataTo.newAlignment);
707                 // but do not add to the alignment panel list - since we need to
708                 // create a whole new alignFrame set.
709               }
710               destAls.add(destAl);
711               destColsel.add(destCs);
712             }
713           }
714           else
715           {
716             // alignment(s) returned by service is to be re-integrated and
717             // displayed
718             if (destAls.size() > als)
719             {
720               if (!vsepjobs)
721               {
722                 // TODO: decide if multiple multiple alignments returned by
723                 // non-vseparable services are allowed.
724                 Cache.log
725                         .warn("dealing with multiple alignment products returned by non-vertically separable service.");
726               }
727               // recover reference to last alignment created for this rest frame
728               // ready for extension
729               destAl = destAls.get(als);
730               destCs = destColsel.get(als);
731             }
732             else
733             {
734               Object[] newview;
735
736               if (!hsepjobs)
737               {
738                 // single alignment for any job that gets mapped back on to
739                 // input data. Reconstruct by interleaving parts of returned
740                 // alignment with hidden parts of input data.
741                 SequenceI[][] nsq = splitSeqsOnVisibleContigs(rseqs[nrj],
742                         contigs, gapCharacter);
743                 AlignmentOrder alo[] = new AlignmentOrder[nsq.length];
744                 for (int no = 0; no < alo.length; no++)
745                 {
746                   alo[no] = new AlignmentOrder(orders[nrj].getOrder());
747                 }
748                 newview = input.getUpdatedView(nsq, orders, gapCharacter);
749               }
750               else
751               {
752                 // each job maps to a single visible contig, and all need to be
753                 // stitched back together.
754                 // reconstruct using sub-region based MSA alignment construction
755                 // mechanism
756                 newview = input.getUpdatedView(rseqs, orders, gapCharacter);
757               }
758               destAl = new Alignment((SequenceI[]) newview[0]);
759               destCs = (ColumnSelection) newview[1];
760               newAlignment = true;
761               // TODO create alignment from result data with propagated
762               // references.
763               destAls.add(destAl);
764               destColsel.add(destCs);
765               resultDest.add(AddDataTo.newAlignment);
766               throw new Error("Impl. Error! TODO: ");
767             }
768           }
769           /**
770            * save initial job in this set in case alignment is h-separable
771            */
772           int initnrj = nrj;
773           // Now add in groups
774           for (int ncnt = 0; ncnt < contigs.length; ncnt += 2)
775           {
776             if (!hsepjobs)
777             {
778               // single alignment for any job that gets mapped back on to input
779               // data.
780             }
781             else
782             {
783               // each job maps to a single visible contig, and all need to be
784               // stitched back together.
785               if (ncnt > 0)
786               {
787                 nrj++;
788               }
789               // TODO: apply options for group merging and annotation merging.
790               // If merging not supported, then either clear hashtables now or
791               // use them to rename the new annotation/groups for each contig if
792               // a conflict occurs.
793             }
794             if (sgrp[nrj] != null)
795             {
796               for (SequenceGroup sg : sgrp[nrj])
797               {
798                 boolean recovered = false;
799                 SequenceGroup exsg = null;
800                 if (sg.getName() != null)
801                 {
802                   exsg = groupNames.get(sg.getName());
803                 }
804                 if (exsg == null)
805                 {
806                   exsg = new SequenceGroup(sg);
807                   groupNames.put(exsg.getName(), exsg);
808                   visgrps.add(exsg);
809                   exsg.setStartRes(sg.getStartRes() + contigs[ncnt]);
810                   exsg.setEndRes(sg.getEndRes() + contigs[ncnt]);
811                 } else {
812                   recovered = true;
813                 }
814                 // now replace any references from the result set with
815                 // corresponding refs from alignment input set.
816
817                 // TODO: cope with recovering hidden sequences from
818                 // resultContext
819                 {
820                   Vector sqs = sg.getSequences(null);
821                   for (int sqsi = 0, iSize = sqs.size(); sqsi < iSize; sqsi++)
822                   {
823                     SequenceI oseq = (SequenceI) sqs.get(sqsi);
824                     SequenceI nseq = getNewSeq(oseq, rseqs[nrj],
825                             ordermap[nrj], destAl);
826                     if (nseq != null)
827                     {
828                       if (!recovered)
829                       {
830                         exsg.deleteSequence(oseq, false);
831                       }
832                       exsg.addSequence(nseq, false);
833                     }
834                     else
835                     {
836                       Cache.log
837                               .warn("Couldn't resolve original sequence for new sequence.");
838                     }
839                   }
840                   if (sg.hasSeqrep())
841                   {
842                     if (exsg.getSeqrep() == sg.getSeqrep())
843                     {
844                       // lift over sequence rep reference too
845                       SequenceI oseq = sg.getSeqrep();
846                       SequenceI nseq = getNewSeq(oseq, rseqs[nrj],
847                               ordermap[nrj], destAl);
848                       if (nseq != null)
849                       {
850                         exsg.setSeqrep(nseq);
851                       }
852                     }
853                   }
854                 }
855                 if (recovered)
856                 {
857                   // adjust boundaries of recovered group w.r.t. new group being
858                   // merged on to original alignment.
859                   int start = sg.getStartRes() + contigs[ncnt], end = sg
860                           .getEndRes() + contigs[ncnt];
861                   if (start < exsg.getStartRes())
862                   {
863                     exsg.setStartRes(start);
864                   }
865                   if (end > exsg.getEndRes())
866                   {
867                     exsg.setEndRes(end);
868                   }
869                 }
870               }
871             }
872           }
873           // reset job counter
874           nrj = initnrj;
875           // and finally add in annotation and any trees for each job
876           for (int ncnt = 0; ncnt < contigs.length; ncnt += 2)
877           {
878             if (!hsepjobs)
879             {
880               // single alignment for any job that gets mapped back on to input
881               // data.
882             }
883             else
884             {
885               // each job maps to a single visible contig, and all need to be
886               // stitched back together.
887               if (ncnt > 0)
888               {
889                 nrj++;
890               }
891             }
892
893             // merge alignmentAnnotation into one row
894             if (alan[nrj] != null)
895             {
896               for (int an = 0; an < alan[nrj].length; an++)
897               {
898                 SequenceI sqass = null;
899                 SequenceGroup grass = null;
900                 if (alan[nrj][an].sequenceRef != null)
901                 {
902                   // TODO: ensure this relocates sequence reference to local
903                   // context.
904                   sqass = getNewSeq(alan[nrj][an].sequenceRef, rseqs[nrj],
905                           ordermap[nrj], destAl);
906                 }
907                 if (alan[nrj][an].groupRef != null)
908                 {
909                   // TODO: verify relocate group reference to local context
910                   // works correctly
911                   grass = groupNames.get(alan[nrj][an].groupRef.getName());
912                   if (grass == null)
913                   {
914                     Cache.log
915                             .error("Couldn't relocate group referemce for group "
916                                     + alan[nrj][an].groupRef.getName());
917                   }
918                 }
919                 if (visAlAn == null)
920                 {
921                   visAlAn = new ArrayList<AlignmentAnnotation>();
922                 }
923                 AlignmentAnnotation visan = null;
924                 for (AlignmentAnnotation v : visAlAn)
925                 {
926                   if (v.label != null
927                           && v.label.equals(alan[nrj][an].label))
928                   {
929                     visan = v;
930                   }
931                 }
932                 if (visan == null)
933                 {
934                   visan = new AlignmentAnnotation(alan[nrj][an]);
935                   // copy annotations, and wipe out/update refs.
936                   visan.annotations = new Annotation[input.getWidth()];
937                   visan.groupRef = grass;
938                   visan.sequenceRef = sqass;
939                   visAlAn.add(visan);
940                 }
941                 if (contigs[ncnt]+alan[nrj][an].annotations.length>visan.annotations.length)
942                 {
943                   // increase width of annotation row
944                   Annotation[] newannv = new Annotation[contigs[ncnt]+alan[nrj][an].annotations.length];
945                   System.arraycopy(visan.annotations, 0, newannv, 0, visan.annotations.length);
946                   visan.annotations=newannv;
947                 }
948                 // now copy local annotation data into correct position
949                 System.arraycopy(alan[nrj][an].annotations, 0,
950                         visan.annotations, contigs[ncnt],
951                         alan[nrj][an].annotations.length);
952
953               }
954             }
955             // Trees
956             if (trees.get(nrj).size() > 0)
957             {
958               for (NewickFile nf : trees.get(nrj))
959               {
960                 // TODO: process each newick file, lifting over sequence refs to
961                 // current alignment, if necessary.
962                 Cache.log
963                         .error("Tree recovery from restjob not yet implemented.");
964               }
965             }
966           }
967         }
968       } // end of vseps loops.
969       if (visAlAn != null)
970       {
971         for (AlignmentAnnotation v : visAlAn)
972         {
973           destAls.get(als).addAnnotation(v);
974         }
975       }
976       if (visgrps != null)
977       {
978         for (SequenceGroup sg : visgrps)
979         {
980           destAls.get(als).addGroup(sg);
981         }
982       }
983     } while (++als < numAlSets);
984     // Finally, assemble each new alignment, and create new gui components to
985     // present it.
986     /**
987      * current AlignFrame where results will go.
988      */
989     AlignFrame destaf = restClient.recoverAlignFrameForView();
990     /**
991      * current pane being worked with
992      */
993     jalview.gui.AlignmentPanel destPanel = restClient
994             .recoverAlignPanelForView();
995     als = 0;
996     for (AddDataTo action : resultDest)
997     {
998       AlignmentI destal;
999       ColumnSelection destcs;
1000       String alTitle = restClient.service.details.Action + " using "
1001               + restClient.service.details.Name + " on "+restClient.viewTitle;
1002       switch (action)
1003       {
1004       case newAlignment:
1005         destal = destAls.get(als);
1006         destcs = destColsel.get(als);
1007         destaf = new AlignFrame(destal, destcs, AlignFrame.DEFAULT_WIDTH,
1008                 AlignFrame.DEFAULT_HEIGHT);
1009         PaintRefresher.Refresh(destaf, destaf.getViewport().getSequenceSetId());
1010         // todo transfer any feature settings and colouring
1011         /*
1012          * destaf.getFeatureRenderer().transferSettings(this.featureSettings);
1013          * // update orders if (alorders.size() > 0) { if (alorders.size() == 1)
1014          * { af.addSortByOrderMenuItem(WebServiceName + " Ordering",
1015          * (AlignmentOrder) alorders.get(0)); } else { // construct a
1016          * non-redundant ordering set Vector names = new Vector(); for (int i =
1017          * 0, l = alorders.size(); i < l; i++) { String orderName = new
1018          * String(" Region " + i); int j = i + 1;
1019          * 
1020          * while (j < l) { if (((AlignmentOrder) alorders.get(i))
1021          * .equals(((AlignmentOrder) alorders.get(j)))) { alorders.remove(j);
1022          * l--; orderName += "," + j; } else { j++; } }
1023          * 
1024          * if (i == 0 && j == 1) { names.add(new String("")); } else {
1025          * names.add(orderName); } } for (int i = 0, l = alorders.size(); i < l;
1026          * i++) { af.addSortByOrderMenuItem( WebServiceName + ((String)
1027          * names.get(i)) + " Ordering", (AlignmentOrder) alorders.get(i)); } } }
1028          */
1029         // TODO: modify this and previous alignment's title if many alignments have been returned.
1030         Desktop.addInternalFrame(destaf, alTitle, AlignFrame.DEFAULT_WIDTH,
1031                 AlignFrame.DEFAULT_HEIGHT);
1032
1033         break;
1034       case newView:
1035         // TODO: determine title for view
1036         break;
1037       case currentView:
1038         break;
1039       }
1040     }
1041     if (!newAlignment)
1042     {
1043       if (restClient.isShowResultsInNewView())
1044       {
1045         // destPanel = destPanel.alignFrame.newView(false);
1046       }
1047     }
1048     else
1049     {
1050
1051     }
1052     /*
1053      * if (als) // add the destination panel to frame zero of result panel set }
1054      * } if (destPanels.size()==0) { AlignFrame af = new AlignFrame((AlignmentI)
1055      * idat[0], (ColumnSelection) idat[1], AlignFrame.DEFAULT_WIDTH,
1056      * AlignFrame.DEFAULT_HEIGHT);
1057      * 
1058      * jalview.gui.Desktop.addInternalFrame(af, "Results for " +
1059      * restClient.service.details.Name + " " + restClient.service.details.Action
1060      * + " on " + restClient.af.getTitle(), AlignFrame.DEFAULT_WIDTH,
1061      * AlignFrame.DEFAULT_HEIGHT); destPanel = af.alignPanel; // create totally
1062      * new alignment from stashed data/results
1063      */
1064
1065     /*
1066      */
1067
1068     /**
1069      * alignments. New alignments are added to dataset, and subsequently
1070      * annotated/visualised accordingly. 1. New alignment frame created for new
1071      * alignment. Decide if any vis settings should be inherited from old
1072      * alignment frame (e.g. sequence ordering ?). 2. Subsequent data added to
1073      * alignment as below:
1074      */
1075     /**
1076      * annotation update to original/newly created context alignment: 1.
1077      * identify alignment where annotation is to be loaded onto. 2. Add
1078      * annotation, excluding any duplicates. 3. Ensure annotation is visible on
1079      * alignment - honouring ordering given by file.
1080      */
1081     /**
1082      * features updated to original or newly created context alignment: 1.
1083      * Features are(or were already) added to dataset. 2. Feature settings
1084      * modified to ensure all features are displayed - honouring any ordering
1085      * given by result file. Consider merging action with the code used by the
1086      * DAS fetcher to update alignment views with new info.
1087      */
1088     /**
1089      * Seq associated data files (PDB files). 1. locate seq association in
1090      * current dataset/alignment context and add file as normal - keep handle of
1091      * any created ref objects. 2. decide if new data should be displayed : PDB
1092      * display: if alignment has PDB display already, should new pdb files be
1093      * aligned to it ?
1094      * 
1095      */
1096     // destPanel.adjustAnnotationHeight();
1097
1098   }
1099
1100   /**
1101    * split the given array of sequences into blocks of subsequences
1102    * corresponding to each visible contig
1103    * 
1104    * @param sequenceIs
1105    * @param contigs
1106    * @param gapChar
1107    *          padding character for ragged ends of visible contig region.
1108    * @return
1109    */
1110   private SequenceI[][] splitSeqsOnVisibleContigs(SequenceI[] sequenceIs,
1111           int[] contigs, char gapChar)
1112   {
1113     int nvc = contigs == null ? 1 : contigs.length / 2;
1114     SequenceI[][] blocks = new SequenceI[nvc][];
1115     if (contigs == null)
1116     {
1117       blocks[0] = new SequenceI[sequenceIs.length];
1118       System.arraycopy(sequenceIs, 0, blocks[0], 0, sequenceIs.length);
1119     }
1120     else
1121     {
1122       // deja vu - have I written this before ?? propagateGaps does this in a
1123       // way
1124       char[] gapset = null;
1125       int start = 0, width = 0;
1126       for (int c = 0; c < nvc; c++)
1127       {
1128         width = contigs[c * 2 + 1] - contigs[c * 2] + 1;
1129         for (int s = 0; s < sequenceIs.length; s++)
1130         {
1131           int end = sequenceIs[s].getLength();
1132           if (start < end)
1133           {
1134             if (start + width < end)
1135             {
1136               blocks[c][s] = sequenceIs[s].getSubSequence(start, start
1137                       + width);
1138             }
1139             else
1140             {
1141               blocks[c][s] = sequenceIs[s].getSubSequence(start, end);
1142               String sq = blocks[c][s].getSequenceAsString();
1143               for (int n = start + width; n > end; n--)
1144               {
1145                 sq += gapChar;
1146               }
1147             }
1148           }
1149           else
1150           {
1151             if (gapset == null || gapset.length < width)
1152             {
1153               char ng[] = new char[width];
1154               int gs = 0;
1155               if (gapset != null)
1156               {
1157                 System.arraycopy(gapset, 0, ng, 0, gs = gapset.length);
1158               }
1159               while (gs < ng.length)
1160               {
1161                 ng[gs++] = gapChar;
1162               }
1163             }
1164             blocks[c][s] = sequenceIs[s].getSubSequence(end, end);
1165             blocks[c][s].setSequence(gapset.toString().substring(0, width));
1166           }
1167         }
1168         if (c > 0)
1169         {
1170           // adjust window for next visible segnment
1171           start += contigs[c * 2 + 1] - contigs[c * 2];
1172         }
1173       }
1174     }
1175     return blocks;
1176   }
1177
1178   /**
1179    * recover corresponding sequence from original input data corresponding to
1180    * sequence in a specific job's input data.
1181    * 
1182    * @param oseq
1183    * @param sequenceIs
1184    * @param is
1185    * @param destAl
1186    * @return
1187    */
1188   private SequenceI getNewSeq(SequenceI oseq, SequenceI[] sequenceIs,
1189           int[] is, AlignmentI destAl)
1190   {
1191     int p = 0;
1192     while (p < sequenceIs.length && sequenceIs[p] != oseq)
1193     {
1194       p++;
1195     }
1196     if (p < sequenceIs.length && p < destAl.getHeight())
1197     {
1198       return destAl.getSequenceAt(is[p]);
1199     }
1200     return null;
1201   }
1202
1203   /**
1204    * 
1205    * @return true if the run method is safe to call
1206    */
1207   public boolean isValid()
1208   {
1209     ArrayList<String> _warnings=new ArrayList<String>();
1210     boolean validt=true;
1211     if (jobs != null)
1212     {
1213       for (RestJob rj : (RestJob[]) jobs)
1214       {
1215         if (!rj.hasValidInput())
1216         {
1217           // invalid input for this job
1218           System.err.println("Job " + rj.getJobnum()
1219                   + " has invalid input. ( "+rj.getStatus()+")");
1220           if (rj.hasStatus() && !_warnings.contains(rj.getStatus()))
1221           {
1222             _warnings.add(rj.getStatus());
1223           }
1224           validt=false;
1225         }
1226       }
1227     }
1228     if (!validt)
1229     {
1230       warnings = "";
1231       for (String st : _warnings) {
1232         if (warnings.length()>0) { warnings+="\n";
1233         }
1234         warnings += st;
1235         
1236       }
1237     }
1238     return validt;
1239   }
1240
1241   protected String warnings;
1242   public boolean hasWarnings()
1243   {
1244     // TODO Auto-generated method stub
1245     return warnings!=null && warnings.length()>0;
1246   }
1247
1248   /**
1249    * get any informative messages about why the job thread couldn't start.
1250    * @return
1251    */
1252   public String getWarnings()
1253   {
1254     return isValid() ? "Job can be started. No warnings." :  hasWarnings() ? warnings : "";
1255   }
1256
1257 }