0d56e87d151f067eff977a92b0e77a0beea2e31c
[jalview.git] / src / jalview / ws / rest / RestClient.java
1 /**
2  * 
3  */
4 package jalview.ws.rest;
5
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.util.Collection;
9 import java.util.Hashtable;
10 import java.util.Vector;
11
12 import javax.swing.JMenu;
13 import javax.swing.JMenuItem;
14 import javax.swing.JOptionPane;
15 import javax.swing.event.MenuEvent;
16 import javax.swing.event.MenuListener;
17
18 import jalview.bin.Cache;
19 import jalview.datamodel.AlignmentView;
20 import jalview.gui.AlignFrame;
21 import jalview.gui.AlignViewport;
22 import jalview.gui.AlignmentPanel;
23 import jalview.gui.Desktop;
24 import jalview.gui.WebserviceInfo;
25 import jalview.io.packed.DataProvider.JvDataType;
26 import jalview.ws.WSClient;
27 import jalview.ws.WSClientI;
28 import jalview.ws.WSMenuEntryProviderI;
29
30 /**
31  * @author JimP
32  * 
33  */
34 public class RestClient extends WSClient implements WSClientI,
35         WSMenuEntryProviderI
36 {
37   RestServiceDescription service;
38
39   public RestClient(RestServiceDescription rsd)
40   {
41     service = rsd;
42   }
43
44   /**
45    * parent alignframe for this job
46    */
47   AlignFrame af;
48
49   /**
50    * alignment view which provides data for job.
51    */
52   AlignViewport av;
53
54   /**
55    * get the alignFrame for the associated input data if it exists.
56    * 
57    * @return
58    */
59   protected AlignFrame recoverAlignFrameForView()
60   {
61     return jalview.gui.Desktop.getAlignFrameFor(av);
62   }
63
64   public RestClient(RestServiceDescription service2, AlignFrame alignFrame)
65   {
66     service = service2;
67     af = alignFrame;
68     av = alignFrame.getViewport();
69     constructJob();
70   }
71
72   public void setWebserviceInfo(boolean headless)
73   {
74     WebServiceJobTitle = service.details.Action + " using "
75             + service.details.Name;
76     WebServiceName = service.details.Name;
77     WebServiceReference = "No reference - go to url for more info";
78     if (service.details.description != null)
79     {
80       WebServiceReference = service.details.description;
81     }
82     if (!headless)
83     {
84       wsInfo = new WebserviceInfo(WebServiceJobTitle, WebServiceName + "\n"
85               + WebServiceReference);
86       wsInfo.setRenderAsHtml(true);
87     }
88
89   }
90
91   @Override
92   public boolean isCancellable()
93   {
94     // TODO define process for cancelling rsbws jobs
95     return false;
96   }
97
98   @Override
99   public boolean canMergeResults()
100   {
101     // TODO process service definition to identify if the results might be
102     // mergeable
103     // TODO: change comparison for annotation merge
104     return false;
105   }
106
107   @Override
108   public void cancelJob()
109   {
110     System.err.println("Cannot cancel this job type: " + service);
111   }
112
113   @Override
114   public void attachWSMenuEntry(final JMenu wsmenu,
115           final AlignFrame alignFrame)
116   {
117     JMenuItem submit = new JMenuItem(service.details.Name);
118     submit.setToolTipText(service.details.Action+" using "+service.details.Name);
119     submit.addActionListener(new ActionListener()
120     {
121
122       @Override
123       public void actionPerformed(ActionEvent e)
124       {
125         new RestClient(service, alignFrame);
126       }
127
128     });
129     wsmenu.add(submit);
130     // TODO: menu listener should enable/disable entry depending upon selection
131     // state of the alignment
132     wsmenu.addMenuListener(new MenuListener()
133     {
134
135       @Override
136       public void menuSelected(MenuEvent e)
137       {
138         // TODO Auto-generated method stub
139
140       }
141
142       @Override
143       public void menuDeselected(MenuEvent e)
144       {
145         // TODO Auto-generated method stub
146
147       }
148
149       @Override
150       public void menuCanceled(MenuEvent e)
151       {
152         // TODO Auto-generated method stub
153
154       }
155
156     });
157
158   }
159
160   /**
161    * record of initial undoredo hash for the alignFrame providing data for this
162    * job.
163    */
164   long[] undoredo = null;
165
166   /**
167    * Compare the original input data to the data currently presented to the
168    * user. // LOGIC: compare undo/redo - if same, merge regardless (coping with
169    * any changes in hidden columns as normal) // if different undo/redo then
170    * compare region that was submitted // if same, then merge as before, if
171    * different then prompt user to open a new window.
172    * 
173    * @return
174    */
175   protected boolean isAlignmentModified()
176   {
177     if (undoredo == null || av==null || av.getAlignment()==null)
178     {
179       // always return modified if we don't have access to live GUI elements anymore.
180       return true;
181     }
182     if (av.isUndoRedoHashModified(undoredo))
183     {
184       // alignment has been modified in some way. 
185       return true;
186     }
187     // TODO: look deeper into modification of selection state, etc that may affect RestJobThread.realiseResults(boolean merge); 
188     return false;
189
190   }
191
192   /**
193    * TODO: combine to form a dataset+alignment+annotation context
194    */
195   AlignmentView _input;
196
197   /**
198    * input data context
199    */
200   jalview.io.packed.JalviewDataset jds;
201
202   /**
203    * informative name for results
204    */
205   public String viewTitle;
206
207   protected void constructJob()
208   {
209     service.setInvolvesFlags();
210     // record all aspects of alignment view so we can merge back or recreate
211     // later
212     undoredo = av.getUndoRedoHash();
213     /**
214      * delete ? Vector sgs = av.getAlignment().getGroups(); if (sgs!=null) {
215      * _sgs = new SequenceGroup[sgs.size()]; sgs.copyInto(_sgs); } else { _sgs =
216      * new SequenceGroup[0]; }
217      */
218     boolean selExists = (av.getSelectionGroup() != null)
219             && (av.getSelectionGroup().getSize() > 1);
220     // TODO: JAL-715: refactor to alignViewport methods and revise to full focus+context+dataset input data staging model
221     if (selExists)
222     {
223       if (service.partitiondata)
224       {
225         if (av.getAlignment().getGroups()!=null && av.getAlignment().getGroups().size() > 0)
226         {
227           // intersect groups with selected region
228           _input = new AlignmentView(av.getAlignment(), 
229                   av.getColumnSelection(), 
230                   av.getSelectionGroup(), 
231                   av.hasHiddenColumns(), 
232                   true, 
233                   true);
234           viewTitle = "selected "+(av.hasHiddenColumns() ? "visible" : "") + " region of "+af.getTitle();
235         }
236         else
237         {
238           // use selected region to partition alignment
239           _input = new AlignmentView(av.getAlignment(), 
240                   av.getColumnSelection(), 
241                   av.getSelectionGroup(), 
242                   av.hasHiddenColumns(), 
243                   false, 
244                   true);
245         }
246         viewTitle = "select and unselected "+(av.hasHiddenColumns() ? "visible" : "") + " regions from "+af.getTitle();
247       }
248       else
249       {
250         // just take selected region intersection
251         _input = new AlignmentView(av.getAlignment(), 
252                 av.getColumnSelection(), 
253                 av.getSelectionGroup(), 
254                 av.hasHiddenColumns(), 
255                 true, 
256                 true);
257         viewTitle = "selected "+(av.hasHiddenColumns() ? "visible" : "") + " region of "+af.getTitle();
258       }
259     } else {
260       // standard alignment view without selection present
261       _input = new AlignmentView(av.getAlignment(), 
262               av.getColumnSelection(), 
263               null, 
264               av.hasHiddenColumns(), 
265               false, 
266               true);
267       viewTitle = ""+(av.hasHiddenColumns() ? "visible region of " : "") + af.getTitle();
268     }
269     
270     RestJobThread jobsthread = new RestJobThread(this);
271     
272     if (jobsthread.isValid())
273     {
274       setWebserviceInfo(false);
275       wsInfo.setthisService(this);
276       jobsthread.setWebServiceInfo(wsInfo);
277       jobsthread.start();
278     }
279     else
280     {
281       // TODO: try to tell the user why the job couldn't be started.
282       JOptionPane.showMessageDialog(Desktop.desktop,
283               (jobsthread.hasWarnings() ? jobsthread.getWarnings() : "The Job couldn't be started. Please check your input, and the Jalview console for any warning messages."),
284               "Unable to start web service analysis", JOptionPane.WARNING_MESSAGE);
285     }
286   }
287
288   public static RestClient makeShmmrRestClient()
289   {
290     String action = "Analysis", description = "Sequence Harmony and Multi-Relief (UNSTABLE!)", name = "Sequence Harmony";
291     Hashtable<String, InputType> iparams = new Hashtable<String, InputType>();
292     jalview.ws.rest.params.JobConstant toolp;
293     //toolp = new jalview.ws.rest.JobConstant("tool","jalview");
294     //iparams.put(toolp.token, toolp);
295     toolp = new jalview.ws.rest.params.JobConstant("mbjob[method]","shmr");
296     iparams.put(toolp.token, toolp);
297     toolp = new jalview.ws.rest.params.JobConstant("mbjob[description]","step 1");
298     iparams.put(toolp.token, toolp);
299     toolp = new jalview.ws.rest.params.JobConstant("start_search","1");
300     iparams.put(toolp.token, toolp);
301     toolp = new jalview.ws.rest.params.JobConstant("blast","0");
302     iparams.put(toolp.token, toolp);
303     
304     jalview.ws.rest.params.Alignment aliinput = new jalview.ws.rest.params.Alignment();
305     aliinput.token = "ali";//_file";
306     aliinput.writeAsFile=false;//true;
307     //aliinput.token = "ali_file";
308     //aliinput.writeAsFile=true;
309     iparams.put(aliinput.token, aliinput);
310     jalview.ws.rest.params.SeqGroupIndexVector sgroups = new jalview.ws.rest.params.SeqGroupIndexVector();
311     sgroups.setMinsize(2);
312     sgroups.min=2;// need at least two group defined to make a partition
313     iparams.put("groups", sgroups);
314     sgroups.token = "groups";
315     sgroups.sep = " ";
316     RestServiceDescription shmrService = new RestServiceDescription(
317             action,
318             description,
319             name,
320             "http://www.ibi.vu.nl/programs/shmrwww/index.php?tool=jalview",// ?tool=jalview&mbjob[method]=shmr&mbjob[description]=step1",
321             "?tool=jalview", iparams, true, false, '-');
322     // a priori knowledge of the data returned from the service
323     shmrService.addResultDatatype(JvDataType.ANNOTATION);
324     return new RestClient(shmrService);
325   }
326
327   public AlignmentPanel recoverAlignPanelForView()
328   {
329     AlignmentPanel[] aps = Desktop.getAlignmentPanels(av.getSequenceSetId());
330     for (AlignmentPanel alp:aps)
331     {
332       if (alp.av == av)
333       {
334         return alp;
335       }
336     }
337     return null;
338   }
339
340   public boolean isShowResultsInNewView()
341   {
342     // TODO make this a property of the service
343     return true;
344   }
345
346   protected static Vector<String> services=null;
347   public static final String RSBS_SERVICES="RSBS_SERVICES";
348   public static RestClient[] getRestClients()
349   {
350     if (services==null)
351     {
352       services = new Vector<String>();
353       try {
354       for (RestServiceDescription descr: RestServiceDescription.parseDescriptions(jalview.bin.Cache.getDefault(RSBS_SERVICES,makeShmmrRestClient().service.toString()))) 
355       {
356         services.add(descr.toString());
357       }
358       }
359       catch (Exception ex) {
360         System.err.println("Serious - RSBS descriptions in user preferences are corrupt!");
361         ex.printStackTrace();
362       }
363       
364     }
365     RestClient[] lst = new RestClient[services.size()];
366     int i=0;
367     for (String svc:services) {
368       lst[i++] = new RestClient(new RestServiceDescription(svc));
369     }
370     return lst;
371   }
372   public static void main(String args[])
373   {
374     try {
375       RestClient[] clients = getRestClients();
376       System.out.println("Got "+clients.length+" clients.");
377       int i=0;
378       Vector<String> urls=new Vector<String>();
379       for (RestClient cl:clients) {
380         System.out.println(""+(++i)+": "+cl.service.toString());
381         urls.add(cl.service.toString());
382       }
383       setRsbsServices(urls);
384       if (clients.length!=getRestClients().length)
385       {
386         System.err.println("Failed. Differing numbers of clients when stringified and parsed again.");
387       }
388       
389     } catch (Throwable x)
390     {
391       System.err.println("Failed. Unexpected exception.");
392       x.printStackTrace();
393     }
394   }
395   public String getAction()
396   {
397     return service.details.Action;
398   }
399
400   public RestServiceDescription getRestDescription()
401   {
402     return service;
403   }
404
405   public static Vector<String> getRsbsDescriptions()
406   {
407     Vector<String> rsbsDescrs = new Vector<String>();
408     for (RestClient rsbs:getRestClients())
409     {
410       rsbsDescrs.add(rsbs.getRestDescription().toString());
411     }
412     return rsbsDescrs;
413   }
414
415   public static void setRsbsServices(Vector<String> rsbsUrls)
416   {
417     if (rsbsUrls!=null)
418     {
419       // TODO: consider validating services ?
420       services = new Vector<String>(rsbsUrls);
421       StringBuffer sprop = new StringBuffer();
422       for (String s:services)
423       {
424         sprop.append(s);
425       }
426       Cache.setProperty(RSBS_SERVICES, sprop.toString());
427     } 
428     else
429     {
430       Cache.removeProperty(RSBS_SERVICES);
431     }
432   }
433
434 }