Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.ws2.client.api

File AbstractWebServiceDiscoverer.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
13% of files have more coverage

Code metrics

22
72
12
1
225
191
29
0.4
6
12
2.42

Classes

Class Line # Actions
AbstractWebServiceDiscoverer 18 72 29
0.820754782.1%
 

Contributing tests

This file is covered by 411 tests. .

Source view

1    package jalview.ws2.client.api;
2   
3    import java.io.IOException;
4    import java.net.MalformedURLException;
5    import java.net.URL;
6    import java.util.ArrayList;
7    import java.util.Collections;
8    import java.util.List;
9    import java.util.Objects;
10    import java.util.concurrent.CompletableFuture;
11    import java.util.concurrent.atomic.AtomicInteger;
12   
13    import jalview.bin.Cache;
14    import jalview.bin.Console;
15    import jalview.ws2.actions.api.ActionI;
16    import jalview.ws2.api.WebService;
17   
 
18    public abstract class AbstractWebServiceDiscoverer implements WebServiceDiscovererI
19    {
20    // TODO: we can use linked hash map to group and retrieve services by type.
21    protected List<WebService<?>> services = List.of();
22   
 
23  42961 toggle @Override
24    public List<WebService<?>> getServices()
25    {
26  42961 return services;
27    }
28   
 
29  0 toggle @Override
30    public <A extends ActionI<?>> List<WebService<A>> getServices(Class<A> type)
31    {
32  0 List<WebService<A>> list = new ArrayList<>();
33  0 for (WebService<?> service : services)
34    {
35  0 if (service.getActionClass().equals(type))
36    {
37  0 @SuppressWarnings("unchecked")
38    WebService<A> _service = (WebService<A>) service;
39  0 list.add(_service);
40    }
41    }
42  0 return list;
43    }
44   
 
45  281 toggle @Override
46    public List<URL> getUrls()
47    {
48  281 String key = getUrlsPropertyKey();
49  281 if (key == null)
50    // unmodifiable urls list, return default
51  144 return List.of(getDefaultUrl());
52  137 String surls = Cache.getProperty(key);
53  137 if (surls == null)
54  128 return List.of(getDefaultUrl());
55  9 String[] urls = surls.split(",");
56  9 ArrayList<URL> valid = new ArrayList<>(urls.length);
57  9 for (String url : urls)
58    {
59  11 try
60    {
61  11 valid.add(new URL(url));
62    } catch (MalformedURLException e)
63    {
64  4 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  4 Console.debug("Exception occurred while reading url list", e);
70    }
71    }
72  9 return valid;
73    }
74   
 
75  6 toggle @Override
76    public void setUrls(List<URL> wsUrls)
77    {
78  6 String key = getUrlsPropertyKey();
79  6 if (key == null)
80  0 throw new UnsupportedOperationException("setting urls not supported");
81  6 if (wsUrls != null && !wsUrls.isEmpty())
82    {
83  4 String[] surls = new String[wsUrls.size()];
84  4 var iter = wsUrls.iterator();
85  9 for (int i = 0; iter.hasNext(); i++)
86  5 surls[i] = iter.next().toString();
87  4 Cache.setProperty(key, String.join(",", surls));
88    }
89    else
90    {
91  2 Cache.removeProperty(key);
92    }
93    }
94   
95    /**
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.
98    *
99    * @return urls entry key
100    */
101    protected abstract String getUrlsPropertyKey();
102   
103    /**
104    * Get the default url for web service discovery for this discoverer.
105    *
106    * @return default discovery url
107    */
108    protected abstract URL getDefaultUrl();
109   
 
110  7958 toggle @Override
111    public boolean hasServices()
112    {
113  7958 return !isRunning() && services.size() > 0;
114    }
115   
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<>();
121   
 
122  50919 toggle @Override
123    public boolean isRunning()
124    {
125  50919 return (state.get() & (BEGIN | AGAIN)) != 0;
126    }
127   
 
128  35958 toggle @Override
129    public boolean isDone()
130    {
131  35958 return state.get() == END && discoveryTask.isDone();
132    }
133   
 
134  498 toggle @Override
135    public synchronized final CompletableFuture<List<WebService<?>>> startDiscoverer()
136    {
137  498 Console.debug("Requesting service discovery");
138  498 while (true)
139    {
140  498 if (state.get() == AGAIN)
141    {
142  0 return discoveryTask;
143    }
144  498 if (state.compareAndSet(END, BEGIN) || state.compareAndSet(BEGIN, AGAIN))
145    {
146  498 Console.debug("State changed to " + state.get());
147  498 final var oldTask = discoveryTask;
148  498 CompletableFuture<List<WebService<?>>> task = oldTask
149    .handleAsync((_r, _e) -> {
150  260 Console.info("Reloading services for " + this);
151  260 fireServicesChanged(services = Collections.emptyList());
152  260 var allServices = new ArrayList<WebService<?>>();
153  260 for (var url : getUrls())
154    {
155  260 Console.info("Fetching list of services from " + url);
156  260 try
157    {
158  260 allServices.addAll(fetchServices(url));
159    }
160    catch (IOException e)
161    {
162  0 Console.error("Failed to get services from " + url, e);
163    }
164    }
165  256 return services = allServices;
166    });
167  498 task.<Void>handle((services, exception) -> {
168  494 while (true)
169    {
170  494 if (state.get() == END)
171    // should never happen, throw exception to break the loop just in case
172  0 throw new AssertionError();
173  494 if (state.compareAndSet(BEGIN, END) || state.compareAndSet(AGAIN, BEGIN))
174  494 Console.debug("Discovery ended, state is " + state.get());
175  494 break;
176    }
177  494 if (services != null)
178  166 fireServicesChanged(services);
179  484 return null;
180    });
181  498 Console.debug("Spawned task " + task);
182  498 Console.debug("Killing task " + oldTask);
183  498 oldTask.cancel(false);
184  498 return discoveryTask = task;
185    }
186    }
187    }
188   
189    protected abstract List<WebService<?>> fetchServices(URL url) throws IOException;
190   
191    private List<ServicesChangeListener> listeners = new ArrayList<>();
192   
 
193  426 toggle private void fireServicesChanged(List<WebService<?>> services)
194    {
195  426 for (var listener : listeners)
196    {
197  18643 try
198    {
199  18643 listener.servicesChanged(this, services);
200    }
201    catch (Exception e)
202    {
203  0 Console.warn(e.toString(), e);
204    }
205    }
206    }
207   
 
208  1317 toggle @Override
209    public final void addServicesChangeListener(ServicesChangeListener listener)
210    {
211  1317 listeners.add(listener);
212    }
213   
 
214  652 toggle @Override
215    public final void removeServicesChangeListener(ServicesChangeListener listener)
216    {
217  652 listeners.remove(listener);
218    }
219   
 
220  260 toggle @Override
221    public String toString()
222    {
223  260 return getClass().getName();
224    }
225    }