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

knowledgepixels / nanodash / 24565015084

17 Apr 2026 12:27PM UTC coverage: 17.442% (+1.6%) from 15.798%
24565015084

push

github

web-flow
Merge pull request #441 from knowledgepixels/refactoring-LookupApis

Refactoring lookup apis

900 of 6186 branches covered (14.55%)

Branch coverage included in aggregate %.

2156 of 11335 relevant lines covered (19.02%)

2.64 hits per line

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

72.58
src/main/java/com/knowledgepixels/nanodash/LookupApis.java
1
package com.knowledgepixels.nanodash;
2

3
import java.io.InputStream;
4
import java.net.URLEncoder;
5
import java.nio.charset.StandardCharsets;
6
import java.util.List;
7
import java.util.Map;
8

9
import org.apache.commons.io.IOUtils;
10
import org.apache.http.HttpHeaders;
11
import org.apache.http.HttpResponse;
12
import org.apache.http.NameValuePair;
13
import org.apache.http.client.methods.HttpGet;
14
import org.apache.http.client.methods.HttpPost;
15
import org.apache.http.client.utils.URLEncodedUtils;
16
import org.apache.http.util.EntityUtils;
17
import org.apache.http.impl.client.CloseableHttpClient;
18
import org.apache.http.impl.client.HttpClientBuilder;
19
import org.nanopub.extra.services.ApiResponse;
20
import org.nanopub.extra.services.ApiResponseEntry;
21
import org.nanopub.extra.services.QueryRef;
22
import org.slf4j.Logger;
23
import org.slf4j.LoggerFactory;
24

25
import com.beust.jcommander.Strings;
26
import com.github.openjson.JSONArray;
27
import com.github.openjson.JSONObject;
28
import com.google.common.collect.ArrayListMultimap;
29
import com.google.common.collect.Multimap;
30
import com.knowledgepixels.nanodash.component.QueryParamField;
31

32
/**
33
 * Utility class for APIs look up and parsing.
34
 */
35
public class LookupApis {
36

37
    private static final Logger logger = LoggerFactory.getLogger(LookupApis.class);
12✔
38

39
    private LookupApis() {
40
    }  // no instances allowed
41

42
    /**
43
     * Parses a JSON response from a grlc API for nanopublications and extracts URIs and labels.
44
     *
45
     * @param grlcJsonObject the JSON object containing the grlc API response
46
     * @param labelMap       a map to store URIs and their corresponding labels
47
     * @param values         a list to store the extracted URIs
48
     */
49
    public static void parseNanopubGrlcApi(JSONObject grlcJsonObject, Map<String, String> labelMap, List<String> values) {
50
        // Aimed to resolve Nanopub grlc API: http://grlc.nanopubs.lod.labs.vu.nl/api/local/local/find_signed_nanopubs_with_text?text=covid
51
        JSONArray resultsArray = grlcJsonObject.getJSONObject("results").getJSONArray("bindings");
18✔
52
        for (int i = 0; i < resultsArray.length(); i++) {
24✔
53
            JSONObject resultObject = resultsArray.getJSONObject(i);
12✔
54
            // Get the nanopub URI
55
            String uri = resultObject.getJSONObject("thing").getString("value");
18✔
56
            // Get the string which matched with the search term
57
            String label = resultObject.getJSONObject("label").getString("value");
18✔
58
            values.add(uri);
12✔
59
            labelMap.put(uri, label);
15✔
60
        }
61
    }
3✔
62

63
    /**
64
     * Fetches possible values from an API based on the provided search term.
65
     *
66
     * @param apiString  the API endpoint URL to query
67
     * @param searchterm the search term to use for querying the API
68
     * @param labelMap   a map to store URIs and their corresponding labels
69
     * @param values     a list to store the extracted URIs
70
     */
71
    public static void getPossibleValues(String apiString, String searchterm, Map<String, String> labelMap, List<String> values) {
72
        // TODO This method is a mess and needs some serious clean-up and structuring...
73
        try {
74
            if (apiString.startsWith("https://w3id.org/np/l/nanopub-query-1.1/api/") || apiString.startsWith("http://purl.org/nanopub/api/find_signed_things?")) {
24✔
75
                lookupNanopubNetwork(apiString, searchterm, labelMap, values);
15✔
76
                return;
3✔
77
            }
78

79
            if (apiString.startsWith("https://vodex.")) {
12✔
80
                searchterm = expandSearchTerm(searchterm);
9✔
81
            }
82
            String callUrl;
83
            if (apiString.contains(" ")) {
12✔
84
                callUrl = apiString.replaceAll(" ", URLEncoder.encode(searchterm, StandardCharsets.UTF_8.toString()));
27✔
85
            } else {
86
                callUrl = apiString + URLEncoder.encode(searchterm, StandardCharsets.UTF_8.toString());
21✔
87
            }
88
            HttpGet get = new HttpGet(callUrl);
15✔
89
            get.setHeader(HttpHeaders.ACCEPT, "application/json");
12✔
90
            get.setHeader("User-Agent", NanodashPreferences.get().getWebsiteUrl() + "#user-agent");
18✔
91
            String respString;
92
            try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
9✔
93
                HttpResponse resp = client.execute(get);
12✔
94
                if (resp.getStatusLine().getStatusCode() == 405) {
15!
95
                    // Method not allowed, trying POST
96
                    EntityUtils.consume(resp.getEntity());
×
97
                    HttpPost post = new HttpPost(apiString + URLEncoder.encode(searchterm, StandardCharsets.UTF_8.toString()));
×
98
                    resp = client.execute(post);
×
99
                }
100
                // TODO: support other content types (CSV, XML, ...)
101
                // System.err.println(resp.getHeaders("Content-Type")[0]);
102
                try (InputStream in = resp.getEntity().getContent()) {
12✔
103
                    respString = IOUtils.toString(in, StandardCharsets.UTF_8);
12✔
104
                }
105
            }
106
            // System.out.println(respString);
107

108
            if (apiString.startsWith("https://w3id.org/np/l/nanopub-query") || apiString.startsWith("https://grlc.") || apiString.contains("/sparql?")) {
36✔
109
                parseNanopubGrlcApi(new JSONObject(respString), labelMap, values);
24✔
110
            } else if (apiString.startsWith("https://www.ebi.ac.uk/ols/api/select")) {
12✔
111
                // Resolve EBI Ontology Lookup Service
112
                // e.g. https://www.ebi.ac.uk/ols/api/select?q=interacts%20with
113
                // response.docs.[].iri/label
114
                JSONArray responseArray = new JSONObject(respString).getJSONObject("response").getJSONArray("docs");
27✔
115
                for (int i = 0; i < responseArray.length(); i++) {
24✔
116
                    String uri = responseArray.getJSONObject(i).getString("iri");
18✔
117
                    String label = responseArray.getJSONObject(i).getString("label");
18✔
118
                    try {
119
                        label += " - " + responseArray.getJSONObject(i).getJSONArray("description").getString(0);
30✔
120
                    } catch (Exception ex) {
×
121
                        logger.error("No description found for {}", uri, ex);
×
122
                    }
3✔
123
                    if (!values.contains(uri)) {
12!
124
                        values.add(uri);
12✔
125
                        labelMap.put(uri, label);
15✔
126
                    }
127
                }
128
            } else if (apiString.startsWith("https://api.openaire.eu/graph/v")) {
15✔
129
                String type = apiString.replaceFirst("^https://api\\.openaire\\.eu/graph/(v[0-9]+/[a-zA-Z]+).*$", "$1");
15✔
130
                for (Object obj : new JSONObject(respString).getJSONArray("results")) {
42✔
131
                    if (obj instanceof JSONObject jsonObj) {
18!
132
                        String uri = "https://api.openaire.eu/graph/" + type + "/" + jsonObj.getString("id");
18✔
133
                        if (!values.contains(uri)) {
12!
134
                            values.add(uri);
12✔
135
                            String label = uri;
6✔
136
                            if (jsonObj.has("mainTitle")) {
12!
137
                                label = jsonObj.getString("mainTitle");
15✔
138
                            } else if (jsonObj.has("legalShortName")) {
×
139
                                label = jsonObj.getString("legalShortName");
×
140
                            } else if (jsonObj.has("officialName")) {
×
141
                                label = jsonObj.getString("officialName");
×
142
                            } else if (jsonObj.has("title")) {
×
143
                                label = jsonObj.getString("title");
×
144
                            } else if (jsonObj.has("familyName")) {
×
145
                                label = jsonObj.getString("familyName");
×
146
                                if (jsonObj.has("givenName")) {
×
147
                                    label = jsonObj.getString("givenName") + " " + label;
×
148
                                }
149
                            }
150
                            labelMap.put(uri, label);
15✔
151
                        }
152
                    }
153
                }
3✔
154
            } else if (apiString.startsWith("https://api.gbif.org/v1/species/suggest")) {
15✔
155
                JSONArray responseArray = new JSONArray(respString);
15✔
156
                for (int i = 0; i < responseArray.length(); i++) {
24✔
157
                    String uri = "https://www.gbif.org/species/" + responseArray.getJSONObject(i).getString("key");
21✔
158
                    String label = responseArray.getJSONObject(i).getString("scientificName");
18✔
159
                    if (!values.contains(uri)) {
12!
160
                        values.add(uri);
12✔
161
                        labelMap.put(uri, label);
15✔
162
                    }
163
                }
164
            } else if (apiString.startsWith("https://api.catalogueoflife.org/dataset/3LR/nameusage/search")) {
15✔
165
                JSONArray responseArray = new JSONObject(respString).getJSONArray("result");
21✔
166
                for (int i = 0; i < responseArray.length(); i++) {
24✔
167
                    String uri = "https://www.catalogueoflife.org/data/taxon/" + responseArray.getJSONObject(i).getString("id");
21✔
168
                    String label = responseArray.getJSONObject(i).getJSONObject("usage").getString("label");
24✔
169
                    if (!values.contains(uri)) {
12!
170
                        values.add(uri);
12✔
171
                        labelMap.put(uri, label);
15✔
172
                    }
173
                }
174
            } else if (apiString.startsWith("https://vodex.")) {
15✔
175
                // TODO This is just a test and needs to be improved
176
                JSONArray responseArray = new JSONObject(respString).getJSONObject("response").getJSONArray("docs");
27✔
177
                for (int i = 0; i < responseArray.length(); i++) {
24✔
178
                    String uri = responseArray.getJSONObject(i).getString("id");
18✔
179
                    String label = responseArray.getJSONObject(i).getJSONArray("label").get(0).toString();
27✔
180
                    if (!values.contains(uri)) {
12!
181
                        values.add(uri);
12✔
182
                        labelMap.put(uri, label);
15✔
183
                    }
184
                }
185
            } else if (apiString.startsWith("https://api.ror.org/organizations")) {
15✔
186
                // TODO This is just a test and needs to be improved
187
                JSONArray responseArray = new JSONObject(respString).getJSONArray("items");
21✔
188
                for (int i = 0; i < responseArray.length(); i++) {
24✔
189
                    String uri = responseArray.getJSONObject(i).getString("id");
18✔
190
                    JSONArray namesArray = responseArray.getJSONObject(i).getJSONArray("names");
18✔
191
                    if (namesArray.length() == 0) continue;
9!
192
                    String label = namesArray.getJSONObject(0).getString("value");
18✔
193
                    if (!values.contains(uri)) {
12!
194
                        values.add(uri);
12✔
195
                        labelMap.put(uri, label);
15✔
196
                    }
197
                }
198
            } else {
3✔
199
                // TODO: create parseJsonApi() ?
200
                boolean foundId = false;
6✔
201
                JSONObject json = new JSONObject(respString);
15✔
202
                for (String key : json.keySet()) {
33✔
203
                    if (values.size() > 9) break;
12!
204
                    if (!(json.get(key) instanceof JSONArray)) continue;
18✔
205
                    JSONArray a = json.getJSONArray(key);
12✔
206
                    for (int i = 0; i < a.length(); i++) {
24✔
207
                        if (values.size() > 9) break;
12!
208
                        if (!(a.get(i) instanceof JSONObject)) continue;
15!
209
                        JSONObject o = a.getJSONObject(i);
12✔
210
                        String uri = null;
6✔
211
                        for (String s : new String[]{"@id", "concepturi", "uri"}) {
87!
212
                            if (o.has(s)) {
12✔
213
                                uri = o.get(s).toString();
15✔
214
                                foundId = true;
6✔
215
                                break;
3✔
216
                            }
217
                        }
218
                        if (uri != null) {
6!
219
                            values.add(uri);
12✔
220
                            String label = "";
6✔
221
                            for (String s : new String[]{"prefLabel", "label"}) {
75!
222
                                if (o.has(s)) {
12✔
223
                                    label = o.get(s).toString().replaceAll(" - ", " -- ");
24✔
224
                                    break;
3✔
225
                                }
226
                            }
227
                            String desc = "";
6✔
228
                            for (String s : new String[]{"definition", "description"}) {
75!
229
                                if (o.has(s)) {
12✔
230
                                    desc = o.get(s).toString();
15✔
231
                                    break;
3✔
232
                                }
233
                            }
234
                            if (!label.isEmpty() && !desc.isEmpty()) desc = " - " + desc;
27!
235
                            labelMap.put(uri, label + desc);
21✔
236
                        }
237
                    }
238
                }
3✔
239
                if (foundId == false) {
6!
240
                    // ID key not found, try to get results for following format
241
                    // {result1: ["label 1", "label 2"], result2: ["label 3", "label 4"]}
242
                    // Aims to resolve https://name-resolution-sri.renci.org/docs#
243

244
                    // TODO: It seems this is triggered too often and adds 'https://identifiers.org/search' when it
245
                    //       shouldn't. Manually filtering these out for now...
246
                    for (String key : json.keySet()) {
×
247
                        if (!(json.get(key) instanceof JSONArray)) continue;
×
248
                        if ("search".equals(key)) continue;
×
249
                        JSONArray labelArray = json.getJSONArray(key);
×
250
                        String uri = key;
×
251
                        String label = "";
×
252
                        String desc = "";
×
253
                        if (labelArray.length() > 0) label = labelArray.getString(0);
×
254
                        if (labelArray.length() > 1) desc = labelArray.getString(1);
×
255
                        if (desc.length() > 80) desc = desc.substring(0, 77) + "...";
×
256
                        if (!label.isEmpty() && !desc.isEmpty()) desc = " - " + desc;
×
257
                        // Quick fix to convert CURIE to URI, as Nanodash only accepts URIs here
258
                        if (!(uri.startsWith("http://") || uri.startsWith("https://"))) {
×
259
                            uri = "https://identifiers.org/" + uri;
×
260
                        }
261
                        values.add(uri);
×
262
                        labelMap.put(uri, label + desc);
×
263
                    }
×
264
                }
265
            }
266
        } catch (Exception ex) {
×
267
            logger.error("Error fetching possible values from API: {}", apiString, ex);
×
268
        }
3✔
269
    }
3✔
270

271
    private static void lookupNanopubNetwork(String apiString, String searchterm, Map<String, String> labelMap, List<String> values) {
272
        String queryId = QueryApiAccess.FIND_THINGS;
6✔
273
        if (apiString.startsWith("https://w3id.org/np/l/nanopub-query-1.1/api/")) {
12✔
274
            queryId = apiString.replace("https://w3id.org/np/l/nanopub-query-1.1/api/", "");
15✔
275
            if (queryId.contains("?")) queryId = queryId.substring(0, queryId.indexOf("?"));
33✔
276
        }
277
        Multimap<String, String> params = ArrayListMultimap.create();
6✔
278
        if (apiString.contains("?")) {
12✔
279
            List<NameValuePair> urlParams = URLEncodedUtils.parse(apiString.substring(apiString.indexOf("?") + 1), StandardCharsets.UTF_8);
30✔
280
            for (NameValuePair p : urlParams) {
30✔
281
                params.put(p.getName(), p.getValue());
21✔
282
            }
3✔
283
        }
284
        GrlcQuery q = GrlcQuery.get(queryId);
9✔
285
        if (q.getEndpoint().stringValue().endsWith("/text")) {
18✔
286
            searchterm = expandSearchTerm(searchterm);
9✔
287
        }
288
        String queryParamName = "query";
6✔
289
        if (q.getPlaceholdersList().size() == 1) {
15!
290
            queryParamName = QueryParamField.getParamName(q.getPlaceholdersList().get(0));
×
291
        }
292
        params.put(queryParamName, searchterm);
15✔
293
        ApiResponse result = ApiCache.retrieveResponseSync(new QueryRef(queryId, params), false);
24✔
294
        int count = 0;
6✔
295
        for (ApiResponseEntry r : result.getData()) {
33✔
296
            String uri = r.get("thing");
12✔
297
            values.add(uri);
12✔
298
            String desc = r.get("description");
12✔
299
            if (desc == null) desc = "";
12!
300
            if (desc.length() > 80) desc = desc.substring(0, 77) + "...";
12!
301
            if (!desc.isEmpty()) desc = " - " + desc;
9!
302
            labelMap.put(uri, r.get("label") + desc);
27✔
303
            count++;
3✔
304
            if (count > 9) return;
9!
305
        }
3✔
306
    }
3✔
307

308
    private static String expandSearchTerm(String searchTerm) {
309
        String expanded = "";
6✔
310
        boolean insideQuotes = false;
6✔
311
        searchTerm = searchTerm.replaceAll("\\s+", " ").trim();
18✔
312
        for (char c : searchTerm.toCharArray()) {
51✔
313
            if (c == '\n') {
9!
314
                continue;
×
315
            } else if (c == '"') {
9✔
316
                expanded += '"';
9✔
317
                insideQuotes = !insideQuotes;
21✔
318
            } else if (c == ' ') {
9✔
319
                if (insideQuotes) {
6✔
320
                    expanded += ' ';
12✔
321
                } else {
322
                    expanded += '\n';
12✔
323
                }
324
            } else if (("" + c).matches("\\w") || c == '-' || c == '_') {
15!
325
                expanded += c;
15✔
326
            } else {
327
                if (insideQuotes) {
×
328
                    expanded += ' ';
×
329
                } else {
330
                    expanded += '\n';
×
331
                }
332
            }
333
        }
334
        String extra = "*";
6✔
335
        expanded = expanded.replaceAll("\\n+", "\n").replaceAll("\"", "\\\\\\\"").trim();
27✔
336
        if (expanded.endsWith("\"") || insideQuotes) extra = "";
24!
337
        return "( " + Strings.join(" AND ", expanded.split("\n")) + extra + " )";
24✔
338
    }
339

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