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