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