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