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