fdbb74078512636cb711e681248b381464a630a9
[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     Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(), service2);
166
167     services.add(service); 
168     // retrieve the presets and parameter set and cache now
169     service.getParamStore().getPresets();
170     service.hasParameters();
171   }
172
173   public class Jws2Instance
174   {
175     public String hosturl;
176
177     public String serviceType;
178
179     public MsaWS service;
180
181     public Jws2Instance(String hosturl, String serviceType, MsaWS service)
182     {
183       super();
184       this.hosturl = hosturl;
185       this.serviceType = serviceType;
186       this.service = service;
187     }
188
189     PresetManager presets = null;
190
191     public JabaParamStore paramStore = null;
192
193     /**
194      * non thread safe - gets the presets for this service (blocks whilst it
195      * calls the service to get the preset set)
196      * 
197      * @return service presets or null if exceptions were raised.
198      */
199     public PresetManager getPresets()
200     {
201       if (presets == null)
202       {
203         try
204         {
205           presets = service.getPresets();
206         } catch (Exception ex)
207         {
208           System.err
209                   .println("Exception when retrieving presets for service "
210                           + serviceType + " at " + hosturl);
211         }
212       }
213       return presets;
214     }
215
216     public String getHost()
217     {
218       return hosturl;
219       /*
220        * try { URL serviceurl = new URL(hosturl); if (serviceurl.getPort()!=80)
221        * { return serviceurl.getHost()+":"+serviceurl.getPort(); } return
222        * serviceurl.getHost(); } catch (Exception e) {
223        * System.err.println("Failed to parse service URL '" + hosturl +
224        * "' as a valid URL!"); } return null;
225        */
226     }
227
228     /**
229      * @return short description of what the service will do
230      */
231     public String getActionText()
232     {
233       return "Align with " + serviceType;
234     }
235
236     /**
237      * non-thread safe - blocks whilst accessing service to get complete set of
238      * available options and parameters
239      * 
240      * @return
241      */
242     public RunnerConfig getRunnerConfig()
243     {
244       return service.getRunnerOptions();
245     }
246
247     @Override
248     protected void finalize() throws Throwable
249     {
250       if (service != null)
251       {
252         try
253         {
254           Closeable svc = (Closeable) service;
255           service = null;
256           svc.close();
257         } catch (Exception e)
258         {
259         }
260         ;
261       }
262       super.finalize();
263     }
264
265     public ParamDatastoreI getParamStore()
266     {
267       if (paramStore == null)
268       {
269         try
270         {
271           paramStore = new JabaParamStore(this,
272                   (Desktop.instance != null ? Desktop
273                           .getUserParameterStore() : null));
274         } catch (Exception ex)
275         {
276         }
277
278       }
279       return paramStore;
280     }
281
282     public String getUri()
283     {
284       // this is only valid for Jaba 1.0 - this formula might have to change!
285       return hosturl+(hosturl.lastIndexOf("/")==(hosturl.length()-1) ? "/" : "") +serviceType;
286     }
287     private boolean hasParams=false,lookedForParams=false;
288
289     public boolean hasParameters()
290     {
291       if (!lookedForParams)
292       {
293         lookedForParams=true;
294       try
295       {
296         hasParams = (getRunnerConfig().getArguments().size() > 0);
297       } catch (Exception e)
298       {
299
300       }
301       }
302       return hasParams;
303     }
304   };
305
306   /**
307    * holds list of services.
308    */
309   protected Vector<Jws2Instance> services;
310
311   /**
312    * find or add a submenu with the given title in the given menu
313    * 
314    * @param menu
315    * @param submenu
316    * @return the new or existing submenu
317    */
318   private JMenu findOrCreateMenu(JMenu menu, String submenu)
319   {
320     JMenu submenuinstance = null;
321     for (int i = 0, iSize = menu.getMenuComponentCount(); i < iSize; i++)
322     {
323       if (menu.getMenuComponent(i) instanceof JMenu
324               && ((JMenu) menu.getMenuComponent(i)).getText().equals(
325                       submenu))
326       {
327         submenuinstance = (JMenu) menu.getMenuComponent(i);
328       }
329     }
330     if (submenuinstance == null)
331     {
332       submenuinstance = new JMenu(submenu);
333       menu.add(submenuinstance);
334     }
335     return submenuinstance;
336
337   }
338
339   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
340   {
341     // dynamically regenerate service list.
342     final JMenu jws2al = new JMenu("JABAWS Alignment");
343     jws2al.addMenuListener(new MenuListener()
344     {
345       // TODO: future: add menu listener to parent menu - so submenus are
346       // populated *before* they are selected.
347       @Override
348       public void menuSelected(MenuEvent e)
349       {
350         populateWSMenuEntry(jws2al, alignFrame);
351       }
352
353       @Override
354       public void menuDeselected(MenuEvent e)
355       {
356         // TODO Auto-generated method stub
357
358       }
359
360       @Override
361       public void menuCanceled(MenuEvent e)
362       {
363         // TODO Auto-generated method stub
364
365       }
366
367     });
368     wsmenu.add(jws2al);
369     
370   }
371
372   private void populateWSMenuEntry(JMenu jws2al, final AlignFrame alignFrame)
373   {
374     if (running || services == null || services.size() == 0)
375     {
376       return;
377     }
378     boolean byhost = Cache.getDefault("WSMENU_BYHOST", true), bytype = Cache
379             .getDefault("WSMENU_BYTYPE", true);
380     /**
381      * eventually, JWS2 services will appear under the same align/etc submenus.
382      * for moment we keep them separate.
383      */
384     JMenu atpoint;
385     MsaWSClient msacl = new MsaWSClient();
386     Vector hostLabels = new Vector();
387     jws2al.removeAll();
388     String lasthost=null;
389     for (final Jws2Instance service : services)
390     {
391       atpoint = jws2al;
392       String host = service.getHost();
393       String type = service.serviceType;
394       if (byhost)
395       {
396         atpoint = findOrCreateMenu(atpoint, host);
397         if (atpoint.getToolTipText() == null)
398         {
399           atpoint.setToolTipText("Services at " + host);
400         }
401       }
402       if (bytype)
403       {
404         atpoint = findOrCreateMenu(atpoint, type);
405         if (atpoint.getToolTipText() == null)
406         {
407           atpoint.setToolTipText(service.getActionText());
408         }
409       }
410       if (!byhost && !hostLabels.contains(host + service.serviceType+service.getActionText()))
411               //!hostLabels.contains(host + (bytype ? service.serviceType+service.getActionText() : "")))
412       {
413         // add a marker indicating where this service is hosted
414         // relies on services from the same host being listed in a contiguous
415         // group
416         JMenuItem hitm;
417         atpoint.addSeparator();
418         if (lasthost==null || !lasthost.equals(host))
419         {
420           atpoint.add(hitm = new JMenuItem(host));
421           hitm.setForeground(Color.blue);
422           lasthost = host;
423         }
424         hostLabels.addElement(host + service.serviceType+service.getActionText() );
425         // hostLabels.addElement(host + (bytype ? service.serviceType+service.getActionText() : ""));
426       }
427       msacl.attachWSMenuEntry(atpoint, service, alignFrame);
428       /*
429        * JMenuItem sitem = new JMenuItem(service.serviceType);
430        * sitem.setToolTipText("Hosted at " + service.hosturl);
431        * sitem.addActionListener(new ActionListener() {
432        * 
433        * @Override public void actionPerformed(ActionEvent e) { AlignmentView
434        * msa = alignFrame.gatherSequencesForAlignment(); MsaWSClient client =
435        * new MsaWSClient(service, "JWS2 Alignment of " + alignFrame.getTitle(),
436        * msa, false, true, alignFrame.getViewport().getAlignment().getDataset(),
437        * alignFrame); } });
438        */
439     }
440   }
441
442   public static void main(String[] args)
443   {
444     Thread runner = new Thread(getDiscoverer());
445     getDiscoverer().addPropertyChangeListener(new PropertyChangeListener()
446     {
447
448       public void propertyChange(PropertyChangeEvent evt)
449       {
450         System.out.println("Changesupport: There are now "
451                 + getDiscoverer().services.size() + " services");
452       }
453     });
454     runner.start();
455     while (runner.isAlive())
456     {
457       try
458       {
459         Thread.sleep(50);
460       } catch (InterruptedException e)
461       {
462       }
463       ;
464     }
465   }
466
467   private static Jws2Discoverer discoverer;
468
469   public static Jws2Discoverer getDiscoverer()
470   {
471     if (discoverer == null)
472     {
473       discoverer = new Jws2Discoverer();
474     }
475     return discoverer;
476   }
477
478   public boolean hasServices()
479   {
480     // TODO Auto-generated method stub
481     return !running && services != null && services.size() > 0;
482   }
483
484   public boolean isRunning()
485   {
486     return running;
487   }
488
489   /**
490    * the jalview .properties entry for JWS2 URLS
491    */
492   final static String JWS2HOSTURLS = "JWS2HOSTURLS";
493
494   public static void setServiceUrls(Vector<String> urls)
495   {
496     if (urls != null)
497     {
498       StringBuffer urlbuffer = new StringBuffer();
499       String sep = "";
500       for (String url : urls)
501       {
502         urlbuffer.append(sep);
503         urlbuffer.append(url);
504         sep = ",";
505       }
506       Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
507     }
508     else
509     {
510       Cache.removeProperty(JWS2HOSTURLS);
511     }
512   }
513
514   public static Vector<String> getServiceUrls()
515   {
516     String surls = Cache.getDefault(JWS2HOSTURLS,
517             "http://www.compbio.dundee.ac.uk/jabaws");
518     Vector<String> urls = new Vector<String>();
519     try
520     {
521       StringTokenizer st = new StringTokenizer(surls, ",");
522       while (st.hasMoreElements())
523       {
524         String url = null;
525         try
526         {
527           java.net.URL u = new java.net.URL(url = st.nextToken());
528           if (!urls.contains(url))
529           {
530             urls.add(url);
531           }
532           else
533           {
534             jalview.bin.Cache.log.info("Ignoring duplicate url in "
535                     + JWS2HOSTURLS + " list");
536           }
537         } catch (Exception ex)
538         {
539           jalview.bin.Cache.log
540                   .warn("Problem whilst trying to make a URL from '"
541                           + ((url != null) ? url : "<null>") + "'");
542           jalview.bin.Cache.log
543                   .warn("This was probably due to a malformed comma separated list"
544                           + " in the "
545                           + JWS2HOSTURLS
546                           + " entry of $(HOME)/.jalview_properties)");
547           jalview.bin.Cache.log.debug("Exception was ", ex);
548         }
549       }
550     } catch (Exception ex)
551     {
552       jalview.bin.Cache.log.warn(
553               "Error parsing comma separated list of urls in "
554                       + JWS2HOSTURLS + " preference.", ex);
555     }
556     if (urls.size() >= 0)
557     {
558       return urls;
559     }
560     return null;
561   }
562
563   public Vector<Jws2Instance> getServices()
564   {
565     return (services == null) ? new Vector<Jws2Instance>()
566             : new Vector<Jws2Instance>(services);
567   }
568
569   /**
570    * test the given URL with the JabaWS test code
571    * 
572    * @param foo
573    * @return
574    */
575   public static boolean testServiceUrl(URL foo)
576   {
577     try
578     {
579       compbio.ws.client.WSTester.main(new String[]
580       { "-h=" + foo.toString() });
581     } catch (Exception e)
582     {
583       return false;
584     } catch (OutOfMemoryError e)
585     {
586       return false;
587     } catch (Error e)
588     {
589       return false;
590     }
591     return true;
592   }
593
594 }