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