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

Nanopublication / nanopub-java / 19272062092

11 Nov 2025 04:27PM UTC coverage: 51.902% (+0.06%) from 51.843%
19272062092

push

github

Ziroli Plutschow
Enable logging for tests

Now we dont need System.out any more in the tests. We can just specify the log LEVEL (Default for tests is DEBUG), and the slf4j simple logger reroutes everything to Stdout. So we also see the tested classes log, what we usually want.

The simplelogger.properties files do handle that task when running the tests in maven.

Note for IntelliJ:
Run -> Edit Configurations
-Dorg.slf4j.simpleLogger.defaultLogLevel=warn
does not seem to work. I don't know why.

1011 of 2912 branches covered (34.72%)

Branch coverage included in aggregate %.

5212 of 9078 relevant lines covered (57.41%)

2.69 hits per line

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

79.72
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();
3!
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;
5✔
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());
7✔
167
                }
168
                finished(this, resp, apiUrl);
7✔
169
            } catch (Exception ex) {
1✔
170
                if (resp != null) EntityUtils.consumeQuietly(resp.getEntity());
5!
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;
×
178
            if (get.isAborted()) return;
×
179
            get.abort();
×
180
        }
×
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