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