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