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