Merge branch 'features/JAL-1333' into Release_2_8_2_Branch
[jalview.git] / src / jalview / ws / jws2 / Jws2Discoverer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ws.jws2;
22
23 import jalview.bin.Cache;
24 import jalview.gui.AlignFrame;
25 import jalview.gui.Desktop;
26 import jalview.gui.JvSwingUtils;
27 import jalview.ws.WSMenuEntryProviderI;
28 import jalview.ws.jws2.jabaws2.Jws2Instance;
29 import jalview.ws.params.ParamDatastoreI;
30
31 import java.awt.Color;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyChangeListener;
36 import java.net.URL;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Hashtable;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.StringTokenizer;
46 import java.util.Vector;
47
48 import javax.swing.JMenu;
49 import javax.swing.JMenuItem;
50
51 import compbio.ws.client.Services;
52
53 /**
54  * discoverer for jws2 services. Follows the lightweight service discoverer
55  * pattern (archetyped by EnfinEnvision2OneWay)
56  * 
57  * @author JimP
58  * 
59  */
60 public class Jws2Discoverer implements Runnable, WSMenuEntryProviderI
61 {
62   private java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
63           this);
64
65   /**
66    * change listeners are notified of "services" property changes
67    * 
68    * @param listener
69    *          to be added that consumes new services Hashtable object.
70    */
71   public void addPropertyChangeListener(
72           java.beans.PropertyChangeListener listener)
73   {
74     changeSupport.addPropertyChangeListener(listener);
75   }
76
77   /**
78    * 
79    * 
80    * @param listener
81    *          to be removed
82    */
83   public void removePropertyChangeListener(
84           java.beans.PropertyChangeListener listener)
85   {
86     changeSupport.removePropertyChangeListener(listener);
87   }
88
89   boolean running = false, aborted = false;
90
91   /**
92    * @return the aborted
93    */
94   public boolean isAborted()
95   {
96     return aborted;
97   }
98
99   /**
100    * @param aborted
101    *          the aborted to set
102    */
103   public void setAborted(boolean aborted)
104   {
105     this.aborted = aborted;
106   }
107
108   Thread oldthread = null;
109
110   public void run()
111   {
112
113     if (running && oldthread != null && oldthread.isAlive())
114     {
115       if (!aborted)
116       {
117         return;
118       }
119       while (running)
120       {
121         try
122         {
123           Cache.log
124                   .debug("Waiting around for old discovery thread to finish.");
125           // wait around until old discoverer dies
126           Thread.sleep(100);
127         } catch (Exception e)
128         {
129         }
130       }
131       Cache.log.debug("Old discovery thread has finished.");
132     }
133     running = true;
134
135     // first set up exclusion list if needed
136     final Set<String> ignoredServices = new HashSet<String>();
137     for (String ignored : jalview.bin.Cache.getDefault(
138             "IGNORED_JABAWS_SERVICETYPES", "")
139             .split("\\|"))
140     {
141       ignoredServices.add(ignored);
142     }
143
144     changeSupport.firePropertyChange("services", services, new Vector());
145     oldthread = Thread.currentThread();
146     try
147     {
148       Class foo = getClass().getClassLoader().loadClass(
149               "compbio.ws.client.Jws2Client");
150     } catch (ClassNotFoundException e)
151     {
152       System.err
153               .println("Not enabling JABA Webservices : client jar is not available."
154                       + "\nPlease check that your webstart JNLP file is up to date!");
155       running = false;
156       return;
157     }
158     // reinitialise records of good and bad service URLs
159     if (services != null)
160     {
161       services.removeAllElements();
162     }
163     if (urlsWithoutServices != null)
164     {
165       urlsWithoutServices.removeAllElements();
166     }
167     if (invalidServiceUrls != null)
168     {
169       invalidServiceUrls.removeAllElements();
170     }
171     if (validServiceUrls != null)
172     {
173       validServiceUrls.removeAllElements();
174     }
175     ArrayList<String> svctypes = new ArrayList<String>();
176
177     List<JabaWsServerQuery> qrys = new ArrayList<JabaWsServerQuery>();
178     for (final String jwsservers : getServiceUrls())
179     {
180       JabaWsServerQuery squery = new JabaWsServerQuery(this, jwsservers);
181       if (svctypes.size() == 0)
182       {
183         // TODO: remove this ugly hack to get Canonical JABA service ordering
184         // for all possible services
185         for (Services sv : squery.JABAWS2SERVERS)
186         {
187           if (!ignoredServices.contains(sv.toString()))
188           {
189             svctypes.add(sv.toString());
190           }
191         }
192
193       }
194       qrys.add(squery);
195       new Thread(squery).start();
196     }
197     boolean finished = true;
198     do
199     {
200       finished = true;
201       try
202       {
203         Thread.sleep(100);
204       } catch (Exception e)
205       {
206       }
207       ;
208       for (JabaWsServerQuery squery : qrys)
209       {
210         if (squery.isRunning())
211         {
212           finished = false;
213         }
214       }
215       if (aborted)
216       {
217         Cache.log.debug("Aborting " + qrys.size()
218                 + " JABAWS discovery threads.");
219         for (JabaWsServerQuery squery : qrys)
220         {
221           squery.setQuit(true);
222         }
223       }
224     } while (!aborted && !finished);
225     if (!aborted)
226     {
227       // resort services according to order found in jabaws service list
228       // also ensure servics for each host are ordered in same way.
229
230       if (services != null && services.size() > 0)
231       {
232         Jws2Instance[] svcs = new Jws2Instance[services.size()];
233         int[] spos = new int[services.size()];
234         int ipos = 0;
235         Vector svcUrls = getServiceUrls();
236         for (Jws2Instance svc : services)
237         {
238           svcs[ipos] = svc;
239           spos[ipos++] = 1000 * svcUrls.indexOf(svc.getHost()) + 1
240                   + svctypes.indexOf(svc.serviceType);
241         }
242         jalview.util.QuickSort.sort(spos, svcs);
243         services = new Vector<Jws2Instance>();
244         for (Jws2Instance svc : svcs)
245         {
246           if (!ignoredServices.contains(svc.serviceType))
247           {
248             services.add(svc);
249           }
250         }
251       }
252     }
253     oldthread = null;
254     running = false;
255     changeSupport.firePropertyChange("services", new Vector(), services);
256   }
257
258   /**
259    * record this service endpoint so we can use it
260    * 
261    * @param jwsservers
262    * @param srv
263    * @param service2
264    */
265   synchronized void addService(String jwsservers, Jws2Instance service)
266   {
267     if (services == null)
268     {
269       services = new Vector<Jws2Instance>();
270     }
271     System.out.println("Discovered service: " + jwsservers + " "
272             + service.toString());
273     // Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
274     // service2);
275
276     services.add(service);
277     // retrieve the presets and parameter set and cache now
278     ParamDatastoreI pds = service.getParamStore();
279     if (pds != null)
280     {
281       pds.getPresets();
282     }
283     service.hasParameters();
284     if (validServiceUrls == null)
285     {
286       validServiceUrls = new Vector();
287     }
288     validServiceUrls.add(jwsservers);
289   }
290
291   /**
292    * holds list of services.
293    */
294   protected Vector<Jws2Instance> services;
295
296   /**
297    * attach all available web services to the appropriate submenu in the given
298    * JMenu
299    */
300   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
301   {
302     // dynamically regenerate service list.
303     populateWSMenuEntry(wsmenu, alignFrame, null);
304   }
305
306   private boolean isRecalculable(String action)
307   {
308     return (action != null && action.equalsIgnoreCase("conservation"));
309   }
310
311   private void populateWSMenuEntry(JMenu jws2al,
312           final AlignFrame alignFrame, String typeFilter)
313   {
314     if (running || services == null || services.size() == 0)
315     {
316       return;
317     }
318     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
319             .getDefault("WSMENU_BYTYPE", false);
320     /**
321      * eventually, JWS2 services will appear under the same align/etc submenus.
322      * for moment we keep them separate.
323      */
324     JMenu atpoint;
325     List<Jws2Instance> enumerableServices = new ArrayList<Jws2Instance>();
326     // jws2al.removeAll();
327     Map<String, Jws2Instance> preferredHosts = new HashMap<String, Jws2Instance>();
328     Map<String, List<Jws2Instance>> alternates = new HashMap<String, List<Jws2Instance>>();
329     for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
330     {
331       if (!isRecalculable(service.action))
332       {
333         // add 'one shot' services to be displayed using the classic menu
334         // structure
335         enumerableServices.add(service);
336       }
337       else
338       {
339         if (!preferredHosts.containsKey(service.serviceType))
340         {
341           Jws2Instance preferredInstance = getPreferredServiceFor(
342                   alignFrame, service.serviceType);
343           if (preferredInstance != null)
344           {
345             preferredHosts.put(service.serviceType, preferredInstance);
346           }
347           else
348           {
349             preferredHosts.put(service.serviceType, service);
350           }
351         }
352         List<Jws2Instance> ph = alternates.get(service.serviceType);
353         if (preferredHosts.get(service.serviceType) != service)
354         {
355           if (ph == null)
356           {
357             ph = new ArrayList<Jws2Instance>();
358           }
359           ph.add(service);
360           alternates.put(service.serviceType, ph);
361         }
362       }
363
364     }
365
366     // create GUI element for classic services
367     addEnumeratedServices(jws2al, alignFrame, enumerableServices);
368     // and the instantaneous services
369     for (final Jws2Instance service : preferredHosts.values())
370     {
371       atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
372       JMenuItem hitm;
373       if (atpoint.getItemCount() > 1)
374       {
375         // previous service of this type already present
376         atpoint.addSeparator();
377       }
378       atpoint.add(hitm = new JMenuItem(service.getHost()));
379       hitm.setForeground(Color.blue);
380       hitm.addActionListener(new ActionListener()
381       {
382
383         @Override
384         public void actionPerformed(ActionEvent e)
385         {
386           Desktop.showUrl(service.getHost());
387         }
388       });
389       hitm.setToolTipText(JvSwingUtils
390               .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
391
392       service.attachWSMenuEntry(atpoint, alignFrame);
393       if (alternates.containsKey(service.serviceType))
394       {
395         atpoint.add(hitm = new JMenu("Switch server"));
396         hitm.setToolTipText(JvSwingUtils
397                 .wrapTooltip("Choose a server for running this service"));
398         for (final Jws2Instance sv : alternates.get(service.serviceType))
399         {
400           JMenuItem itm;
401           hitm.add(itm = new JMenuItem(sv.getHost()));
402           itm.setForeground(Color.blue);
403           itm.addActionListener(new ActionListener()
404           {
405
406             @Override
407             public void actionPerformed(ActionEvent arg0)
408             {
409               new Thread(new Runnable()
410               {
411                 public void run()
412                 {
413                   setPreferredServiceFor(alignFrame, sv.serviceType,
414                           sv.action, sv);
415                   changeSupport.firePropertyChange("services",
416                           new Vector(), services);
417                 };
418               }).start();
419
420             }
421           });
422         }
423         /*
424          * hitm.addActionListener(new ActionListener() {
425          * 
426          * @Override public void actionPerformed(ActionEvent arg0) { new
427          * Thread(new Runnable() {
428          * 
429          * @Override public void run() { new SetPreferredServer(alignFrame,
430          * service.serviceType, service.action); } }).start(); } });
431          */
432       }
433     }
434   }
435
436   /**
437    * add services using the Java 2.5/2.6/2.7 system which optionally creates
438    * submenus to index by host and service program type
439    */
440   private void addEnumeratedServices(final JMenu jws2al,
441           final AlignFrame alignFrame, List<Jws2Instance> enumerableServices)
442   {
443     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
444             .getDefault("WSMENU_BYTYPE", false);
445     /**
446      * eventually, JWS2 services will appear under the same align/etc submenus.
447      * for moment we keep them separate.
448      */
449     JMenu atpoint;
450     MsaWSClient msacl = new MsaWSClient();
451     List<String> hostLabels = new ArrayList<String>();
452     Hashtable<String, String> lasthostFor = new Hashtable<String, String>();
453     Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<String, ArrayList<Jws2Instance>>();
454     ArrayList<String> hostlist = new ArrayList<String>();
455     for (Jws2Instance service : enumerableServices)
456     {
457       ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
458       if (hostservices == null)
459       {
460         hosts.put(service.getHost(),
461                 hostservices = new ArrayList<Jws2Instance>());
462         hostlist.add(service.getHost());
463       }
464       hostservices.add(service);
465     }
466     // now add hosts in order of the given array
467     for (String host : hostlist)
468     {
469       Jws2Instance orderedsvcs[] = hosts.get(host).toArray(
470               new Jws2Instance[1]);
471       String sortbytype[] = new String[orderedsvcs.length];
472       for (int i = 0; i < sortbytype.length; i++)
473       {
474         sortbytype[i] = orderedsvcs[i].serviceType;
475       }
476       jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
477       for (final Jws2Instance service : orderedsvcs)
478       {
479         atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
480         String type = service.serviceType;
481         if (byhost)
482         {
483           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
484           if (atpoint.getToolTipText() == null)
485           {
486             atpoint.setToolTipText("Services at " + host);
487           }
488         }
489         if (bytype)
490         {
491           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
492           if (atpoint.getToolTipText() == null)
493           {
494             atpoint.setToolTipText(service.getActionText());
495           }
496         }
497         if (!byhost
498                 && !hostLabels.contains(host + service.serviceType
499                         + service.getActionText()))
500         // !hostLabels.contains(host + (bytype ?
501         // service.serviceType+service.getActionText() : "")))
502         {
503           // add a marker indicating where this service is hosted
504           // relies on services from the same host being listed in a
505           // contiguous
506           // group
507           JMenuItem hitm;
508           if (hostLabels.contains(host))
509           {
510             atpoint.addSeparator();
511           }
512           else
513           {
514             hostLabels.add(host);
515           }
516           if (lasthostFor.get(service.action) == null
517                   || !lasthostFor.get(service.action).equals(host))
518           {
519             atpoint.add(hitm = new JMenuItem(host));
520             hitm.setForeground(Color.blue);
521             hitm.addActionListener(new ActionListener()
522             {
523
524               @Override
525               public void actionPerformed(ActionEvent e)
526               {
527                 Desktop.showUrl(service.getHost());
528               }
529             });
530             hitm.setToolTipText(JvSwingUtils
531                     .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
532             lasthostFor.put(service.action, host);
533           }
534           hostLabels.add(host + service.serviceType
535                   + service.getActionText());
536         }
537
538         service.attachWSMenuEntry(atpoint, alignFrame);
539       }
540     }
541   }
542
543   public static void main(String[] args)
544   {
545     if (args.length > 0)
546     {
547       testUrls = new Vector<String>();
548       for (String url : args)
549       {
550         testUrls.add(url);
551       }
552       ;
553     }
554     Thread runner = getDiscoverer().startDiscoverer(
555             new PropertyChangeListener()
556             {
557
558               public void propertyChange(PropertyChangeEvent evt)
559               {
560                 if (getDiscoverer().services != null)
561                 {
562                   System.out.println("Changesupport: There are now "
563                           + getDiscoverer().services.size() + " services");
564                   int i = 1;
565                   for (Jws2Instance instance : getDiscoverer().services)
566                   {
567                     System.out.println("Service " + i++ + " "
568                             + instance.getClass() + "@"
569                             + instance.getHost() + ": "
570                             + instance.getActionText());
571                   }
572
573                 }
574               }
575             });
576     while (runner.isAlive())
577     {
578       try
579       {
580         Thread.sleep(50);
581       } catch (InterruptedException e)
582       {
583       }
584       ;
585     }
586     try
587     {
588       Thread.sleep(50);
589     } catch (InterruptedException x)
590     {
591     }
592   }
593
594   private static Jws2Discoverer discoverer;
595
596   public static Jws2Discoverer getDiscoverer()
597   {
598     if (discoverer == null)
599     {
600       discoverer = new Jws2Discoverer();
601     }
602     return discoverer;
603   }
604
605   public boolean hasServices()
606   {
607     // TODO Auto-generated method stub
608     return !running && services != null && services.size() > 0;
609   }
610
611   public boolean isRunning()
612   {
613     return running;
614   }
615
616   /**
617    * the jalview .properties entry for JWS2 URLS
618    */
619   final static String JWS2HOSTURLS = "JWS2HOSTURLS";
620
621   public static void setServiceUrls(Vector<String> urls)
622   {
623     if (urls != null)
624     {
625       StringBuffer urlbuffer = new StringBuffer();
626       String sep = "";
627       for (String url : urls)
628       {
629         urlbuffer.append(sep);
630         urlbuffer.append(url);
631         sep = ",";
632       }
633       Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
634     }
635     else
636     {
637       Cache.removeProperty(JWS2HOSTURLS);
638     }
639   }
640
641   private static Vector<String> testUrls = null;
642
643   public static Vector<String> getServiceUrls()
644   {
645     if (testUrls != null)
646     {
647       // return test urls, if there are any, instead of touching cache
648       return testUrls;
649     }
650     String surls = Cache.getDefault(JWS2HOSTURLS,
651             "http://www.compbio.dundee.ac.uk/jabaws");
652     Vector<String> urls = new Vector<String>();
653     try
654     {
655       StringTokenizer st = new StringTokenizer(surls, ",");
656       while (st.hasMoreElements())
657       {
658         String url = null;
659         try
660         {
661           java.net.URL u = new java.net.URL(url = st.nextToken());
662           if (!urls.contains(url))
663           {
664             urls.add(url);
665           }
666           else
667           {
668             jalview.bin.Cache.log.info("Ignoring duplicate url in "
669                     + JWS2HOSTURLS + " list");
670           }
671         } catch (Exception ex)
672         {
673           jalview.bin.Cache.log
674                   .warn("Problem whilst trying to make a URL from '"
675                           + ((url != null) ? url : "<null>") + "'");
676           jalview.bin.Cache.log
677                   .warn("This was probably due to a malformed comma separated list"
678                           + " in the "
679                           + JWS2HOSTURLS
680                           + " entry of $(HOME)/.jalview_properties)");
681           jalview.bin.Cache.log.debug("Exception was ", ex);
682         }
683       }
684     } catch (Exception ex)
685     {
686       jalview.bin.Cache.log.warn(
687               "Error parsing comma separated list of urls in "
688                       + JWS2HOSTURLS + " preference.", ex);
689     }
690     if (urls.size() >= 0)
691     {
692       return urls;
693     }
694     return null;
695   }
696
697   public Vector<Jws2Instance> getServices()
698   {
699     return (services == null) ? new Vector<Jws2Instance>()
700             : new Vector<Jws2Instance>(services);
701   }
702
703   /**
704    * test the given URL with the JabaWS test code
705    * 
706    * @param foo
707    * @return
708    */
709   public static boolean testServiceUrl(URL foo)
710   {
711     try
712     {
713       compbio.ws.client.WSTester.main(new String[]
714       { "-h=" + foo.toString() });
715     } catch (Exception e)
716     {
717       e.printStackTrace();
718       return false;
719     } catch (OutOfMemoryError e)
720     {
721       e.printStackTrace();
722       return false;
723     } catch (Error e)
724     {
725       e.printStackTrace();
726       return false;
727     }
728
729     return true;
730   }
731
732   /**
733    * Start a fresh discovery thread and notify the given object when we're
734    * finished. Any known existing threads will be killed before this one is
735    * started.
736    * 
737    * @param changeSupport2
738    * @return new thread
739    */
740   public Thread startDiscoverer(PropertyChangeListener changeSupport2)
741   {
742     if (isRunning())
743     {
744       setAborted(true);
745     }
746     addPropertyChangeListener(changeSupport2);
747     Thread thr = new Thread(this);
748     thr.start();
749     return thr;
750   }
751
752   Vector<String> invalidServiceUrls = null, urlsWithoutServices = null,
753           validServiceUrls = null;
754
755   /**
756    * @return the invalidServiceUrls
757    */
758   public Vector<String> getInvalidServiceUrls()
759   {
760     return invalidServiceUrls;
761   }
762
763   /**
764    * @return the urlsWithoutServices
765    */
766   public Vector<String> getUrlsWithoutServices()
767   {
768     return urlsWithoutServices;
769   }
770
771   /**
772    * add an 'empty' JABA server to the list. Only servers not already in the
773    * 'bad URL' list will be added to this list.
774    * 
775    * @param jwsservers
776    */
777   public synchronized void addUrlwithnoservices(String jwsservers)
778   {
779     if (urlsWithoutServices == null)
780     {
781       urlsWithoutServices = new Vector<String>();
782     }
783
784     if ((invalidServiceUrls == null || !invalidServiceUrls
785             .contains(jwsservers))
786             && !urlsWithoutServices.contains(jwsservers))
787     {
788       urlsWithoutServices.add(jwsservers);
789     }
790   }
791
792   /**
793    * add a bad URL to the list
794    * 
795    * @param jwsservers
796    */
797   public synchronized void addInvalidServiceUrl(String jwsservers)
798   {
799     if (invalidServiceUrls == null)
800     {
801       invalidServiceUrls = new Vector<String>();
802     }
803     if (!invalidServiceUrls.contains(jwsservers))
804     {
805       invalidServiceUrls.add(jwsservers);
806     }
807   }
808
809   /**
810    * 
811    * @return a human readable report of any problems with the service URLs used
812    *         for discovery
813    */
814   public String getErrorMessages()
815   {
816     if (!isRunning() && !isAborted())
817     {
818       StringBuffer ermsg = new StringBuffer();
819       boolean list = false;
820       if (getInvalidServiceUrls() != null
821               && getInvalidServiceUrls().size() > 0)
822       {
823         ermsg.append("URLs that could not be contacted: \n");
824         for (String svcurl : getInvalidServiceUrls())
825         {
826           if (list)
827           {
828             ermsg.append(", ");
829           }
830           list = true;
831           ermsg.append(svcurl);
832         }
833         ermsg.append("\n\n");
834       }
835       list = false;
836       if (getUrlsWithoutServices() != null
837               && getUrlsWithoutServices().size() > 0)
838       {
839         ermsg.append("URLs without any JABA Services : \n");
840         for (String svcurl : getUrlsWithoutServices())
841         {
842           if (list)
843           {
844             ermsg.append(", ");
845           }
846           list = true;
847           ermsg.append(svcurl);
848         }
849         ermsg.append("\n");
850       }
851       if (ermsg.length() > 1)
852       {
853         return ermsg.toString();
854       }
855
856     }
857     return null;
858   }
859
860   public int getServerStatusFor(String url)
861   {
862     if (validServiceUrls != null && validServiceUrls.contains(url))
863     {
864       return 1;
865     }
866     if (urlsWithoutServices != null && urlsWithoutServices.contains(url))
867       return 0;
868     if (invalidServiceUrls != null && invalidServiceUrls.contains(url))
869     {
870       return -1;
871     }
872     return -2;
873   }
874
875   /**
876    * pick the user's preferred service based on a set of URLs (jaba server
877    * locations) and service URIs (specifying version and service interface
878    * class)
879    * 
880    * @param serviceURL
881    * @return null or best match for given uri/ls.
882    */
883   public Jws2Instance getPreferredServiceFor(String[] serviceURLs)
884   {
885     HashSet<String> urls = new HashSet<String>();
886     urls.addAll(Arrays.asList(serviceURLs));
887     Jws2Instance match = null;
888     if (services != null)
889     {
890       for (Jws2Instance svc : services)
891       {
892         if (urls.contains(svc.getServiceTypeURI()))
893         {
894           if (match == null)
895           {
896             // for moment we always pick service from server ordered first in
897             // user's preferences
898             match = svc;
899           }
900           if (urls.contains(svc.getUri()))
901           {
902             // stop and return - we've matched type URI and URI for service
903             // endpoint
904             return svc;
905           }
906         }
907       }
908     }
909     return match;
910   }
911
912   Map<String, Map<String, String>> preferredServiceMap = new HashMap<String, Map<String, String>>();;
913
914   /**
915    * get current preferred service of the given type, or global default
916    * 
917    * @param af
918    *          null or a specific alignFrame
919    * @param serviceType
920    *          Jws2Instance.serviceType for service
921    * @return null if no service of this type is available, the preferred service
922    *         for the serviceType and af if specified and if defined.
923    */
924   public Jws2Instance getPreferredServiceFor(AlignFrame af,
925           String serviceType)
926   {
927     String serviceurl = null;
928     synchronized (preferredServiceMap)
929     {
930       String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
931       Map<String, String> prefmap = preferredServiceMap.get(afid);
932       if (afid.length() > 0 && prefmap == null)
933       {
934         // recover global setting, if any
935         prefmap = preferredServiceMap.get("");
936       }
937       if (prefmap != null)
938       {
939         serviceurl = prefmap.get(serviceType);
940       }
941
942     }
943     Jws2Instance response = null;
944     for (Jws2Instance svc : services)
945     {
946       if (svc.serviceType.equals(serviceType))
947       {
948         if (serviceurl == null || serviceurl.equals(svc.getHost()))
949         {
950           response = svc;
951           break;
952         }
953       }
954     }
955     return response;
956   }
957
958   public void setPreferredServiceFor(AlignFrame af, String serviceType,
959           String serviceAction, Jws2Instance selectedServer)
960   {
961     String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
962     if (preferredServiceMap == null)
963     {
964       preferredServiceMap = new HashMap<String, Map<String, String>>();
965     }
966     Map<String, String> prefmap = preferredServiceMap.get(afid);
967     if (prefmap == null)
968     {
969       prefmap = new HashMap<String, String>();
970       preferredServiceMap.put(afid, prefmap);
971     }
972     prefmap.put(serviceType, selectedServer.getHost());
973     prefmap.put(serviceAction, selectedServer.getHost());
974   }
975
976   public void setPreferredServiceFor(String serviceType,
977           String serviceAction, Jws2Instance selectedServer)
978   {
979     setPreferredServiceFor(null, serviceType, serviceAction, selectedServer);
980   }
981 }