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