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