update author list in license for (JAL-826)
[jalview.git] / src / jalview / ws / rest / RestJob.java
1 /*******************************************************************************
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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.io.IOException;
21 import java.sql.ResultSet;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Hashtable;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import java.util.Vector;
30
31 import jalview.datamodel.AlignmentAnnotation;
32 import jalview.datamodel.AlignmentI;
33 import jalview.datamodel.AlignmentOrder;
34 import jalview.datamodel.AlignmentView;
35 import jalview.datamodel.SequenceGroup;
36 import jalview.datamodel.SequenceI;
37 import jalview.io.packed.DataProvider;
38 import jalview.io.packed.JalviewDataset;
39 import jalview.io.packed.ParsePackedSet;
40 import jalview.io.packed.SimpleDataProvider;
41 import jalview.io.packed.DataProvider.JvDataType;
42 import jalview.ws.AWsJob;
43 import jalview.ws.rest.params.Alignment;
44 import jalview.ws.rest.params.SeqGroupIndexVector;
45
46 public class RestJob extends AWsJob
47 {
48
49   // TODO: input alignmentview and other data for this job
50   RestServiceDescription rsd;
51
52   // boolean submitted;
53   boolean gotresponse;
54
55   boolean error;
56
57   boolean waiting;
58
59   boolean gotresult;
60
61   Hashtable squniq;
62
63   /**
64    * dataset associated with this input data.
65    */
66   AlignmentI dsForIO;
67
68   AlignmentOrder inputOrder;
69
70   /**
71    * context of input data with respect to an AlignmentView's visible contigs.
72    */
73   int[] origviscontig;
74
75   private AlignmentI contextAl=null;
76
77   /**
78    * create a rest job using data bounded by the given start/end column.
79    * 
80    * @param addJobPane
81    * @param restJobThread
82    * @param _input
83    * @param viscontigs
84    *          visible contigs of an alignment view from which _input was derived
85    */
86   public RestJob(int jobNum, RestJobThread restJobThread,
87           AlignmentI _input, int[] viscontigs)
88   {
89     rsd = restJobThread.restClient.service;
90     jobnum = jobNum;
91     if (viscontigs != null)
92     {
93       origviscontig = new int[viscontigs.length];
94       System.arraycopy(viscontigs, 0, origviscontig, 0, viscontigs.length);
95     }
96     // get sequences for the alignmentI
97     // get groups trimmed to alignment columns
98     // get any annotation trimmed to start/end columns, too.
99
100     // prepare input
101     // form alignment+groups+annotation,preprocess and then record references
102     // for formatters
103     ArrayList<InputType> alinp = new ArrayList<InputType>();
104     int paramsWithData = 0;
105     // we cheat for moment - since we know a-priori what data is available and
106     // what inputs we have implemented so far
107     for (Map.Entry<String, InputType> prm : rsd.inputParams.entrySet())
108     {
109       if (!prm.getValue().isConstant())
110       {
111         if (prm.getValue() instanceof Alignment)
112         {
113           alinp.add(prm.getValue());
114         }
115         else
116         {
117           // TODO: move validation of input data to SeqGroupIndexVector
118           if ((prm.getValue() instanceof SeqGroupIndexVector)
119                   && (_input.getGroups() != null && _input.getGroups()
120                           .size() >= prm.getValue().min))
121           {
122             alinp.add(prm.getValue());
123           } else {
124             statMessage=("Not enough groups defined on the alignment - need at least "+prm.getValue().min);
125           }
126         }
127       }
128       else
129       {
130         paramsWithData++;
131       }
132     }
133     if ((paramsWithData + alinp.size()) == rsd.inputParams.size())
134     {
135       inputOrder = new AlignmentOrder(_input);
136       if ((dsForIO = _input.getDataset()) == null)
137       {
138         _input.setDataset(null);
139       }
140       dsForIO = _input.getDataset();
141       if (contextAl==null)
142       {
143         contextAl = _input;
144       }
145       setAlignmentForInputs(alinp, _input);
146       validInput = true;
147     }
148     else
149     {
150       // not enough data, so we bail.
151       validInput = false;
152     }
153   }
154   
155   boolean validInput = false;
156
157   @Override
158   public boolean hasResults()
159   {
160     return gotresult && (parsedResults ? validJvresults : true);
161   }
162
163   @Override
164   public boolean hasValidInput()
165   {
166     return validInput;
167   }
168
169   @Override
170   public boolean isRunning()
171   {
172     return running; // TODO: can we check the response body for status messages
173                     // ?
174   }
175
176   @Override
177   public boolean isQueued()
178   {
179     return waiting;
180   }
181
182   @Override
183   public boolean isFinished()
184   {
185     return resSet != null;
186   }
187
188   @Override
189   public boolean isFailed()
190   {
191     // TODO logic for error
192     return error;
193   }
194
195   @Override
196   public boolean isBroken()
197   {
198     // TODO logic for error
199     return error;
200   }
201
202   @Override
203   public boolean isServerError()
204   {
205     // TODO logic for error
206     return error;
207   }
208
209   @Override
210   public boolean hasStatus()
211   {
212     return statMessage != null;
213   }
214
215   protected String statMessage = null;
216
217   public HttpResultSet resSet;
218
219   @Override
220   public String getStatus()
221   {
222     return statMessage;
223   }
224
225   @Override
226   public boolean hasResponse()
227   {
228     return statMessage != null || resSet != null;
229   }
230
231   @Override
232   public void clearResponse()
233   {
234     // only clear the transient server response
235     // statMessage=null;
236   }
237
238   /*
239    * (non-Javadoc)
240    * 
241    * @see jalview.ws.AWsJob#getState()
242    */
243   @Override
244   public String getState()
245   {
246     // TODO generate state string - prolly should have a default abstract method
247     // for this
248     return "Job is clueless";
249   }
250
251   public String getPostUrl()
252   {
253
254     // TODO Auto-generated method stub
255     return rsd.postUrl;
256   }
257
258   public Set<Map.Entry<String, InputType>> getInputParams()
259   {
260     return rsd.inputParams.entrySet();
261   }
262
263   // return the URL that should be polled for this job
264   public String getPollUrl()
265   {
266     return rsd.getDecoratedResultUrl(jobId);
267   }
268
269   /**
270    * 
271    * @return the context for parsing results from service
272    */
273   public JalviewDataset newJalviewDataset()
274   {
275     if (context == null)
276     {
277       context = new JalviewDataset(dsForIO, null, squniq, null);
278       if (contextAl!=null)
279       {
280         // TODO devise way of merging new annotation onto (identical) existing annotation that was used as input
281         // delete all input annotation 
282         if (contextAl.getAlignmentAnnotation()!=null) {
283           for (AlignmentAnnotation alan: contextAl.getAlignmentAnnotation()) {
284             contextAl.deleteAnnotation(alan);
285           }
286         }
287         // TODO devise way of merging new groups onto (identical) existing 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   protected boolean parsedResults = false;
423   protected boolean validJvresults=false;
424   Object[] jvresultobj = null;
425   /**
426    * process the results obtained from the server into jalview datamodel objects
427    * ready to be merged/added to the users' view. Use hasResults to test if results were added to context. 
428    */
429   public void parseResultSet() throws Exception, Error
430   {
431     if (!parsedResults) {
432       parsedResults=true;
433       jvresultobj = resSet.parseResultSet();
434       validJvresults = true;
435     }
436   }
437
438   /**
439    * 
440    * @return true if job has an input alignment and it was annotated when results were parsed
441    */
442   public boolean isInputContextModified()
443   {
444     return contextAl!=null && validJvresults && context.getAl().get(0).isModified();
445   }
446
447   /**
448    * 
449    * @return true if the ID/metadata for the input sequences were saved and sequence IDs renamed.
450    */
451   public boolean isInputUniquified()
452   {
453     // TODO Auto-generated method stub
454     return false;
455   }
456
457   /**
458    * Return map between ordering of alignment submitted as input, and ordering of alignment as provided by user
459    * @return int[sequence index in submitted data]==sequence index in input.
460    */
461   public int[] getOrderMap()
462   {
463     SequenceI[] contseq = contextAl.getSequencesArray();
464     int map[] = new int[contseq.length];
465     for (int i=0;i<contseq.length;i++)
466     {
467       // TODO: optimise for large N - build a lookup hash for IDs returning order, and then lookup each sequ's original order
468       map[i] = inputOrder.getOrder().indexOf(contseq[i]);
469     }
470     return map;
471   }
472
473 }