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