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