JAL-3690 refactoring web-services discovery
[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.beans.PropertyChangeListener;
8 import java.io.IOException;
9 import java.net.MalformedURLException;
10 import java.net.URL;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.List;
14 import java.util.Set;
15 import java.util.Vector;
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 import java.util.concurrent.FutureTask;
21
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     serviceListeners.add(l);
53   }
54   
55   @Override
56   public void removeServiceChangeListener(ServiceChangeListener l) {
57     serviceListeners.remove(l);
58   }
59   
60   public void notifyServiceListeners(List<ServiceWithParameters> services) {
61     for (var listener : serviceListeners) {
62       listener.servicesChanged(this, services);
63     }
64   }
65
66   private final ExecutorService executor = Executors.newSingleThreadExecutor();
67   private Vector<Future<?>> discoveryTasks = new Vector<>();
68
69   public Future<WSDiscovererI> startDiscoverer()
70   {
71     FutureTask<WSDiscovererI> task = new FutureTask<>(this::reloadServices, this);
72     discoveryTasks.add(task);
73     executor.execute(task);
74     return task;
75   }
76
77   private List<ServiceWithParameters> reloadServices()
78   {
79     Cache.log.info("Reloading Slivka services");
80     notifyServiceListeners(Collections.emptyList());
81     ArrayList<ServiceWithParameters> instances = new ArrayList<>();
82
83     for (String url : getServiceUrls())
84     {
85       Cache.log.info(url);
86       SlivkaClient client;
87       client = new SlivkaClient(url);
88       try
89       {
90         for (SlivkaService service : client.getServices())
91         {
92           SlivkaWSInstance newinstance = null;
93           for (String classifier : service.classifiers)
94           {
95             if (classifier.contains("Multiple sequence alignment"))
96             {
97               newinstance = new SlivkaMsaServiceInstance(client, service);
98             }
99             if (classifier.contains("Protein sequence analysis")
100                 && newinstance == null)
101             {
102               newinstance = new SlivkaAnnotationServiceInstance(client,
103                   service, false);
104             }
105             if (classifier
106                 .contains("Sequence alignment analysis (conservation)"))
107             {
108               newinstance = new SlivkaAnnotationServiceInstance(client,
109                   service, true);
110             }
111           }
112           if (newinstance != null)
113           {
114             instances.add(newinstance);
115           }
116         }
117       } catch (IOException e)
118       {
119         e.printStackTrace();
120         continue;
121       }
122     }
123
124     services = instances;
125     notifyServiceListeners(instances);
126     Cache.log.info("Slivka services reloading finished");
127     return instances;
128   }
129
130   @Override
131   public List<ServiceWithParameters> getServices()
132   {
133     return services;
134   }
135
136   @Override
137   public boolean hasServices()
138   {
139     return !isRunning() && services.size() > 0;
140   }
141
142   @Override
143   public boolean isRunning()
144   {
145     return !discoveryTasks.stream().allMatch(Future::isDone);
146   }
147
148   @Override
149   public void setServiceUrls(List<String> wsUrls)
150   {
151     if (wsUrls != null && !wsUrls.isEmpty())
152     {
153       Cache.setProperty(SLIVKA_HOST_URLS, String.join(",", wsUrls));
154     }
155     else
156     {
157       Cache.removeProperty(SLIVKA_HOST_URLS);
158     }
159   }
160
161   @Override
162   public List<String> getServiceUrls()
163   {
164     String surls = Cache.getDefault(SLIVKA_HOST_URLS, COMPBIO_SLIVKA);
165     String[] urls = surls.split(",");
166     ArrayList<String> valid = new ArrayList<>(urls.length);
167     for (String url : urls)
168     {
169       try
170       {
171         new URL(url);
172         valid.add(url);
173       } catch (MalformedURLException e)
174       {
175         Cache.log.warn("Problem whilst trying to make a URL from '"
176             + ((url != null) ? url : "<null>") + "'");
177         Cache.log.warn(
178             "This was probably due to a malformed comma separated list"
179                 + " in the " + SLIVKA_HOST_URLS
180                 + " entry of $(HOME)/.jalview_properties)");
181         Cache.log.debug("Exception was ", e);
182       }
183     }
184     return valid;
185   }
186
187   @Override
188   public boolean testServiceUrl(URL url)
189   {
190     return getServerStatusFor(url.toString()) == STATUS_OK;
191   }
192
193   @Override
194   public int getServerStatusFor(String url)
195   {
196     try
197     {
198       List<?> services = new SlivkaClient(url).getServices();
199       return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
200     } catch (IOException e)
201     {
202       Cache.log.error("Slivka could not retrieve services list", e);
203       return STATUS_INVALID;
204     }
205   }
206
207   @Override
208   public String getErrorMessages()
209   {
210     // TODO Auto-generated method stub
211     return "";
212   }
213 }