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