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

knowledgepixels / nanopub-query / 23632443330

27 Mar 2026 05:20AM UTC coverage: 66.744% (-0.3%) from 67.082%
23632443330

Pull #61

github

web-flow
Merge 59686a478 into 1927e9847
Pull Request #61: feat: preview unpublished nanopub queries via URL parameter

228 of 376 branches covered (60.64%)

Branch coverage included in aggregate %.

637 of 920 relevant lines covered (69.24%)

10.33 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 com.github.jsonldjava.shaded.com.google.common.base.Charsets;
4
import com.knowledgepixels.query.GrlcSpec.InvalidGrlcSpecException;
5
import io.micrometer.prometheus.PrometheusMeterRegistry;
6
import io.vertx.core.AbstractVerticle;
7
import io.vertx.core.Future;
8
import io.vertx.core.Promise;
9
import io.vertx.core.buffer.Buffer;
10
import io.vertx.core.http.*;
11
import io.vertx.ext.web.Router;
12
import io.vertx.ext.web.RoutingContext;
13
import io.vertx.ext.web.handler.CorsHandler;
14
import io.vertx.ext.web.handler.StaticHandler;
15
import io.vertx.ext.web.proxy.handler.ProxyHandler;
16
import io.vertx.httpproxy.*;
17
import io.vertx.micrometer.PrometheusScrapingHandler;
18
import io.vertx.micrometer.backends.BackendRegistries;
19
import org.eclipse.rdf4j.model.Value;
20
import org.slf4j.Logger;
21
import org.slf4j.LoggerFactory;
22

23
import java.io.InputStream;
24
import java.net.URLEncoder;
25
import java.util.ArrayList;
26
import java.util.Collections;
27
import java.util.List;
28
import java.util.Scanner;
29
import java.util.concurrent.Executors;
30
import java.util.concurrent.TimeUnit;
31

32
/**
33
 * Main verticle that coordinates the incoming HTTP requests.
34
 */
35
@GeneratedFlagForDependentElements
36
public class MainVerticle extends AbstractVerticle {
37

38
    private static String css = null;
39

40
    private static final Logger log = LoggerFactory.getLogger(MainVerticle.class);
41

42
    /**
43
     * Start the main verticle.
44
     *
45
     * @param startPromise the promise to complete when the verticle is started
46
     * @throws Exception if an error occurs during startup
47
     */
48
    @Override
49
    public void start(Promise<Void> startPromise) throws Exception {
50
        HttpClient httpClient = vertx.createHttpClient(
51
                new HttpClientOptions()
52
                        .setConnectTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_CONNECT_TIMEOUT", 1000))
53
                        .setIdleTimeoutUnit(TimeUnit.SECONDS)
54
                        .setIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60))
55
                        .setReadIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60))
56
                        .setWriteIdleTimeout(Utils.getEnvInt("NANOPUB_QUERY_VERTX_IDLE_TIMEOUT", 60)),
57
                new PoolOptions().setHttp1MaxSize(200).setHttp2MaxSize(200)
58
        );
59

60
        HttpServer proxyServer = vertx.createHttpServer(
61
                new HttpServerOptions().setMaxInitialLineLength(65536)
62
        );
63
        Router proxyRouter = Router.router(vertx);
64
        proxyRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*"));
65

66
        // Metrics
67
        final var metricsHttpServer = vertx.createHttpServer();
68
        final var metricsRouter = Router.router(vertx);
69
        metricsHttpServer.requestHandler(metricsRouter).listen(9394);
70

71
        final var metricsRegistry = (PrometheusMeterRegistry) BackendRegistries.getDefaultNow();
72
        final var collector = new MetricsCollector(metricsRegistry);
73
        metricsRouter.route("/metrics").handler(PrometheusScrapingHandler.create(metricsRegistry));
74
        // ----------
75
        // This part is only used if the redirection is not done through Nginx.
76
        // See nginx.conf and this bug report: https://github.com/eclipse-rdf4j/rdf4j/discussions/5120
77
        HttpProxy rdf4jProxy = HttpProxy.reverseProxy(httpClient);
78
        String proxy = Utils.getEnvString("RDF4J_PROXY_HOST", "rdf4j");
79
        int proxyPort = Utils.getEnvInt("RDF4J_PROXY_PORT", 8080);
80
        rdf4jProxy.origin(proxyPort, proxy);
81

82
        rdf4jProxy.addInterceptor(new ProxyInterceptor() {
×
83

84
            @Override
85
            @GeneratedFlagForDependentElements
86
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
87
                ProxyRequest request = context.request();
88
                request.setURI(request.getURI().replaceAll("/", "_").replaceFirst("^_repo_", "/rdf4j-server/repositories/"));
89
                // For later to try to get HTML tables out:
90
//                                if (request.headers().get("Accept") == null) {
91
//                                        request.putHeader("Accept", "text/html");
92
//                                }
93
//                                request.putHeader("Accept", "application/json");
94
                return ProxyInterceptor.super.handleProxyRequest(context);
95
            }
96

97
            @Override
98
            @GeneratedFlagForDependentElements
99
            public Future<Void> handleProxyResponse(ProxyContext context) {
100
                ProxyResponse resp = context.response();
101
                resp.putHeader("Access-Control-Allow-Origin", "*");
102
                resp.putHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
103
                // For later to try to get HTML tables out:
104
//                                String acceptHeader = context.request().headers().get("Accept");
105
//                                if (acceptHeader != null && acceptHeader.contains("text/html")) {
106
//                                        resp.putHeader("Content-Type", "text/html");
107
//                                        resp.setBody(Body.body(Buffer.buffer("<html><body><strong>test</strong></body></html>")));
108
//                                }
109
                return ProxyInterceptor.super.handleProxyResponse(context);
110
            }
111

112
        });
113
        // ----------
114

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

300
        // TODO This is no longer needed and can be removed at some point:
301
        proxyRouter.route(HttpMethod.GET, "/grlc-spec/*").handler(req -> {
302
            vertx.<String>executeBlocking(() -> {
303
                GrlcSpec gsp = new GrlcSpec(req.normalizedPath(), req.queryParams());
304
                return gsp.getSpec();
305
            }, false).onSuccess(spec -> {
306
                req.response().putHeader("content-type", "text/yaml").end(spec);
307
            }).onFailure(ex -> {
308
                if (ex instanceof InvalidGrlcSpecException) {
309
                    req.response().setStatusCode(400).end(ex.getMessage());
310
                } else {
311
                    req.response().setStatusCode(500).end("Unexpected error: " + ex.getMessage());
312
                }
313
            });
314
        });
315

316
        proxyRouter.route(HttpMethod.GET, "/openapi/spec/*").handler(req -> {
317
            vertx.<String>executeBlocking(() -> {
318
                OpenApiSpecPage osp = new OpenApiSpecPage(req.normalizedPath(), req.queryParams());
319
                return osp.getSpec();
320
            }, false).onSuccess(spec -> {
321
                req.response().putHeader("content-type", "text/yaml").end(spec);
322
            }).onFailure(ex -> {
323
                if (ex instanceof InvalidGrlcSpecException) {
324
                    req.response().setStatusCode(400).end("Invalid grlc API definition: " + ex.getMessage());
325
                } else {
326
                    req.response().setStatusCode(500).end("Unexpected error: " + ex.getMessage());
327
                }
328
            });
329
        });
330

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

333
        HttpProxy grlcxProxy = HttpProxy.reverseProxy(httpClient);
334
        grlcxProxy.origin(proxyPort, proxy);
335

336
        grlcxProxy.addInterceptor(new ProxyInterceptor() {
×
337

338
            @Override
339
            @GeneratedFlagForDependentElements
340
            public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
341
                final ProxyRequest req = context.request();
342
                final String apiPattern = "^/api/(RA[a-zA-Z0-9-_]{43})/([a-zA-Z0-9-_]+)([.]csv|[.]json|[.]srx)?([?].*)?$";
343
                if (req.getURI().matches(apiPattern)) {
344
                    try {
345
                        req.setMethod(HttpMethod.POST);
346
                        if (req.getURI().matches(".*[.]csv([?].*)?$")) {
347
                            req.putHeader("Accept", "text/csv");
348
                            req.setURI(req.getURI().replaceFirst("[.]csv([?].*)?$", "$1"));
349
                        } else if (req.getURI().matches(".*[.]json([?].*)?$")) {
350
                            req.putHeader("Accept", "application/json");
351
                            req.setURI(req.getURI().replaceFirst("[.]json([?].*)?$", "$1"));
352
                        } else if (req.getURI().matches(".*[.]srx([?].*)?$")) {
353
                            req.putHeader("Accept", "application/xml");
354
                            req.setURI(req.getURI().replaceFirst("[.]srx([?].*)?$", "$1"));
355
                        }
356
                        GrlcSpec grlcSpec = new GrlcSpec(req.getURI(), req.proxiedRequest().params());
357

358
                        // Variant 1:
359
                        req.putHeader("Content-Type", "application/sparql-query");
360
                        req.setBody(Body.body(Buffer.buffer(grlcSpec.expandQuery())));
361
                        // Variant 2:
362
                        //req.putHeader("Content-Type", "application/x-www-form-urlencoded");
363
                        //req.setBody(Body.body(Buffer.buffer("query=" + URLEncoder.encode(grlcSpec.getExpandedQueryContent(), Charsets.UTF_8))));
364

365
                        req.setURI("/rdf4j-server/repositories/" + grlcSpec.getRepoName());
366
                        log.info("Forwarding apix request to /rdf4j-server/repositories/", grlcSpec.getRepoName());
367
                    } catch (InvalidGrlcSpecException ex) {
368
                        return Future.succeededFuture(context.request()
369
                                .response()
370
                                .setStatusCode(400)
371
                                .putHeader("Content-Type", "text/plain")
372
                                .setBody(Body.body(Buffer.buffer("Bad request: " + ex.getMessage()))));
373
                    } catch (Exception ex) {
374
                        return Future.succeededFuture(context.request()
375
                                .response()
376
                                .setStatusCode(500)
377
                                .putHeader("Content-Type", "text/plain")
378
                                .setBody(Body.body(Buffer.buffer("Unexpected error: " + ex.getMessage()))));
379
                    }
380
                }
381
                return ProxyInterceptor.super.handleProxyRequest(context);
382
            }
383

384
            @Override
385
            @GeneratedFlagForDependentElements
386
            public Future<Void> handleProxyResponse(ProxyContext context) {
387
                log.info("Receiving api response");
388
                ProxyResponse resp = context.response();
389
                resp.putHeader("Access-Control-Allow-Origin", "*");
390
                resp.putHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
391
                resp.putHeader("Content-Disposition", "inline");
392
                return ProxyInterceptor.super.handleProxyResponse(context);
393
            }
394

395
        });
396
        proxyRouter.route(HttpMethod.GET, "/api/*").handler(ProxyHandler.create(grlcxProxy));
397

398
        proxyServer.requestHandler(req -> {
399
            applyGlobalHeaders(req.response());
400
            proxyRouter.handle(req);
401
        });
402
        proxyServer.listen(9393);
403

404
        // Periodic metrics update
405
        vertx.setPeriodic(1000, id -> collector.updateMetrics());
406

407

408
        new Thread(() -> {
409
            try {
410
                var status = StatusController.get().initialize();
411
                log.info("Current state: {}, last committed counter: {}", status.state, status.loadCounter);
412
                // Restore or fetch the registry setup ID
413
                Long storedSetupId = StatusController.get().getRegistrySetupId();
414
                if (storedSetupId != null) {
415
                    JellyNanopubLoader.setLastKnownSetupId(storedSetupId);
416
                    log.info("Restored registry setupId: {}", storedSetupId);
417
                } else {
418
                    try {
419
                        var metadata = JellyNanopubLoader.fetchRegistryMetadata();
420
                        JellyNanopubLoader.setLastKnownSetupId(metadata.setupId());
421
                        if (metadata.setupId() != null) {
422
                            StatusController.get().setRegistrySetupId(metadata.setupId());
423
                            log.info("Fetched initial registry setupId: {}", metadata.setupId());
424
                        }
425
                    } catch (Exception e) {
426
                        log.warn("Could not fetch initial registry setupId", e);
427
                    }
428
                }
429
                if (status.state == StatusController.State.LAUNCHING || status.state == StatusController.State.LOADING_INITIAL) {
430
                    // Do the initial nanopublication loading
431
                    StatusController.get().setLoadingInitial(status.loadCounter);
432
                    // Fall back to local nanopub loading if the local files are present
433
                    if (!LocalNanopubLoader.init()) {
434
                        JellyNanopubLoader.loadInitial(status.loadCounter);
435
                    } else {
436
                        log.info("Local nanopublication loading finished");
437
                    }
438
                    StatusController.get().setReady();
439
                } else {
440
                    log.info("Initial load is already done");
441
                    StatusController.get().setReady();
442
                }
443
            } catch (Exception ex) {
444
                log.info("Initial load failed, terminating...", ex);
445
                Runtime.getRuntime().exit(1);
446
            }
447

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

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

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

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

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