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