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