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.ws2.actions.api.ActionI;
15 import jalview.ws2.api.WebService;
17 public abstract class AbstractWebServiceDiscoverer implements WebServiceDiscovererI
19 // TODO: we can use linked hash map to group and retrieve services by type.
20 protected List<WebService<?>> services = List.of();
23 public List<WebService<?>> getServices()
29 public <A extends ActionI<?>> List<WebService<A>> getServices(Class<A> type)
31 List<WebService<A>> list = new ArrayList<>();
32 for (WebService<?> service : services)
34 if (service.getActionClass().equals(type))
36 @SuppressWarnings("unchecked")
37 WebService<A> _service = (WebService<A>) service;
45 public List<URL> getUrls()
47 String key = getUrlsPropertyKey();
49 // unmodifiable urls list, return default
50 return List.of(getDefaultUrl());
51 String surls = Cache.getProperty(key);
53 return List.of(getDefaultUrl());
54 String[] urls = surls.split(",");
55 ArrayList<URL> valid = new ArrayList<>(urls.length);
56 for (String url : urls)
60 valid.add(new URL(url));
61 } catch (MalformedURLException e)
63 Cache.log.warn(String.format(
64 "Problem whilst trying to make a URL from '%s'. " +
65 "This was probably due to malformed comma-separated-list " +
66 "in the %s entry of ${HOME}/.jalview-properties",
67 Objects.toString(url, "<null>"), key));
68 Cache.log.debug("Exception occurred while reading url list", e);
75 public void setUrls(List<URL> wsUrls)
77 String key = getUrlsPropertyKey();
79 throw new UnsupportedOperationException("setting urls not supported");
80 if (wsUrls != null && !wsUrls.isEmpty())
82 String[] surls = new String[wsUrls.size()];
83 var iter = wsUrls.iterator();
84 for (int i = 0; iter.hasNext(); i++)
85 surls[i] = iter.next().toString();
86 Cache.setProperty(key, String.join(",", surls));
90 Cache.removeProperty(key);
95 * Get the key in jalview property file where the urls for this discoverer are
96 * stored. Return null if modifying urls is not supported.
98 * @return urls entry key
100 protected abstract String getUrlsPropertyKey();
103 * Get the default url for web service discovery for this discoverer.
105 * @return default discovery url
107 protected abstract URL getDefaultUrl();
110 public boolean hasServices()
112 return !isRunning() && services.size() > 0;
115 private static final int END = 0x01;
116 private static final int BEGIN = 0x02;
117 private static final int AGAIN = 0x04;
118 private final AtomicInteger state = new AtomicInteger(END);
119 private CompletableFuture<List<WebService<?>>> discoveryTask = new CompletableFuture<>();
122 public boolean isRunning()
124 return (state.get() & (BEGIN | AGAIN)) != 0;
128 public boolean isDone()
130 return state.get() == END && discoveryTask.isDone();
134 public synchronized final CompletableFuture<List<WebService<?>>> startDiscoverer()
138 if (state.get() == AGAIN)
140 return discoveryTask;
142 if (state.compareAndSet(END, BEGIN) || state.compareAndSet(BEGIN, AGAIN))
144 final var oldTask = discoveryTask;
145 CompletableFuture<List<WebService<?>>> task = oldTask
146 .handleAsync((_r, _e) -> {
147 Cache.log.info("Reloading services for " + this);
148 fireServicesChanged(Collections.emptyList());
149 var allServices = new ArrayList<WebService<?>>();
150 for (var url : getUrls())
152 Cache.log.info("Fetching list of services from " + url);
155 allServices.addAll(getServices(url));
157 catch (IOException e)
159 Cache.log.error("Failed to get services from " + url, e);
162 return services = allServices;
164 task.thenAccept(services -> {
167 if (state.get() == END)
168 // should never happen, throw exception to break the loop just in case
169 throw new AssertionError();
170 if (state.compareAndSet(BEGIN, END) || state.compareAndSet(AGAIN, BEGIN))
173 fireServicesChanged(services);
175 oldTask.cancel(false);
176 return discoveryTask = task;
181 protected abstract List<WebService<?>> getServices(URL url) throws IOException;
183 private List<ServicesChangeListener> listeners = new ArrayList<>();
185 private void fireServicesChanged(List<WebService<?>> services)
187 for (var listener : listeners)
191 listener.servicesChanged(this, services);
201 public final void addServicesChangeListener(ServicesChangeListener listener)
203 listeners.add(listener);
207 public final void removeServicesChangeListener(ServicesChangeListener listener)
209 listeners.remove(listener);
213 public String toString()
215 return getClass().getName();