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