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