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