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