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