JAL-1137 clean up old-style menu code and look
[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.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 for all possible services 
168         for (Services sv:squery.JABAWS2SERVERS)
169         {
170           svctypes.add(sv.toString());
171         }
172
173       }
174       qrys.add(squery);
175       new Thread(squery).start();
176     }
177     boolean finished = true;
178     do
179     {
180       finished=true;
181       try
182       {
183         Thread.sleep(100);
184       } catch (Exception e)
185       {
186       }
187       ;
188       for (JabaWsServerQuery squery : qrys)
189       {
190         finished = finished && !squery.isRunning();
191       }
192       if (aborted)
193       {
194         Cache.log.debug("Aborting " + qrys.size()
195                 + " JABAWS discovery threads.");
196         for (JabaWsServerQuery squery : qrys)
197         {
198           squery.setQuit(true);
199         }
200       }
201     } while (!aborted && !finished);
202     if (!aborted)
203     {
204       // resort services according to order found in jabaws service list
205       // also ensure servics for each host are ordered in same way.
206       
207       if (services!=null && services.size()>0)
208       {
209         Jws2Instance[] svcs=new Jws2Instance[services.size()];
210         int[] spos=new int[services.size()];
211         int ipos=0;
212         Vector svcUrls = getServiceUrls();
213         for (Jws2Instance svc:services)
214         {
215           svcs[ipos]=svc;
216           spos[ipos++]=1000*svcUrls.indexOf(svc.getHost()) + 1+svctypes.indexOf(svc.serviceType);
217         }
218         jalview.util.QuickSort.sort(spos, svcs);
219         services=new Vector<Jws2Instance>();
220         for (Jws2Instance svc:svcs) {
221           services.add(svc);
222         }
223       }
224     }
225     oldthread = null;
226     running = false;
227     changeSupport.firePropertyChange("services", new Vector(), services);
228   }
229
230   /**
231    * record this service endpoint so we can use it
232    * 
233    * @param jwsservers
234    * @param srv
235    * @param service2
236    */
237   synchronized void addService(String jwsservers, Jws2Instance service)
238   {
239     if (services == null)
240     {
241       services = new Vector<Jws2Instance>();
242     }
243     System.out.println("Discovered service: " + jwsservers + " "
244             + service.toString());
245 //    Jws2Instance service = new Jws2Instance(jwsservers, srv.toString(),
246 //            service2);
247
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)
253     {
254       validServiceUrls=new Vector();
255     }
256     validServiceUrls.add(jwsservers);
257   }
258
259   /**
260    * holds list of services.
261    */
262   protected Vector<Jws2Instance> services;
263   /**
264    * attach all available web services to the appropriate submenu in the given JMenu
265    */
266   public void attachWSMenuEntry(JMenu wsmenu, final AlignFrame alignFrame)
267   {
268     // dynamically regenerate service list.
269     populateWSMenuEntry(wsmenu, alignFrame, null);
270   }
271   private boolean isRecalculable(String action) {
272     return (action!=null && action.equalsIgnoreCase("conservation"));
273   }
274   private void populateWSMenuEntry(JMenu jws2al, final AlignFrame alignFrame, String typeFilter)
275   {
276     if (running || services == null || services.size() == 0)
277     {
278       return;
279     }
280     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
281             .getDefault("WSMENU_BYTYPE", false);
282     /**
283      * eventually, JWS2 services will appear under the same align/etc submenus.
284      * for moment we keep them separate.
285      */
286     JMenu atpoint;
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]))
292     {
293       if (!isRecalculable(service.action)) {
294         // add 'one shot' services to be displayed using the classic menu structure
295         enumerableServices.add(service);
296       } else {
297         if (!preferredHosts.containsKey(service.serviceType))
298         {
299           Jws2Instance preferredInstance = getPreferredServiceFor(alignFrame,
300                   service.serviceType);
301           if (preferredInstance != null)
302           {
303             preferredHosts.put(service.serviceType, preferredInstance);
304           }
305           else
306           {
307             preferredHosts.put(service.serviceType, service);
308           }
309         }
310         List<Jws2Instance> ph=alternates.get(service.serviceType);
311         if (preferredHosts.get(service.serviceType)!=service)
312         {
313           if (ph==null)
314           {
315             ph=new ArrayList<Jws2Instance>();
316           }
317           ph.add(service);
318           alternates.put(service.serviceType, ph);
319         }
320       }
321
322     }
323
324     // create GUI element for classic services
325     addEnumeratedServices(jws2al, alignFrame, enumerableServices);
326     // and the instantaneous services
327     for (final Jws2Instance service : preferredHosts.values())
328     {
329       atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
330       JMenuItem hitm;
331       if (atpoint.getItemCount()>1) {
332         // previous service of this type already present
333         atpoint.addSeparator();
334       }
335       atpoint.add(hitm = new JMenuItem(service.getHost()));
336       hitm.setForeground(Color.blue);
337       hitm.addActionListener(new ActionListener()
338       {
339
340         @Override
341         public void actionPerformed(ActionEvent e)
342         {
343           Desktop.showUrl(service.getHost());
344         }
345       });
346       hitm.setToolTipText(JvSwingUtils
347               .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
348
349       service.attachWSMenuEntry(atpoint, alignFrame);
350       if (alternates.containsKey(service.serviceType))
351       {
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))
355         {
356           JMenuItem itm;
357           hitm.add(itm=new JMenuItem(sv.getHost()));
358           itm.setForeground(Color.blue);
359           itm.addActionListener(new ActionListener()
360           {
361
362             @Override
363             public void actionPerformed(ActionEvent arg0)
364             {
365               new Thread(new Runnable() { 
366                 public void run() {
367                   setPreferredServiceFor(alignFrame, sv.serviceType, sv.action, sv);
368                   changeSupport.firePropertyChange("services", new Vector(), services);
369                 };
370               }).start();
371
372             }
373           });
374         }
375         /*hitm.addActionListener(new ActionListener()
376               {
377
378                 @Override
379                 public void actionPerformed(ActionEvent arg0)
380                 {
381                   new Thread(new Runnable() {
382                     @Override
383                     public void run()
384                     {
385                       new SetPreferredServer(alignFrame, service.serviceType, service.action);
386                     }
387                   }).start();
388                 }
389               });*/
390       }
391     }
392   }
393   /**
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
395    */
396   private void addEnumeratedServices(final JMenu jws2al, final AlignFrame alignFrame, List<Jws2Instance> enumerableServices)
397   {
398     boolean byhost = Cache.getDefault("WSMENU_BYHOST", false), bytype = Cache
399             .getDefault("WSMENU_BYTYPE", false);
400     /**
401      * eventually, JWS2 services will appear under the same align/etc submenus.
402      * for moment we keep them separate.
403      */
404     JMenu atpoint;
405     MsaWSClient msacl = new MsaWSClient();
406     List<String> hostLabels = new ArrayList<String>();
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)
411     {
412       ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
413       if (hostservices == null)
414       {
415         hosts.put(service.getHost(),
416                 hostservices = new ArrayList<Jws2Instance>());
417         hostlist.add(service.getHost());
418       }
419       hostservices.add(service);
420     }
421     // now add hosts in order of the given array
422     for (String host : hostlist)
423     {
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++)
428       {
429         sortbytype[i] = orderedsvcs[i].serviceType;
430       }
431       jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
432       for (final Jws2Instance service : orderedsvcs)
433       {
434         atpoint = JvSwingUtils.findOrCreateMenu(jws2al,service.action);
435         String type = service.serviceType;
436         if (byhost)
437         {
438           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
439           if (atpoint.getToolTipText() == null)
440           {
441             atpoint.setToolTipText("Services at " + host);
442           }
443         }
444         if (bytype)
445         {
446           atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
447           if (atpoint.getToolTipText() == null)
448           {
449             atpoint.setToolTipText(service.getActionText());
450           }
451         }
452         if (!byhost
453                 && !hostLabels.contains(host + service.serviceType
454                         + service.getActionText()))
455         // !hostLabels.contains(host + (bytype ?
456         // service.serviceType+service.getActionText() : "")))
457         {
458           // add a marker indicating where this service is hosted
459           // relies on services from the same host being listed in a
460           // contiguous
461           // group
462           JMenuItem hitm;
463           if (hostLabels.contains(host)) {
464             atpoint.addSeparator();
465           } else {
466             hostLabels.add(host);
467           }
468           if (lasthostFor.get(service.action) == null || !lasthostFor.get(service.action).equals(host))
469           {
470             atpoint.add(hitm = new JMenuItem(host));
471             hitm.setForeground(Color.blue);
472             hitm.addActionListener(new ActionListener()
473             {
474
475               @Override
476               public void actionPerformed(ActionEvent e)
477               {
478                 Desktop.showUrl(service.getHost());
479               }
480             });
481             hitm.setToolTipText(JvSwingUtils
482                     .wrapTooltip("Opens the JABAWS server's homepage in web browser"));
483             lasthostFor.put(service.action,host);
484           }
485           hostLabels.add(host + service.serviceType
486                   + service.getActionText());
487         }
488         
489         service.attachWSMenuEntry(atpoint, alignFrame);
490       }
491     }
492   }
493   public static void main(String[] args)
494   {
495     if (args.length>0)
496     {
497       testUrls = new Vector<String>();
498       for (String url:args)
499       {
500         testUrls.add(url);
501       };
502     }
503     Thread runner = getDiscoverer().startDiscoverer(
504             new PropertyChangeListener()
505             {
506
507               public void propertyChange(PropertyChangeEvent evt)
508               {
509                 if (getDiscoverer().services != null)
510                 {
511                   System.out.println("Changesupport: There are now "
512                           + getDiscoverer().services.size() + " services");
513                   int i=1;
514                   for (Jws2Instance instance:getDiscoverer().services)
515                   {
516                     System.out.println("Service "+i+++" "+instance.getClass()+"@"+instance.getHost()+": "+instance.getActionText());
517                   }
518
519                 }
520               }
521             });
522     while (runner.isAlive())
523     {
524       try
525       {
526         Thread.sleep(50);
527       } catch (InterruptedException e)
528       {
529       }
530       ;
531     }
532     try {
533       Thread.sleep(50);
534     } catch (InterruptedException x) {}
535   }
536
537   private static Jws2Discoverer discoverer;
538
539   public static Jws2Discoverer getDiscoverer()
540   {
541     if (discoverer == null)
542     {
543       discoverer = new Jws2Discoverer();
544     }
545     return discoverer;
546   }
547
548   public boolean hasServices()
549   {
550     // TODO Auto-generated method stub
551     return !running && services != null && services.size() > 0;
552   }
553
554   public boolean isRunning()
555   {
556     return running;
557   }
558
559   /**
560    * the jalview .properties entry for JWS2 URLS
561    */
562   final static String JWS2HOSTURLS = "JWS2HOSTURLS";
563
564   public static void setServiceUrls(Vector<String> urls)
565   {
566     if (urls != null)
567     {
568       StringBuffer urlbuffer = new StringBuffer();
569       String sep = "";
570       for (String url : urls)
571       {
572         urlbuffer.append(sep);
573         urlbuffer.append(url);
574         sep = ",";
575       }
576       Cache.setProperty(JWS2HOSTURLS, urlbuffer.toString());
577     }
578     else
579     {
580       Cache.removeProperty(JWS2HOSTURLS);
581     }
582   }
583
584   private static Vector<String> testUrls=null;
585   public static Vector<String> getServiceUrls()
586   {
587     if (testUrls!=null)
588     {
589       // return test urls, if there are any, instead of touching cache
590       return testUrls;
591     }
592     String surls = Cache.getDefault(JWS2HOSTURLS,
593             "http://www.compbio.dundee.ac.uk/jabaws");
594     Vector<String> urls = new Vector<String>();
595     try
596     {
597       StringTokenizer st = new StringTokenizer(surls, ",");
598       while (st.hasMoreElements())
599       {
600         String url = null;
601         try
602         {
603           java.net.URL u = new java.net.URL(url = st.nextToken());
604           if (!urls.contains(url))
605           {
606             urls.add(url);
607           }
608           else
609           {
610             jalview.bin.Cache.log.info("Ignoring duplicate url in "
611                     + JWS2HOSTURLS + " list");
612           }
613         } catch (Exception ex)
614         {
615           jalview.bin.Cache.log
616                   .warn("Problem whilst trying to make a URL from '"
617                           + ((url != null) ? url : "<null>") + "'");
618           jalview.bin.Cache.log
619                   .warn("This was probably due to a malformed comma separated list"
620                           + " in the "
621                           + JWS2HOSTURLS
622                           + " entry of $(HOME)/.jalview_properties)");
623           jalview.bin.Cache.log.debug("Exception was ", ex);
624         }
625       }
626     } catch (Exception ex)
627     {
628       jalview.bin.Cache.log.warn(
629               "Error parsing comma separated list of urls in "
630                       + JWS2HOSTURLS + " preference.", ex);
631     }
632     if (urls.size() >= 0)
633     {
634       return urls;
635     }
636     return null;
637   }
638
639   public Vector<Jws2Instance> getServices()
640   {
641     return (services == null) ? new Vector<Jws2Instance>()
642             : new Vector<Jws2Instance>(services);
643   }
644
645   /**
646    * test the given URL with the JabaWS test code
647    * 
648    * @param foo
649    * @return
650    */
651   public static boolean testServiceUrl(URL foo)
652   {
653     try
654     {
655       compbio.ws.client.WSTester.main(new String[]
656       { "-h=" + foo.toString() });
657     } catch (Exception e)
658     {
659       e.printStackTrace();
660       return false;
661     } catch (OutOfMemoryError e)
662     {
663       e.printStackTrace();
664       return false;
665     } catch (Error e)
666     {
667       e.printStackTrace();
668       return false;
669     }
670
671     return true;
672   }
673
674   /**
675    * Start a fresh discovery thread and notify the given object when we're
676    * finished. Any known existing threads will be killed before this one is
677    * started.
678    * 
679    * @param changeSupport2
680    * @return new thread
681    */
682   public Thread startDiscoverer(PropertyChangeListener changeSupport2)
683   {
684     if (isRunning())
685     {
686       setAborted(true);
687     }
688     addPropertyChangeListener(changeSupport2);
689     Thread thr = new Thread(this);
690     thr.start();
691     return thr;
692   }
693
694   Vector<String> invalidServiceUrls = null, urlsWithoutServices = null, validServiceUrls=null;
695
696   /**
697    * @return the invalidServiceUrls
698    */
699   public Vector<String> getInvalidServiceUrls()
700   {
701     return invalidServiceUrls;
702   }
703
704   /**
705    * @return the urlsWithoutServices
706    */
707   public Vector<String> getUrlsWithoutServices()
708   {
709     return urlsWithoutServices;
710   }
711
712   /**
713    * add an 'empty' JABA server to the list. Only servers not already in the
714    * 'bad URL' list will be added to this list.
715    * 
716    * @param jwsservers
717    */
718   public synchronized void addUrlwithnoservices(String jwsservers)
719   {
720     if (urlsWithoutServices == null)
721     {
722       urlsWithoutServices = new Vector<String>();
723     }
724
725     if ((invalidServiceUrls == null || !invalidServiceUrls
726             .contains(jwsservers))
727             && !urlsWithoutServices.contains(jwsservers))
728     {
729       urlsWithoutServices.add(jwsservers);
730     }
731   }
732
733   /**
734    * add a bad URL to the list
735    * 
736    * @param jwsservers
737    */
738   public synchronized void addInvalidServiceUrl(String jwsservers)
739   {
740     if (invalidServiceUrls == null)
741     {
742       invalidServiceUrls = new Vector<String>();
743     }
744     if (!invalidServiceUrls.contains(jwsservers))
745     {
746       invalidServiceUrls.add(jwsservers);
747     }
748   }
749
750   /**
751    * 
752    * @return a human readable report of any problems with the service URLs used
753    *         for discovery
754    */
755   public String getErrorMessages()
756   {
757     if (!isRunning() && !isAborted())
758     {
759       StringBuffer ermsg = new StringBuffer();
760       boolean list = false;
761       if (getInvalidServiceUrls() != null
762               && getInvalidServiceUrls().size() > 0)
763       {
764         ermsg.append("URLs that could not be contacted: \n");
765         for (String svcurl : getInvalidServiceUrls())
766         {
767           if (list)
768           {
769             ermsg.append(", ");
770           }
771           list = true;
772           ermsg.append(svcurl);
773         }
774         ermsg.append("\n\n");
775       }
776       list = false;
777       if (getUrlsWithoutServices() != null
778               && getUrlsWithoutServices().size() > 0)
779       {
780         ermsg.append("URLs without any JABA Services : \n");
781         for (String svcurl : getUrlsWithoutServices())
782         {
783           if (list)
784           {
785             ermsg.append(", ");
786           }
787           list = true;
788           ermsg.append(svcurl);
789         }
790         ermsg.append("\n");
791       }
792       if (ermsg.length() > 1)
793       {
794         return ermsg.toString();
795       }
796
797     }
798     return null;
799   }
800
801   public int getServerStatusFor(String url)
802   {
803     if (validServiceUrls!=null && validServiceUrls.contains(url))
804     {
805       return 1;
806     }
807     if (urlsWithoutServices!=null && urlsWithoutServices.contains(url))
808     return 0;
809     if (invalidServiceUrls!=null && invalidServiceUrls.contains(url))
810     {
811       return -1;
812     }
813     return -2;
814   }
815
816   /**
817    * pick the user's preferred service based on a set of URLs (jaba server locations) and service URIs (specifying version and service interface class)
818    * 
819    * @param serviceURL
820    * @return null or best match for given uri/ls.
821    */
822   public Jws2Instance getPreferredServiceFor(String[] serviceURLs)
823   {
824     HashSet<String> urls=new HashSet<String>();
825     urls.addAll(Arrays.asList(serviceURLs));
826     Jws2Instance match=null;
827     for (Jws2Instance svc:services)
828     {
829       if (urls.contains(svc.getServiceTypeURI()))
830       {
831         if (match==null) {
832           // for moment we always pick service from server ordered first in user's preferences
833           match=svc;
834         }
835         if (urls.contains(svc.getUri()))
836         {
837           // stop and return - we've matched type URI and URI for service endpoint
838           return svc;
839         }
840       }
841     }
842     return match;
843   }
844   
845   Map<String, Map<String, String>> preferredServiceMap = new HashMap<String, Map<String, String>>();
846 ;
847
848   /**
849    * get current preferred service of the given type, or global default
850    * @param af null or a specific alignFrame 
851    * @param serviceType Jws2Instance.serviceType for service
852    * @return null if no service of this type is available, the preferred service for the serviceType and af if specified and if defined. 
853    */
854   public Jws2Instance getPreferredServiceFor(AlignFrame af,
855           String serviceType)
856   {
857     String serviceurl = null;
858     synchronized (preferredServiceMap)
859     {
860       String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
861       Map<String, String> prefmap = preferredServiceMap.get(afid);
862       if (afid.length() > 0 && prefmap == null)
863       {
864         // recover global setting, if any
865         prefmap = preferredServiceMap.get("");
866       }
867         if (prefmap != null)
868         {
869           serviceurl = prefmap.get(serviceType);
870         }
871       
872     }
873     Jws2Instance response = null;
874     for (Jws2Instance svc : services)
875     {
876       if (svc.serviceType.equals(serviceType))
877       {
878         if (serviceurl == null || serviceurl.equals(svc.getHost()))
879         {
880           response = svc;
881           break;
882         }
883       }
884     }
885     return response;
886   }
887
888   public void setPreferredServiceFor(AlignFrame af, String serviceType,
889           String serviceAction, Jws2Instance selectedServer)
890   {
891     String afid = (af == null) ? "" : af.getViewport().getSequenceSetId();
892     if (preferredServiceMap == null)
893     {
894       preferredServiceMap = new HashMap<String, Map<String, String>>();
895     }
896     Map<String, String> prefmap = preferredServiceMap.get(afid);
897     if (prefmap == null)
898     {
899       prefmap = new HashMap<String, String>();
900       preferredServiceMap.put(afid, prefmap);
901     }
902     prefmap.put(serviceType, selectedServer.getHost());
903     prefmap.put(serviceAction, selectedServer.getHost());
904   }
905
906   public void setPreferredServiceFor(String serviceType,
907           String serviceAction, Jws2Instance selectedServer)
908   {
909     setPreferredServiceFor(null, serviceType, serviceAction, selectedServer);
910   }
911 }