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