d21d5d1f85c6d4e96f57c22d42d09135e5afc2e6
[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 "rna secondary structure prediction":
120               newInstance = new RNAalifoldServiceInstance(client,
121                       service, "Secondary Structure Prediction");
122               break;
123             case "sequence alignment analysis (conservation)":
124               newInstance = new SlivkaAnnotationServiceInstance(client,
125                       service, Category.CATEGORY_CONSERVATION);
126               break;
127             case "protein sequence analysis":
128               newInstance = new SlivkaAnnotationServiceInstance(client,
129                       service, Category.CATEGORY_DISORDER);
130               break;
131             case "protein secondary structure prediction":
132               newInstance = new SlivkaAnnotationServiceInstance(client,
133                       service, "Secondary Structure Prediction");
134               break;
135             case "multiple sequence alignment":
136               newInstance = new SlivkaMsaServiceInstance(client, service,
137                       Category.CATEGORY_ALIGNMENT);
138               break;
139             }
140           }
141           if (newInstance != null)
142             break;
143         }
144         if (newInstance != null)
145           instances.add(newInstance);
146       }
147     }
148
149     services = instances;
150     Console.info("Slivka services reloading finished");
151     notifyServiceListeners(instances);
152     return instances;
153   }
154
155   @Override
156   public List<ServiceWithParameters> getServices()
157   {
158     return services;
159   }
160
161   @Override
162   public boolean hasServices()
163   {
164     return !isRunning() && services.size() > 0;
165   }
166
167   @Override
168   public boolean isRunning()
169   {
170     return !discoveryTasks.stream().allMatch(Future::isDone);
171   }
172
173   @Override
174   public void setServiceUrls(List<String> wsUrls)
175   {
176     if (wsUrls != null && !wsUrls.isEmpty())
177     {
178       Cache.setProperty(SLIVKA_HOST_URLS, String.join(",", wsUrls));
179     }
180     else
181     {
182       Cache.removeProperty(SLIVKA_HOST_URLS);
183     }
184   }
185
186   @Override
187   public List<String> getServiceUrls()
188   {
189     String surls = Cache.getDefault(SLIVKA_HOST_URLS, COMPBIO_SLIVKA);
190     String[] urls = surls.split(",");
191     ArrayList<String> valid = new ArrayList<>(urls.length);
192     for (String url : urls)
193     {
194       try
195       {
196         new URL(url);
197         valid.add(url);
198       } catch (MalformedURLException e)
199       {
200         Console.warn("Problem whilst trying to make a URL from '"
201                 + ((url != null) ? url : "<null>") + "'");
202         Console.warn(
203                 "This was probably due to a malformed comma separated list"
204                         + " in the " + SLIVKA_HOST_URLS
205                         + " entry of $(HOME)/.jalview_properties)");
206         Console.debug("Exception was ", e);
207       }
208     }
209     return valid;
210   }
211
212   @Override
213   public boolean testServiceUrl(URL url)
214   {
215     return getServerStatusFor(url.toString()) == STATUS_OK;
216   }
217
218   @Override
219   public int getServerStatusFor(String url)
220   {
221     try
222     {
223       List<?> services = new SlivkaClient(url).getServices();
224       return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
225     } catch (IOException | org.json.JSONException e)
226     {
227       Console.error("Slivka could not retrieve services list", e);
228       return STATUS_INVALID;
229     }
230   }
231
232   @Override
233   public String getErrorMessages()
234   {
235     // TODO Auto-generated method stub
236     return "";
237   }
238 }