e56a5e51ac9278ff67c6ac7953d65d32bda87714
[jalview.git] / src / jalview / ws / jws2 / Jws2Discoverer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.ws.jws2;
19
20 import java.awt.Color;
21 import java.awt.event.ActionEvent;
22 import java.awt.event.ActionListener;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
25 import java.io.Closeable;
26 import java.net.ConnectException;
27 import java.net.URL;
28 import java.util.HashSet;
29 import java.util.Hashtable;
30 import java.util.StringTokenizer;
31 import java.util.Vector;
32
33 import javax.swing.JMenu;
34 import javax.swing.JMenuItem;
35 import javax.swing.event.MenuEvent;
36 import javax.swing.event.MenuListener;
37
38 import org.apache.log4j.Level;
39
40 import jalview.bin.Cache;
41 import jalview.datamodel.AlignmentView;
42 import jalview.gui.AlignFrame;
43 import jalview.gui.Desktop;
44 import jalview.ws.WSMenuEntryProviderI;
45 import jalview.ws.params.ParamDatastoreI;
46 import compbio.data.msa.MsaWS;
47 import compbio.metadata.Option;
48 import compbio.metadata.Preset;
49 import compbio.metadata.PresetManager;
50 import compbio.metadata.RunnerConfig;
51 import compbio.ws.client.Jws2Client;
52 import compbio.ws.client.Services;
53
54 /**
55  * discoverer for jws2 services. Follows the lightweight service discoverer
56  * pattern (archetyped by EnfinEnvision2OneWay)
57  * 
58  * @author JimP
59  * 
60  */
61 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
62 {
63   private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
64           this);
65
66   /**
67    * change listeners are notified of "services" property changes
68    * 
69    * @param listener
70    *          to be added that consumes new services Hashtable object.
71    */
72   public void addPropertyChangeListener(
73           java.beans.PropertyChangeListener listener)
74   {
75     changeSupport.addPropertyChangeListener(listener);
76   }
77
78   /**
79    * 
80    * 
81    * @param listener
82    *          to be removed
83    */
84   public void removePropertyChangeListener(
85           java.beans.PropertyChangeListener listener)
86   {
87     changeSupport.removePropertyChangeListener(listener);
88   }
89
90   boolean running = false;
91
92   Thread oldthread = null;
93
94   public void run()
95   {
96     if (running && oldthread != null && oldthread.isAlive())
97     {
98       return;
99     }
100     running = true;
101     oldthread = Thread.currentThread();
102     try
103     {
104       Class foo = getClass().getClassLoader().loadClass(
105               "compbio.ws.client.Jws2Client");
106     } catch (ClassNotFoundException e)
107     {
108       System.err
109               .println("Not enabling Jalview Webservices version 2: client jar is not available."
110                       + "\nPlease check that your webstart JNLP file is up to date!");
111       running = false;
112       return;
113     }
114     if (services != null)
115     {
116       services.removeAllElements();
117     }
118     for (String jwsservers : getServiceUrls())
119     {
120       try
121       {
122         if (Jws2Client.validURL(jwsservers))
123         {
124           // look for services
125           for (Services srv : Services.values())
126           {
127             MsaWS service = null;
128             try
129             {
130               service = Jws2Client.connect(jwsservers, srv);
131             } catch (Exception e)
132             {
133               System.err.println("Jws2 Discoverer: Problem on "
134                       + jwsservers + " with service " + srv + ":\n"
135                       + e.getMessage());
136               if (!(e instanceof javax.xml.ws.WebServiceException))
137               {
138                 e.printStackTrace();
139               }
140             }
141             ;
142             if (service != null)
143             {
144               addService(jwsservers, srv, service);
145             }
146           }
147
148         }
149         else
150         {
151           Cache.log.info("Ignoring invalid Jws2 service url " + jwsservers);
152         }
153       } catch (Exception e)
154       {
155         e.printStackTrace();
156         Cache.log.warn("Exception when discovering Jws2 services.", e);
157       } catch (Error e)
158       {
159         Cache.log.error("Exception when discovering Jws2 services.", e);
160       }
161     }
162     oldthread = null;
163     running = false;
164     changeSupport.firePropertyChange("services", new Vector(), services);
165   }
166
167   /**
168    * record this service endpoint so we can use it
169    * 
170    * @param jwsservers
171    * @param srv
172    * @param service2
173    */
174   private void addService(String jwsservers, Services srv, MsaWS service2)
175   {
176     if (services == null)
177     {
178       services = new Vector<Jws2Instance>();
179     }
180     System.out.println("Discovered service: " + jwsservers + " "
181             + srv.toString());
182     Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
183             service2);
184
185     services.add(service);
186     // retrieve the presets and parameter set and cache now
187     service.getParamStore().getPresets();
188     service.hasParameters();
189   }
190
191   public class Jws2Instance
192   {
193     public String hosturl;
194
195     public String serviceType;
196
197     public MsaWS service;
198
199     public Jws2Instance(String hosturl, String serviceType, MsaWS service)
200     {
201       super();
202       this.hosturl = hosturl;
203       this.serviceType = serviceType;
204       this.service = service;
205     }
206
207     PresetManager presets = null;
208
209     public JabaParamStore paramStore = null;
210
211     /**
212      * non thread safe - gets the presets for this service (blocks whilst it
213      * calls the service to get the preset set)
214      * 
215      * @return service presets or null if exceptions were raised.
216      */
217     public PresetManager getPresets()
218     {
219       if (presets == null)
220       {
221         try
222         {
223           presets = service.getPresets();
224         } catch (Exception ex)
225         {
226           System.err
227                   .println("Exception when retrieving presets for service "
228                           + serviceType + " at " + hosturl);
229         }
230       }
231       return presets;
232     }
233
234     public String getHost()
235     {
236       return hosturl;
237       /*
238        * try { URL serviceurl = new URL(hosturl); if (serviceurl.getPort()!=80)
239        * { return serviceurl.getHost()+":"+serviceurl.getPort(); } return
240        * serviceurl.getHost(); } catch (Exception e) {
241        * System.err.println("Failed to parse service URL '" + hosturl +
242        * "' as a valid URL!"); } return null;
243        */
244     }
245
246     /**
247      * @return short description of what the service will do
248      */
249     public String getActionText()
250     {
251       return "Align with " + serviceType;
252     }
253
254     /**
255      * non-thread safe - blocks whilst accessing service to get complete set of
256      * available options and parameters
257      * 
258      * @return
259      */
260     public RunnerConfig getRunnerConfig()
261     {
262       return service.getRunnerOptions();
263     }
264
265     @Override
266     protected void finalize() throws Throwable
267     {
268       if (service != null)
269       {
270         try
271         {
272           Closeable svc = (Closeable) service;
273           service = null;
274           svc.close();
275         } catch (Exception e)
276         {
277         }
278         ;
279       }
280       super.finalize();
281     }
282
283     public ParamDatastoreI getParamStore()
284     {
285       if (paramStore == null)
286       {
287         try
288         {
289           paramStore = new JabaParamStore(this,
290                   (Desktop.instance != null ? Desktop
291                           .getUserParameterStore() : null));
292         } catch (Exception ex)
293         {
294         }
295
296       }
297       return paramStore;
298     }
299
300     public String getUri()
301     {
302       // this is only valid for Jaba 1.0 - this formula might have to change!
303       return hosturl
304               + (hosturl.lastIndexOf("/") == (hosturl.length() - 1) ? "/"
305                       : "") + serviceType;
306     }
307
308     private boolean hasParams = false, lookedForParams = false;
309
310     public boolean hasParameters()
311     {
312       if (!lookedForParams)
313       {
314         lookedForParams = true;
315         try
316         {
317           hasParams = (getRunnerConfig().getArguments().size() > 0);
318         } catch (Exception e)
319         {
320
321         }
322       }
323       return hasParams;
324     }
325   };
326
327   /**
328    * holds list of services.
329    */
330   protected Vector<Jws2Instance> services;
331
332   /**
333    * find or add a submenu with the given title in the given menu
334    * 
335    * @param menu
336    * @param submenu
337    * @return the new or existing submenu
338    */
339   private JMenu findOrCreateMenu(JMenu menu, String submenu)
340   {
341     JMenu submenuinstance = null;
342     for (int i = 0, iSize = menu.getMenuComponentCount(); i < iSize; i++)
343     {
344       if (menu.getMenuComponent(i) instanceof JMenu
345               && ((JMenu) menu.getMenuComponent(i)).getText().equals(
346                       submenu))
347       {
348         submenuinstance = (JMenu) menu.getMenuComponent(i);
349       }
350     }
351     if (submenuinstance == null)
352     {
353       submenuinstance = new JMenu(submenu);
354       menu.add(submenuinstance);
355     }
356     return submenuinstance;
357
358   }
359
360   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
361   {
362     // dynamically regenerate service list.
363     final JMenu jws2al = new JMenu("JABAWS Alignment");
364     jws2al.addMenuListener(new MenuListener()
365     {
366       // TODO: future: add menu listener to parent menu - so submenus are
367       // populated *before* they are selected.
368       @Override
369       public void menuSelected(MenuEvent e)
370       {
371         populateWSMenuEntry(jws2al, alignFrame);
372       }
373
374       @Override
375       public void menuDeselected(MenuEvent e)
376       {
377         // TODO Auto-generated method stub
378
379       }
380
381       @Override
382       public void menuCanceled(MenuEvent e)
383       {
384         // TODO Auto-generated method stub
385
386       }
387
388     });
389     wsmenu.add(jws2al);
390
391   }
392
393   private void populateWSMenuEntry(JMenu jws2al, final AlignFrame alignFrame)
394   {
395     if (running || services == null || services.size() == 0)
396     {
397       return;
398     }
399     boolean byhost = Cache.getDefault("WSMENU_BYHOST", true), bytype = Cache
400             .getDefault("WSMENU_BYTYPE", true);
401     /**
402      * eventually, JWS2 services will appear under the same align/etc submenus.
403      * for moment we keep them separate.
404      */
405     JMenu atpoint;
406     MsaWSClient msacl = new MsaWSClient();
407     Vector hostLabels = new Vector();
408     jws2al.removeAll();
409     String lasthost = null;
410     for (final Jws2Instance service : services)
411     {
412       atpoint = jws2al;
413       String host = service.getHost();
414       String type = service.serviceType;
415       if (byhost)
416       {
417         atpoint = findOrCreateMenu(atpoint, host);
418         if (atpoint.getToolTipText() == null)
419         {
420           atpoint.setToolTipText("Services at " + host);
421         }
422       }
423       if (bytype)
424       {
425         atpoint = findOrCreateMenu(atpoint, type);
426         if (atpoint.getToolTipText() == null)
427         {
428           atpoint.setToolTipText(service.getActionText());
429         }
430       }
431       if (!byhost
432               && !hostLabels.contains(host + service.serviceType
433                       + service.getActionText()))
434       // !hostLabels.contains(host + (bytype ?
435       // service.serviceType+service.getActionText() : "")))
436       {
437         // add a marker indicating where this service is hosted
438         // relies on services from the same host being listed in a contiguous
439         // group
440         JMenuItem hitm;
441         atpoint.addSeparator();
442         if (lasthost == null || !lasthost.equals(host))
443         {
444           atpoint.add(hitm = new JMenuItem(host));
445           hitm.setForeground(Color.blue);
446           lasthost = host;
447         }
448         hostLabels.addElement(host + service.serviceType
449                 + service.getActionText());
450         // hostLabels.addElement(host + (bytype ?
451         // service.serviceType+service.getActionText() : ""));
452       }
453       msacl.attachWSMenuEntry(atpoint, service, alignFrame);
454       /*
455        * JMenuItem sitem = new JMenuItem(service.serviceType);
456        * sitem.setToolTipText("Hosted at " + service.hosturl);
457        * sitem.addActionListener(new ActionListener() {
458        * 
459        * @Override public void actionPerformed(ActionEvent e) { AlignmentView
460        * msa = alignFrame.gatherSequencesForAlignment(); MsaWSClient client =
461        * new MsaWSClient(service, "JWS2 Alignment of " + alignFrame.getTitle(),
462        * msa, false, true, alignFrame.getViewport().getAlignment().getDataset(),
463        * alignFrame); } });
464        */
465     }
466   }
467
468   public static void main(String[] args)
469   {
470     Thread runner = new Thread(getDiscoverer());
471     getDiscoverer().addPropertyChangeListener(new PropertyChangeListener()
472     {
473
474       public void propertyChange(PropertyChangeEvent evt)
475       {
476         System.out.println("Changesupport: There are now "
477                 + getDiscoverer().services.size() + " services");
478       }
479     });
480     runner.start();
481     while (runner.isAlive())
482     {
483       try
484       {
485         Thread.sleep(50);
486       } catch (InterruptedException e)
487       {
488       }
489       ;
490     }
491   }
492
493   private static Jws2Discoverer discoverer;
494
495   public static Jws2Discoverer getDiscoverer()
496   {
497     if (discoverer == null)
498     {
499       discoverer = new Jws2Discoverer();
500     }
501     return discoverer;
502   }
503
504   public boolean hasServices()
505   {
506     // TODO Auto-generated method stub
507     return !running && services != null && services.size() > 0;
508   }
509
510   public boolean isRunning()
511   {
512     return running;
513   }
514
515   /**
516    * the jalview .properties entry for JWS2 URLS
517    */
518   final static String JWS2HOSTURLS = "JWS2HOSTURLS";
519
520   public static void setServiceUrls(Vector<String> urls)
521   {
522     if (urls != null)
523     {
524       StringBuffer urlbuffer = new StringBuffer();
525       String sep = "";
526       for (String url : urls)
527       {
528         urlbuffer.append(sep);
529         urlbuffer.append(url);
530         sep = ",";
531       }
532       Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
533     }
534     else
535     {
536       Cache.removeProperty(JWS2HOSTURLS);
537     }
538   }
539
540   public static Vector<String> getServiceUrls()
541   {
542     String surls = Cache.getDefault(JWS2HOSTURLS,
543             "http://www.compbio.dundee.ac.uk/jabaws");
544     Vector<String> urls = new Vector<String>();
545     try
546     {
547       StringTokenizer st = new StringTokenizer(surls, ",");
548       while (st.hasMoreElements())
549       {
550         String url = null;
551         try
552         {
553           java.net.URL u = new java.net.URL(url = st.nextToken());
554           if (!urls.contains(url))
555           {
556             urls.add(url);
557           }
558           else
559           {
560             jalview.bin.Cache.log.info("Ignoring duplicate url in "
561                     + JWS2HOSTURLS + " list");
562           }
563         } catch (Exception ex)
564         {
565           jalview.bin.Cache.log
566                   .warn("Problem whilst trying to make a URL from '"
567                           + ((url != null) ? url : "<null>") + "'");
568           jalview.bin.Cache.log
569                   .warn("This was probably due to a malformed comma separated list"
570                           + " in the "
571                           + JWS2HOSTURLS
572                           + " entry of $(HOME)/.jalview_properties)");
573           jalview.bin.Cache.log.debug("Exception was ", ex);
574         }
575       }
576     } catch (Exception ex)
577     {
578       jalview.bin.Cache.log.warn(
579               "Error parsing comma separated list of urls in "
580                       + JWS2HOSTURLS + " preference.", ex);
581     }
582     if (urls.size() >= 0)
583     {
584       return urls;
585     }
586     return null;
587   }
588
589   public Vector<Jws2Instance> getServices()
590   {
591     return (services == null) ? new Vector<Jws2Instance>()
592             : new Vector<Jws2Instance>(services);
593   }
594
595   /**
596    * test the given URL with the JabaWS test code
597    * 
598    * @param foo
599    * @return
600    */
601   public static boolean testServiceUrl(URL foo)
602   {
603     try
604     {
605       compbio.ws.client.WSTester.main(new String[]
606       { "-h=" + foo.toString() });
607     } catch (Exception e)
608     {
609       return false;
610     } catch (OutOfMemoryError e)
611     {
612       return false;
613     } catch (Error e)
614     {
615       return false;
616     }
617     return true;
618   }
619
620 }