JAL-3821 ported to refactored FlatFile parser
[jalview.git] / src / jalview / ws / rest / RestJob.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.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.AlignmentOrder;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.io.packed.JalviewDataset;
29 import jalview.ws.AWsJob;
30 import jalview.ws.rest.params.Alignment;
31 import jalview.ws.rest.params.SeqGroupIndexVector;
32
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Hashtable;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.Vector;
39
40 public class RestJob extends AWsJob
41 {
42
43   // TODO: input alignmentview and other data for this job
44   RestServiceDescription rsd;
45
46   // boolean submitted;
47   boolean gotresponse;
48
49   boolean error;
50
51   boolean waiting;
52
53   boolean gotresult;
54
55   Hashtable squniq;
56
57   /**
58    * dataset associated with this input data.
59    */
60   AlignmentI dsForIO;
61
62   AlignmentOrder inputOrder;
63
64   /**
65    * context of input data with respect to an AlignmentView's visible contigs.
66    */
67   int[] origviscontig;
68
69   private AlignmentI contextAl = null;
70
71   /**
72    * create a rest job using data bounded by the given start/end column.
73    * 
74    * @param addJobPane
75    * @param restJobThread
76    * @param _input
77    * @param viscontigs
78    *          visible contigs of an alignment view from which _input was derived
79    */
80   public RestJob(int jobNum, RestJobThread restJobThread, AlignmentI _input,
81           int[] viscontigs)
82   {
83     rsd = restJobThread.restClient.service;
84     jobnum = jobNum;
85     if (viscontigs != null)
86     {
87       origviscontig = new int[viscontigs.length];
88       System.arraycopy(viscontigs, 0, origviscontig, 0, viscontigs.length);
89     }
90     // get sequences for the alignmentI
91     // get groups trimmed to alignment columns
92     // get any annotation trimmed to start/end columns, too.
93     squniq = jalview.analysis.SeqsetUtils
94             .uniquify(_input.getSequencesArray(), true);
95     // prepare input
96     // form alignment+groups+annotation,preprocess and then record references
97     // for formatters
98     ArrayList<InputType> alinp = new ArrayList<InputType>();
99     int paramsWithData = 0;
100     // TODO: JAL-715 - generalise the following validation logic for all
101     // parameter types
102     // we cheat for moment - since we know a-priori what data is available and
103     // what inputs we have implemented so far
104     for (Map.Entry<String, InputType> prm : rsd.inputParams.entrySet())
105     {
106       if (!prm.getValue().isConstant())
107       {
108         if (prm.getValue() instanceof Alignment)
109         {
110           alinp.add(prm.getValue());
111         }
112         else
113         {
114           if (prm.getValue() instanceof SeqGroupIndexVector
115                   && _input.getGroups() != null
116                   && _input.getGroups().size() >= -1 + prm.getValue().min)
117           {
118             // the test above is not rigorous but fixes JAL-1298, since
119             // submission will fail if the partition set doesn't contain at
120             // least one partition
121             alinp.add(prm.getValue());
122           }
123           else
124           {
125             statMessage = ("Not enough groups defined on the alignment - need at least "
126                     + prm.getValue().min);
127           }
128         }
129       }
130       else
131       {
132         paramsWithData++;
133       }
134     }
135     if ((paramsWithData + alinp.size()) == rsd.inputParams.size())
136     {
137       inputOrder = new AlignmentOrder(_input);
138       if ((dsForIO = _input.getDataset()) == null)
139       {
140         _input.setDataset(null);
141       }
142       dsForIO = _input.getDataset();
143       if (contextAl == null)
144       {
145         contextAl = _input;
146       }
147       setAlignmentForInputs(alinp, _input);
148       validInput = true;
149     }
150     else
151     {
152       // not enough data, so we bail.
153       validInput = false;
154     }
155   }
156
157   boolean validInput = false;
158
159   @Override
160   public boolean hasResults()
161   {
162     return gotresult && (parsedResults ? validJvresults : true);
163   }
164
165   @Override
166   public boolean hasValidInput()
167   {
168     return validInput;
169   }
170
171   @Override
172   public boolean isRunning()
173   {
174     return running; // TODO: can we check the response body for status messages
175                     // ?
176   }
177
178   @Override
179   public boolean isQueued()
180   {
181     return waiting;
182   }
183
184   @Override
185   public boolean isFinished()
186   {
187     return resSet != null;
188   }
189
190   @Override
191   public boolean isFailed()
192   {
193     // TODO logic for error
194     return error;
195   }
196
197   @Override
198   public boolean isBroken()
199   {
200     // TODO logic for error
201     return error;
202   }
203
204   @Override
205   public boolean isServerError()
206   {
207     // TODO logic for error
208     return error;
209   }
210
211   @Override
212   public boolean hasStatus()
213   {
214     return statMessage != null;
215   }
216
217   protected String statMessage = null;
218
219   public HttpResultSet resSet;
220
221   @Override
222   public String getStatus()
223   {
224     return statMessage;
225   }
226
227   @Override
228   public boolean hasResponse()
229   {
230     return statMessage != null || resSet != null;
231   }
232
233   @Override
234   public void clearResponse()
235   {
236     // only clear the transient server response
237     // statMessage=null;
238   }
239
240   /*
241    * (non-Javadoc)
242    * 
243    * @see jalview.ws.AWsJob#getState()
244    */
245   @Override
246   public String getState()
247   {
248     // TODO generate state string - prolly should have a default abstract method
249     // for this
250     return "Job is clueless";
251   }
252
253   public String getPostUrl()
254   {
255
256     // TODO Auto-generated method stub
257     return rsd.postUrl;
258   }
259
260   public Set<Map.Entry<String, InputType>> getInputParams()
261   {
262     return rsd.inputParams.entrySet();
263   }
264
265   // return the URL that should be polled for this job
266   public String getPollUrl()
267   {
268     return rsd.getDecoratedResultUrl(jobId);
269   }
270
271   /**
272    * 
273    * @return the context for parsing results from service
274    */
275   public JalviewDataset newJalviewDataset()
276   {
277     if (context == null)
278     {
279       context = new JalviewDataset(dsForIO, null, squniq, null);
280       if (contextAl != null)
281       {
282         // TODO devise way of merging new annotation onto (identical) existing
283         // annotation that was used as input
284         // delete all input annotation
285         if (contextAl.getAlignmentAnnotation() != null)
286         {
287           for (AlignmentAnnotation alan : contextAl
288                   .getAlignmentAnnotation())
289           {
290             contextAl.deleteAnnotation(alan);
291           }
292         }
293         // TODO devise way of merging new groups onto (identical) existing
294         // groups when they were used as input to service
295         // delete all existing groups
296         if (contextAl.getGroups() != null)
297         {
298           contextAl.deleteAllGroups();
299         }
300         context.addAlignment(contextAl);
301       }
302
303     }
304     return context;
305   }
306
307   /**
308    * Extract list of sequence IDs for input parameter 'token' with given
309    * molecule type
310    * 
311    * @param token
312    * @param type
313    * @return
314    */
315   public SequenceI[] getSequencesForInput(String token,
316           InputType.molType type) throws NoValidInputDataException
317   {
318     Object sgdat = inputData.get(token);
319     // can we form an alignment from this data ?
320     if (sgdat == null)
321     {
322       throw new NoValidInputDataException(
323               "No Sequence vector data bound to input '" + token
324                       + "' for service at " + rsd.postUrl);
325     }
326     if (sgdat instanceof AlignmentI)
327     {
328       return ((AlignmentI) sgdat).getSequencesArray();
329     }
330     if (sgdat instanceof SequenceGroup)
331     {
332       return ((SequenceGroup) sgdat).getSequencesAsArray(null);
333     }
334     if (sgdat instanceof Vector)
335     {
336       if (((Vector) sgdat).size() > 0
337               && ((Vector) sgdat).get(0) instanceof SequenceI)
338       {
339         SequenceI[] sq = new SequenceI[((Vector) sgdat).size()];
340         ((Vector) sgdat).copyInto(sq);
341         return sq;
342       }
343     }
344     throw new NoValidInputDataException(
345             "No Sequence vector data bound to input '" + token
346                     + "' for service at " + rsd.postUrl);
347   }
348
349   /**
350    * binding between input data (AlignmentI, SequenceGroup, NJTree) and input
351    * param names.
352    */
353   private Hashtable<String, Object> inputData = new Hashtable<String, Object>();
354
355   /**
356    * is the job fully submitted to server and apparently in progress ?
357    */
358   public boolean running = false;
359
360   /**
361    * 
362    * @param itypes
363    * @param al
364    *          - reference to object to be stored as input. Note - input data may
365    *          be modifed by formatter
366    */
367   public void setAlignmentForInputs(Collection<InputType> itypes,
368           AlignmentI al)
369   {
370     for (InputType itype : itypes)
371     {
372       if (!rsd.inputParams.values().contains(itype))
373       {
374         throw new IllegalArgumentException("InputType " + itype.getClass()
375                 + " is not valid for service at " + rsd.postUrl);
376       }
377       if (itype instanceof AlignmentProcessor)
378       {
379         ((AlignmentProcessor) itype).prepareAlignment(al);
380       }
381       // stash a reference for recall when the alignment data is formatted
382       inputData.put(itype.token, al);
383     }
384
385   }
386
387   /**
388    * 
389    * @param token
390    * @param type
391    * @return alignment object bound to the given token
392    * @throws NoValidInputDataException
393    */
394   public AlignmentI getAlignmentForInput(String token,
395           InputType.molType type) throws NoValidInputDataException
396   {
397     Object al = inputData.get(token);
398     // can we form an alignment from this data ?
399     if (al == null || !(al instanceof AlignmentI))
400     {
401       throw new NoValidInputDataException(
402               "No alignment data bound to input '" + token
403                       + "' for service at " + rsd.postUrl);
404     }
405     return (AlignmentI) al;
406   }
407
408   /**
409    * test to see if the job has data of type cl that's needed for the job to run
410    * 
411    * @param cl
412    * @return true or false
413    */
414   public boolean hasDataOfType(Class cl)
415   {
416     if (AlignmentI.class.isAssignableFrom(cl))
417     {
418       return true;
419     }
420     // TODO: add more source data types
421
422     return false;
423   }
424
425   /**
426    * context used to parse results from service
427    */
428   JalviewDataset context = null;
429
430   protected boolean parsedResults = false;
431
432   protected boolean validJvresults = false;
433
434   Object[] jvresultobj = null;
435
436   /**
437    * process the results obtained from the server into jalview datamodel objects
438    * ready to be merged/added to the users' view. Use hasResults to test if
439    * results were added to context.
440    */
441   public void parseResultSet() throws Exception, Error
442   {
443     if (!parsedResults)
444     {
445       parsedResults = true;
446       jvresultobj = resSet.parseResultSet();
447       validJvresults = true;
448     }
449   }
450
451   /**
452    * 
453    * @return true if job has an input alignment and it was annotated when
454    *         results were parsed
455    */
456   public boolean isInputContextModified()
457   {
458     return contextAl != null && validJvresults
459             && context.getAl().get(0).isModified();
460   }
461
462   /**
463    * 
464    * @return true if the ID/metadata for the input sequences were saved and
465    *         sequence IDs renamed.
466    */
467   public boolean isInputUniquified()
468   {
469     // TODO Auto-generated method stub
470     return false;
471   }
472
473   /**
474    * Return map between ordering of alignment submitted as input, and ordering
475    * of alignment as provided by user
476    * 
477    * @return int[sequence index in submitted data]==sequence index in input.
478    */
479   public int[] getOrderMap()
480   {
481     SequenceI[] contseq = contextAl.getSequencesArray();
482     int map[] = new int[contseq.length];
483     for (int i = 0; i < contseq.length; i++)
484     {
485       // TODO: optimise for large N - build a lookup hash for IDs returning
486       // order, and then lookup each sequ's original order
487       map[i] = inputOrder.getOrder().indexOf(contseq[i]);
488     }
489     return map;
490   }
491
492 }