From a0cf4ae3ebd47c81acbe690caa118cd676e16524 Mon Sep 17 00:00:00 2001 From: Mateusz Warowny Date: Fri, 18 Mar 2022 17:05:36 +0100 Subject: [PATCH] JAL-3878 Create abstract ws discoverer providing common skeleton --- .../client/api/AbstractWebServiceDiscoverer.java | 217 ++++++++++++++++++++ .../ws2/client/api/WebServiceDiscovererI.java | 4 +- 2 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/jalview/ws2/client/api/AbstractWebServiceDiscoverer.java diff --git a/src/jalview/ws2/client/api/AbstractWebServiceDiscoverer.java b/src/jalview/ws2/client/api/AbstractWebServiceDiscoverer.java new file mode 100644 index 0000000..a526e72 --- /dev/null +++ b/src/jalview/ws2/client/api/AbstractWebServiceDiscoverer.java @@ -0,0 +1,217 @@ +package jalview.ws2.client.api; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import jalview.bin.Cache; +import jalview.ws2.actions.api.ActionI; +import jalview.ws2.api.WebService; + +public abstract class AbstractWebServiceDiscoverer implements WebServiceDiscovererI +{ + // TODO: we can use linked hash map to group and retrieve services by type. + protected List> services = List.of(); + + @Override + public List> getServices() + { + return services; + } + + @Override + public > List> getServices(Class type) + { + List> list = new ArrayList<>(); + for (WebService service : services) + { + if (service.getActionClass().equals(type)) + { + @SuppressWarnings("unchecked") + WebService _service = (WebService) service; + list.add(_service); + } + } + return list; + } + + @Override + public List getUrls() + { + String key = getUrlsPropertyKey(); + if (key == null) + // unmodifiable urls list, return default + return List.of(getDefaultUrl()); + String surls = Cache.getProperty(key); + if (surls == null) + return List.of(getDefaultUrl()); + String[] urls = surls.split(","); + ArrayList valid = new ArrayList<>(urls.length); + for (String url : urls) + { + try + { + valid.add(new URL(url)); + } catch (MalformedURLException e) + { + Cache.log.warn(String.format( + "Problem whilst trying to make a URL from '%s'. " + + "This was probably due to malformed comma-separated-list " + + "in the %s entry of ${HOME}/.jalview-properties", + Objects.toString(url, ""), key)); + Cache.log.debug("Exception occurred while reading url list", e); + } + } + return valid; + } + + @Override + public void setUrls(List wsUrls) + { + String key = getUrlsPropertyKey(); + if (key == null) + throw new UnsupportedOperationException("setting urls not supported"); + if (wsUrls != null && !wsUrls.isEmpty()) + { + String[] surls = new String[wsUrls.size()]; + var iter = wsUrls.iterator(); + for (int i = 0; iter.hasNext(); i++) + surls[i] = iter.next().toString(); + Cache.setProperty(key, String.join(",", surls)); + } + else + { + Cache.removeProperty(key); + } + } + + /** + * Get the key in jalview property file where the urls for this discoverer are + * stored. Return null if modifying urls is not supported. + * + * @return urls entry key + */ + protected abstract String getUrlsPropertyKey(); + + /** + * Get the default url for web service discovery for this discoverer. + * + * @return default discovery url + */ + protected abstract URL getDefaultUrl(); + + @Override + public boolean hasServices() + { + return !isRunning() && services.size() > 0; + } + + private static final int END = 0x01; + private static final int BEGIN = 0x02; + private static final int AGAIN = 0x04; + private final AtomicInteger state = new AtomicInteger(END); + private CompletableFuture>> discoveryTask = new CompletableFuture<>(); + + @Override + public boolean isRunning() + { + return (state.get() & (BEGIN | AGAIN)) != 0; + } + + @Override + public boolean isDone() + { + return state.get() == END && discoveryTask.isDone(); + } + + @Override + public synchronized final CompletableFuture>> startDiscoverer() + { + while (true) + { + if (state.get() == AGAIN) + { + return discoveryTask; + } + if (state.compareAndSet(END, BEGIN) || state.compareAndSet(BEGIN, AGAIN)) + { + final var oldTask = discoveryTask; + CompletableFuture>> task = oldTask + .handleAsync((_r, _e) -> { + Cache.log.info("Reloading services for " + this); + fireServicesChanged(Collections.emptyList()); + var allServices = new ArrayList>(); + for (var url : getUrls()) + { + Cache.log.info("Fetching list of services from " + url); + try + { + allServices.addAll(getServices(url)); + } + catch (IOException e) + { + Cache.log.error("Failed to get services from " + url, e); + } + } + return services = allServices; + }); + task.thenAccept(services -> { + while (true) + { + if (state.get() == END) + // should never happen, throw exception to break the loop just in case + throw new AssertionError(); + if (state.compareAndSet(BEGIN, END) || state.compareAndSet(AGAIN, BEGIN)) + break; + } + fireServicesChanged(services); + }); + oldTask.cancel(false); + return discoveryTask = task; + } + } + } + + protected abstract List> getServices(URL url) throws IOException; + + private List listeners = new ArrayList<>(); + + private void fireServicesChanged(List> services) + { + for (var listener : listeners) + { + try + { + listener.servicesChanged(this, services); + } + catch (Exception e) + { + Cache.log.warn(e); + } + } + } + + @Override + public final void addServicesChangeListener(ServicesChangeListener listener) + { + listeners.add(listener); + } + + @Override + public final void removeServicesChangeListener(ServicesChangeListener listener) + { + listeners.remove(listener); + } + + @Override + public String toString() + { + return getClass().getName(); + } +} diff --git a/src/jalview/ws2/client/api/WebServiceDiscovererI.java b/src/jalview/ws2/client/api/WebServiceDiscovererI.java index 951c86f..4a09ef7 100644 --- a/src/jalview/ws2/client/api/WebServiceDiscovererI.java +++ b/src/jalview/ws2/client/api/WebServiceDiscovererI.java @@ -79,7 +79,7 @@ public interface WebServiceDiscovererI extends WebServiceProviderI * * @return services list future result */ - CompletableFuture> startDiscoverer(); + CompletableFuture>> startDiscoverer(); /** * An interface for the services list observers. @@ -99,7 +99,7 @@ public interface WebServiceDiscovererI extends WebServiceProviderI * @param list */ public void servicesChanged(WebServiceDiscovererI discoverer, - List services); + List> services); } /** -- 1.7.10.2