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