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