b440de8f8909011b2fef16d31960c23bb68621d0
[jalview.git] / src / jalview / ws / jws2 / Jws2Discoverer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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 jalview.bin.Cache;
21 import jalview.gui.AlignFrame;
22 import jalview.gui.Desktop;
23 import jalview.gui.JvSwingUtils;
24 import jalview.ws.WSMenuEntryProviderI;
25 import jalview.ws.jws2.jabaws2.Jws2Instance;
26
27 import java.awt.Color;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.beans.PropertyChangeEvent;
31 import java.beans.PropertyChangeListener;
32 import java.net.URL;
33 import java.util.ArrayList;
34 import java.util.Hashtable;
35 import java.util.List;
36 import java.util.StringTokenizer;
37 import java.util.Vector;
38
39 import javax.swing.JMenu;
40 import javax.swing.JMenuItem;
41
42 import compbio.ws.client.Services;
43
44 /**
45  * discoverer for jws2 services. Follows the lightweight service discoverer
46  * pattern (archetyped by EnfinEnvision2OneWay)
47  * 
48  * @author JimP
49  * 
50  */
51 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
52 {
53   private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
54           this);
55
56   /**
57    * change listeners are notified of "services" property changes
58    * 
59    * @param listener
60    *          to be added that consumes new services Hashtable object.
61    */
62   public void addPropertyChangeListener(
63           java.beans.PropertyChangeListener listener)
64   {
65     changeSupport.addPropertyChangeListener(listener);
66   }
67
68   /**
69    * 
70    * 
71    * @param listener
72    *          to be removed
73    */
74   public void removePropertyChangeListener(
75           java.beans.PropertyChangeListener listener)
76   {
77     changeSupport.removePropertyChangeListener(listener);
78   }
79
80   boolean running = false, aborted = false;
81
82   /**
83    * @return the aborted
84    */
85   public boolean isAborted()
86   {
87     return aborted;
88   }
89
90   /**
91    * @param aborted
92    *          the aborted to set
93    */
94   public void setAborted(boolean aborted)
95   {
96     this.aborted = aborted;
97   }
98
99   Thread oldthread = null;
100
101   public void run()
102   {
103     if (running && oldthread != null && oldthread.isAlive())
104     {
105       if (!aborted)
106       {
107         return;
108       }
109       while (running)
110       {
111         try
112         {
113           Cache.log
114                   .debug("Waiting around for old discovery thread to finish.");
115           // wait around until old discoverer dies
116           Thread.sleep(100);
117         } catch (Exception e)
118         {
119         }
120       }
121       Cache.log.debug("Old discovery thread has finished.");
122     }
123     running = true;
124     changeSupport.firePropertyChange("services", services, new Vector());
125     oldthread = Thread.currentThread();
126     try
127     {
128       Class foo = getClass().getClassLoader().loadClass(
129               "compbio.ws.client.Jws2Client");
130     } catch (ClassNotFoundException e)
131     {
132       System.err
133               .println("Not enabling JABA Webservices : client jar is not available."
134                       + "\nPlease check that your webstart JNLP file is up to date!");
135       running = false;
136       return;
137     }
138     // reinitialise records of good and bad service URLs
139     if (services != null)
140     {
141       services.removeAllElements();
142     }
143     if (urlsWithoutServices != null)
144     {
145       urlsWithoutServices.removeAllElements();
146     }
147     if (invalidServiceUrls != null)
148     {
149       invalidServiceUrls.removeAllElements();
150     }
151     if (validServiceUrls != null)
152     {
153       validServiceUrls.removeAllElements();
154     }
155     ArrayList<String> svctypes=new ArrayList<String>();
156
157     List<JabaWsServerQuery> qrys = new ArrayList<JabaWsServerQuery>();
158     for (final String jwsservers : getServiceUrls())
159     {
160       JabaWsServerQuery squery = new JabaWsServerQuery(this, jwsservers);
161       if (svctypes.size()==0)
162       {
163         // TODO: remove this ugly hack to get Canonical JABA service ordering for all possible services 
164         for (Services sv:squery.JABAWS2SERVERS)
165         {
166           svctypes.add(sv.toString());
167         }
168
169       }
170       qrys.add(squery);
171       new Thread(squery).start();
172     }
173     boolean finished = true;
174     do
175     {
176       finished=true;
177       try
178       {
179         Thread.sleep(100);
180       } catch (Exception e)
181       {
182       }
183       ;
184       for (JabaWsServerQuery squery : qrys)
185       {
186         finished = finished && !squery.isRunning();
187       }
188       if (aborted)
189       {
190         Cache.log.debug("Aborting " + qrys.size()
191                 + " JABAWS discovery threads.");
192         for (JabaWsServerQuery squery : qrys)
193         {
194           squery.setQuit(true);
195         }
196       }
197     } while (!aborted && !finished);
198     if (!aborted)
199     {
200       // resort services according to order found in jabaws service list
201       // also ensure servics for each host are ordered in same way.
202       
203       if (services!=null && services.size()>0)
204       {
205         Jws2Instance[] svcs=new Jws2Instance[services.size()];
206         int[] spos=new int[services.size()];
207         int ipos=0;
208         Vector svcUrls = getServiceUrls();
209         for (Jws2Instance svc:services)
210         {
211           svcs[ipos]=svc;
212           spos[ipos++]=1000*svcUrls.indexOf(svc.getHost()) + 1+svctypes.indexOf(svc.serviceType);
213         }
214         jalview.util.QuickSort.sort(spos, svcs);
215         services=new Vector<Jws2Instance>();
216         for (Jws2Instance svc:svcs) {
217           services.add(svc);
218         }
219       }
220     }
221     oldthread = null;
222     running = false;
223     changeSupport.firePropertyChange("services", new Vector(), services);
224   }
225
226   /**
227    * record this service endpoint so we can use it
228    * 
229    * @param jwsservers
230    * @param srv
231    * @param service2
232    */
233   synchronized void addService(String jwsservers, Jws2Instance service)
234   {
235     if (services == null)
236     {
237       services = new Vector<Jws2Instance>();
238     }
239     System.out.println("Discovered service: " + jwsservers + " "
240             + service.toString());
241 //    Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
242 //            service2);
243
244     services.add(service);
245     // retrieve the presets and parameter set and cache now
246     service.getParamStore().getPresets();
247     service.hasParameters();
248     if (validServiceUrls==null)
249     {
250       validServiceUrls=new Vector();
251     }
252     validServiceUrls.add(jwsservers);
253   }
254
255   /**
256    * holds list of services.
257    */
258   protected Vector<Jws2Instance> services;
259   /**
260    * attach all available web services to the appropriate submenu in the given JMenu
261    */
262   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
263   {
264     // dynamically regenerate service list.
265     populateWSMenuEntry(wsmenu, alignFrame, null);
266   }
267
268   private void populateWSMenuEntry(JMenu jws2al, final AlignFrame alignFrame, String typeFilter)
269   {
270     if (running || services == null || services.size() == 0)
271     {
272       return;
273     }
274     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
275             .getDefault("WSMENU_BYTYPE", false);
276     /**
277      * eventually, JWS2 services will appear under the same align/etc submenus.
278      * for moment we keep them separate.
279      */
280     JMenu atpoint;
281     MsaWSClient msacl = new MsaWSClient();
282     Vector hostLabels = new Vector();
283     jws2al.removeAll();
284     Hashtable<String,String> lasthostFor = new Hashtable<String,String>();
285     Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
286     ArrayList<String> hostlist=new ArrayList<String>();
287     for (Jws2Instance service : services)
288     {
289       ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
290       if (hostservices == null)
291       {
292         hosts.put(service.getHost(),
293                 hostservices = new ArrayList<Jws2Instance>());
294         hostlist.add(service.getHost());
295       }
296       hostservices.add(service);
297     }
298     // now add hosts in order of the given array
299     for (String host : hostlist)
300     {
301       Jws2Instance orderedsvcs[] = hosts.get(host).toArray(
302               new Jws2Instance[1]);
303       String sortbytype[] = new String[orderedsvcs.length];
304       for (int i = 0; i < sortbytype.length; i++)
305       {
306         sortbytype[i] = orderedsvcs[i].serviceType;
307       }
308       jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
309       for (final Jws2Instance service : orderedsvcs)
310       {
311         atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
312         String type = service.serviceType;
313         if (byhost)
314         {
315           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
316           if (atpoint.getToolTipText() == null)
317           {
318             atpoint.setToolTipText("Services at " + host);
319           }
320         }
321         if (bytype)
322         {
323           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
324           if (atpoint.getToolTipText() == null)
325           {
326             atpoint.setToolTipText(service.getActionText());
327           }
328         }
329         if (!byhost
330                 && !hostLabels.contains(host + service.serviceType
331                         + service.getActionText()))
332         // !hostLabels.contains(host + (bytype ?
333         // service.serviceType+service.getActionText() : "")))
334         {
335           // add a marker indicating where this service is hosted
336           // relies on services from the same host being listed in a
337           // contiguous
338           // group
339           JMenuItem hitm;
340           atpoint.addSeparator();
341           if (lasthostFor.get(service.action) == null || !lasthostFor.get(service.action).equals(host))
342           {
343             atpoint.add(hitm = new JMenuItem(host));
344             hitm.setForeground(Color.blue);
345             hitm.addActionListener(new ActionListener()
346             {
347
348               @Override
349               public void actionPerformed(ActionEvent e)
350               {
351                 Desktop.showUrl(service.getHost());
352               }
353             });
354             hitm.setToolTipText(JvSwingUtils
355                     .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
356             lasthostFor.put(service.action,host);
357           }
358           hostLabels.addElement(host + service.serviceType
359                   + service.getActionText());
360           // hostLabels.addElement(host + (bytype ?
361           // service.serviceType+service.getActionText() : ""));
362         }
363         
364         service.attachWSMenuEntry(atpoint, alignFrame);
365         /*
366          * JMenuItem sitem = new JMenuItem(service.serviceType);
367          * sitem.setToolTipText("Hosted at " + service.hosturl);
368          * sitem.addActionListener(new ActionListener() {
369          * 
370          * @Override public void actionPerformed(ActionEvent e) { AlignmentView
371          * msa = alignFrame.gatherSequencesForAlignment(); MsaWSClient client =
372          * new MsaWSClient(service, "JWS2 Alignment of " +
373          * alignFrame.getTitle(), msa, false, true,
374          * alignFrame.getViewport().getAlignment().getDataset(), alignFrame); }
375          * });
376          */
377       }
378     }
379
380   }
381
382   public static void main(String[] args)
383   {
384     if (args.length>0)
385     {
386       testUrls = new Vector<String>();
387       for (String url:args)
388       {
389         testUrls.add(url);
390       };
391     }
392     Thread runner = getDiscoverer().startDiscoverer(
393             new PropertyChangeListener()
394             {
395
396               public void propertyChange(PropertyChangeEvent evt)
397               {
398                 if (getDiscoverer().services != null)
399                 {
400                   System.out.println("Changesupport: There are now "
401                           + getDiscoverer().services.size() + " services");
402                   int i=1;
403                   for (Jws2Instance instance:getDiscoverer().services)
404                   {
405                     System.out.println("Service "+i+++" "+instance.getClass()+"@"+instance.getHost()+": "+instance.getActionText());
406                   }
407
408                 }
409               }
410             });
411     while (runner.isAlive())
412     {
413       try
414       {
415         Thread.sleep(50);
416       } catch (InterruptedException e)
417       {
418       }
419       ;
420     }
421     try {
422       Thread.sleep(50);
423     } catch (InterruptedException x) {}
424   }
425
426   private static Jws2Discoverer discoverer;
427
428   public static Jws2Discoverer getDiscoverer()
429   {
430     if (discoverer == null)
431     {
432       discoverer = new Jws2Discoverer();
433     }
434     return discoverer;
435   }
436
437   public boolean hasServices()
438   {
439     // TODO Auto-generated method stub
440     return !running && services != null && services.size() > 0;
441   }
442
443   public boolean isRunning()
444   {
445     return running;
446   }
447
448   /**
449    * the jalview .properties entry for JWS2 URLS
450    */
451   final static String JWS2HOSTURLS = "JWS2HOSTURLS";
452
453   public static void setServiceUrls(Vector<String> urls)
454   {
455     if (urls != null)
456     {
457       StringBuffer urlbuffer = new StringBuffer();
458       String sep = "";
459       for (String url : urls)
460       {
461         urlbuffer.append(sep);
462         urlbuffer.append(url);
463         sep = ",";
464       }
465       Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
466     }
467     else
468     {
469       Cache.removeProperty(JWS2HOSTURLS);
470     }
471   }
472
473   private static Vector<String> testUrls=null;
474   public static Vector<String> getServiceUrls()
475   {
476     if (testUrls!=null)
477     {
478       // return test urls, if there are any, instead of touching cache
479       return testUrls;
480     }
481     String surls = Cache.getDefault(JWS2HOSTURLS,
482             "http://www.compbio.dundee.ac.uk/jabaws");
483     Vector<String> urls = new Vector<String>();
484     try
485     {
486       StringTokenizer st = new StringTokenizer(surls, ",");
487       while (st.hasMoreElements())
488       {
489         String url = null;
490         try
491         {
492           java.net.URL u = new java.net.URL(url = st.nextToken());
493           if (!urls.contains(url))
494           {
495             urls.add(url);
496           }
497           else
498           {
499             jalview.bin.Cache.log.info("Ignoring duplicate url in "
500                     + JWS2HOSTURLS + " list");
501           }
502         } catch (Exception ex)
503         {
504           jalview.bin.Cache.log
505                   .warn("Problem whilst trying to make a URL from '"
506                           + ((url != null) ? url : "<null>") + "'");
507           jalview.bin.Cache.log
508                   .warn("This was probably due to a malformed comma separated list"
509                           + " in the "
510                           + JWS2HOSTURLS
511                           + " entry of $(HOME)/.jalview_properties)");
512           jalview.bin.Cache.log.debug("Exception was ", ex);
513         }
514       }
515     } catch (Exception ex)
516     {
517       jalview.bin.Cache.log.warn(
518               "Error parsing comma separated list of urls in "
519                       + JWS2HOSTURLS + " preference.", ex);
520     }
521     if (urls.size() >= 0)
522     {
523       return urls;
524     }
525     return null;
526   }
527
528   public Vector<Jws2Instance> getServices()
529   {
530     return (services == null) ? new Vector<Jws2Instance>()
531             : new Vector<Jws2Instance>(services);
532   }
533
534   /**
535    * test the given URL with the JabaWS test code
536    * 
537    * @param foo
538    * @return
539    */
540   public static boolean testServiceUrl(URL foo)
541   {
542     try
543     {
544       compbio.ws.client.WSTester.main(new String[]
545       { "-h=" + foo.toString() });
546     } catch (Exception e)
547     {
548       e.printStackTrace();
549       return false;
550     } catch (OutOfMemoryError e)
551     {
552       e.printStackTrace();
553       return false;
554     } catch (Error e)
555     {
556       e.printStackTrace();
557       return false;
558     }
559
560     return true;
561   }
562
563   /**
564    * Start a fresh discovery thread and notify the given object when we're
565    * finished. Any known existing threads will be killed before this one is
566    * started.
567    * 
568    * @param changeSupport2
569    * @return new thread
570    */
571   public Thread startDiscoverer(PropertyChangeListener changeSupport2)
572   {
573     if (isRunning())
574     {
575       setAborted(true);
576     }
577     addPropertyChangeListener(changeSupport2);
578     Thread thr = new Thread(this);
579     thr.start();
580     return thr;
581   }
582
583   Vector<String> invalidServiceUrls = null, urlsWithoutServices = null, validServiceUrls=null;
584
585   /**
586    * @return the invalidServiceUrls
587    */
588   public Vector<String> getInvalidServiceUrls()
589   {
590     return invalidServiceUrls;
591   }
592
593   /**
594    * @return the urlsWithoutServices
595    */
596   public Vector<String> getUrlsWithoutServices()
597   {
598     return urlsWithoutServices;
599   }
600
601   /**
602    * add an 'empty' JABA server to the list. Only servers not already in the
603    * 'bad URL' list will be added to this list.
604    * 
605    * @param jwsservers
606    */
607   public synchronized void addUrlwithnoservices(String jwsservers)
608   {
609     if (urlsWithoutServices == null)
610     {
611       urlsWithoutServices = new Vector<String>();
612     }
613
614     if ((invalidServiceUrls == null || !invalidServiceUrls
615             .contains(jwsservers))
616             && !urlsWithoutServices.contains(jwsservers))
617     {
618       urlsWithoutServices.add(jwsservers);
619     }
620   }
621
622   /**
623    * add a bad URL to the list
624    * 
625    * @param jwsservers
626    */
627   public synchronized void addInvalidServiceUrl(String jwsservers)
628   {
629     if (invalidServiceUrls == null)
630     {
631       invalidServiceUrls = new Vector<String>();
632     }
633     if (!invalidServiceUrls.contains(jwsservers))
634     {
635       invalidServiceUrls.add(jwsservers);
636     }
637   }
638
639   /**
640    * 
641    * @return a human readable report of any problems with the service URLs used
642    *         for discovery
643    */
644   public String getErrorMessages()
645   {
646     if (!isRunning() && !isAborted())
647     {
648       StringBuffer ermsg = new StringBuffer();
649       boolean list = false;
650       if (getInvalidServiceUrls() != null
651               && getInvalidServiceUrls().size() > 0)
652       {
653         ermsg.append("URLs that could not be contacted: \n");
654         for (String svcurl : getInvalidServiceUrls())
655         {
656           if (list)
657           {
658             ermsg.append(", ");
659           }
660           list = true;
661           ermsg.append(svcurl);
662         }
663         ermsg.append("\n\n");
664       }
665       list = false;
666       if (getUrlsWithoutServices() != null
667               && getUrlsWithoutServices().size() > 0)
668       {
669         ermsg.append("URLs without any JABA Services : \n");
670         for (String svcurl : getUrlsWithoutServices())
671         {
672           if (list)
673           {
674             ermsg.append(", ");
675           }
676           list = true;
677           ermsg.append(svcurl);
678         }
679         ermsg.append("\n");
680       }
681       if (ermsg.length() > 1)
682       {
683         return ermsg.toString();
684       }
685
686     }
687     return null;
688   }
689
690   public int getServerStatusFor(String url)
691   {
692     if (validServiceUrls!=null && validServiceUrls.contains(url))
693     {
694       return 1;
695     }
696     if (urlsWithoutServices!=null && urlsWithoutServices.contains(url))
697     return 0;
698     if (invalidServiceUrls!=null && invalidServiceUrls.contains(url))
699     {
700       return -1;
701     }
702     return -2;
703   }
704 }