JAL-3690 alternative servers menu fix
[jalview.git] / src / jalview / ws / slivkaws / SlivkaWSDiscoverer.java
1 package jalview.ws.slivkaws;
2
3 import jalview.bin.Cache;
4 import jalview.ws.ServiceChangeListener;
5 import jalview.ws.WSDiscovererI;
6 import jalview.ws.api.ServiceWithParameters;
7 import java.io.IOException;
8 import java.net.MalformedURLException;
9 import java.net.URL;
10 import java.util.ArrayList;
11 import java.util.Collections;
12 import java.util.List;
13 import java.util.Set;
14 import java.util.Vector;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.concurrent.CopyOnWriteArraySet;
17 import java.util.concurrent.ExecutorService;
18 import java.util.concurrent.Executors;
19 import java.util.concurrent.Future;
20 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
21 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
22
23 public class SlivkaWSDiscoverer implements WSDiscovererI
24 {
25   private static final String SLIVKA_HOST_URLS = "SLIVKAHOSTURLS";
26
27   private static final String COMPBIO_SLIVKA = "https://www.compbio.dundee.ac.uk/slivka/";
28
29   private static SlivkaWSDiscoverer instance = null;
30
31   private List<ServiceWithParameters> services = List.of();
32
33   private SlivkaWSDiscoverer()
34   {
35   }
36
37   public static SlivkaWSDiscoverer getInstance()
38   {
39     if (instance == null)
40     {
41       instance = new SlivkaWSDiscoverer();
42     }
43     return instance;
44   }
45
46   private Set<ServiceChangeListener> serviceListeners = new CopyOnWriteArraySet<>();
47
48   @Override
49   public void addServiceChangeListener(ServiceChangeListener l)
50   {
51     serviceListeners.add(l);
52   }
53
54   @Override
55   public void removeServiceChangeListener(ServiceChangeListener l)
56   {
57     serviceListeners.remove(l);
58   }
59
60   public void notifyServiceListeners(List<ServiceWithParameters> services)
61   {
62     for (var listener : serviceListeners)
63     {
64       listener.servicesChanged(this, services);
65     }
66   }
67
68   private final ExecutorService executor = Executors.newSingleThreadExecutor();
69   private Vector<Future<?>> discoveryTasks = new Vector<>();
70
71   public CompletableFuture<WSDiscovererI> startDiscoverer()
72   {
73     CompletableFuture<WSDiscovererI> task = CompletableFuture
74             .supplyAsync(() -> {
75               reloadServices();
76               return SlivkaWSDiscoverer.this;
77             }, executor);
78     discoveryTasks.add(task);
79     return task;
80   }
81
82   private List<ServiceWithParameters> reloadServices()
83   {
84     Cache.log.info("Reloading Slivka services");
85     notifyServiceListeners(Collections.emptyList());
86     ArrayList<ServiceWithParameters> instances = new ArrayList<>();
87
88     for (String url : getServiceUrls())
89     {
90       Cache.log.info(url);
91       SlivkaClient client;
92       client = new SlivkaClient(url);
93       try
94       {
95         for (SlivkaService service : client.getServices())
96         {
97           SlivkaWSInstance newinstance = null;
98           for (String classifier : service.classifiers)
99           {
100             if (classifier.contains("Multiple sequence alignment"))
101             {
102               newinstance = new SlivkaMsaServiceInstance(client, service);
103             }
104             if (classifier.contains("Protein sequence analysis")
105                 && newinstance == null)
106             {
107               newinstance = new SlivkaAnnotationServiceInstance(client,
108                   service, false);
109             }
110             if (classifier
111                 .contains("Sequence alignment analysis (conservation)"))
112             {
113               newinstance = new SlivkaAnnotationServiceInstance(client,
114                   service, true);
115             }
116           }
117           if (newinstance != null)
118           {
119             instances.add(newinstance);
120           }
121         }
122       } catch (IOException e)
123       {
124         e.printStackTrace();
125         continue;
126       }
127     }
128
129     services = instances;
130     notifyServiceListeners(instances);
131     Cache.log.info("Slivka services reloading finished");
132     return instances;
133   }
134
135   @Override
136   public List<ServiceWithParameters> getServices()
137   {
138     return services;
139   }
140
141   @Override
142   public boolean hasServices()
143   {
144     return !isRunning() && services.size() > 0;
145   }
146
147   @Override
148   public boolean isRunning()
149   {
150     return !discoveryTasks.stream().allMatch(Future::isDone);
151   }
152
153   @Override
154   public void setServiceUrls(List<String> wsUrls)
155   {
156     if (wsUrls != null && !wsUrls.isEmpty())
157     {
158       Cache.setProperty(SLIVKA_HOST_URLS, String.join(",", wsUrls));
159     }
160     else
161     {
162       Cache.removeProperty(SLIVKA_HOST_URLS);
163     }
164   }
165
166   @Override
167   public List<String> getServiceUrls()
168   {
169     String surls = Cache.getDefault(SLIVKA_HOST_URLS, COMPBIO_SLIVKA);
170     String[] urls = surls.split(",");
171     ArrayList<String> valid = new ArrayList<>(urls.length);
172     for (String url : urls)
173     {
174       try
175       {
176         new URL(url);
177         valid.add(url);
178       } catch (MalformedURLException e)
179       {
180         Cache.log.warn("Problem whilst trying to make a URL from '"
181             + ((url != null) ? url : "<null>") + "'");
182         Cache.log.warn(
183             "This was probably due to a malformed comma separated list"
184                 + " in the " + SLIVKA_HOST_URLS
185                 + " entry of $(HOME)/.jalview_properties)");
186         Cache.log.debug("Exception was ", e);
187       }
188     }
189     return valid;
190   }
191
192   @Override
193   public boolean testServiceUrl(URL url)
194   {
195     return getServerStatusFor(url.toString()) == STATUS_OK;
196   }
197
198   @Override
199   public int getServerStatusFor(String url)
200   {
201     try
202     {
203       List<?> services = new SlivkaClient(url).getServices();
204       return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
205     } catch (IOException e)
206     {
207       Cache.log.error("Slivka could not retrieve services list", e);
208       return STATUS_INVALID;
209     }
210   }
211
212   @Override
213   public String getErrorMessages()
214   {
215     // TODO Auto-generated method stub
216     return "";
217   }
218 }