Skip to content

refine-jabsrv #13044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fa9bb0d
Execute only one test a time
koppor May 1, 2025
39f9ff6
Merge `origin/main` into `refine-http-api-tests`
koppor May 1, 2025
d1d4169
Fix server start
koppor May 1, 2025
b2e0997
Move Chocolate.bib to main resources
koppor May 1, 2025
8260609
Move rest-api.http to correct path
koppor May 1, 2025
7ee2017
Fix native-access-warning
koppor May 1, 2025
18450a4
Enable exception output in cae of error
koppor May 1, 2025
9d53e7c
Fix :jabsrv:run
koppor May 1, 2025
710b143
Fix passing of available libraries
koppor May 1, 2025
7ec9804
Use Chocolate.bib
koppor May 2, 2025
4639440
Minor final fixes
koppor May 2, 2025
baf3538
Fix checkstyle
koppor May 2, 2025
da416d2
Result of OpenRewrite rewriteRun
koppor May 2, 2025
bbf8a7c
Fix test
koppor May 2, 2025
9d7479a
Get test starting again
koppor May 2, 2025
926f318
Inject Gson, too
koppor May 2, 2025
88874ef
Enable full exception logging
koppor May 2, 2025
f906f28
Add link to test-logger
koppor May 2, 2025
6a3e720
Fix checkstyle
koppor May 2, 2025
ba364d2
Add GlobalExceptionMapper
koppor May 2, 2025
1d207c8
Try to fix logging
koppor May 2, 2025
b5b4d8c
Try to fix binding
koppor May 2, 2025
1cee088
Fix path
koppor May 2, 2025
c7266eb
Fix spaces
koppor May 2, 2025
d8198d0
Fix spaces - the other way
koppor May 2, 2025
68f2e91
Fix "
koppor May 2, 2025
bafa751
Fix openrewrite
koppor May 2, 2025
23df77a
Update sub modules
koppor May 2, 2025
548e0b5
Fix List modification
koppor May 2, 2025
2c7758c
Compile fix
koppor May 2, 2025
b719a3a
Improve start output
koppor May 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ requirementTracing {

subprojects {
plugins.apply("checkstyle")

plugins.apply("com.github.andygoossens.modernizer")

// Hint from https://stackoverflow.com/a/46533151/873282
plugins.apply("com.adarshr.test-logger")

plugins.apply("com.github.koppor.gradle-modules-plugin")

checkstyle {
Expand Down
31 changes: 30 additions & 1 deletion jabsrv/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat

plugins {
id("buildlogic.java-common-conventions")

Expand All @@ -8,13 +10,17 @@ plugins {

application{
mainClass.set("org.jabref.http.server.Server")
mainModule.set("org.jabref.jabsrv")

applicationDefaultJvmArgs = listOf(
"--enable-native-access=com.sun.jna"
)
}

dependencies {
implementation(project(":jablib"))

implementation("org.slf4j:slf4j-api:2.0.17")
implementation("org.tinylog:tinylog-api:2.7.0")
implementation("org.tinylog:slf4j-tinylog:2.7.0")
implementation("org.tinylog:tinylog-impl:2.7.0")
// route all requests to java.util.logging to SLF4J (which in turn routes to tinylog)
Expand All @@ -31,6 +37,7 @@ dependencies {
// Injection framework
implementation("org.glassfish.jersey.inject:jersey-hk2:3.1.10")
implementation("org.glassfish.hk2:hk2-api:3.1.1")
implementation("org.glassfish.hk2:hk2-utils:3.1.1")

// testImplementation("org.glassfish.hk2:hk2-testing:3.0.4")
// implementation("org.glassfish.hk2:hk2-testing-jersey:3.0.4")
Expand All @@ -39,7 +46,11 @@ dependencies {
// HTTP server
// implementation("org.glassfish.jersey.containers:jersey-container-netty-http:3.1.1")
implementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.10")
implementation("org.glassfish.grizzly:grizzly-http-server:4.0.2")
implementation("org.glassfish.grizzly:grizzly-framework:4.0.2")
testImplementation("org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:3.1.10")
implementation("jakarta.validation:jakarta.validation-api:3.1.1")
implementation("org.hibernate.validator:hibernate-validator:8.0.2.Final")

implementation("com.konghq:unirest-modules-gson:4.4.6")

Expand Down Expand Up @@ -69,3 +80,21 @@ javafx {
// because of afterburner.fx
modules = listOf("javafx.base", "javafx.controls", "javafx.fxml")
}

tasks.test {
testLogging {
// set options for log level LIFECYCLE
events("FAILED")
exceptionFormat = TestExceptionFormat.FULL
}
maxParallelForks = 1
}

tasks.named<JavaExec>("run") {
doFirst {
application.applicationDefaultJvmArgs =
listOf(
"--enable-native-access=com.sun.jna"
)
}
}
17 changes: 13 additions & 4 deletions jabsrv/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
module org.jabref.jabsrv {
exports org.jabref.http.server;
opens org.jabref.http.server
to org.glassfish.hk2.utilities,
org.glassfish.hk2.locator;

exports org.jabref.http.dto to com.google.gson, org.glassfish.hk2.locator;

opens org.jabref.http.server to org.glassfish.hk2.utilities, org.glassfish.hk2.locator;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'opens' directive is reformatted without adding new statements. Reformatting should only occur with new statements to avoid unnecessary changes.


requires org.jabref.jablib;

requires org.slf4j;
requires jul.to.slf4j;
requires org.apache.logging.log4j.to.slf4j;
requires org.tinylog.api;
requires org.tinylog.api.slf4j;
requires org.tinylog.impl;

Expand All @@ -29,8 +29,14 @@
// requires org.glassfish.jaxb.runtime;

requires org.glassfish.grizzly;
requires org.glassfish.grizzly.http;
requires org.glassfish.grizzly.http.server;
requires jakarta.validation;
requires jakarta.ws.rs;

requires jersey.common;


requires net.harawata.appdirs;
requires com.sun.jna;
requires com.sun.jna.platform;
Expand All @@ -39,4 +45,7 @@
requires citeproc.java;

requires transitive org.jspecify;
requires java.logging;
requires jersey.container.grizzly2.http;
requires jersey.server;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jabref.http.dto;

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Provider
public class GlobalExceptionMapper implements ExceptionMapper<Throwable> {

private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionMapper.class.getName());

@Override
public Response toResponse(Throwable exception) {
LOGGER.error("Unhandled exception on server", exception);
return Response.serverError().entity("Internal Server Error").build();
}
}
31 changes: 0 additions & 31 deletions jabsrv/src/main/java/org/jabref/http/server/Application.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.List;

import org.jabref.logic.preferences.CliPreferences;
import org.jabref.http.server.services.FilesToServe;
import org.jabref.logic.util.io.BackupFileUtil;

import com.google.gson.Gson;
Expand All @@ -15,14 +15,17 @@
@Path("libraries")
public class LibrariesResource {
@Inject
CliPreferences preferences;
private FilesToServe filesToServe;

@Inject
private Gson gson;

@GET
@Produces(MediaType.APPLICATION_JSON)
public String get() {
List<String> fileNamesWithUniqueSuffix = preferences.getLastFilesOpenedPreferences().getLastFilesOpened().stream()
List<String> fileNamesWithUniqueSuffix = filesToServe.getFilesToServe().stream()
.map(p -> p.getFileName() + "-" + BackupFileUtil.getUniqueFilePrefix(p))
.toList();
return new Gson().toJson(fileNamesWithUniqueSuffix);
return gson.toJson(fileNamesWithUniqueSuffix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.jabref.http.JabrefMediaType;
import org.jabref.http.dto.BibEntryDTO;
import org.jabref.http.server.services.FilesToServe;
import org.jabref.logic.citationstyle.JabRefItemDataProvider;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.importer.fileformat.BibtexImporter;
Expand Down Expand Up @@ -36,6 +37,9 @@ public class LibraryResource {
@Inject
CliPreferences preferences;

@Inject
FilesToServe filesToServe;

@Inject
Gson gson;

Expand Down Expand Up @@ -89,7 +93,7 @@ public Response getBibtex(@PathParam("id") String id) {
}

private java.nio.file.Path getLibraryPath(String id) {
return preferences.getLastFilesOpenedPreferences().getLastFilesOpened()
return filesToServe.getFilesToServe()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method getLibraryPath uses a filter to find a file path, but it throws a NotFoundException if no match is found. This is using exceptions for control flow, which is not recommended.

.stream()
.filter(p -> (p.getFileName() + "-" + BackupFileUtil.getUniqueFilePrefix(p)).equals(id))
.findAny()
Expand Down
104 changes: 65 additions & 39 deletions jabsrv/src/main/java/org/jabref/http/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.net.ssl.SSLContext;

import org.jabref.architecture.AllowedToUseStandardStreams;
import org.jabref.http.dto.GlobalExceptionMapper;
import org.jabref.http.dto.GsonFactory;
import org.jabref.http.server.services.FilesToServe;
import org.jabref.logic.os.OS;
import org.jabref.logic.preferences.JabRefCliPreferences;

import jakarta.ws.rs.SeBootstrap;
import net.harawata.appdirs.AppDirsFactory;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -24,7 +33,7 @@
public class Server {
private static final Logger LOGGER = LoggerFactory.getLogger(Server.class);

private static SeBootstrap.Instance serverInstance;
private static final String BASE_URI = "http://localhost:6050/";

/**
* Starts an http server serving the last files opened in JabRef<br>
Expand All @@ -34,7 +43,7 @@ public static void main(final String[] args) throws InterruptedException {
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();

final List<Path> lastFilesOpened = new ArrayList<>(); // JabRefCliPreferences.getInstance().getGuiPreferences().getLastFilesOpened();
final List<Path> filesToServe = JabRefCliPreferences.getInstance().getLastFilesOpenedPreferences().getLastFilesOpened().stream().collect(Collectors.toCollection(ArrayList::new));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of Collectors.toCollection(ArrayList::new) is unnecessary. The Stream API provides a simpler toList() method that should be used for better readability and maintainability.


// The server serves the last opened files (see org.jabref.http.server.LibraryResource.getLibraryPath)
// In a testing environment, this might be difficult to handle
Expand All @@ -44,63 +53,80 @@ public static void main(final String[] args) throws InterruptedException {
List<Path> filesToAdd = Arrays.stream(args)
.map(Path::of)
.filter(Files::exists)
.filter(path -> !lastFilesOpened.contains(path))
.filter(path -> !filesToServe.contains(path))
.toList();

LOGGER.debug("Adding following files to the list of opened libraries: {}", filesToAdd);

// add the files in the front of the last opened libraries
for (Path path : filesToAdd.reversed()) {
lastFilesOpened.addFirst(path);
filesToServe.addFirst(path);
}
}

if (lastFilesOpened.isEmpty()) {
LOGGER.debug("still no library available to serve, serve the demo library");
if (filesToServe.isEmpty()) {
LOGGER.debug("Still no library available to serve, serving the demo library...");
// Server.class.getResource("...") is always null here, thus trying relative path
// Path bibPath = Path.of(Server.class.getResource("http-server-demo.bib").toURI());
Path bibPath = Path.of("src/main/resources/org/jabref/http/server/http-server-demo.bib").toAbsolutePath();
LOGGER.debug("Location of demo library: {}", bibPath);
lastFilesOpened.add(bibPath);
filesToServe.add(bibPath);
}

LOGGER.debug("Libraries served: {}", lastFilesOpened);
LOGGER.debug("Libraries to serve: {}", filesToServe);

Server.startServer();
FilesToServe filesToServeService = new FilesToServe();
filesToServeService.setFilesToServe(filesToServe);

Server.startServer(filesToServeService);

// Keep the http server running until user kills the process (e.g., presses Ctrl+C)
Thread.currentThread().join();
}

private static void startServer() {
SeBootstrap.Configuration configuration;
if (!sslCertExists()) {
LOGGER.info("SSL certificate not found. Server starts in non-SSL mode.");
configuration = SeBootstrap.Configuration.builder()
.protocol("HTTP")
.port(6050)
.build();
} else {
LOGGER.info("SSL certificate found. Server starts in SSL mode.");
SSLContext sslContext = getSslContext();
configuration = SeBootstrap.Configuration.builder()
.sslContext(sslContext)
.protocol("HTTPS")
.port(6051)
.build();
}
public static HttpServer startServer(ServiceLocator serviceLocator) {
// see https://stackoverflow.com/a/33794265/873282
final ResourceConfig resourceConfig = new ResourceConfig();
// TODO: Add SSL
resourceConfig.register(LibrariesResource.class);
resourceConfig.register(LibraryResource.class);
resourceConfig.register(CORSFilter.class);
resourceConfig.register(GlobalExceptionMapper.class);

LOGGER.debug("Starting server...");
SeBootstrap.start(Application.class, configuration).thenAccept(instance -> {
LOGGER.debug("Server started.");
instance.stopOnShutdown(stopResult ->
LOGGER.debug("Stop result: {} [Native stop result: {}].", stopResult,
stopResult.unwrap(Object.class)));
final URI uri = instance.configuration().baseUri();
LOGGER.debug("Instance {} running at {} [Native handle: {}].%n", instance, uri,
instance.unwrap(Object.class));
LOGGER.debug("Send SIGKILL to shutdown.");
serverInstance = instance;
});
final HttpServer httpServer =
GrizzlyHttpServerFactory
.createHttpServer(URI.create(BASE_URI), resourceConfig, serviceLocator);
return httpServer;
}

private static void startServer(FilesToServe filesToServe) {
ServiceLocator serviceLocator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
ServiceLocatorUtilities.addFactoryConstants(serviceLocator, new GsonFactory());
ServiceLocatorUtilities.addFactoryConstants(serviceLocator, new PreferencesFactory());
ServiceLocatorUtilities.addOneConstant(serviceLocator, filesToServe);

try {
final HttpServer httpServer = startServer(serviceLocator);

// add jvm shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
System.out.println("Shutting down jabsrv...");
httpServer.shutdownNow();
System.out.println("Done, exit.");
} catch (Exception e) {
LOGGER.error("Could not shut down server", e);
}
}));

System.out.println("JabSrv started.");
System.out.println("Stop JabSrv using Ctrl+C");

Thread.currentThread().join();
} catch (InterruptedException ex) {
LOGGER.error("Could not start down server", ex);
}
}

private static boolean sslCertExists() {
Expand Down Expand Up @@ -132,6 +158,6 @@ private static Path getSslCert() {
}

static void stopServer() {
serverInstance.stop();
// serverInstance.stop();
}
}
Loading