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