1 package jalview.ws2.client.api;
3 import java.io.IOException;
4 import java.net.MalformedURLException;
6 import java.util.ArrayList;
7 import java.util.Collections;
9 import java.util.Objects;
10 import java.util.concurrent.CompletableFuture;
11 import java.util.concurrent.atomic.AtomicInteger;
13 import jalview.bin.Cache;
14 import jalview.bin.Console;
15 import jalview.ws2.actions.api.ActionI;
16 import jalview.ws2.api.WebService;
18 public abstract class AbstractWebServiceDiscoverer implements WebServiceDiscovererI
20 // TODO: we can use linked hash map to group and retrieve services by type.
21 protected List<WebService<?>> services = List.of();
24 public List<WebService<?>> getServices()
30 public <A extends ActionI<?>> List<WebService<A>> getServices(Class<A> type)
32 List<WebService<A>> list = new ArrayList<>();
33 for (WebService<?> service : services)
35 if (service.getActionClass().equals(type))
37 @SuppressWarnings("unchecked")
38 WebService<A> _service = (WebService<A>) service;
46 public List<URL> getUrls()
48 String key = getUrlsPropertyKey();
50 // unmodifiable urls list, return default
51 return List.of(getDefaultUrl());
52 String surls = Cache.getProperty(key);
54 return List.of(getDefaultUrl());
55 String[] urls = surls.split(",");
56 ArrayList<URL> valid = new ArrayList<>(urls.length);
57 for (String url : urls)
61 valid.add(new URL(url));
62 } catch (MalformedURLException e)
64 Console.warn(String.format(
65 "Problem whilst trying to make a URL from '%s'. " +
66 "This was probably due to malformed comma-separated-list " +
67 "in the %s entry of ${HOME}/.jalview-properties",
68 Objects.toString(url, "<null>"), key));
69 Console.debug("Exception occurred while reading url list", e);
76 public void setUrls(List<URL> wsUrls)
78 String key = getUrlsPropertyKey();
80 throw new UnsupportedOperationException("setting urls not supported");
81 if (wsUrls != null && !wsUrls.isEmpty())
83 String[] surls = new String[wsUrls.size()];
84 var iter = wsUrls.iterator();
85 for (int i = 0; iter.hasNext(); i++)
86 surls[i] = iter.next().toString();
87 Cache.setProperty(key, String.join(",", surls));
91 Cache.removeProperty(key);
96 * Get the key in jalview property file where the urls for this discoverer are
97 * stored. Return null if modifying urls is not supported.
99 * @return urls entry key
101 protected abstract String getUrlsPropertyKey();
104 * Get the default url for web service discovery for this discoverer.
106 * @return default discovery url
108 protected abstract URL getDefaultUrl();
111 public boolean hasServices()
113 return !isRunning() && services.size() > 0;
116 private static final int END = 0x01;
117 private static final int BEGIN = 0x02;
118 private static final int AGAIN = 0x04;
119 private final AtomicInteger state = new AtomicInteger(END);
120 private CompletableFuture<List<WebService<?>>> discoveryTask = new CompletableFuture<>();
123 public boolean isRunning()
125 return (state.get() & (BEGIN | AGAIN)) != 0;
129 public boolean isDone()
131 return state.get() == END && discoveryTask.isDone();
135 public synchronized final CompletableFuture<List<WebService<?>>> startDiscoverer()
137 Console.debug("Requesting service discovery");
140 if (state.get() == AGAIN)
142 return discoveryTask;
144 if (state.compareAndSet(END, BEGIN) || state.compareAndSet(BEGIN, AGAIN))
146 Console.debug("State changed to " + state.get());
147 final var oldTask = discoveryTask;
148 CompletableFuture<List<WebService<?>>> task = oldTask
149 .handleAsync((_r, _e) -> {
150 Console.info("Reloading services for " + this);
151 fireServicesChanged(services = Collections.emptyList());
152 var allServices = new ArrayList<WebService<?>>();
153 for (var url : getUrls())
155 Console.info("Fetching list of services from " + url);
158 allServices.addAll(fetchServices(url));
160 catch (IOException e)
162 Console.error("Failed to get services from " + url, e);
165 return services = allServices;
167 task.<Void>handle((services, exception) -> {
170 if (state.get() == END)
171 // should never happen, throw exception to break the loop just in case
172 throw new AssertionError();
173 if (state.compareAndSet(BEGIN, END) || state.compareAndSet(AGAIN, BEGIN))
174 Console.debug("Discovery ended, state is " + state.get());
177 if (services != null)
178 fireServicesChanged(services);
181 Console.debug("Spawned task " + task);
182 Console.debug("Killing task " + oldTask);
183 oldTask.cancel(false);
184 return discoveryTask = task;
189 protected abstract List<WebService<?>> fetchServices(URL url) throws IOException;
191 private List<ServicesChangeListener> listeners = new ArrayList<>();
193 private void fireServicesChanged(List<WebService<?>> services)
195 for (var listener : listeners)
199 listener.servicesChanged(this, services);
203 Console.warn(e.toString(), e);
209 public final void addServicesChangeListener(ServicesChangeListener listener)
211 listeners.add(listener);
215 public final void removeServicesChangeListener(ServicesChangeListener listener)
217 listeners.remove(listener);
221 public String toString()
223 return getClass().getName();