e04a68124e431ec2ce8541eb8dc81b8f1c6b30a1
[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() >= prm.getValue().min))
104           {
105             alinp.add(prm.getValue());
106           } else {
107             statMessage=("Not enough groups defined on the alignment - need at least "+prm.getValue().min);
108           }
109         }
110       }
111       else
112       {
113         paramsWithData++;
114       }
115     }
116     if ((paramsWithData + alinp.size()) == rsd.inputParams.size())
117     {
118       inputOrder = new AlignmentOrder(_input);
119       if ((dsForIO = _input.getDataset()) == null)
120       {
121         _input.setDataset(null);
122       }
123       dsForIO = _input.getDataset();
124       if (contextAl==null)
125       {
126         contextAl = _input;
127       }
128       setAlignmentForInputs(alinp, _input);
129       validInput = true;
130     }
131     else
132     {
133       // not enough data, so we bail.
134       validInput = false;
135     }
136   }
137   
138   boolean validInput = false;
139
140   @Override
141   public boolean hasResults()
142   {
143     return gotresult && (parsedResults ? validJvresults : true);
144   }
145
146   @Override
147   public boolean hasValidInput()
148   {
149     return validInput;
150   }
151
152   @Override
153   public boolean isRunning()
154   {
155     return running; // TODO: can we check the response body for status messages
156                     // ?
157   }
158
159   @Override
160   public boolean isQueued()
161   {
162     return waiting;
163   }
164
165   @Override
166   public boolean isFinished()
167   {
168     return resSet != null;
169   }
170
171   @Override
172   public boolean isFailed()
173   {
174     // TODO logic for error
175     return error;
176   }
177
178   @Override
179   public boolean isBroken()
180   {
181     // TODO logic for error
182     return error;
183   }
184
185   @Override
186   public boolean isServerError()
187   {
188     // TODO logic for error
189     return error;
190   }
191
192   @Override
193   public boolean hasStatus()
194   {
195     return statMessage != null;
196   }
197
198   protected String statMessage = null;
199
200   public HttpResultSet resSet;
201
202   @Override
203   public String getStatus()
204   {
205     return statMessage;
206   }
207
208   @Override
209   public boolean hasResponse()
210   {
211     return statMessage != null || resSet != null;
212   }
213
214   @Override
215   public void clearResponse()
216   {
217     // only clear the transient server response
218     // statMessage=null;
219   }
220
221   /*
222    * (non-Javadoc)
223    * 
224    * @see jalview.ws.AWsJob#getState()
225    */
226   @Override
227   public String getState()
228   {
229     // TODO generate state string - prolly should have a default abstract method
230     // for this
231     return "Job is clueless";
232   }
233
234   public String getPostUrl()
235   {
236
237     // TODO Auto-generated method stub
238     return rsd.postUrl;
239   }
240
241   public Set<Map.Entry<String, InputType>> getInputParams()
242   {
243     return rsd.inputParams.entrySet();
244   }
245
246   // return the URL that should be polled for this job
247   public String getPollUrl()
248   {
249     return rsd.getDecoratedResultUrl(jobId);
250   }
251
252   /**
253    * 
254    * @return the context for parsing results from service
255    */
256   public JalviewDataset newJalviewDataset()
257   {
258     if (context == null)
259     {
260       context = new JalviewDataset(dsForIO, null, squniq, null);
261       if (contextAl!=null)
262       {
263         // TODO devise way of merging new annotation onto (identical) existing annotation that was used as input
264         // delete all input annotation 
265         if (contextAl.getAlignmentAnnotation()!=null) {
266           for (AlignmentAnnotation alan: contextAl.getAlignmentAnnotation()) {
267             contextAl.deleteAnnotation(alan);
268           }
269         }
270         // TODO devise way of merging new groups onto (identical) existing groups when they were used as input to service
271         // delete all existing groups 
272         if (contextAl.getGroups()!=null)
273         {
274           contextAl.deleteAllGroups();
275         }
276         context.addAlignment(contextAl);
277       }
278       
279     }
280     return context;
281   }
282
283   /**
284    * Extract list of sequence IDs for input parameter 'token' with given
285    * molecule type
286    * 
287    * @param token
288    * @param type
289    * @return
290    */
291   public SequenceI[] getSequencesForInput(String token,
292           InputType.molType type) throws NoValidInputDataException
293   {
294     Object sgdat = inputData.get(token);
295     // can we form an alignment from this data ?
296     if (sgdat == null)
297     {
298       throw new NoValidInputDataException(
299               "No Sequence vector data bound to input '" + token
300                       + "' for service at " + rsd.postUrl);
301     }
302     if (sgdat instanceof AlignmentI)
303     {
304       return ((AlignmentI) sgdat).getSequencesArray();
305     }
306     if (sgdat instanceof SequenceGroup)
307     {
308       return ((SequenceGroup) sgdat).getSequencesAsArray(null);
309     }
310     if (sgdat instanceof Vector)
311     {
312       if (((Vector) sgdat).size() > 0
313               && ((Vector) sgdat).get(0) instanceof SequenceI)
314       {
315         SequenceI[] sq = new SequenceI[((Vector) sgdat).size()];
316         ((Vector) sgdat).copyInto(sq);
317         return sq;
318       }
319     }
320     throw new NoValidInputDataException(
321             "No Sequence vector data bound to input '" + token
322                     + "' for service at " + rsd.postUrl);
323   }
324
325   /**
326    * binding between input data (AlignmentI, SequenceGroup, NJTree) and input
327    * param names.
328    */
329   private Hashtable<String, Object> inputData = new Hashtable<String, Object>();
330
331   /**
332    * is the job fully submitted to server and apparently in progress ?
333    */
334   public boolean running = false;
335
336   /**
337    * 
338    * @param itypes
339    * @param al
340    *          - reference to object to be stored as input. Note - input data may
341    *          be modifed by formatter
342    */
343   public void setAlignmentForInputs(Collection<InputType> itypes,
344           AlignmentI al)
345   {
346     for (InputType itype : itypes)
347     {
348       if (!rsd.inputParams.values().contains(itype))
349       {
350         throw new IllegalArgumentException("InputType " + itype.getClass()
351                 + " is not valid for service at " + rsd.postUrl);
352       }
353       if (itype instanceof AlignmentProcessor)
354       {
355         ((AlignmentProcessor) itype).prepareAlignment(al);
356       }
357       // stash a reference for recall when the alignment data is formatted
358       inputData.put(itype.token, al);
359     }
360
361   }
362
363   /**
364    * 
365    * @param token
366    * @param type
367    * @return alignment object bound to the given token
368    * @throws NoValidInputDataException
369    */
370   public AlignmentI getAlignmentForInput(String token,
371           InputType.molType type) throws NoValidInputDataException
372   {
373     Object al = inputData.get(token);
374     // can we form an alignment from this data ?
375     if (al == null || !(al instanceof AlignmentI))
376     {
377       throw new NoValidInputDataException(
378               "No alignment data bound to input '" + token
379                       + "' for service at " + rsd.postUrl);
380     }
381     return (AlignmentI) al;
382   }
383
384   /**
385    * test to see if the job has data of type cl that's needed for the job to run
386    * 
387    * @param cl
388    * @return true or false
389    */
390   public boolean hasDataOfType(Class cl)
391   {
392     if (AlignmentI.class.isAssignableFrom(cl))
393     {
394       return true;
395     }
396     // TODO: add more source data types
397
398     return false;
399   }
400
401   /**
402    * context used to parse results from service
403    */
404   JalviewDataset context = null;
405   protected boolean parsedResults = false;
406   protected boolean validJvresults=false;
407   Object[] jvresultobj = null;
408   /**
409    * process the results obtained from the server into jalview datamodel objects
410    * ready to be merged/added to the users' view. Use hasResults to test if results were added to context. 
411    */
412   public void parseResultSet() throws Exception, Error
413   {
414     if (!parsedResults) {
415       parsedResults=true;
416       jvresultobj = resSet.parseResultSet();
417       validJvresults = true;
418     }
419   }
420
421   /**
422    * 
423    * @return true if job has an input alignment and it was annotated when results were parsed
424    */
425   public boolean isInputContextModified()
426   {
427     return contextAl!=null && validJvresults && context.getAl().get(0).isModified();
428   }
429
430   /**
431    * 
432    * @return true if the ID/metadata for the input sequences were saved and sequence IDs renamed.
433    */
434   public boolean isInputUniquified()
435   {
436     // TODO Auto-generated method stub
437     return false;
438   }
439
440   /**
441    * Return map between ordering of alignment submitted as input, and ordering of alignment as provided by user
442    * @return int[sequence index in submitted data]==sequence index in input.
443    */
444   public int[] getOrderMap()
445   {
446     SequenceI[] contseq = contextAl.getSequencesArray();
447     int map[] = new int[contseq.length];
448     for (int i=0;i<contseq.length;i++)
449     {
450       // TODO: optimise for large N - build a lookup hash for IDs returning order, and then lookup each sequ's original order
451       map[i] = inputOrder.getOrder().indexOf(contseq[i]);
452     }
453     return map;
454   }
455
456 }