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

knowledgepixels / nanopub-query / 17767759901

16 Sep 2025 01:40PM UTC coverage: 71.59% (-0.2%) from 71.741%
17767759901

push

github

Ziroli Plutschow
Replacing newest System.err with slf4j logging

- Note: Also upgraded mockito, since it's transient dependency byte-buddy could not handle a jvm > 21

233 of 350 branches covered (66.57%)

Branch coverage included in aggregate %.

591 of 801 relevant lines covered (73.78%)

3.72 hits per line

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

0.0
src/main/java/com/knowledgepixels/query/MainVerticle.java
1
package com.knowledgepixels.query;
2

3
import java.io.InputStream;
4
import java.net.URLEncoder;
5
import java.util.ArrayList;
6
import java.util.Collections;
7
import java.util.List;
8
import java.util.Map.Entry;
9
import java.util.Scanner;
10
import java.util.concurrent.Executors;
11
import java.util.concurrent.TimeUnit;
12

13
import org.eclipse.rdf4j.model.Value;
14

15
import com.github.jsonldjava.shaded.com.google.common.base.Charsets;
16

17
import io.micrometer.prometheus.PrometheusMeterRegistry;
18
import io.vertx.core.AbstractVerticle;
19
import io.vertx.core.Future;
20
import io.vertx.core.MultiMap;
21
import io.vertx.core.Promise;
22
import io.vertx.core.buffer.Buffer;
23
import io.vertx.core.http.HttpClient;
24
import io.vertx.core.http.HttpClientOptions;
25
import io.vertx.core.http.HttpMethod;
26
import io.vertx.core.http.HttpServer;
27
import io.vertx.core.http.HttpServerResponse;
28
import io.vertx.core.http.PoolOptions;
29
import io.vertx.ext.web.Router;
30
import io.vertx.ext.web.RoutingContext;
31
import io.vertx.ext.web.handler.CorsHandler;
32
import io.vertx.ext.web.handler.StaticHandler;
33
import io.vertx.ext.web.proxy.handler.ProxyHandler;
34
import io.vertx.httpproxy.Body;
35
import io.vertx.httpproxy.HttpProxy;
36
import io.vertx.httpproxy.ProxyContext;
37
import io.vertx.httpproxy.ProxyInterceptor;
38
import io.vertx.httpproxy.ProxyRequest;
39
import io.vertx.httpproxy.ProxyResponse;
40
import io.vertx.micrometer.PrometheusScrapingHandler;
41
import io.vertx.micrometer.backends.BackendRegistries;
42
import org.slf4j.Logger;
43
import org.slf4j.LoggerFactory;
44

45

46
// TODO merge this class with GrlcQuery of Nanodash and move to a library like nanopub-java
47
/**
48
 * Main verticle that coordinates the incoming HTTP requests.
49
 */
50
@GeneratedFlagForDependentElements
51
public class MainVerticle extends AbstractVerticle {
52

53
    private static String css = null;
54

55
    private static final Logger log = LoggerFactory.getLogger(MainVerticle.class);
56

57
    /**
58
     * Start the main verticle.
59
     *
60
     * @param startPromise the promise to complete when the verticle is started
61
     * @throws Exception if an error occurs during startup
62
     */
63
    @Override
64
    public void start(Promise<Void> startPromise) throws Exception {
65
        HttpClient httpClient = vertx.createHttpClient(
66
                new HttpClientOptions()
67
                        .setConnectTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_CONNECT_TIMEOUT", 1000))
68
                        .setIdleTimeoutUnit(TimeUnit.SECONDS)
69
                        .setIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60))
70
                        .setReadIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60))
71
                        .setWriteIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60)),
72
                new PoolOptions().setHttp1MaxSize(200).setHttp2MaxSize(200)
73
        );
74

75
        HttpServer proxyServer = vertx.createHttpServer();
76
        Router proxyRouter = Router.router(vertx);
77
        proxyRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*"));
78

79
        // Metrics
80
        final var metricsHttpServer = vertx.createHttpServer();
81
        final var metricsRouter = Router.router(vertx);
82
        metricsHttpServer.requestHandler(metricsRouter).listen(9394);
83

84
        final var metricsRegistry = (PrometheusMeterRegistry) BackendRegistries.getDefaultNow();
85
        final var collector = new MetricsCollector(metricsRegistry);
86
        metricsRouter.route("/metrics").handler(PrometheusScrapingHandler.create(metricsRegistry));
87
        // ----------
88
        // This part is only used if the redirection is not done through Nginx.
89
        // See nginx.conf and this bug report: https://github.com/eclipse-rdf4j/rdf4j/discussions/5120
90
        HttpProxy rdf4jProxy = HttpProxy.reverseProxy(httpClient);
91
        String proxy = Utils.getEnvString("RDF4J_PROXY_HOST", "rdf4j");
92
        int proxyPort = Utils.getEnvInt("RDF4J_PROXY_PORT", 8080);
93
        rdf4jProxy.origin(proxyPort, proxy);
94

95
        rdf4jProxy.addInterceptor(new ProxyInterceptor() {
×
96

97
            @Override
98
            @GeneratedFlagForDependentElements
99
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
100
                ProxyRequest request = context.request();
101
                request.setURI(request.getURI().replaceAll("/", "_").replaceFirst("^_repo_", "/rdf4j-server/repositories/"));
102
                // For later to try to get HTML tables out:
103
//                                if (request.headers().get("Accept") == null) {
104
//                                        request.putHeader("Accept", "text/html");
105
//                                }
106
//                                request.putHeader("Accept", "application/json");
107
                return ProxyInterceptor.super.handleProxyRequest(context);
108
            }
109

110
            @Override
111
            @GeneratedFlagForDependentElements
112
            public Future<Void> handleProxyResponse(ProxyContext context) {
113
                ProxyResponse resp = context.response();
114
                resp.putHeader("Access-Control-Allow-Origin", "*");
115
                resp.putHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
116
                // For later to try to get HTML tables out:
117
//                                String acceptHeader = context.request().headers().get("Accept");
118
//                                if (acceptHeader != null && acceptHeader.contains("text/html")) {
119
//                                        resp.putHeader("Content-Type", "text/html");
120
//                                        resp.setBody(Body.body(Buffer.buffer("<html><body><strong>test</strong></body></html>")));
121
//                                }
122
                return ProxyInterceptor.super.handleProxyResponse(context);
123
            }
124

125
        });
126
        // ----------
127

128
        proxyRouter.route(HttpMethod.GET, "/repo").handler(req -> handleRedirect(req, "/repo"));
129
        proxyRouter.route(HttpMethod.GET, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
130
        proxyRouter.route(HttpMethod.POST, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
131
        proxyRouter.route(HttpMethod.HEAD, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
132
        proxyRouter.route(HttpMethod.OPTIONS, "/repo/*").handler(ProxyHandler.create(rdf4jProxy));
133
        proxyRouter.route(HttpMethod.GET, "/tools/*").handler(req -> {
134
            final String yasguiPattern = "^/tools/([a-zA-Z0-9-_]+)(/([a-zA-Z0-9-_]+))?/yasgui\\.html$";
135
            if (req.normalizedPath().matches(yasguiPattern)) {
136
                String repo = req.normalizedPath().replaceFirst(yasguiPattern, "$1$2");
137
                req.response()
138
                        .putHeader("content-type", "text/html")
139
                        .end("<!DOCTYPE html>\n"
140
                                + "<html lang=\"en\">\n"
141
                                + "<head>\n"
142
                                + "<meta charset=\"utf-8\">\n"
143
                                + "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
144
                                + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
145
                                + "<title>Nanopub Query SPARQL Editor for repository: " + repo + "</title>\n"
146
                                + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
147
                                + "<link href='https://cdn.jsdelivr.net/yasgui/2.6.1/yasgui.min.css' rel='stylesheet' type='text/css'/>\n"
148
                                + "<style>.yasgui .endpointText {display:none !important;}</style>\n"
149
                                + "<script type=\"text/javascript\">localStorage.clear();</script>\n"
150
                                + "</head>\n"
151
                                + "<body>\n"
152
                                + "<h3>Nanopub Query SPARQL Editor for repository: " + repo + "</h3>\n"
153
                                + "<div id='yasgui'></div>\n"
154
                                + "<script src='https://cdn.jsdelivr.net/yasgui/2.6.1/yasgui.min.js'></script>\n"
155
                                + "<script type=\"text/javascript\">\n"
156
                                + "var yasgui = YASGUI(document.getElementById(\"yasgui\"), {\n"
157
                                + "  yasqe:{sparql:{endpoint:'/repo/" + repo + "'},value:'" + Utils.defaultQuery.replaceAll("\n", "\\\\n") + "'}\n"
158
                                + "});\n"
159
                                + "</script>\n"
160
                                + "</body>\n"
161
                                + "</html>");
162
            } else {
163
                req.response()
164
                        .putHeader("content-type", "text/plain")
165
                        .setStatusCode(404)
166
                        .end("not found");
167
            }
168
        });
169
        proxyRouter.route(HttpMethod.GET, "/page").handler(req -> handleRedirect(req, "/page"));
170
        proxyRouter.route(HttpMethod.GET, "/page/*").handler(req -> {
171
            final String pagePattern = "^/page/([a-zA-Z0-9-_]+)(/([a-zA-Z0-9-_]+))?$";
172
            if (req.normalizedPath().matches(pagePattern)) {
173
                String repo = req.normalizedPath().replaceFirst(pagePattern, "$1$2");
174
                req.response()
175
                        .putHeader("content-type", "text/html")
176
                        .end("<!DOCTYPE html>\n"
177
                                + "<html lang=\"en\">\n"
178
                                + "<head>\n"
179
                                + "<meta charset=\"utf-8\">\n"
180
                                + "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
181
                                + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
182
                                + "<title>Nanopub Query repo: " + repo + "</title>\n"
183
                                + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
184
                                + "</head>\n"
185
                                + "<body>\n"
186
                                + "<h3>Nanopub Query repo: " + repo + "</h3>\n"
187
                                + "<p>Endpoint: <a href=\"/repo/" + repo + "\">/repo/" + repo + "</a></p>"
188
                                + "<p>YASGUI: <a href=\"/tools/" + repo + "/yasgui.html\">/tools/" + repo + "/yasgui.hml</a></p>"
189
                                + "</body>\n"
190
                                + "</html>");
191
            } else {
192
                req.response()
193
                        .putHeader("content-type", "text/plain")
194
                        .setStatusCode(404)
195
                        .end("not found");
196
            }
197
        });
198
        proxyRouter.route(HttpMethod.GET, "/").handler(req -> {
199
            String repos = "";
200
            List<String> repoList = new ArrayList<>(TripleStore.get().getRepositoryNames());
201
            Collections.sort(repoList);
202
            for (String s : repoList) {
203
                if (s.startsWith("pubkey_") || s.startsWith("type_")) continue;
204
                repos += "<li><code><a href=\"/page/" + s + "\">" + s + "</a></code></li>";
205
            }
206
            String pinnedApisValue = Utils.getEnvString("NANOPUB_QUERY_PINNED_APIS", "");
207
            String[] pinnedApis = pinnedApisValue.split(" ");
208
            String pinnedApiLinks = "";
209
            if (!pinnedApisValue.isEmpty()) {
210
                for (String s : pinnedApis) {
211
                    pinnedApiLinks = pinnedApiLinks + "<li><a href=\"openapi/?url=spec/" + s + "%3Fapi-version=latest\">" + s.replaceFirst("^.*/", "") + "</a></li>";
212
                }
213
                pinnedApiLinks = "<p>Pinned APIs:</p>\n" +
214
                        "<ul>\n" +
215
                        pinnedApiLinks +
216
                        "</ul>\n";
217
            }
218
            req.response()
219
                    .putHeader("content-type", "text/html")
220
                    .end("<!DOCTYPE html>\n"
221
                            + "<html lang='en'>\n"
222
                            + "<head>\n"
223
                            + "<title>Nanopub Query</title>\n"
224
                            + "<meta charset='utf-8'>\n"
225
                            + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
226
                            + "</head>\n"
227
                            + "<body>\n"
228
                            + "<h1>Nanopub Query</h1>"
229
                            + "<p>General repos:</p>"
230
                            + "<ul>" + repos + "</ul>"
231
                            + "<p>Specific repos:</p>"
232
                            + "<ul>"
233
                            + "<li><a href=\"/pubkeys\">Pubkey Repos</a></li>"
234
                            + "<li><a href=\"/types\">Type Repos</a></li>"
235
                            + "</ul>"
236
                            + pinnedApiLinks
237
                            + "</body>\n"
238
                            + "</html>");
239
        });
240
        proxyRouter.route(HttpMethod.GET, "/pubkeys").handler(req -> {
241
            String repos = "";
242
            List<String> repoList = new ArrayList<>(TripleStore.get().getRepositoryNames());
243
            Collections.sort(repoList);
244
            for (String s : repoList) {
245
                if (!s.startsWith("pubkey_")) continue;
246
                String hash = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$2");
247
                Value hashObj = Utils.getObjectForHash(hash);
248
                String label;
249
                if (hashObj == null) {
250
                    label = "";
251
                } else {
252
                    label = " (" + Utils.getShortPubkeyName(hashObj.stringValue()) + ")";
253
                }
254
                s = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$1/$2");
255
                repos += "<li><code><a href=\"/page/" + s + "\">" + s + "</a>" + label + "</code></li>";
256
            }
257
            req.response()
258
                    .putHeader("content-type", "text/html")
259
                    .end("<!DOCTYPE html>\n"
260
                            + "<html lang='en'>\n"
261
                            + "<head>\n"
262
                            + "<title>Nanopub Query: Pubkey Repos</title>\n"
263
                            + "<meta charset='utf-8'>\n"
264
                            + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
265
                            + "</head>\n"
266
                            + "<body>\n"
267
                            + "<h3>Pubkey Repos</h3>"
268
                            + "<p>Repos:</p>"
269
                            + "<ul>" + repos + "</ul>"
270
                            + "</body>\n"
271
                            + "</html>");
272
        });
273
        proxyRouter.route(HttpMethod.GET, "/types").handler(req -> {
274
            String repos = "";
275
            List<String> repoList = new ArrayList<>(TripleStore.get().getRepositoryNames());
276
            Collections.sort(repoList);
277
            for (String s : repoList) {
278
                if (!s.startsWith("type_")) continue;
279
                String hash = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$2");
280
                Value hashObj = Utils.getObjectForHash(hash);
281
                String label;
282
                if (hashObj == null) {
283
                    label = "";
284
                } else {
285
                    label = " (" + hashObj.stringValue() + ")";
286
                }
287
                s = s.replaceFirst("^([a-zA-Z0-9-]+)_([a-zA-Z0-9-_]+)$", "$1/$2");
288
                repos += "<li><code><a href=\"/page/" + s + "\">" + s + "</a>" + label + "</code></li>";
289
            }
290
            req.response()
291
                    .putHeader("content-type", "text/html")
292
                    .end("<!DOCTYPE html>\n"
293
                            + "<html lang='en'>\n"
294
                            + "<head>\n"
295
                            + "<title>Nanopub Query: Type Repos</title>\n"
296
                            + "<meta charset='utf-8'>\n"
297
                            + "<link rel=\"stylesheet\" href=\"/style.css\">\n"
298
                            + "</head>\n"
299
                            + "<body>\n"
300
                            + "<h3>Type Repos</h3>"
301
                            + "<p>Repos:</p>"
302
                            + "<ul>" + repos + "</ul>"
303
                            + "</body>\n"
304
                            + "</html>");
305
        });
306
        proxyRouter.route(HttpMethod.GET, "/style.css").handler(req -> {
307
            if (css == null) {
308
                css = getResourceAsString("style.css");
309
            }
310
            req.response().end(css);
311
        });
312

313
        proxyRouter.route(HttpMethod.GET, "/grlc-spec/*").handler(req -> {
314
            GrlcSpec gsp = new GrlcSpec(req.normalizedPath(), req.queryParams());
315
            String spec = gsp.getSpec();
316
            if (spec == null) {
317
                req.response().setStatusCode(404).end("query definition not found / not valid");
318
            } else {
319
                req.response().putHeader("content-type", "text/yaml").end(spec);
320
            }
321
        });
322

323
        proxyRouter.route(HttpMethod.GET, "/openapi/spec/*").handler(req -> {
324
            OpenApiSpecPage osp = new OpenApiSpecPage(req.normalizedPath(), req.queryParams());
325
            String spec = osp.getSpec();
326
            if (spec == null) {
327
                req.response().setStatusCode(404).end("query definition not found / not valid");
328
            } else {
329
                req.response().putHeader("content-type", "text/yaml").end(spec);
330
            }
331
        });
332

333
        proxyRouter.route("/openapi/*").handler(StaticHandler.create("com/knowledgepixels/query/swagger"));
334

335
        HttpProxy grlcProxy = HttpProxy.reverseProxy(httpClient);
336
        grlcProxy.origin(80, "grlc");
337
        grlcProxy.addInterceptor(new ProxyInterceptor() {
×
338

339
            @Override
340
            @GeneratedFlagForDependentElements
341
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
342
                final String apiPattern = "^/api/(RA[a-zA-Z0-9-_]{43})/([a-zA-Z0-9-_]+)([?].*)?$";
343
                if (context.request().getURI().matches(apiPattern)) {
344
                    String artifactCode = context.request().getURI().replaceFirst(apiPattern, "$1");
345
                    String queryName = context.request().getURI().replaceFirst(apiPattern, "$2");
346
                    String grlcUrlParams = "";
347
                    String grlcSpecUrlParams = "";
348
                    MultiMap pm = context.request().proxiedRequest().params();
349
                    for (Entry<String, String> e : pm) {
350
                        if (e.getKey().equals("api-version")) {
351
                            grlcSpecUrlParams += "&" + e.getKey() + "=" + URLEncoder.encode(e.getValue(), Charsets.UTF_8);
352
                        } else {
353
                            grlcUrlParams += "&" + e.getKey() + "=" + URLEncoder.encode(e.getValue(), Charsets.UTF_8);
354
                        }
355
                    }
356
                    String url = "/api-url/" + queryName +
357
                            "?specUrl=" + URLEncoder.encode(GrlcSpec.nanopubQueryUrl + "grlc-spec/" + artifactCode + "/?" +
358
                            grlcSpecUrlParams, Charsets.UTF_8) + grlcUrlParams;
359
                    context.request().setURI(url);
360
                }
361
                return context.sendRequest();
362
            }
363

364
            @Override
365
            @GeneratedFlagForDependentElements
366
            public Future<Void> handleProxyResponse(ProxyContext context) {
367
                // To avoid double entries:
368
                context.response().headers().remove("Access-Control-Allow-Origin");
369
                return context.sendResponse();
370
            }
371

372
        });
373

374
        HttpProxy grlcxProxy = HttpProxy.reverseProxy(httpClient);
375
        grlcxProxy.origin(proxyPort, proxy);
376

377
        grlcxProxy.addInterceptor(new ProxyInterceptor() {
×
378

379
            @Override
380
            @GeneratedFlagForDependentElements
381
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
382
                final ProxyRequest req = context.request();
383
                final String apiPattern = "^/apix/(RA[a-zA-Z0-9-_]{43})/([a-zA-Z0-9-_]+)([?].*)?$";
384
                if (req.getURI().matches(apiPattern)) {
385
                    GrlcSpec grlcSpec = new GrlcSpec(req.getURI(), req.proxiedRequest().params());
386
                    req.setMethod(HttpMethod.POST);
387

388
                    // Variant 1:
389
                    req.putHeader("Content-Type", "application/sparql-query");
390
                    req.setBody(Body.body(Buffer.buffer(grlcSpec.getExpandedQueryContent())));
391
                    // Variant 2:
392
                    //req.putHeader("Content-Type", "application/x-www-form-urlencoded");
393
                    //req.setBody(Body.body(Buffer.buffer("query=" + URLEncoder.encode(grlcSpec.getExpandedQueryContent(), Charsets.UTF_8))));
394

395
                    req.setURI("/rdf4j-server/repositories/" + grlcSpec.getRepoName());
396
                    log.info("Forwarding apix request to /rdf4j-server/repositories/", grlcSpec.getRepoName());
397
                }
398
                return ProxyInterceptor.super.handleProxyRequest(context);
399
            }
400

401
            @Override
402
            @GeneratedFlagForDependentElements
403
            public Future<Void> handleProxyResponse(ProxyContext context) {
404
                log.info("Receiving apix response");
405
                ProxyResponse resp = context.response();
406
                resp.putHeader("Access-Control-Allow-Origin", "*");
407
                resp.putHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
408
                return ProxyInterceptor.super.handleProxyResponse(context);
409
            }
410

411
        });
412
        proxyRouter.route(HttpMethod.GET, "/apix/*").handler(ProxyHandler.create(grlcxProxy));
413

414
        proxyServer.requestHandler(req -> {
415
            applyGlobalHeaders(req.response());
416
            proxyRouter.handle(req);
417
        });
418
        proxyServer.listen(9393);
419

420
        proxyRouter.route("/api/*").handler(ProxyHandler.create(grlcProxy));
421
        proxyRouter.route("/static/*").handler(ProxyHandler.create(grlcProxy));
422

423
        // Periodic metrics update
424
        vertx.setPeriodic(1000, id -> collector.updateMetrics());
425

426

427
        new Thread(() -> {
428
            try {
429
                var status = StatusController.get().initialize();
430
                log.info("Current state: {}, last committed counter: {}", status.state, status.loadCounter);
431
                if (status.state == StatusController.State.LAUNCHING || status.state == StatusController.State.LOADING_INITIAL) {
432
                    // Do the initial nanopublication loading
433
                    StatusController.get().setLoadingInitial(status.loadCounter);
434
                    // Fall back to local nanopub loading if the local files are present
435
                    if (!LocalNanopubLoader.init()) {
436
                        JellyNanopubLoader.loadInitial(status.loadCounter);
437
                    } else {
438
                        log.info("Local nanopublication loading finished");
439
                    }
440
                    StatusController.get().setReady();
441
                } else {
442
                    log.info("Initial load is already done");
443
                    StatusController.get().setReady();
444
                }
445
            } catch (Exception ex) {
446
                log.info("Initial load failed, terminating...", ex);
447
                Runtime.getRuntime().exit(1);
448
            }
449

450
            // Start periodic nanopub loading
451
            log.info("Starting periodic nanopub loading...");
452
            var executor = Executors.newSingleThreadScheduledExecutor();
453
            executor.scheduleWithFixedDelay(
454
                    JellyNanopubLoader::loadUpdates,
455
                    JellyNanopubLoader.UPDATES_POLL_INTERVAL,
456
                    JellyNanopubLoader.UPDATES_POLL_INTERVAL,
457
                    TimeUnit.MILLISECONDS
458
            );
459
        }).start();
460

461
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
462
            try {
463
                log.info("Gracefully shutting down...");
464
                TripleStore.get().shutdownRepositories();
465
                vertx.close().toCompletionStage().toCompletableFuture().get(5, TimeUnit.SECONDS);
466
                log.info("Graceful shutdown completed");
467
            } catch (Exception ex) {
468
                log.info("Graceful shutdown failed", ex);
469
            }
470
        }));
471
    }
472

473
    private String getResourceAsString(String file) {
474
        InputStream is = getClass().getClassLoader().getResourceAsStream("com/knowledgepixels/query/" + file);
475
        try (Scanner s = new Scanner(is).useDelimiter("\\A")) {
476
            String fileContent = s.hasNext() ? s.next() : "";
477
            return fileContent;
478
        }
479
    }
480

481
    private static void handleRedirect(RoutingContext req, String path) {
482
        String queryString = "";
483
        if (!req.queryParam("query").isEmpty())
484
            queryString = "?query=" + URLEncoder.encode(req.queryParam("query").get(0), Charsets.UTF_8);
485
        if (req.queryParam("for-type").size() == 1) {
486
            String type = req.queryParam("for-type").get(0);
487
            req.response().putHeader("location", path + "/type/" + Utils.createHash(type) + queryString);
488
            req.response().setStatusCode(301).end();
489
        } else if (req.queryParam("for-pubkey").size() == 1) {
490
            String type = req.queryParam("for-pubkey").get(0);
491
            req.response().putHeader("location", path + "/pubkey/" + Utils.createHash(type) + queryString);
492
            req.response().setStatusCode(301).end();
493
        } else if (req.queryParam("for-user").size() == 1) {
494
            String type = req.queryParam("for-user").get(0);
495
            req.response().putHeader("location", path + "/user/" + Utils.createHash(type) + queryString);
496
            req.response().setStatusCode(301).end();
497
        }
498
    }
499

500
    /**
501
     * Apply headers to the response that should be present for all requests.
502
     *
503
     * @param response The response to which the headers should be applied.
504
     */
505
    private static void applyGlobalHeaders(HttpServerResponse response) {
506
        response.putHeader("Nanopub-Query-Status", StatusController.get().getState().state.toString());
507
    }
508
}
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