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