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
5 * This file is part of Jalview.
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.
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.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
18 package jalview.ws.jws2;
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;
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;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Hashtable;
38 import java.util.List;
40 import java.util.StringTokenizer;
41 import java.util.Vector;
43 import javax.swing.JMenu;
44 import javax.swing.JMenuItem;
46 import compbio.ws.client.Services;
49 * discoverer for jws2 services. Follows the lightweight service discoverer
50 * pattern (archetyped by EnfinEnvision2OneWay)
55 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
57 private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
61 * change listeners are notified of "services" property changes
64 * to be added that consumes new services Hashtable object.
66 public void addPropertyChangeListener(
67 java.beans.PropertyChangeListener listener)
69 changeSupport.addPropertyChangeListener(listener);
78 public void removePropertyChangeListener(
79 java.beans.PropertyChangeListener listener)
81 changeSupport.removePropertyChangeListener(listener);
84 boolean running = false, aborted = false;
89 public boolean isAborted()
98 public void setAborted(boolean aborted)
100 this.aborted = aborted;
103 Thread oldthread = null;
107 if (running && oldthread != null && oldthread.isAlive())
118 .debug("Waiting around for old discovery thread to finish.");
119 // wait around until old discoverer dies
121 } catch (Exception e)
125 Cache.log.debug("Old discovery thread has finished.");
128 changeSupport.firePropertyChange("services", services, new Vector());
129 oldthread = Thread.currentThread();
132 Class foo = getClass().getClassLoader().loadClass(
133 "compbio.ws.client.Jws2Client");
134 } catch (ClassNotFoundException e)
137 .println("Not enabling JABA Webservices : client jar is not available."
138 + "\nPlease check that your webstart JNLP file is up to date!");
142 // reinitialise records of good and bad service URLs
143 if (services != null)
145 services.removeAllElements();
147 if (urlsWithoutServices != null)
149 urlsWithoutServices.removeAllElements();
151 if (invalidServiceUrls != null)
153 invalidServiceUrls.removeAllElements();
155 if (validServiceUrls != null)
157 validServiceUrls.removeAllElements();
159 ArrayList<String> svctypes=new ArrayList<String>();
161 List<JabaWsServerQuery> qrys = new ArrayList<JabaWsServerQuery>();
162 for (final String jwsservers : getServiceUrls())
164 JabaWsServerQuery squery = new JabaWsServerQuery(this, jwsservers);
165 if (svctypes.size()==0)
167 // TODO: remove this ugly hack to get Canonical JABA service ordering for all possible services
168 for (Services sv:squery.JABAWS2SERVERS)
170 svctypes.add(sv.toString());
175 new Thread(squery).start();
177 boolean finished = true;
184 } catch (Exception e)
188 for (JabaWsServerQuery squery : qrys)
190 finished = finished && !squery.isRunning();
194 Cache.log.debug("Aborting " + qrys.size()
195 + " JABAWS discovery threads.");
196 for (JabaWsServerQuery squery : qrys)
198 squery.setQuit(true);
201 } while (!aborted && !finished);
204 // resort services according to order found in jabaws service list
205 // also ensure servics for each host are ordered in same way.
207 if (services!=null && services.size()>0)
209 Jws2Instance[] svcs=new Jws2Instance[services.size()];
210 int[] spos=new int[services.size()];
212 Vector svcUrls = getServiceUrls();
213 for (Jws2Instance svc:services)
216 spos[ipos++]=1000*svcUrls.indexOf(svc.getHost()) + 1+svctypes.indexOf(svc.serviceType);
218 jalview.util.QuickSort.sort(spos, svcs);
219 services=new Vector<Jws2Instance>();
220 for (Jws2Instance svc:svcs) {
227 changeSupport.firePropertyChange("services", new Vector(), services);
231 * record this service endpoint so we can use it
237 synchronized void addService(String jwsservers, Jws2Instance service)
239 if (services == null)
241 services = new Vector<Jws2Instance>();
243 System.out.println("Discovered service: " + jwsservers + " "
244 + service.toString());
245 // Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
248 services.add(service);
249 // retrieve the presets and parameter set and cache now
250 service.getParamStore().getPresets();
251 service.hasParameters();
252 if (validServiceUrls==null)
254 validServiceUrls=new Vector();
256 validServiceUrls.add(jwsservers);
260 * holds list of services.
262 protected Vector<Jws2Instance> services;
264 * attach all available web services to the appropriate submenu in the given JMenu
266 public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
268 // dynamically regenerate service list.
269 populateWSMenuEntry(wsmenu, alignFrame, null);
271 private boolean isRecalculable(String action) {
272 return (action!=null && action.equalsIgnoreCase("conservation"));
274 private void populateWSMenuEntry(JMenu jws2al, final AlignFrame alignFrame, String typeFilter)
276 if (running || services == null || services.size() == 0)
280 boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
281 .getDefault("WSMENU_BYTYPE", false);
283 * eventually, JWS2 services will appear under the same align/etc submenus.
284 * for moment we keep them separate.
287 List<Jws2Instance> enumerableServices=new ArrayList<Jws2Instance>();
288 //jws2al.removeAll();
289 Map<String, Jws2Instance> preferredHosts=new HashMap<String,Jws2Instance>();
290 Map<String, List<Jws2Instance>> alternates = new HashMap<String, List<Jws2Instance>>();
291 for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
293 if (!isRecalculable(service.action)) {
294 // add 'one shot' services to be displayed using the classic menu structure
295 enumerableServices.add(service);
297 if (!preferredHosts.containsKey(service.serviceType))
299 Jws2Instance preferredInstance = getPreferredServiceFor(alignFrame,
300 service.serviceType);
301 if (preferredInstance != null)
303 preferredHosts.put(service.serviceType, preferredInstance);
307 preferredHosts.put(service.serviceType, service);
310 List<Jws2Instance> ph=alternates.get(service.serviceType);
311 if (preferredHosts.get(service.serviceType)!=service)
315 ph=new ArrayList<Jws2Instance>();
318 alternates.put(service.serviceType, ph);
324 // create GUI element for classic services
325 addEnumeratedServices(jws2al, alignFrame, enumerableServices);
326 // and the instantaneous services
327 for (final Jws2Instance service : preferredHosts.values())
329 atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
331 if (atpoint.getItemCount()>1) {
332 // previous service of this type already present
333 atpoint.addSeparator();
335 atpoint.add(hitm = new JMenuItem(service.getHost()));
336 hitm.setForeground(Color.blue);
337 hitm.addActionListener(new ActionListener()
341 public void actionPerformed(ActionEvent e)
343 Desktop.showUrl(service.getHost());
346 hitm.setToolTipText(JvSwingUtils
347 .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
349 service.attachWSMenuEntry(atpoint, alignFrame);
350 if (alternates.containsKey(service.serviceType))
352 atpoint.add(hitm=new JMenu("Switch server"));
353 hitm.setToolTipText(JvSwingUtils.wrapTooltip("Choose a server for running this service"));
354 for (final Jws2Instance sv:alternates.get(service.serviceType))
357 hitm.add(itm=new JMenuItem(sv.getHost()));
358 itm.setForeground(Color.blue);
359 itm.addActionListener(new ActionListener()
363 public void actionPerformed(ActionEvent arg0)
365 new Thread(new Runnable() {
367 setPreferredServiceFor(alignFrame, sv.serviceType, sv.action, sv);
368 changeSupport.firePropertyChange("services", new Vector(), services);
375 /*hitm.addActionListener(new ActionListener()
379 public void actionPerformed(ActionEvent arg0)
381 new Thread(new Runnable() {
385 new SetPreferredServer(alignFrame, service.serviceType, service.action);
394 * add services using the Java 2.5/2.6/2.7 system which optionally creates submenus to index by host and service program type
396 private void addEnumeratedServices(final JMenu jws2al, final AlignFrame alignFrame, List<Jws2Instance> enumerableServices)
398 boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
399 .getDefault("WSMENU_BYTYPE", false);
401 * eventually, JWS2 services will appear under the same align/etc submenus.
402 * for moment we keep them separate.
405 MsaWSClient msacl = new MsaWSClient();
406 Vector hostLabels = new Vector();
407 Hashtable<String,String> lasthostFor = new Hashtable<String,String>();
408 Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
409 ArrayList<String> hostlist=new ArrayList<String>();
410 for (Jws2Instance service : enumerableServices)
412 ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
413 if (hostservices == null)
415 hosts.put(service.getHost(),
416 hostservices = new ArrayList<Jws2Instance>());
417 hostlist.add(service.getHost());
419 hostservices.add(service);
421 // now add hosts in order of the given array
422 for (String host : hostlist)
424 Jws2Instance orderedsvcs[] = hosts.get(host).toArray(
425 new Jws2Instance[1]);
426 String sortbytype[] = new String[orderedsvcs.length];
427 for (int i = 0; i < sortbytype.length; i++)
429 sortbytype[i] = orderedsvcs[i].serviceType;
431 jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
432 for (final Jws2Instance service : orderedsvcs)
434 atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
435 String type = service.serviceType;
438 atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
439 if (atpoint.getToolTipText() == null)
441 atpoint.setToolTipText("Services at " + host);
446 atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
447 if (atpoint.getToolTipText() == null)
449 atpoint.setToolTipText(service.getActionText());
453 && !hostLabels.contains(host + service.serviceType
454 + service.getActionText()))
455 // !hostLabels.contains(host + (bytype ?
456 // service.serviceType+service.getActionText() : "")))
458 // add a marker indicating where this service is hosted
459 // relies on services from the same host being listed in a
463 atpoint.addSeparator();
464 if (lasthostFor.get(service.action) == null || !lasthostFor.get(service.action).equals(host))
466 atpoint.add(hitm = new JMenuItem(host));
467 hitm.setForeground(Color.blue);
468 hitm.addActionListener(new ActionListener()
472 public void actionPerformed(ActionEvent e)
474 Desktop.showUrl(service.getHost());
477 hitm.setToolTipText(JvSwingUtils
478 .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
479 lasthostFor.put(service.action,host);
481 hostLabels.addElement(host + service.serviceType
482 + service.getActionText());
483 // hostLabels.addElement(host + (bytype ?
484 // service.serviceType+service.getActionText() : ""));
487 service.attachWSMenuEntry(atpoint, alignFrame);
489 * JMenuItem sitem = new JMenuItem(service.serviceType);
490 * sitem.setToolTipText("Hosted at " + service.hosturl);
491 * sitem.addActionListener(new ActionListener() {
493 * @Override public void actionPerformed(ActionEvent e) { AlignmentView
494 * msa = alignFrame.gatherSequencesForAlignment(); MsaWSClient client =
495 * new MsaWSClient(service, "JWS2 Alignment of " +
496 * alignFrame.getTitle(), msa, false, true,
497 * alignFrame.getViewport().getAlignment().getDataset(), alignFrame); }
503 public static void main(String[] args)
507 testUrls = new Vector<String>();
508 for (String url:args)
513 Thread runner = getDiscoverer().startDiscoverer(
514 new PropertyChangeListener()
517 public void propertyChange(PropertyChangeEvent evt)
519 if (getDiscoverer().services != null)
521 System.out.println("Changesupport: There are now "
522 + getDiscoverer().services.size() + " services");
524 for (Jws2Instance instance:getDiscoverer().services)
526 System.out.println("Service "+i+++" "+instance.getClass()+"@"+instance.getHost()+": "+instance.getActionText());
532 while (runner.isAlive())
537 } catch (InterruptedException e)
544 } catch (InterruptedException x) {}
547 private static Jws2Discoverer discoverer;
549 public static Jws2Discoverer getDiscoverer()
551 if (discoverer == null)
553 discoverer = new Jws2Discoverer();
558 public boolean hasServices()
560 // TODO Auto-generated method stub
561 return !running && services != null && services.size() > 0;
564 public boolean isRunning()
570 * the jalview .properties entry for JWS2 URLS
572 final static String JWS2HOSTURLS = "JWS2HOSTURLS";
574 public static void setServiceUrls(Vector<String> urls)
578 StringBuffer urlbuffer = new StringBuffer();
580 for (String url : urls)
582 urlbuffer.append(sep);
583 urlbuffer.append(url);
586 Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
590 Cache.removeProperty(JWS2HOSTURLS);
594 private static Vector<String> testUrls=null;
595 public static Vector<String> getServiceUrls()
599 // return test urls, if there are any, instead of touching cache
602 String surls = Cache.getDefault(JWS2HOSTURLS,
603 "http://www.compbio.dundee.ac.uk/jabaws");
604 Vector<String> urls = new Vector<String>();
607 StringTokenizer st = new StringTokenizer(surls, ",");
608 while (st.hasMoreElements())
613 java.net.URL u = new java.net.URL(url = st.nextToken());
614 if (!urls.contains(url))
620 jalview.bin.Cache.log.info("Ignoring duplicate url in "
621 + JWS2HOSTURLS + " list");
623 } catch (Exception ex)
625 jalview.bin.Cache.log
626 .warn("Problem whilst trying to make a URL from '"
627 + ((url != null) ? url : "<null>") + "'");
628 jalview.bin.Cache.log
629 .warn("This was probably due to a malformed comma separated list"
632 + " entry of $(HOME)/.jalview_properties)");
633 jalview.bin.Cache.log.debug("Exception was ", ex);
636 } catch (Exception ex)
638 jalview.bin.Cache.log.warn(
639 "Error parsing comma separated list of urls in "
640 + JWS2HOSTURLS + " preference.", ex);
642 if (urls.size() >= 0)
649 public Vector<Jws2Instance> getServices()
651 return (services == null) ? new Vector<Jws2Instance>()
652 : new Vector<Jws2Instance>(services);
656 * test the given URL with the JabaWS test code
661 public static boolean testServiceUrl(URL foo)
665 compbio.ws.client.WSTester.main(new String[]
666 { "-h=" + foo.toString() });
667 } catch (Exception e)
671 } catch (OutOfMemoryError e)
685 * Start a fresh discovery thread and notify the given object when we're
686 * finished. Any known existing threads will be killed before this one is
689 * @param changeSupport2
692 public Thread startDiscoverer(PropertyChangeListener changeSupport2)
698 addPropertyChangeListener(changeSupport2);
699 Thread thr = new Thread(this);
704 Vector<String> invalidServiceUrls = null, urlsWithoutServices = null, validServiceUrls=null;
707 * @return the invalidServiceUrls
709 public Vector<String> getInvalidServiceUrls()
711 return invalidServiceUrls;
715 * @return the urlsWithoutServices
717 public Vector<String> getUrlsWithoutServices()
719 return urlsWithoutServices;
723 * add an 'empty' JABA server to the list. Only servers not already in the
724 * 'bad URL' list will be added to this list.
728 public synchronized void addUrlwithnoservices(String jwsservers)
730 if (urlsWithoutServices == null)
732 urlsWithoutServices = new Vector<String>();
735 if ((invalidServiceUrls == null || !invalidServiceUrls
736 .contains(jwsservers))
737 && !urlsWithoutServices.contains(jwsservers))
739 urlsWithoutServices.add(jwsservers);
744 * add a bad URL to the list
748 public synchronized void addInvalidServiceUrl(String jwsservers)
750 if (invalidServiceUrls == null)
752 invalidServiceUrls = new Vector<String>();
754 if (!invalidServiceUrls.contains(jwsservers))
756 invalidServiceUrls.add(jwsservers);
762 * @return a human readable report of any problems with the service URLs used
765 public String getErrorMessages()
767 if (!isRunning() && !isAborted())
769 StringBuffer ermsg = new StringBuffer();
770 boolean list = false;
771 if (getInvalidServiceUrls() != null
772 && getInvalidServiceUrls().size() > 0)
774 ermsg.append("URLs that could not be contacted: \n");
775 for (String svcurl : getInvalidServiceUrls())
782 ermsg.append(svcurl);
784 ermsg.append("\n\n");
787 if (getUrlsWithoutServices() != null
788 && getUrlsWithoutServices().size() > 0)
790 ermsg.append("URLs without any JABA Services : \n");
791 for (String svcurl : getUrlsWithoutServices())
798 ermsg.append(svcurl);
802 if (ermsg.length() > 1)
804 return ermsg.toString();
811 public int getServerStatusFor(String url)
813 if (validServiceUrls!=null && validServiceUrls.contains(url))
817 if (urlsWithoutServices!=null && urlsWithoutServices.contains(url))
819 if (invalidServiceUrls!=null && invalidServiceUrls.contains(url))
827 * pick the user's preferred service based on a set of URLs (jaba server locations) and service URIs (specifying version and service interface class)
830 * @return null or best match for given uri/ls.
832 public Jws2Instance getPreferredServiceFor(String[] serviceURLs)
834 HashSet<String> urls=new HashSet<String>();
835 urls.addAll(Arrays.asList(serviceURLs));
836 Jws2Instance match=null;
837 for (Jws2Instance svc:services)
839 if (urls.contains(svc.getServiceTypeURI()))
842 // for moment we always pick service from server ordered first in user's preferences
845 if (urls.contains(svc.getUri()))
847 // stop and return - we've matched type URI and URI for service endpoint
855 Map<String, Map<String, String>> preferredServiceMap = new HashMap<String, Map<String, String>>();
859 * get current preferred service of the given type, or global default
860 * @param af null or a specific alignFrame
861 * @param serviceType Jws2Instance.serviceType for service
862 * @return null if no service of this type is available, the preferred service for the serviceType and af if specified and if defined.
864 public Jws2Instance getPreferredServiceFor(AlignFrame af,
867 String serviceurl = null;
868 synchronized (preferredServiceMap)
870 String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
871 Map<String, String> prefmap = preferredServiceMap.get(afid);
872 if (afid.length() > 0 && prefmap == null)
874 // recover global setting, if any
875 prefmap = preferredServiceMap.get("");
879 serviceurl = prefmap.get(serviceType);
883 Jws2Instance response = null;
884 for (Jws2Instance svc : services)
886 if (svc.serviceType.equals(serviceType))
888 if (serviceurl == null || serviceurl.equals(svc.getHost()))
898 public void setPreferredServiceFor(AlignFrame af, String serviceType,
899 String serviceAction, Jws2Instance selectedServer)
901 String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
902 if (preferredServiceMap == null)
904 preferredServiceMap = new HashMap<String, Map<String, String>>();
906 Map<String, String> prefmap = preferredServiceMap.get(afid);
909 prefmap = new HashMap<String, String>();
910 preferredServiceMap.put(afid, prefmap);
912 prefmap.put(serviceType, selectedServer.getHost());
913 prefmap.put(serviceAction, selectedServer.getHost());
916 public void setPreferredServiceFor(String serviceType,
917 String serviceAction, Jws2Instance selectedServer)
919 setPreferredServiceFor(null, serviceType, serviceAction, selectedServer);