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