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