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