• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Nanopublication / nanopub-java / 17069388833

19 Aug 2025 12:21PM UTC coverage: 45.825% (-1.9%) from 47.675%
17069388833

push

github

Ziroli Plutschow
Fixed some TODOs

825 of 2880 branches covered (28.65%)

Branch coverage included in aggregate %.

4603 of 8965 relevant lines covered (51.34%)

2.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

33.12
src/main/java/org/nanopub/extra/services/QueryCall.java
1
package org.nanopub.extra.services;
2

3
import org.apache.commons.codec.Charsets;
4
import org.apache.http.HttpResponse;
5
import org.apache.http.client.methods.HttpGet;
6
import org.apache.http.util.EntityUtils;
7
import org.nanopub.NanopubUtils;
8

9
import java.io.IOException;
10
import java.net.URLEncoder;
11
import java.util.ArrayList;
12
import java.util.LinkedList;
13
import java.util.List;
14
import java.util.Map;
15

16
/**
17
 * Second-generation query API call.
18
 */
19
public class QueryCall {
20

21
    private static int parallelCallCount = 2;
2✔
22
    private static int maxRetryCount = 3;
2✔
23

24
    /**
25
     * Run a query call with the given query ID and parameters.
26
     *
27
     * @param queryId the ID of the query to run
28
     * @param params  the parameters to pass to the query
29
     * @return the HTTP response from the query API
30
     */
31
    public static HttpResponse run(String queryId, Map<String, String> params) {
32
        int retryCount = 0;
2✔
33
        while (retryCount < maxRetryCount) {
3!
34
            QueryCall apiCall = new QueryCall(queryId, params);
×
35
            apiCall.run();
×
36
            while (!apiCall.calls.isEmpty() && apiCall.resp == null) {
×
37
                try {
38
                    Thread.sleep(50);
×
39
                } catch (InterruptedException ex) {
×
40
                    Thread.currentThread().interrupt();
×
41
                }
×
42
            }
43
            if (apiCall.resp != null) {
×
44
                return apiCall.resp;
×
45
            }
46
            retryCount = retryCount + 1;
×
47
        }
×
48
        throw new RuntimeException("Giving up contacting API: " + queryId);
×
49
    }
50

51
    /**
52
     * List of available query API instances.
53
     */
54
    // TODO Available services should be retrieved from a setting, not hard-coded:
55
    public static String[] queryApiInstances = new String[]{
16✔
56
            "https://query.knowledgepixels.com/",
57
            "https://query.petapico.org/",
58
            "https://query.np.trustyuri.net/"
59
    };
60

61
    private static List<String> checkedApiInstances;
62

63
    /**
64
     * Returns the list of available query API instances that are currently accessible.
65
     *
66
     * @return a list of accessible query API instance
67
     */
68
    public static List<String> getApiInstances() {
69
        if (checkedApiInstances != null) return checkedApiInstances;
4✔
70
        checkedApiInstances = new ArrayList<>();
4✔
71
        for (String a : queryApiInstances) {
16✔
72
            try {
73
                System.err.println("Checking API instance: " + a);
4✔
74
                HttpResponse resp = NanopubUtils.getHttpClient().execute(new HttpGet(a));
7✔
75
                if (wasSuccessful(resp)) {
3✔
76
                    System.err.println("SUCCESS: Nanopub Query instance is accessible: " + a);
4✔
77
                    checkedApiInstances.add(a);
5✔
78
                } else {
79
                    System.err.println("FAILURE: Nanopub Query instance isn't accessible: " + a);
4✔
80
                }
81
            } catch (IOException ex) {
×
82
                System.err.println("FAILURE: Nanopub Query instance isn't accessible: " + a);
×
83
            }
1✔
84
        }
85
        System.err.println(checkedApiInstances.size() + " accessible Nanopub Query instances");
5✔
86
        if (checkedApiInstances.size() < 2) {
4✔
87
            checkedApiInstances = null;
2✔
88
            throw new RuntimeException("Not enough healthy Nanopub Query instances available");
5✔
89
        }
90
        return checkedApiInstances;
2✔
91
    }
92

93
    private String queryId;
94
    private String paramString;
95
    private List<String> apisToCall = new ArrayList<>();
5✔
96
    private List<Call> calls = new ArrayList<>();
5✔
97

98
    private HttpResponse resp;
99

100
    private QueryCall(String queryId, Map<String, String> params) {
2✔
101
        this.queryId = queryId;
3✔
102
        paramString = "";
3✔
103
        if (params != null) {
2!
104
            paramString = "?";
3✔
105
            for (String k : params.keySet()) {
11!
106
                if (paramString.length() > 1) paramString += "&";
5!
107
                paramString += k + "=";
6✔
108
                paramString += URLEncoder.encode(params.get(k), Charsets.UTF_8);
×
109
            }
×
110
        }
111
        System.err.println("Invoking API operation " + queryId + " " + paramString);
×
112
    }
×
113

114
    private void run() {
115
        List<String> apiInstancesToTry = new LinkedList<>(getApiInstances());
×
116
        while (!apiInstancesToTry.isEmpty() && apisToCall.size() < parallelCallCount) {
×
117
            int randomIndex = (int) ((Math.random() * apiInstancesToTry.size()));
×
118
            String apiUrl = apiInstancesToTry.get(randomIndex);
×
119
            apisToCall.add(apiUrl);
×
120
            System.err.println("Trying API (" + apisToCall.size() + ") " + apiUrl);
×
121
            apiInstancesToTry.remove(randomIndex);
×
122
        }
×
123
        for (String api : apisToCall) {
×
124
            Call call = new Call(api);
×
125
            calls.add(call);
×
126
            new Thread(call).start();
×
127
        }
×
128
    }
×
129

130
    private synchronized void finished(Call call, HttpResponse resp, String apiUrl) {
131
        if (this.resp != null) { // result already in
×
132
            EntityUtils.consumeQuietly(resp.getEntity());
×
133
            return;
×
134
        }
135
        System.err.println("Result in from " + apiUrl + ":");
×
136
        System.err.println("- Request: " + queryId + " " + paramString);
×
137
        System.err.println("- Response size: " + resp.getEntity().getContentLength());
×
138
        this.resp = resp;
×
139

140
        for (Call c : calls) {
×
141
            if (c != call) c.abort();
×
142
        }
×
143
    }
×
144

145
    private static boolean wasSuccessful(HttpResponse resp) {
146
        if (resp == null || resp.getEntity() == null) return false;
5!
147
        int c = resp.getStatusLine().getStatusCode();
4✔
148
        if (c < 200 || c >= 300) return false;
8!
149
        return true;
2✔
150
    }
151

152
    private static boolean wasSuccessfulNonempty(HttpResponse resp) {
153
        if (!wasSuccessful(resp)) return false;
×
154
        if (resp.getEntity().getContentLength() < 0) return false;
×
155
        return true;
×
156
    }
157

158

159
    private class Call implements Runnable {
160

161
        private String apiUrl;
162
        private HttpGet get;
163

164
        public Call(String apiUrl) {
×
165
            this.apiUrl = apiUrl;
×
166
        }
×
167

168
        public void run() {
169
            get = new HttpGet(apiUrl + "api/" + queryId + paramString);
×
170
            get.setHeader("Accept", "text/csv");
×
171
            HttpResponse resp = null;
×
172
            try {
173
                resp = NanopubUtils.getHttpClient().execute(get);
×
174
                if (!wasSuccessfulNonempty(resp)) {
×
175
                    throw new IOException(resp.getStatusLine().toString());
×
176
                }
177
                finished(this, resp, apiUrl);
×
178
            } catch (Exception ex) {
×
179
                if (resp != null) EntityUtils.consumeQuietly(resp.getEntity());
×
180
                System.err.println("Request to " + apiUrl + " was not successful: " + ex.getMessage());
×
181
            }
×
182
            calls.remove(this);
×
183
        }
×
184

185
        private void abort() {
186
            if (get == null) return;
×
187
            if (get.isAborted()) return;
×
188
            get.abort();
×
189
        }
×
190

191
    }
192

193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc