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