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

Nanopublication / nanopub-java / 19637760718

24 Nov 2025 02:27PM UTC coverage: 51.823% (-0.08%) from 51.902%
19637760718

push

github

Ziroli Plutschow
Bring Cli-Option -v (Verbose) together with Logging

Little hack:
- We interpret an enabled DEBUG Log like the command line flag "verbose".
- The other way around, we do interpret the command line concept of setting the verbose flag as  activation of debug log.

TODO Issue: The verbose mode does not seem to work!

1012 of 2922 branches covered (34.63%)

Branch coverage included in aggregate %.

5215 of 9094 relevant lines covered (57.35%)

2.69 hits per line

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

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

3
import java.io.IOException;
4
import java.util.ArrayList;
5
import java.util.LinkedList;
6
import java.util.List;
7

8
import org.apache.http.HttpResponse;
9
import org.apache.http.client.methods.HttpGet;
10
import org.apache.http.util.EntityUtils;
11
import org.nanopub.NanopubUtils;
12
import org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
14

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

20
    private static int parallelCallCount = 2;
2✔
21
    private static int maxRetryCount = 3;
2✔
22
    private static final Logger logger = LoggerFactory.getLogger(QueryCall.class);
3✔
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(QueryRef queryRef) throws APINotReachableException, NotEnoughAPIInstancesException {
32
        int retryCount = 0;
2✔
33
        while (retryCount < maxRetryCount) {
3!
34
            QueryCall apiCall = new QueryCall(queryRef);
5✔
35
            apiCall.run();
2✔
36
            while (!apiCall.calls.isEmpty() && apiCall.resp == null) {
7!
37
                try {
38
                    Thread.sleep(200);
2✔
39
                } catch (InterruptedException ex) {
×
40
                    Thread.currentThread().interrupt();
×
41
                }
1✔
42
            }
43
            if (apiCall.resp != null) {
3!
44
                return apiCall.resp;
3✔
45
            }
46
            retryCount = retryCount + 1;
×
47
        }
×
48
        throw new APINotReachableException("Giving up contacting API: " + queryRef.getName());
×
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() throws NotEnoughAPIInstancesException {
69
        if (checkedApiInstances != null) return checkedApiInstances;
4✔
70
        checkedApiInstances = new ArrayList<>();
4✔
71
        for (String a : queryApiInstances) {
16✔
72
            try {
73
                logger.info("Checking API instance: {}", a);
4✔
74
                HttpResponse resp = NanopubUtils.getHttpClient().execute(new HttpGet(a));
7✔
75
                if (wasSuccessful(resp)) {
3✔
76
                    logger.info("SUCCESS: Nanopub Query instance is accessible: {}", a);
4✔
77
                    checkedApiInstances.add(a);
5✔
78
                } else {
79
                    logger.error("FAILURE: Nanopub Query instance isn't accessible: {}", a);
4✔
80
                }
81
            } catch (IOException ex) {
×
82
                logger.error("FAILURE: Nanopub Query instance isn't accessible: {}", a);
×
83
            }
1✔
84
        }
85
        logger.info("{} accessible Nanopub Query instances", checkedApiInstances.size());
6✔
86
        if (checkedApiInstances.size() < 2) {
4✔
87
            checkedApiInstances = null;
2✔
88
            throw new NotEnoughAPIInstancesException("Not enough healthy Nanopub Query instances available");
5✔
89
        }
90
        return checkedApiInstances;
2✔
91
    }
92

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

97
    private HttpResponse resp;
98

99
    private QueryCall(QueryRef queryRef) {
2✔
100
        this.queryRef = queryRef;
3✔
101
        logger.info("Invoking API operation {}", queryRef);
4✔
102
    }
1✔
103

104
    private void run() throws NotEnoughAPIInstancesException {
105
        List<String> apiInstancesToTry = new LinkedList<>(getApiInstances());
5✔
106
        while (!apiInstancesToTry.isEmpty() && apisToCall.size() < parallelCallCount) {
8!
107
            int randomIndex = (int) ((Math.random() * apiInstancesToTry.size()));
7✔
108
            String apiUrl = apiInstancesToTry.get(randomIndex);
5✔
109
            apisToCall.add(apiUrl);
5✔
110
            logger.info("Trying API ({}) {}", apisToCall.size(), apiUrl);
8✔
111
            apiInstancesToTry.remove(randomIndex);
4✔
112
        }
1✔
113
        for (String api : apisToCall) {
11✔
114
            Call call = new Call(api);
6✔
115
            calls.add(call);
5✔
116
            new Thread(call).start();
5✔
117
        }
1✔
118
    }
1✔
119

120
    private synchronized void finished(Call call, HttpResponse resp, String apiUrl) {
121
        if (this.resp != null) { // result already in
3!
122
            EntityUtils.consumeQuietly(resp.getEntity());
×
123
            return;
×
124
        }
125
        logger.info("Result in from {}:", apiUrl);
4✔
126
        logger.info("- Request: {}", queryRef);
5✔
127
        logger.info("- Response size: {}", resp.getEntity().getContentLength());
7✔
128
        this.resp = resp;
3✔
129

130
        for (Call c : calls) {
11✔
131
            if (c != call) c.abort();
5✔
132
        }
1✔
133
    }
1✔
134

135
    private static boolean wasSuccessful(HttpResponse resp) {
136
        if (resp == null || resp.getEntity() == null) return false;
5!
137
        int c = resp.getStatusLine().getStatusCode();
4✔
138
        if (c < 200 || c >= 300) return false;
8!
139
        return true;
2✔
140
    }
141

142
    private static boolean wasSuccessfulNonempty(HttpResponse resp) {
143
        if (!wasSuccessful(resp)) return false;
3!
144
        // TODO Make sure we always return proper error codes, and then this shouldn't be necessary:
145
        if (resp.getHeaders("Content-Length").length > 0 && resp.getEntity().getContentLength() < 0) return false;
11!
146
        return true;
2✔
147
    }
148

149

150
    private class Call implements Runnable {
151

152
        private String apiUrl;
153
        private HttpGet get;
154

155
        public Call(String apiUrl) {
5✔
156
            this.apiUrl = apiUrl;
3✔
157
        }
1✔
158

159
        public void run() {
160
            get = new HttpGet(apiUrl + "api/" + queryRef.getAsUrlString());
12✔
161
            get.setHeader("Accept", "text/csv");
5✔
162
            HttpResponse resp = null;
2✔
163
            try {
164
                resp = NanopubUtils.getHttpClient().execute(get);
5✔
165
                if (!wasSuccessfulNonempty(resp)) {
3!
166
                    throw new IOException(resp.getStatusLine().toString());
×
167
                }
168
                finished(this, resp, apiUrl);
7✔
169
            } catch (Exception ex) {
1✔
170
                if (resp != null) EntityUtils.consumeQuietly(resp.getEntity());
2!
171
                logger.error("Request to {} was not successful: {}", apiUrl, ex.getMessage());
7✔
172
            }
1✔
173
            calls.remove(this);
6✔
174
        }
1✔
175

176
        private void abort() {
177
            if (get == null) return;
3!
178
            if (get.isAborted()) return;
4!
179
            get.abort();
3✔
180
        }
1✔
181

182
    }
183

184
}
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

© 2025 Coveralls, Inc