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