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