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