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