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

knowledgepixels / nanodash / 17852532121

19 Sep 2025 08:10AM UTC coverage: 13.568% (-0.3%) from 13.87%
17852532121

push

github

tkuhn
feat: Switch to QueryRef provided by nanopub, using multimap

428 of 4008 branches covered (10.68%)

Branch coverage included in aggregate %.

1104 of 7283 relevant lines covered (15.16%)

0.68 hits per line

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

0.0
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.impl.client.CloseableHttpClient;
17
import org.apache.http.impl.client.HttpClientBuilder;
18
import org.nanopub.extra.services.ApiResponse;
19
import org.nanopub.extra.services.ApiResponseEntry;
20
import org.nanopub.extra.services.QueryRef;
21
import org.slf4j.Logger;
22
import org.slf4j.LoggerFactory;
23

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

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

36
    private static final Logger logger = LoggerFactory.getLogger(LookupApis.class);
×
37

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

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

62
    /**
63
     * Fetches possible values from an API based on the provided search term.
64
     *
65
     * @param apiString  the API endpoint URL to query
66
     * @param searchterm the search term to use for querying the API
67
     * @param labelMap   a map to store URIs and their corresponding labels
68
     * @param values     a list to store the extracted URIs
69
     */
70
    public static void getPossibleValues(String apiString, String searchterm, Map<String, String> labelMap, List<String> values) {
71
        // TODO This method is a mess and needs some serious clean-up and structuring...
72
        try {
73
            if (apiString.startsWith("https://w3id.org/np/l/nanopub-query-1.1/api/") || apiString.startsWith("http://purl.org/nanopub/api/find_signed_things?")) {
×
74
                String queryName = "find-things";
×
75
                if (apiString.startsWith("https://w3id.org/np/l/nanopub-query-1.1/api/")) {
×
76
                    queryName = apiString.replace("https://w3id.org/np/l/nanopub-query-1.1/api/", "");
×
77
                    if (queryName.contains("?")) queryName = queryName.substring(0, queryName.indexOf("?"));
×
78
                }
79
                Multimap<String, String> params = ArrayListMultimap.create();
×
80
                if (apiString.contains("?")) {
×
81
                    List<NameValuePair> urlParams = URLEncodedUtils.parse(apiString.substring(apiString.indexOf("?") + 1), StandardCharsets.UTF_8);
×
82
                    for (NameValuePair p : urlParams) {
×
83
                        params.put(p.getName(), p.getValue());
×
84
                    }
×
85
                }
86
                String queryId = queryName;
×
87
                if (!queryName.contains("/")) {
×
88
                    queryId = QueryApiAccess.getQueryId(queryName);
×
89
                }
90
                GrlcQuery q = GrlcQuery.get(queryId);
×
91
                if (q.getEndpoint().stringValue().endsWith("/text")) {
×
92
                    searchterm = expandSearchTerm(searchterm);
×
93
                }
94
                String queryParamName = "query";
×
95
                if (q.getPlaceholdersList().size() == 1) {
×
96
                    queryParamName = QueryParamField.getParamName(q.getPlaceholdersList().get(0));
×
97
                }
98
                params.put(queryParamName, searchterm);
×
99
                ApiResponse result = QueryApiAccess.get(new QueryRef(queryName, params));
×
100
                int count = 0;
×
101
                for (ApiResponseEntry r : result.getData()) {
×
102
                    String uri = r.get("thing");
×
103
                    values.add(uri);
×
104
                    String desc = r.get("description");
×
105
                    if (desc == null) desc = "";
×
106
                    if (desc.length() > 80) desc = desc.substring(0, 77) + "...";
×
107
                    if (!desc.isEmpty()) desc = " - " + desc;
×
108
                    labelMap.put(uri, r.get("label") + desc);
×
109
                    count++;
×
110
                    if (count > 9) return;
×
111
                }
×
112
                return;
×
113
            }
114

115
            if (apiString.startsWith("https://vodex.")) {
×
116
                searchterm = expandSearchTerm(searchterm);
×
117
            }
118
            String callUrl;
119
            if (apiString.contains(" ")) {
×
120
                callUrl = apiString.replaceAll(" ", URLEncoder.encode(searchterm, StandardCharsets.UTF_8.toString()));
×
121
            } else {
122
                callUrl = apiString + URLEncoder.encode(searchterm, StandardCharsets.UTF_8.toString());
×
123
            }
124
            HttpGet get = new HttpGet(callUrl);
×
125
            get.setHeader(HttpHeaders.ACCEPT, "application/json");
×
126
            get.setHeader("User-Agent", NanodashPreferences.get().getWebsiteUrl() + "#user-agent");
×
127
            String respString;
128
            try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
×
129
                HttpResponse resp = client.execute(get);
×
130
                if (resp.getStatusLine().getStatusCode() == 405) {
×
131
                    // Method not allowed, trying POST
132
                    HttpPost post = new HttpPost(apiString + URLEncoder.encode(searchterm, StandardCharsets.UTF_8.toString()));
×
133
                    resp = client.execute(post);
×
134
                }
135
                // TODO: support other content types (CSV, XML, ...)
136
                // System.err.println(resp.getHeaders("Content-Type")[0]);
137
                try (InputStream in = resp.getEntity().getContent()) {
×
138
                    respString = IOUtils.toString(in, StandardCharsets.UTF_8);
×
139
                }
140
            }
141
            // System.out.println(respString);
142

143
            if (apiString.startsWith("https://w3id.org/np/l/nanopub-query") || apiString.startsWith("https://grlc.") || apiString.contains("/sparql?")) {
×
144
                JSONArray resultsArray = new JSONObject(respString).getJSONObject("results").getJSONArray("bindings");
×
145
                for (int i = 0; i < resultsArray.length(); i++) {
×
146
                    JSONObject resultObject = resultsArray.getJSONObject(i);
×
147
                    // Get the nanopub URI
148
                    String uri = resultObject.getJSONObject("thing").getString("value");
×
149
                    // Get the string which matched with the search term
150
                    String label = resultObject.getJSONObject("label").getString("value");
×
151
                    values.add(uri);
×
152
                    labelMap.put(uri, label);
×
153
                }
154
            } else if (apiString.startsWith("https://www.ebi.ac.uk/ols/api/select")) {
×
155
                // Resolve EBI Ontology Lookup Service
156
                // e.g. https://www.ebi.ac.uk/ols/api/select?q=interacts%20with
157
                // response.docs.[].iri/label
158
                JSONArray responseArray = new JSONObject(respString).getJSONObject("response").getJSONArray("docs");
×
159
                for (int i = 0; i < responseArray.length(); i++) {
×
160
                    String uri = responseArray.getJSONObject(i).getString("iri");
×
161
                    String label = responseArray.getJSONObject(i).getString("label");
×
162
                    try {
163
                        label += " - " + responseArray.getJSONObject(i).getJSONArray("description").getString(0);
×
164
                    } catch (Exception ex) {
×
165
                        logger.error("No description found for {}", uri, ex);
×
166
                    }
×
167
                    if (!values.contains(uri)) {
×
168
                        values.add(uri);
×
169
                        labelMap.put(uri, label);
×
170
                    }
171
                }
172
            } else if (apiString.startsWith("https://api.gbif.org/v1/species/suggest")) {
×
173
                JSONArray responseArray = new JSONArray(respString);
×
174
                for (int i = 0; i < responseArray.length(); i++) {
×
175
                    String uri = "https://www.gbif.org/species/" + responseArray.getJSONObject(i).getString("key");
×
176
                    String label = responseArray.getJSONObject(i).getString("scientificName");
×
177
                    if (!values.contains(uri)) {
×
178
                        values.add(uri);
×
179
                        labelMap.put(uri, label);
×
180
                    }
181
                }
182
            } else if (apiString.startsWith("https://api.catalogueoflife.org/dataset/3LR/nameusage/search")) {
×
183
                JSONArray responseArray = new JSONObject(respString).getJSONArray("result");
×
184
                for (int i = 0; i < responseArray.length(); i++) {
×
185
                    String uri = "https://www.catalogueoflife.org/data/taxon/" + responseArray.getJSONObject(i).getString("id");
×
186
                    String label = responseArray.getJSONObject(i).getJSONObject("usage").getString("label");
×
187
                    if (!values.contains(uri)) {
×
188
                        values.add(uri);
×
189
                        labelMap.put(uri, label);
×
190
                    }
191
                }
192
            } else if (apiString.startsWith("https://vodex.")) {
×
193
                // TODO This is just a test and needs to be improved
194
                JSONArray responseArray = new JSONObject(respString).getJSONObject("response").getJSONArray("docs");
×
195
                for (int i = 0; i < responseArray.length(); i++) {
×
196
                    String uri = responseArray.getJSONObject(i).getString("id");
×
197
                    String label = responseArray.getJSONObject(i).getJSONArray("label").get(0).toString();
×
198
                    if (!values.contains(uri)) {
×
199
                        values.add(uri);
×
200
                        labelMap.put(uri, label);
×
201
                    }
202
                }
203
            } else if (apiString.startsWith("https://api.ror.org/organizations")) {
×
204
                // TODO This is just a test and needs to be improved
205
                JSONArray responseArray = new JSONObject(respString).getJSONArray("items");
×
206
                for (int i = 0; i < responseArray.length(); i++) {
×
207
                    String uri = responseArray.getJSONObject(i).getString("id");
×
208
                    String label = responseArray.getJSONObject(i).getString("name");
×
209
                    if (!values.contains(uri)) {
×
210
                        values.add(uri);
×
211
                        labelMap.put(uri, label);
×
212
                    }
213
                }
214
            } else {
×
215
                // TODO: create parseJsonApi() ?
216
                boolean foundId = false;
×
217
                JSONObject json = new JSONObject(respString);
×
218
                for (String key : json.keySet()) {
×
219
                    if (values.size() > 9) break;
×
220
                    if (!(json.get(key) instanceof JSONArray)) continue;
×
221
                    JSONArray a = json.getJSONArray(key);
×
222
                    for (int i = 0; i < a.length(); i++) {
×
223
                        if (values.size() > 9) break;
×
224
                        if (!(a.get(i) instanceof JSONObject)) continue;
×
225
                        JSONObject o = a.getJSONObject(i);
×
226
                        String uri = null;
×
227
                        for (String s : new String[]{"@id", "concepturi", "uri"}) {
×
228
                            if (o.has(s)) {
×
229
                                uri = o.get(s).toString();
×
230
                                foundId = true;
×
231
                                break;
×
232
                            }
233
                        }
234
                        if (uri != null) {
×
235
                            values.add(uri);
×
236
                            String label = "";
×
237
                            for (String s : new String[]{"prefLabel", "label"}) {
×
238
                                if (o.has(s)) {
×
239
                                    label = o.get(s).toString().replaceAll(" - ", " -- ");
×
240
                                    break;
×
241
                                }
242
                            }
243
                            String desc = "";
×
244
                            for (String s : new String[]{"definition", "description"}) {
×
245
                                if (o.has(s)) {
×
246
                                    desc = o.get(s).toString();
×
247
                                    break;
×
248
                                }
249
                            }
250
                            if (!label.isEmpty() && !desc.isEmpty()) desc = " - " + desc;
×
251
                            labelMap.put(uri, label + desc);
×
252
                        }
253
                    }
254
                }
×
255
                if (foundId == false) {
×
256
                    // ID key not found, try to get results for following format
257
                    // {result1: ["label 1", "label 2"], result2: ["label 3", "label 4"]}
258
                    // Aims to resolve https://name-resolution-sri.renci.org/docs#
259

260
                    // TODO: It seems this is triggered too often and adds 'https://identifiers.org/search' when it
261
                    //       shouldn't. Manually filtering these out for now...
262
                    for (String key : json.keySet()) {
×
263
                        if (!(json.get(key) instanceof JSONArray)) continue;
×
264
                        if ("search".equals(key)) continue;
×
265
                        JSONArray labelArray = json.getJSONArray(key);
×
266
                        String uri = key;
×
267
                        String label = "";
×
268
                        String desc = "";
×
269
                        if (labelArray.length() > 0) label = labelArray.getString(0);
×
270
                        if (labelArray.length() > 1) desc = labelArray.getString(1);
×
271
                        if (desc.length() > 80) desc = desc.substring(0, 77) + "...";
×
272
                        if (!label.isEmpty() && !desc.isEmpty()) desc = " - " + desc;
×
273
                        // Quick fix to convert CURIE to URI, as Nanodash only accepts URIs here
274
                        if (!(uri.startsWith("http://") || uri.startsWith("https://"))) {
×
275
                            uri = "https://identifiers.org/" + uri;
×
276
                        }
277
                        values.add(uri);
×
278
                        labelMap.put(uri, label + desc);
×
279
                    }
×
280
                }
281
            }
282
        } catch (Exception ex) {
×
283
            logger.error("Error fetching possible values from API: {}", apiString, ex);
×
284
        }
×
285
    }
×
286

287
    private static String expandSearchTerm(String searchTerm) {
288
        String expanded = "";
×
289
        boolean insideQuotes = false;
×
290
        searchTerm = searchTerm.replaceAll("\\s+", " ").trim();
×
291
        for (char c : searchTerm.toCharArray()) {
×
292
            if (c == '\n') {
×
293
                continue;
×
294
            } else if (c == '"') {
×
295
                expanded += '"';
×
296
                insideQuotes = !insideQuotes;
×
297
            } else if (c == ' ') {
×
298
                if (insideQuotes) {
×
299
                    expanded += ' ';
×
300
                } else {
301
                    expanded += '\n';
×
302
                }
303
            } else if (("" + c).matches("\\w") || c == '-' || c == '_') {
×
304
                expanded += c;
×
305
            } else {
306
                if (insideQuotes) {
×
307
                    expanded += ' ';
×
308
                } else {
309
                    expanded += '\n';
×
310
                }
311
            }
312
        }
313
        String extra = "*";
×
314
        expanded = expanded.replaceAll("\\n+", "\n").replaceAll("\"", "\\\\\\\"").trim();
×
315
        if (expanded.endsWith("\"") || insideQuotes) extra = "";
×
316
        return "( " + Strings.join(" AND ", expanded.split("\n")) + extra + " )";
×
317
    }
318

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