From 7c1df88f789051e85c156e161647ca60f36e604c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 9 Apr 2023 00:28:47 +0200 Subject: [PATCH 01/22] Introduces test http server - Resources - /libraries - /libraries/{id} (both (embedded) BibTeX as string and CSL data) - Documentation / code style - Refined BibEntry class (JavaDoc, builder) - Comments to InternalField - move StyleTester to "test" module - package ...testutils/interactive... - Makes use of Jersey, Grizzly - Makes use of HK2 as dependency injection framework - Introduces "application/x-bibtex-library-csl+json" mimetype - Preparation for client/server sync (BibEntryDTO) - Minor - Made class "JabRefItemDataProvider" more visible - Encoding of a .bib file can now be asked for externally - Resorts modle-info.java - Fixes typo in NetworkTabViewModel - Installs SLF4J logging router: If a tool uses java commons logging, tinylog now also handles these logs --- .gitattributes | 2 + build.gradle | 20 ++ .../0027-http-return-bibtex-string.md | 53 +++++ src/main/java/module-info.java | 72 +++--- src/main/java/org/jabref/cli/Launcher.java | 21 +- .../network/NetworkTabViewModel.java | 2 +- src/main/java/org/jabref/http/MediaType.java | 6 + .../java/org/jabref/http/dto/BibEntryDTO.java | 71 ++++++ .../java/org/jabref/http/dto/GsonFactory.java | 18 ++ .../org/jabref/http/server/Application.java | 32 +++ .../org/jabref/http/server/CORSFilter.java | 23 ++ .../jabref/http/server/LibrariesResource.java | 29 +++ .../jabref/http/server/LibraryResource.java | 101 +++++++++ .../org/jabref/http/server/RootResource.java | 22 ++ .../java/org/jabref/http/server/Server.java | 65 ++++++ .../logic/citationstyle/CSLAdapter.java | 177 --------------- .../citationstyle/JabRefItemDataProvider.java | 212 ++++++++++++++++++ .../importer/fileformat/BibtexImporter.java | 53 +++-- .../importer/fileformat/BibtexParser.java | 1 - .../jabref/logic/net/ssl/SSLCertificate.java | 3 +- .../logic/net/ssl/TrustStoreManager.java | 2 +- .../java/org/jabref/model/entry/BibEntry.java | 77 ++++++- .../model/entry/SharedBibEntryData.java | 23 +- .../model/entry/field/InternalField.java | 41 +++- .../jabref/preferences/JabRefPreferences.java | 4 + .../preferences/PreferenceServiceFactory.java | 14 ++ .../preferences/PreferencesService.java | 2 + .../jabref/gui/edit/CopyMoreActionTest.java | 6 +- .../http/server/LibrariesResourceTest.java | 33 +++ .../http/server/LibraryResourceTest.java | 38 ++++ .../org/jabref/http/server/ServerTest.java | 92 ++++++++ .../org/jabref/http/server/TestBibFile.java | 18 ++ .../org/jabref/http/server/TestServer.java | 14 ++ .../jabref/http/server/mwessl/GServer.java | 66 ++++++ .../http/server/mwessl/SimpleHttpHandler.java | 16 ++ .../http/server/mwessl/package-info.java | 8 + .../JabRefItemDataProviderTest.java | 49 ++++ .../testutils/interactive/http/rest-api.http | 35 +++ .../interactive}/styletester/StyleTester.fxml | 2 +- .../styletester/StyleTesterMain.java | 2 +- .../styletester/StyleTesterView.java | 2 +- .../http/server/general-server-test.bib | 7 + 42 files changed, 1284 insertions(+), 250 deletions(-) create mode 100644 docs/decisions/0027-http-return-bibtex-string.md create mode 100644 src/main/java/org/jabref/http/MediaType.java create mode 100644 src/main/java/org/jabref/http/dto/BibEntryDTO.java create mode 100644 src/main/java/org/jabref/http/dto/GsonFactory.java create mode 100644 src/main/java/org/jabref/http/server/Application.java create mode 100644 src/main/java/org/jabref/http/server/CORSFilter.java create mode 100644 src/main/java/org/jabref/http/server/LibrariesResource.java create mode 100644 src/main/java/org/jabref/http/server/LibraryResource.java create mode 100644 src/main/java/org/jabref/http/server/RootResource.java create mode 100644 src/main/java/org/jabref/http/server/Server.java create mode 100644 src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java create mode 100644 src/main/java/org/jabref/preferences/PreferenceServiceFactory.java create mode 100644 src/test/java/org/jabref/http/server/LibrariesResourceTest.java create mode 100644 src/test/java/org/jabref/http/server/LibraryResourceTest.java create mode 100644 src/test/java/org/jabref/http/server/ServerTest.java create mode 100644 src/test/java/org/jabref/http/server/TestBibFile.java create mode 100644 src/test/java/org/jabref/http/server/TestServer.java create mode 100644 src/test/java/org/jabref/http/server/mwessl/GServer.java create mode 100644 src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java create mode 100644 src/test/java/org/jabref/http/server/mwessl/package-info.java create mode 100644 src/test/java/org/jabref/logic/citationstyle/JabRefItemDataProviderTest.java create mode 100644 src/test/java/org/jabref/testutils/interactive/http/rest-api.http rename src/{main/java/org/jabref => test/java/org/jabref/testutils/interactive}/styletester/StyleTester.fxml (99%) rename src/{main/java/org/jabref => test/java/org/jabref/testutils/interactive}/styletester/StyleTesterMain.java (96%) rename src/{main/java/org/jabref => test/java/org/jabref/testutils/interactive}/styletester/StyleTesterView.java (96%) create mode 100644 src/test/resources/org/jabref/http/server/general-server-test.bib diff --git a/.gitattributes b/.gitattributes index 18bbb30fc6d..9a7729617c8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,6 +12,8 @@ gradlew text eol=lf # .bib files have to be written using OS specific line endings to enable our tests working *.bib text !eol +# Exception: The files used for the http server test - they should have linux line endings +src/test/resources/org/jabref/http/server/*.bib text eol=lf # Citavi needs to be LF line ending # This overwrites the setting of "*.bib" diff --git a/build.gradle b/build.gradle index 9d8f1e7bff2..bbc02475859 100644 --- a/build.gradle +++ b/build.gradle @@ -179,6 +179,8 @@ dependencies { implementation "org.tinylog:tinylog-api:2.6.1" implementation "org.tinylog:slf4j-tinylog:2.6.1" implementation "org.tinylog:tinylog-impl:2.6.1" + // route all requests to java.util.logging to SLF4J (which in turn routes to tinylog) + implementation 'org.slf4j:jul-to-slf4j:2.0.7' implementation 'de.undercouch:citeproc-java:3.0.0-beta.2' @@ -199,6 +201,24 @@ dependencies { implementation group: 'net.harawata', name: 'appdirs', version: '1.2.1' + // JAX-RS implemented by Jersey + // API + implementation 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' + // Implementation of the API + implementation 'org.glassfish.jersey.core:jersey-server:3.1.1' + // injection framework + implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.1' + implementation 'org.glassfish.hk2:hk2-api:2.6.1' + // testImplementation 'org.glassfish.hk2:hk2-testing:3.0.4' + // implementation 'org.glassfish.hk2:hk2-testing-jersey:3.0.4' + // testImplementation 'org.glassfish.hk2:hk2-junitrunner:3.0.4' + // 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.1' + testImplementation 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:3.1.1' + // Allow objects "magically" to be mapped to JSON using GSON + // implementation 'org.glassfish.jersey.media:jersey-media-json-gson:3.1.1' + testImplementation 'io.github.classgraph:classgraph:4.8.157' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' testImplementation 'org.junit.platform:junit-platform-launcher:1.9.2' diff --git a/docs/decisions/0027-http-return-bibtex-string.md b/docs/decisions/0027-http-return-bibtex-string.md new file mode 100644 index 00000000000..29658c423a3 --- /dev/null +++ b/docs/decisions/0027-http-return-bibtex-string.md @@ -0,0 +1,53 @@ +--- +nav_order: 27 +parent: Decision Records +--- + + +# Return BibTeX string and CSL Item JSON in the API + +## Context and Problem Statement + +In the context of an http server, when a http client `GETs` a JSON data structure containing BibTeX data, which format should that have? + +## Considered Options + +* Offer both, BibTeX string and CSL JSON +* Return BibTeX as is as string +* Convert BibTeX to JSON + +## Decision Outcome + +Chosen option: "Offer both, BibTeX string and CSL JSON", because there are many browser libraries out there being able to parse BibTeX. Thus, we don't need to convert it. + +## Pros and Cons of the Options + +### Offer both, BibTeX string and CSL JSON + +- Good, because this follows "Backend for Frontend" +- Good, because Word Addin works seamless with the data provided (and does not need another dependency) +- Good, because other clients can work with BibTeX data +- Bad, because two serializations have to be kept + +### Return BibTeX as is as string + +- Good, because we don't need to think about any conversion +- Bad, because it is unclear how to ship BibTeX data where the entry is dependent on +- Bad, because client needs add additional parsing logic + +### Convert BibTeX to JSON + +More thought has to be done when converting to JSON. +There seems to be a JSON format from [@citation-js/plugin-bibtex](https://www.npmjs.com/package/@citation-js/plugin-bibtex). +We could do an additional self-made JSON format, but this increases the number of available JSON serializations for BibTeX. + +- Good, because it could flatten BibTeX data (example: `author = first # " and " # second`) +- Bad, because conversion is difficult in BibTeX special cases. For instance, if Strings are used (example: `author = first # " and " # second`) and one doesn't want to flatten ("normalize") this. + +## More Information + +Existing JavaScript BibTeX libraries: + +* [bibtex-js](https://github.com/digitalheir/bibtex-js) +* [bibtexParseJS](https://github.com/ORCID/bibtexParseJs) +* [@citation-js/plugin-bibtex](https://www.npmjs.com/package/@citation-js/plugin-bibtex) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 475aa85f61d..fd13ffaaf17 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -15,6 +15,9 @@ requires afterburner.fx; requires com.jfoenix; requires de.saxsys.mvvmfx; + requires reactfx; + requires de.saxsys.mvvmfx.validation; + requires org.fxmisc.flowless; requires org.kordamp.ikonli.core; requires org.kordamp.ikonli.javafx; @@ -38,6 +41,7 @@ // Logging requires org.slf4j; + requires jul.to.slf4j; requires org.tinylog.api; requires org.tinylog.api.slf4j; requires org.tinylog.impl; @@ -46,47 +50,58 @@ with org.jabref.gui.logging.GuiWriter, org.jabref.gui.logging.ApplicationInsightsWriter; - // Preferences and XML requires java.prefs; + + // Annotations (@PostConstruct) + requires jakarta.annotation; + requires jakarta.inject; + + // http server and client exchange + requires java.net.http; + requires jakarta.ws.rs; + requires grizzly.framework; + + // data mapping requires jakarta.xml.bind; + requires jdk.xml.dom; + requires com.google.gson; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; + requires com.fasterxml.jackson.datatype.jsr310; // needs to be loaded here as it's otherwise not found at runtime requires org.glassfish.jaxb.runtime; - requires jdk.xml.dom; - // Annotations (@PostConstruct) - requires jakarta.annotation; + // dependency injection using HK2 + requires org.glassfish.hk2.api; // Microsoft application insights requires applicationinsights.core; requires applicationinsights.logging.log4j2; - // Libre Office - requires org.libreoffice.uno; - - // Other modules - requires com.google.common; - requires jakarta.inject; - requires reactfx; - requires commons.cli; - requires com.github.tomtung.latex2unicode; - requires fastparse; - requires jbibtex; - requires citeproc.java; - requires de.saxsys.mvvmfx.validation; - requires com.google.gson; + // http clients requires unirest.java; requires org.apache.httpcomponents.httpclient; requires org.jsoup; - requires org.apache.commons.csv; - requires io.github.javadiffutils; - requires java.string.similarity; + + // SQL databases requires ojdbc10; requires org.postgresql.jdbc; requires org.mariadb.jdbc; uses org.mariadb.jdbc.credential.CredentialPlugin; + + // Apache Commons and other (similar) helper libraries + requires commons.cli; + requires org.apache.commons.csv; requires org.apache.commons.lang3; - requires org.antlr.antlr4.runtime; - requires org.fxmisc.flowless; + requires com.google.common; + requires io.github.javadiffutils; + requires java.string.similarity; + + requires com.github.tomtung.latex2unicode; + requires fastparse; + + requires jbibtex; + requires citeproc.java; requires pdfbox; requires xmpbox; @@ -95,21 +110,16 @@ requires flexmark; requires flexmark.util.ast; requires flexmark.util.data; - requires com.h2database.mvstore; // fulltext search requires org.apache.lucene.core; // In case the version is updated, please also adapt SearchFieldConstants#VERSION to the newly used version uses org.apache.lucene.codecs.lucene94.Lucene94Codec; - requires org.apache.lucene.queryparser; uses org.apache.lucene.queryparser.classic.MultiFieldQueryParser; requires org.apache.lucene.analysis.common; requires org.apache.lucene.highlighter; - requires com.fasterxml.jackson.databind; - requires com.fasterxml.jackson.dataformat.yaml; - requires com.fasterxml.jackson.datatype.jsr310; requires net.harawata.appdirs; requires com.sun.jna; requires com.sun.jna.platform; @@ -117,4 +127,10 @@ requires org.eclipse.jgit; uses org.eclipse.jgit.transport.SshSessionFactory; uses org.eclipse.jgit.lib.GpgSigner; + + // other libraries + requires com.h2database.mvstore; + requires org.antlr.antlr4.runtime; + requires org.libreoffice.uno; + } diff --git a/src/main/java/org/jabref/cli/Launcher.java b/src/main/java/org/jabref/cli/Launcher.java index bc9dd85d26e..0a87bfe5178 100644 --- a/src/main/java/org/jabref/cli/Launcher.java +++ b/src/main/java/org/jabref/cli/Launcher.java @@ -33,6 +33,7 @@ import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; import org.tinylog.configuration.Configuration; /** @@ -47,6 +48,7 @@ public class Launcher { private static String[] ARGUMENTS; public static void main(String[] args) { + routeLoggingToSlf4J(); ARGUMENTS = args; addLogToDisk(); try { @@ -85,6 +87,11 @@ public static void main(String[] args) { } } + private static void routeLoggingToSlf4J() { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } + /** * This needs to be called as early as possible. After the first log write, it * is not possible to alter @@ -93,10 +100,10 @@ public static void main(String[] args) { private static void addLogToDisk() { Path directory = Path.of(AppDirsFactory.getInstance() .getUserDataDir( - OS.APP_DIR_APP_NAME, - "logs", - OS.APP_DIR_APP_AUTHOR)) - .resolve(new BuildInfo().version.toString()); + OS.APP_DIR_APP_NAME, + "logs", + OS.APP_DIR_APP_AUTHOR)) + .resolve(new BuildInfo().version.toString()); try { Files.createDirectories(directory); } catch (IOException e) { @@ -187,9 +194,9 @@ private static void clearOldSearchIndices() { && !path.equals(currentIndexPath)) { LOGGER.info("Deleting out-of-date fulltext search index at {}.", path); Files.walk(path) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); } } } catch (IOException e) { diff --git a/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java b/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java index 8af629de4cf..9e74c55722b 100644 --- a/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java @@ -376,7 +376,7 @@ public void addCertificateFile() { .build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(certPath -> SSLCertificate.fromPath(certPath).ifPresent(sslCertificate -> { - if (!trustStoreManager.isCertificateExist(formatCustomAlias(sslCertificate.getSHA256Thumbprint()))) { + if (!trustStoreManager.certificateExists(formatCustomAlias(sslCertificate.getSHA256Thumbprint()))) { customCertificateListProperty.add(CustomCertificateViewModel.fromSSLCertificate(sslCertificate) .setPath(certPath.toAbsolutePath().toString())); } else { diff --git a/src/main/java/org/jabref/http/MediaType.java b/src/main/java/org/jabref/http/MediaType.java new file mode 100644 index 00000000000..b37297ebd71 --- /dev/null +++ b/src/main/java/org/jabref/http/MediaType.java @@ -0,0 +1,6 @@ +package org.jabref.http; + +public class MediaType { + public static final String BIBTEX = "application/x-bibtex"; + public static final String JSON_CSL_ITEM = "application/x-bibtex-library-csl+json"; +} diff --git a/src/main/java/org/jabref/http/dto/BibEntryDTO.java b/src/main/java/org/jabref/http/dto/BibEntryDTO.java new file mode 100644 index 00000000000..a0d20d4353e --- /dev/null +++ b/src/main/java/org/jabref/http/dto/BibEntryDTO.java @@ -0,0 +1,71 @@ +package org.jabref.http.dto; + +import java.io.IOException; +import java.io.StringWriter; + +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.SharedBibEntryData; + +import com.google.common.base.MoreObjects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The data transfer object (DTO) for an BibEntry + * + * @param sharingMetadata the data used for sharing + * @param userComments the comments before the BibTeX entry + * @param citationKey the citation key (duplicated from BibEntry to ease processing by the client) + * @param bibtex the BibEntry as BibTeX string (see ADR-0027 for more information, why we don't use a HashMap / JSON) + */ +public record BibEntryDTO(SharedBibEntryData sharingMetadata, String userComments, String citationKey, String bibtex) implements Comparable { + + public static final Logger LOGGER = LoggerFactory.getLogger(BibEntryDTO.class); + + public BibEntryDTO(BibEntry bibEntry, BibDatabaseMode bibDatabaseMode, FieldWriterPreferences fieldWriterPreferences, BibEntryTypesManager bibEntryTypesManager) { + this(bibEntry.getSharedBibEntryData(), + bibEntry.getUserComments(), + bibEntry.getCitationKey().orElse(""), + convertToString(bibEntry, bibDatabaseMode, fieldWriterPreferences, bibEntryTypesManager) + ); + } + + private static String convertToString(BibEntry entry, BibDatabaseMode bibDatabaseMode, FieldWriterPreferences fieldWriterPreferences, BibEntryTypesManager bibEntryTypesManager) { + StringWriter rawEntry = new StringWriter(); + BibWriter bibWriter = new BibWriter(rawEntry, "\n"); + BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(fieldWriterPreferences), bibEntryTypesManager); + try { + bibtexEntryWriter.write(entry, bibWriter, bibDatabaseMode); + } catch (IOException e) { + LOGGER.warn("Problem creating BibTeX entry.", e); + return "error"; + } + return rawEntry.toString(); + } + + @Override + public int compareTo(BibEntryDTO o) { + int sharingComparison = sharingMetadata.compareTo(o.sharingMetadata); + if (sharingComparison != 0) { + return sharingComparison; + } + LOGGER.error("Comparing equal DTOs"); + return 0; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("sharingMetadata", sharingMetadata) + .add("userComments", userComments) + .add("citationkey", citationKey) + .add("bibtex", bibtex) + .toString(); + } +} diff --git a/src/main/java/org/jabref/http/dto/GsonFactory.java b/src/main/java/org/jabref/http/dto/GsonFactory.java new file mode 100644 index 00000000000..77da67f9ba4 --- /dev/null +++ b/src/main/java/org/jabref/http/dto/GsonFactory.java @@ -0,0 +1,18 @@ +package org.jabref.http.dto; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.glassfish.hk2.api.Factory; + +public class GsonFactory implements Factory { + @Override + public Gson provide() { + return new GsonBuilder() + .setPrettyPrinting() + .create(); + } + + @Override + public void dispose(Gson instance) { + } +} diff --git a/src/main/java/org/jabref/http/server/Application.java b/src/main/java/org/jabref/http/server/Application.java new file mode 100644 index 00000000000..00567335edb --- /dev/null +++ b/src/main/java/org/jabref/http/server/Application.java @@ -0,0 +1,32 @@ +package org.jabref.http.server; + +import java.util.Set; + +import org.jabref.http.dto.GsonFactory; +import org.jabref.preferences.PreferenceServiceFactory; + +import jakarta.inject.Inject; +import jakarta.ws.rs.ApplicationPath; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.ServiceLocatorUtilities; + +@ApplicationPath("/") +public class Application extends jakarta.ws.rs.core.Application { + + @Inject + ServiceLocator serviceLocator; + + @Override + public Set> getClasses() { + initialize(); + return Set.of(RootResource.class, LibrariesResource.class, LibraryResource.class, CORSFilter.class); + } + + /** + * Separate initialization method, because @Inject does not support injection at the constructor + */ + private void initialize() { + ServiceLocatorUtilities.addFactoryConstants(serviceLocator, new GsonFactory()); + ServiceLocatorUtilities.addFactoryConstants(serviceLocator, new PreferenceServiceFactory()); + } +} diff --git a/src/main/java/org/jabref/http/server/CORSFilter.java b/src/main/java/org/jabref/http/server/CORSFilter.java new file mode 100644 index 00000000000..7489305808f --- /dev/null +++ b/src/main/java/org/jabref/http/server/CORSFilter.java @@ -0,0 +1,23 @@ +package org.jabref.http.server; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class CORSFilter implements ContainerResponseFilter { + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { + String requestOrigin = requestContext.getHeaderString("Origin"); + if (requestOrigin == null) { + // IntelliJ's rest client is calling + responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + } else if (requestOrigin.contains("://localhost")) { + responseContext.getHeaders().add("Access-Control-Allow-Origin", requestOrigin); + } + responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept"); + responseContext.getHeaders().add("Access-Control-Allow-Credentials", "false"); + } +} diff --git a/src/main/java/org/jabref/http/server/LibrariesResource.java b/src/main/java/org/jabref/http/server/LibrariesResource.java new file mode 100644 index 00000000000..a5ea28c0ee1 --- /dev/null +++ b/src/main/java/org/jabref/http/server/LibrariesResource.java @@ -0,0 +1,29 @@ +package org.jabref.http.server; + +import java.util.List; + +import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.preferences.PreferencesService; + +import com.google.gson.Gson; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("libraries") +public class LibrariesResource { + @Inject + PreferencesService preferences; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public String get() { + List fileNamesWithUniqueSuffix = preferences.getGuiPreferences().getLastFilesOpened().stream() + .map(java.nio.file.Path::of) + .map(p -> p.getFileName() + "-" + BackupFileUtil.getUniqueFilePrefix(p)) + .toList(); + return new Gson().toJson(fileNamesWithUniqueSuffix); + } +} diff --git a/src/main/java/org/jabref/http/server/LibraryResource.java b/src/main/java/org/jabref/http/server/LibraryResource.java new file mode 100644 index 00000000000..9e10afa7fa1 --- /dev/null +++ b/src/main/java/org/jabref/http/server/LibraryResource.java @@ -0,0 +1,101 @@ +package org.jabref.http.server; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Objects; + +import org.jabref.gui.Globals; +import org.jabref.http.dto.BibEntryDTO; +import org.jabref.logic.citationstyle.JabRefItemDataProvider; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.fileformat.BibtexImporter; +import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.preferences.PreferencesService; + +import com.google.gson.Gson; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Path("libraries/{id}") +public class LibraryResource { + public static final Logger LOGGER = LoggerFactory.getLogger(LibraryResource.class); + + @Inject + PreferencesService preferences; + + @Inject + Gson gson; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public String getJson(@PathParam("id") String id) { + ParserResult parserResult = getParserResult(id); + List list = parserResult.getDatabase().getEntries().stream() + .map(bibEntry -> { + bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry)); + return bibEntry; + }) + .map(entry -> new BibEntryDTO(entry, parserResult.getDatabaseContext().getMode(), preferences.getFieldWriterPreferences(), Globals.entryTypesManager)) + .toList(); + return gson.toJson(list); + } + + @GET + @Produces(org.jabref.http.MediaType.JSON_CSL_ITEM) + public String getClsItemJson(@PathParam("id") String id) { + ParserResult parserResult = getParserResult(id); + JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); + jabRefItemDataProvider.setData(parserResult.getDatabaseContext(), new BibEntryTypesManager()); + return jabRefItemDataProvider.toJson(); + } + + private ParserResult getParserResult(String id) { + java.nio.file.Path library = getLibraryPath(id); + ParserResult parserResult; + try { + parserResult = new BibtexImporter(preferences.getImportFormatPreferences(), new DummyFileUpdateMonitor()).importDatabase(library); + } catch (IOException e) { + LOGGER.warn("Could not find open library file {}", library, e); + throw new InternalServerErrorException("Could not parse library", e); + } + return parserResult; + } + + @GET + @Produces(org.jabref.http.MediaType.BIBTEX) + public Response getBibtex(@PathParam("id") String id) { + java.nio.file.Path library = getLibraryPath(id); + String libraryAsString; + try { + libraryAsString = Files.readString(library); + } catch (IOException e) { + LOGGER.error("Could not read library {}", library, e); + throw new InternalServerErrorException("Could not read library " + library, e); + } + return Response.ok() + .entity(libraryAsString) + .build(); + } + + private java.nio.file.Path getLibraryPath(String id) { + java.nio.file.Path library = preferences.getGuiPreferences().getLastFilesOpened() + .stream() + .map(java.nio.file.Path::of) + .filter(p -> (p.getFileName() + "-" + BackupFileUtil.getUniqueFilePrefix(p)).equals(id)) + .findAny() + .orElseThrow(() -> new NotFoundException()); + return library; + } +} diff --git a/src/main/java/org/jabref/http/server/RootResource.java b/src/main/java/org/jabref/http/server/RootResource.java new file mode 100644 index 00000000000..c55f2583db3 --- /dev/null +++ b/src/main/java/org/jabref/http/server/RootResource.java @@ -0,0 +1,22 @@ +package org.jabref.http.server; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/") +public class RootResource { + @GET + @Produces(MediaType.TEXT_HTML) + public String get() { + return """ + + +

+ JabRef http API runs. Please navigate to libraries. +

+ +"""; + } +} diff --git a/src/main/java/org/jabref/http/server/Server.java b/src/main/java/org/jabref/http/server/Server.java new file mode 100644 index 00000000000..92ed926ba6e --- /dev/null +++ b/src/main/java/org/jabref/http/server/Server.java @@ -0,0 +1,65 @@ +package org.jabref.http.server; + +import java.net.URI; +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; + +import javax.net.ssl.SSLContext; + +import org.jabref.logic.util.OS; + +import jakarta.ws.rs.SeBootstrap; +import net.harawata.appdirs.AppDirsFactory; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Server { + private static final Logger LOGGER = LoggerFactory.getLogger(Server.class); + + private static SeBootstrap.Instance serverInstance; + + static void startServer(CountDownLatch latch) { + SSLContext sslContext = getSslContext(); + SeBootstrap.Configuration configuration = SeBootstrap.Configuration + .builder() + .sslContext(sslContext) + .protocol("HTTPS") + .port(6051) + .build(); + SeBootstrap.start(Application.class, configuration).thenAccept(instance -> { + instance.stopOnShutdown(stopResult -> + System.out.printf("Stop result: %s [Native stop result: %s].%n", stopResult, + stopResult.unwrap(Object.class))); + final URI uri = instance.configuration().baseUri(); + System.out.printf("Instance %s running at %s [Native handle: %s].%n", instance, uri, + instance.unwrap(Object.class)); + System.out.println("Send SIGKILL to shutdown."); + serverInstance = instance; + latch.countDown(); + }); + } + + private static SSLContext getSslContext() { + SSLContextConfigurator sslContextConfig = new SSLContextConfigurator(); + + // "server.jks" Needs to be generated using following command inside that directory: + // keytool -genkey -keyalg RSA -alias selfsigned -keystore server.jks -storepass changeit -validity 365 -keysize 2048 -dname "CN=localhost, OU=YourOrganizationUnit, O=YourOrganization, L=YourCity, S=YourState, C=YourCountry" + + String keystorePath = Path.of(AppDirsFactory.getInstance() + .getUserDataDir( + OS.APP_DIR_APP_NAME, + "ssl", + OS.APP_DIR_APP_AUTHOR)) + .resolve("server.p12").toString(); + + sslContextConfig.setKeyStoreFile(keystorePath); + sslContextConfig.setKeyStorePass("changeit"); + + return sslContextConfig.createSSLContext(); + } + + static void stopServer() { + serverInstance.stop(); + } +} diff --git a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java index 6bdd2149b52..215c9a14719 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java +++ b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java @@ -1,37 +1,17 @@ package org.jabref.logic.citationstyle; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; -import java.util.Locale; import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import org.jabref.logic.formatter.bibtexfields.RemoveNewlinesFormatter; -import org.jabref.logic.integrity.PagesChecker; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.entry.Month; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.entry.types.StandardEntryType; -import org.jabref.model.strings.LatexToUnicodeAdapter; import de.undercouch.citeproc.CSL; import de.undercouch.citeproc.DefaultAbbreviationProvider; -import de.undercouch.citeproc.ItemDataProvider; -import de.undercouch.citeproc.bibtex.BibTeXConverter; -import de.undercouch.citeproc.csl.CSLItemData; import de.undercouch.citeproc.output.Bibliography; -import org.jbibtex.BibTeXEntry; -import org.jbibtex.DigitStringValue; -import org.jbibtex.Key; /** * Provides an adapter class to CSL. It holds a CSL instance under the hood that is only recreated when @@ -49,7 +29,6 @@ */ public class CSLAdapter { - private static final BibTeXConverter BIBTEX_CONVERTER = new BibTeXConverter(); private final JabRefItemDataProvider dataProvider = new JabRefItemDataProvider(); private String style; private CitationStyleOutputFormat format; @@ -90,160 +69,4 @@ private void initialize(String newStyle, CitationStyleOutputFormat newFormat) th format = newFormat; } } - - /** - * Custom ItemDataProvider that allows to set the data so that we don't have to instantiate a new CSL object - * every time. - */ - private static class JabRefItemDataProvider implements ItemDataProvider { - - private final List data = new ArrayList<>(); - private BibDatabaseContext bibDatabaseContext; - private BibEntryTypesManager entryTypesManager; - private PagesChecker pagesChecker; - - /** - * Converts the {@link BibEntry} into {@link CSLItemData}. - * - *
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
BibTeXBibLaTeXEntryPreview/CSLproposed logic, conditions and info
volumevolumevolume
numberissueissueFor conversion to CSL or BibTeX: BibLaTeX number takes priority and supersedes BibLaTeX issue
numbernumberissuesame as above
pageseidnumberSome journals put the article-number (= eid) into the pages field. If BibLaTeX eid exists, provide csl number to the style. If pages exists, provide csl page. If eid WITHIN the pages field exists, detect the eid and provide csl number. If both eid and pages exists, ideally provide both csl number and csl page. Ideally the citationstyle should be able to flexibly choose the rendering.
pagespagespagesame as above
- */ - private CSLItemData bibEntryToCSLItemData(BibEntry originalBibEntry, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { - // We need to make a deep copy, because we modify the entry according to the logic presented at - // https://github.com/JabRef/jabref/issues/8372#issuecomment-1014941935 - BibEntry bibEntry = (BibEntry) originalBibEntry.clone(); - String citeKey = bibEntry.getCitationKey().orElse(""); - BibTeXEntry bibTeXEntry = new BibTeXEntry(new Key(bibEntry.getType().getName()), new Key(citeKey)); - - // Not every field is already generated into latex free fields - RemoveNewlinesFormatter removeNewlinesFormatter = new RemoveNewlinesFormatter(); - - Optional entryType = entryTypesManager.enrich(bibEntry.getType(), bibDatabaseContext.getMode()); - - if (bibEntry.getType().equals(StandardEntryType.Article)) { - // Patch bibEntry to contain the right BibTeX (not BibLaTeX) fields - // Note that we do not need to convert from "pages" to "page", because CiteProc already handles it - // See BibTeXConverter - if (bibDatabaseContext.isBiblatexMode()) { - // Map "number" to CSL "issue", unless no number exists - Optional numberField = bibEntry.getField(StandardField.NUMBER); - numberField.ifPresent(number -> { - bibEntry.setField(StandardField.ISSUE, number); - bibEntry.clearField(StandardField.NUMBER); - } - ); - - bibEntry.getField(StandardField.EID).ifPresent(eid -> { - if (!bibEntry.hasField(StandardField.NUMBER)) { - bibEntry.setField(StandardField.NUMBER, eid); - bibEntry.clearField(StandardField.EID); - } - }); - } else { - // BibTeX mode - bibEntry.getField(StandardField.NUMBER).ifPresent(number -> { - bibEntry.setField(StandardField.ISSUE, number); - bibEntry.clearField(StandardField.NUMBER); - }); - bibEntry.getField(StandardField.PAGES).ifPresent(pages -> { - if (pages.toLowerCase(Locale.ROOT).startsWith("article ")) { - pages = pages.substring("Article ".length()); - bibEntry.setField(StandardField.NUMBER, pages); - } - }); - bibEntry.getField(StandardField.EID).ifPresent(eid -> { - if (!bibEntry.hasField(StandardField.PAGES)) { - bibEntry.setField(StandardField.PAGES, eid); - bibEntry.clearField(StandardField.EID); - } - }); - } - } - - Set fields = entryType.map(BibEntryType::getAllFields).orElse(bibEntry.getFields()); - fields.addAll(bibEntry.getFields()); - for (Field key : fields) { - bibEntry.getResolvedFieldOrAlias(key, bibDatabaseContext.getDatabase()) - .map(removeNewlinesFormatter::format) - .map(LatexToUnicodeAdapter::format) - .ifPresent(value -> { - if (StandardField.MONTH.equals(key)) { - // Change month from #mon# to mon because CSL does not support the former format - value = bibEntry.getMonth().map(Month::getShortName).orElse(value); - } - bibTeXEntry.addField(new Key(key.getName()), new DigitStringValue(value)); - }); - } - return BIBTEX_CONVERTER.toItemData(bibTeXEntry); - } - - public void setData(List data, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { - this.data.clear(); - this.data.addAll(data); - this.bibDatabaseContext = bibDatabaseContext; - this.entryTypesManager = entryTypesManager; - - // Quick solution to always use BibLaTeX mode at the checker to allow pages ranges with single dash, too - // Example: pages = {1-2} - BibDatabaseContext ctx = new BibDatabaseContext(); - ctx.setMode(BibDatabaseMode.BIBLATEX); - this.pagesChecker = new PagesChecker(ctx); - } - - @Override - public CSLItemData retrieveItem(String id) { - return data.stream() - .filter(entry -> entry.getCitationKey().orElse("").equals(id)) - .map(entry -> bibEntryToCSLItemData(entry, bibDatabaseContext, entryTypesManager)) - .findFirst().orElse(null); - } - - @Override - public Collection getIds() { - return data.stream() - .map(entry -> entry.getCitationKey().orElse("")) - .toList(); - } - } } diff --git a/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java b/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java new file mode 100644 index 00000000000..32c3d95ef6e --- /dev/null +++ b/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java @@ -0,0 +1,212 @@ +package org.jabref.logic.citationstyle; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.logic.formatter.bibtexfields.RemoveNewlinesFormatter; +import org.jabref.logic.integrity.PagesChecker; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.strings.LatexToUnicodeAdapter; + +import de.undercouch.citeproc.ItemDataProvider; +import de.undercouch.citeproc.bibtex.BibTeXConverter; +import de.undercouch.citeproc.csl.CSLItemData; +import de.undercouch.citeproc.helper.json.StringJsonBuilderFactory; +import org.jbibtex.BibTeXEntry; +import org.jbibtex.DigitStringValue; +import org.jbibtex.Key; + +/** + * Custom {@link ItemDataProvider} that allows to set the data so that we don't have to instantiate a new CSL object + * every time. + */ +public class JabRefItemDataProvider implements ItemDataProvider { + + private static final BibTeXConverter BIBTEX_CONVERTER = new BibTeXConverter(); + + private final StringJsonBuilderFactory stringJsonBuilderFactory; + + private final List data = new ArrayList<>(); + + private BibDatabaseContext bibDatabaseContext; + private BibEntryTypesManager entryTypesManager; + private PagesChecker pagesChecker; + + public JabRefItemDataProvider() { + stringJsonBuilderFactory = new StringJsonBuilderFactory(); + } + + /** + * Converts the {@link BibEntry} into {@link CSLItemData}. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
BibTeXBibLaTeXEntryPreview/CSLproposed logic, conditions and info
volumevolumevolume
numberissueissueFor conversion to CSL or BibTeX: BibLaTeX number takes priority and supersedes BibLaTeX issue
numbernumberissuesame as above
pageseidnumberSome journals put the article-number (= eid) into the pages field. If BibLaTeX eid exists, provide csl number to the style. If pages exists, provide csl page. If eid WITHIN the pages field exists, detect the eid and provide csl number. If both eid and pages exists, ideally provide both csl number and csl page. Ideally the citationstyle should be able to flexibly choose the rendering.
pagespagespagesame as above
+ */ + private CSLItemData bibEntryToCSLItemData(BibEntry originalBibEntry, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { + // We need to make a deep copy, because we modify the entry according to the logic presented at + // https://github.com/JabRef/jabref/issues/8372#issuecomment-1014941935 + BibEntry bibEntry = (BibEntry) originalBibEntry.clone(); + String citeKey = bibEntry.getCitationKey().orElse(""); + BibTeXEntry bibTeXEntry = new BibTeXEntry(new Key(bibEntry.getType().getName()), new Key(citeKey)); + + // Not every field is already generated into latex free fields + RemoveNewlinesFormatter removeNewlinesFormatter = new RemoveNewlinesFormatter(); + + Optional entryType = entryTypesManager.enrich(bibEntry.getType(), bibDatabaseContext.getMode()); + + if (bibEntry.getType().equals(StandardEntryType.Article)) { + // Patch bibEntry to contain the right BibTeX (not BibLaTeX) fields + // Note that we do not need to convert from "pages" to "page", because CiteProc already handles it + // See BibTeXConverter + if (bibDatabaseContext.isBiblatexMode()) { + // Map "number" to CSL "issue", unless no number exists + Optional numberField = bibEntry.getField(StandardField.NUMBER); + numberField.ifPresent(number -> { + bibEntry.setField(StandardField.ISSUE, number); + bibEntry.clearField(StandardField.NUMBER); + } + ); + + bibEntry.getField(StandardField.EID).ifPresent(eid -> { + if (!bibEntry.hasField(StandardField.NUMBER)) { + bibEntry.setField(StandardField.NUMBER, eid); + bibEntry.clearField(StandardField.EID); + } + }); + } else { + // BibTeX mode + bibEntry.getField(StandardField.NUMBER).ifPresent(number -> { + bibEntry.setField(StandardField.ISSUE, number); + bibEntry.clearField(StandardField.NUMBER); + }); + bibEntry.getField(StandardField.PAGES).ifPresent(pages -> { + if (pages.toLowerCase(Locale.ROOT).startsWith("article ")) { + pages = pages.substring("Article ".length()); + bibEntry.setField(StandardField.NUMBER, pages); + } + }); + bibEntry.getField(StandardField.EID).ifPresent(eid -> { + if (!bibEntry.hasField(StandardField.PAGES)) { + bibEntry.setField(StandardField.PAGES, eid); + bibEntry.clearField(StandardField.EID); + } + }); + } + } + + Set fields = entryType.map(BibEntryType::getAllFields).orElse(bibEntry.getFields()); + fields.addAll(bibEntry.getFields()); + for (Field key : fields) { + bibEntry.getResolvedFieldOrAlias(key, bibDatabaseContext.getDatabase()) + .map(removeNewlinesFormatter::format) + .map(LatexToUnicodeAdapter::format) + .ifPresent(value -> { + if (StandardField.MONTH.equals(key)) { + // Change month from #mon# to mon because CSL does not support the former format + value = bibEntry.getMonth().map(Month::getShortName).orElse(value); + } + bibTeXEntry.addField(new Key(key.getName()), new DigitStringValue(value)); + }); + } + return BIBTEX_CONVERTER.toItemData(bibTeXEntry); + } + + /** + * Fills the data with all entries in given bibDatabaseContext + */ + public void setData(BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { + this.setData(bibDatabaseContext.getEntries(), bibDatabaseContext, entryTypesManager); + } + + public void setData(List data, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { + this.data.clear(); + this.data.addAll(data); + this.bibDatabaseContext = bibDatabaseContext; + this.entryTypesManager = entryTypesManager; + + // Quick solution to always use BibLaTeX mode at the checker to allow pages ranges with single dash, too + // Example: pages = {1-2} + BibDatabaseContext ctx = new BibDatabaseContext(); + ctx.setMode(BibDatabaseMode.BIBLATEX); + this.pagesChecker = new PagesChecker(ctx); + } + + public String toJson() { + List entries = bibDatabaseContext.getEntries(); + this.setData(entries, bibDatabaseContext, entryTypesManager); + return entries.stream() + .map(entry -> bibEntryToCSLItemData(entry, bibDatabaseContext, entryTypesManager)) + .map(item -> item.toJson(stringJsonBuilderFactory.createJsonBuilder())) + .map(item -> (String) item) + .collect(Collectors.joining(",", "[", "]")); + } + + @Override + public CSLItemData retrieveItem(String id) { + return data.stream() + .filter(entry -> entry.getCitationKey().orElse("").equals(id)) + .map(entry -> bibEntryToCSLItemData(entry, bibDatabaseContext, entryTypesManager)) + .findFirst().orElse(null); + } + + @Override + public Collection getIds() { + return data.stream() + .map(entry -> entry.getCitationKey().orElse("")) + .toList(); + } +} diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexImporter.java index 1b67e2df572..500a14a69c9 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexImporter.java @@ -54,6 +54,36 @@ public boolean isRecognizedFormat(BufferedReader reader) { @Override public ParserResult importDatabase(Path filePath) throws IOException { + EncodingResult result = getEncodingResult(filePath); + + // We replace unreadable characters + // Unfortunately, no warning will be issued to the user + // As this is a very seldom case, we accept that + CharsetDecoder decoder = result.encoding().newDecoder(); + decoder.onMalformedInput(CodingErrorAction.REPLACE); + + try (InputStreamReader inputStreamReader = new InputStreamReader(Files.newInputStream(filePath), decoder); + BufferedReader reader = new BufferedReader(inputStreamReader)) { + ParserResult parserResult = this.importDatabase(reader); + parserResult.getMetaData().setEncoding(result.encoding()); + parserResult.getMetaData().setEncodingExplicitlySupplied(result.encodingExplicitlySupplied()); + parserResult.setPath(filePath); + if (parserResult.getMetaData().getMode().isEmpty()) { + parserResult.getMetaData().setMode(BibDatabaseModeDetection.inferMode(parserResult.getDatabase())); + } + return parserResult; + } + } + + public static Charset getEncoding(Path filePath) throws IOException { + return getEncodingResult(filePath).encoding(); + } + + /** + * Determines the encoding of the supplied BibTeX file. If a JabRef encoding information is present, this information is used. + * If there is none present, {@link com.ibm.icu.text.CharsetDetector#CharsetDetector()} is used. + */ + private static EncodingResult getEncodingResult(Path filePath) throws IOException { // We want to check if there is a JabRef encoding heading in the file, because that would tell us // which character encoding is used. @@ -81,29 +111,16 @@ public ParserResult importDatabase(Path filePath) throws IOException { encoding = suppliedEncoding.orElse(detectedCharset); LOGGER.debug("Encoding used to read the file: {}", encoding); } + EncodingResult result = new EncodingResult(encoding, encodingExplicitlySupplied); + return result; + } - // We replace unreadable characters - // Unfortunately, no warning will be issued to the user - // As this is a very seldom case, we accept that - CharsetDecoder decoder = encoding.newDecoder(); - decoder.onMalformedInput(CodingErrorAction.REPLACE); - - try (InputStreamReader inputStreamReader = new InputStreamReader(Files.newInputStream(filePath), decoder); - BufferedReader reader = new BufferedReader(inputStreamReader)) { - ParserResult parserResult = this.importDatabase(reader); - parserResult.getMetaData().setEncoding(encoding); - parserResult.getMetaData().setEncodingExplicitlySupplied(encodingExplicitlySupplied); - parserResult.setPath(filePath); - if (parserResult.getMetaData().getMode().isEmpty()) { - parserResult.getMetaData().setMode(BibDatabaseModeDetection.inferMode(parserResult.getDatabase())); - } - return parserResult; - } + private record EncodingResult(Charset encoding, boolean encodingExplicitlySupplied) { } /** * This method does not set the metadata encoding information. The caller needs to set the encoding of the supplied - * reader manually to the meta data + * reader manually to the metadata */ @Override public ParserResult importDatabase(BufferedReader reader) throws IOException { diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 5d24472dba0..3be461ab02c 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -226,7 +226,6 @@ private ParserResult parseFileContent() throws IOException { skipWhitespace(); } - // Instantiate meta data try { parserResult.setMetaData(metaDataParser.parse(meta, importFormatPreferences.bibEntryPreferences().getKeywordSeparator())); } catch (ParseException exception) { diff --git a/src/main/java/org/jabref/logic/net/ssl/SSLCertificate.java b/src/main/java/org/jabref/logic/net/ssl/SSLCertificate.java index deeca70f806..d2229b87850 100644 --- a/src/main/java/org/jabref/logic/net/ssl/SSLCertificate.java +++ b/src/main/java/org/jabref/logic/net/ssl/SSLCertificate.java @@ -63,8 +63,7 @@ public Integer getVersion() { /** * @return the SHA-256 of the DER encoding - * - * */ + */ public String getSHA256Thumbprint() { return sha256Thumbprint; } diff --git a/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java b/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java index e5faac98bf2..70c0bed0659 100644 --- a/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java +++ b/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java @@ -61,7 +61,7 @@ public void deleteCertificate(String alias) { } } - public boolean isCertificateExist(String alias) { + public boolean certificateExists(String alias) { Objects.requireNonNull(alias); try { return store.isCertificateEntry(alias); diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index 38e37c40db7..3ec0822ec3e 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -50,9 +50,45 @@ import org.slf4j.LoggerFactory; /** - * Represents a BibTex / BibLaTeX entry. + * Represents a Bib(La)TeX entry, which can be BibTeX or BibLaTeX. + *

+ * Example: + * + *

{@code
+ * Some commment
+ * @misc{key,
+ *   fieldName = {fieldValue},
+ *   otherFieldName = {otherVieldValue}
+ * }
+ *     }
+ * + * Then, + *
    + *
  • "Some comment" is the comment before the entry,
  • + *
  • "misc" is the entry type
  • + *
  • "key" the citation key
  • + *
  • "fieldName" and "otherFieldName" the fields of the BibEntry
  • + *
+ *

+ *

+ * A BibTeX entry has following properties: + *

    + *
  • comments before entry
  • + *
  • entry type
  • + *
  • citation key
  • + *
  • fields
  • + *
+ * In JabRef, this is modeled the following way: + *
    + *
  • comments before entry --> {@link BibEntry#commentsBeforeEntry}
  • + *
  • entry type --> {@link BibEntry#type}
  • + *
  • citation key --> contained in {@link BibEntry#fields} using they hashmap key {@link InternalField#KEY_FIELD}
  • + *
  • fields --> contained in {@link BibEntry#fields}
  • + *
+ *

*

* In case you search for a builder as described in Item 2 of the book "Effective Java", you won't find one. Please use the methods {@link #withCitationKey(String)} and {@link #withField(Field, String)}. + *

*/ @AllowedToUseLogic("because it needs access to parser and writers") public class BibEntry implements Cloneable { @@ -814,6 +850,17 @@ public SharedBibEntryData getSharedBibEntryData() { return sharedBibEntryData; } + public BibEntry withSharedBibEntryData(int sharedId, int version) { + sharedBibEntryData.setSharedID(sharedId); + sharedBibEntryData.setVersion(version); + return this; + } + + public BibEntry withSharedBibEntryData(SharedBibEntryData sharedBibEntryData) { + sharedBibEntryData = sharedBibEntryData; + return this; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -828,9 +875,20 @@ public boolean equals(Object o) { && Objects.equals(commentsBeforeEntry, entry.commentsBeforeEntry); } + /** + * On purpose, this hashes the "content" of the BibEntry, not the {@link #sharedBibEntryData}. + * + * The content is + * + *
    + *
  • comments before entry
  • + *
  • entry type
  • + *
  • fields (including the citation key {@link InternalField#KEY_FIELD}
  • + *
+ */ @Override public int hashCode() { - return Objects.hash(type.getValue(), fields); + return Objects.hash(commentsBeforeEntry, type.getValue(), fields); } public void registerListener(Object object) { @@ -852,6 +910,15 @@ public BibEntry withField(Field field, String value) { return this; } + /** + * A copy is made of the parameter + */ + public BibEntry withFields(Map content) { + this.fields = FXCollections.observableMap(new HashMap<>(content)); + return this; + } + + public BibEntry withDate(Date date) { setDate(date); this.setChanged(false); @@ -871,6 +938,11 @@ public String getUserComments() { return commentsBeforeEntry; } + public BibEntry withUserComments(String commentsBeforeEntry) { + this.commentsBeforeEntry = commentsBeforeEntry; + return this; + } + public List getEntryLinkList(Field field, BibDatabase database) { return getField(field).map(fieldValue -> EntryLinkList.parse(fieldValue, database)) .orElse(Collections.emptyList()); @@ -1086,4 +1158,5 @@ public void mergeWith(BibEntry other, Set otherPrioritizedFields) { } } } + } diff --git a/src/main/java/org/jabref/model/entry/SharedBibEntryData.java b/src/main/java/org/jabref/model/entry/SharedBibEntryData.java index c323a9bc3fe..69c3c6bf976 100644 --- a/src/main/java/org/jabref/model/entry/SharedBibEntryData.java +++ b/src/main/java/org/jabref/model/entry/SharedBibEntryData.java @@ -1,16 +1,20 @@ package org.jabref.model.entry; +import com.google.common.base.MoreObjects; + /** * Stores all information needed to manage entries on a shared (SQL) database. */ -public class SharedBibEntryData { +public class SharedBibEntryData implements Comparable { // This id is set by the remote database system (DBS). // It has to be unique on remote DBS for all connected JabRef instances. // The old id above does not satisfy this requirement. + // This is "ID" in JabDrive sync private int sharedID; // Needed for version controlling if used on shared database + // This is "Revision" in JabDrive sync private int version; public SharedBibEntryData() { @@ -33,4 +37,21 @@ public int getVersion() { public void setVersion(int version) { this.version = version; } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("sharedID", sharedID) + .add("version", version) + .toString(); + } + + @Override + public int compareTo(SharedBibEntryData o) { + if (this.sharedID == o.sharedID) { + return Integer.compare(this.version, o.version); + } else { + return Integer.compare(this.sharedID, o.sharedID); + } + } } diff --git a/src/main/java/org/jabref/model/entry/field/InternalField.java b/src/main/java/org/jabref/model/entry/field/InternalField.java index 75b1573fcdb..d284af643c4 100644 --- a/src/main/java/org/jabref/model/entry/field/InternalField.java +++ b/src/main/java/org/jabref/model/entry/field/InternalField.java @@ -6,20 +6,49 @@ import java.util.Set; /** - * JabRef internal fields. These are not normal fields but mostly place holders with special functions. + * JabRef internal fields. These are not normal fields but mostly placeholders with special functions. */ public enum InternalField implements Field { + /** + * The BibTeX key (which is used at \cite{key} in LaTeX + */ KEY_FIELD("citationkey"), + /** * field which indicates the entrytype + * + * Example: @misc{key} */ TYPE_HEADER("entrytype"), + + /** + * Used in old layout files + */ OBSOLETE_TYPE_HEADER("bibtextype"), - MARKED_INTERNAL("__markedentry"), // used in old versions of JabRef. Currently used for conversion only - // all field names starting with "Jabref-internal-" are not appearing in .bib files - BIBTEX_STRING("__string"), // marker that the content is just a BibTeX string - INTERNAL_ALL_FIELD("all"), // virtual field to denote "all fields". Used in the meta data serialiization for save actions. - INTERNAL_ALL_TEXT_FIELDS_FIELD("all-text-fields"), // virtual field to denote "all text fields". Used in the meta data serialiization for save actions. + + /** + * used in old versions of JabRef. Currently used for conversion only + */ + MARKED_INTERNAL("__markedentry"), + + /** + * Marker that the content is just a BibTeX string + */ + BIBTEX_STRING("__string"), + + /** + * virtual field to denote "all fields". Used in the metadata serialization for save actions. + */ + INTERNAL_ALL_FIELD("all"), + + /** + * virtual field to denote "all text fields". Used in the metadata serialization for save actions. + */ + INTERNAL_ALL_TEXT_FIELDS_FIELD("all-text-fields"), + + /** + * all field names starting with "Jabref-internal-" are not appearing in .bib files + */ INTERNAL_ID_FIELD("JabRef-internal-id"); private final String name; diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 4b6f3c30fe9..ccf0d3fba60 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -116,7 +116,9 @@ import org.jabref.model.strings.StringUtil; import com.tobiasdiez.easybind.EasyBind; +import jakarta.inject.Singleton; import net.harawata.appdirs.AppDirsFactory; +import org.jvnet.hk2.annotations.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -130,6 +132,8 @@ * There are still some similar preferences classes (OpenOfficePreferences and SharedDatabasePreferences) which also use * the {@code java.util.prefs} API. */ +@Singleton +@Service public class JabRefPreferences implements PreferencesService { // Push to application preferences diff --git a/src/main/java/org/jabref/preferences/PreferenceServiceFactory.java b/src/main/java/org/jabref/preferences/PreferenceServiceFactory.java new file mode 100644 index 00000000000..15d323c4caf --- /dev/null +++ b/src/main/java/org/jabref/preferences/PreferenceServiceFactory.java @@ -0,0 +1,14 @@ +package org.jabref.preferences; + +import org.glassfish.hk2.api.Factory; + +public class PreferenceServiceFactory implements Factory { + @Override + public PreferencesService provide() { + return JabRefPreferences.getInstance(); + } + + @Override + public void dispose(PreferencesService instance) { + } +} diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index 5018e68d966..fba07fb2006 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -42,7 +42,9 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; import org.jabref.model.metadata.SaveOrderConfig; +import org.jvnet.hk2.annotations.Contract; +@Contract public interface PreferencesService { InternalPreferences getInternalPreferences(); diff --git a/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java b/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java index 594664f4f47..cd6db4c3716 100644 --- a/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java +++ b/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java @@ -38,9 +38,9 @@ public class CopyMoreActionTest { private PreferencesService preferencesService = mock(PreferencesService.class); private StateManager stateManager = mock(StateManager.class); private BibEntry entry; - private List titles = new ArrayList(); - private List keys = new ArrayList(); - private List dois = new ArrayList(); + private List titles = new ArrayList<>(); + private List keys = new ArrayList<>(); + private List dois = new ArrayList<>(); @BeforeEach public void setUp() { diff --git a/src/test/java/org/jabref/http/server/LibrariesResourceTest.java b/src/test/java/org/jabref/http/server/LibrariesResourceTest.java new file mode 100644 index 00000000000..df7d7784bae --- /dev/null +++ b/src/test/java/org/jabref/http/server/LibrariesResourceTest.java @@ -0,0 +1,33 @@ +package org.jabref.http.server; + +import java.util.EnumSet; +import java.util.stream.Collectors; + +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class LibrariesResourceTest extends ServerTest { + + @Override + protected jakarta.ws.rs.core.Application configure() { + ResourceConfig resourceConfig = new ResourceConfig(LibrariesResource.class); + addPreferencesToResourceConfig(resourceConfig); + return resourceConfig.getApplication(); + } + + @Test + void defaultOneTestLibrary() throws Exception { + assertEquals("[\"" + TestBibFile.GENERAL_SERVER_TEST.id + "\"]", target("/libraries").request().get(String.class)); + } + + @Test + void twoTestLibraries() { + EnumSet availableLibraries = EnumSet.of(TestBibFile.GENERAL_SERVER_TEST, TestBibFile.JABREF_AUTHORS); + setAvailableLibraries(availableLibraries); + // We cannot use a string constant as the path changes from OS to OS. Therefore, we need to dynamically create the expected result. + String expected = availableLibraries.stream().map(file -> file.id).collect(Collectors.joining("\",\"", "[\"", "\"]")); + assertEquals(expected, target("/libraries").request().get(String.class)); + } +} diff --git a/src/test/java/org/jabref/http/server/LibraryResourceTest.java b/src/test/java/org/jabref/http/server/LibraryResourceTest.java new file mode 100644 index 00000000000..650999edab2 --- /dev/null +++ b/src/test/java/org/jabref/http/server/LibraryResourceTest.java @@ -0,0 +1,38 @@ +package org.jabref.http.server; + +import org.jabref.http.MediaType; + +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class LibraryResourceTest extends ServerTest { + + @Override + protected jakarta.ws.rs.core.Application configure() { + ResourceConfig resourceConfig = new ResourceConfig(LibraryResource.class, LibrariesResource.class); + addPreferencesToResourceConfig(resourceConfig); + addGsonToResourceConfig(resourceConfig); + return resourceConfig.getApplication(); + } + + @Test + void getJson() { + assertEquals(""" + @Misc{Author2023test, + author = {Demo Author}, + title = {Demo Title}, + year = {2023}, + } + + @Comment{jabref-meta: databaseType:bibtex;} + """, target("/libraries/" + TestBibFile.GENERAL_SERVER_TEST.id).request(MediaType.BIBTEX).get(String.class)); + } + + @Test + void getClsItemJson() { + assertEquals(""" + [{"id":"Author2023test","type":"article","author":[{"family":"Author","given":"Demo"}],"event-date":{"date-parts":[[2023]]},"issued":{"date-parts":[[2023]]},"title":"Demo Title"}]""", target("/libraries/" + TestBibFile.GENERAL_SERVER_TEST.id).request(MediaType.JSON_CSL_ITEM).get(String.class)); + } +} diff --git a/src/test/java/org/jabref/http/server/ServerTest.java b/src/test/java/org/jabref/http/server/ServerTest.java new file mode 100644 index 00000000000..9395cc93171 --- /dev/null +++ b/src/test/java/org/jabref/http/server/ServerTest.java @@ -0,0 +1,92 @@ +package org.jabref.http.server; + +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; + +import org.jabref.http.dto.GsonFactory; +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.preferences.BibEntryPreferences; +import org.jabref.preferences.GuiPreferences; +import org.jabref.preferences.PreferencesService; + +import com.google.gson.Gson; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.BeforeAll; +import org.slf4j.bridge.SLF4JBridgeHandler; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +abstract class ServerTest extends JerseyTest { + + private static PreferencesService preferencesService; + private static GuiPreferences guiPreferences; + + @BeforeAll + static void installLoggingBridge() { + // Grizzly uses java.commons.logging, but we use TinyLog + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + + initializePreferencesService(); + } + + protected void addGsonToResourceConfig(ResourceConfig resourceConfig) { + resourceConfig.register(new AbstractBinder() { + @Override + protected void configure() { + bind(new GsonFactory().provide()).to(Gson.class).ranked(2); + } + }); + } + + protected void addPreferencesToResourceConfig(ResourceConfig resourceConfig) { + resourceConfig.register(new AbstractBinder() { + @Override + protected void configure() { + bind(preferencesService).to(PreferencesService.class).ranked(2); + } + }); + } + + protected void setAvailableLibraries(EnumSet files) { + when(guiPreferences.getLastFilesOpened()).thenReturn( + FXCollections.observableArrayList( + files.stream() + .map(file -> file.path.toString()) + .collect(Collectors.toList()))); + } + + private static void initializePreferencesService() { + preferencesService = mock(PreferencesService.class); + + ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class); + when(preferencesService.getImportFormatPreferences()).thenReturn(importFormatPreferences); + + BibEntryPreferences bibEntryPreferences = mock(BibEntryPreferences.class); + when(importFormatPreferences.bibEntryPreferences()).thenReturn(bibEntryPreferences); + when(bibEntryPreferences.getKeywordSeparator()).thenReturn(','); + + FieldWriterPreferences fieldWriterPreferences = mock(FieldWriterPreferences.class); + when(preferencesService.getFieldWriterPreferences()).thenReturn(fieldWriterPreferences); + when(fieldWriterPreferences.isResolveStrings()).thenReturn(false); + + // defaults are in {@link org.jabref.preferences.JabRefPreferences.NON_WRAPPABLE_FIELDS} + FieldContentFormatterPreferences fieldContentFormatterPreferences = new FieldContentFormatterPreferences(List.of()); + // used twice, once for reading and once for writing + when(importFormatPreferences.fieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences); + when(preferencesService.getFieldWriterPreferences().getFieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences); + + guiPreferences = mock(GuiPreferences.class); + when(preferencesService.getGuiPreferences()).thenReturn(guiPreferences); + + when(guiPreferences.getLastFilesOpened()).thenReturn(FXCollections.observableArrayList(TestBibFile.GENERAL_SERVER_TEST.path.toString())); + } +} diff --git a/src/test/java/org/jabref/http/server/TestBibFile.java b/src/test/java/org/jabref/http/server/TestBibFile.java new file mode 100644 index 00000000000..4897defde32 --- /dev/null +++ b/src/test/java/org/jabref/http/server/TestBibFile.java @@ -0,0 +1,18 @@ +package org.jabref.http.server; + +import java.nio.file.Path; + +import org.jabref.logic.util.io.BackupFileUtil; + +public enum TestBibFile { + GENERAL_SERVER_TEST("src/test/resources/org/jabref/http/server/general-server-test.bib"), + JABREF_AUTHORS("src/test/resources/testbib/jabref-authors.bib"); + + public final Path path; + public final String id; + + TestBibFile(String locationInSource) { + this.path = Path.of(locationInSource).toAbsolutePath(); + this.id = path.getFileName() + "-" + BackupFileUtil.getUniqueFilePrefix(path); + } +} diff --git a/src/test/java/org/jabref/http/server/TestServer.java b/src/test/java/org/jabref/http/server/TestServer.java new file mode 100644 index 00000000000..6ad1edcd970 --- /dev/null +++ b/src/test/java/org/jabref/http/server/TestServer.java @@ -0,0 +1,14 @@ +package org.jabref.http.server; + +import java.util.concurrent.CountDownLatch; + +import org.slf4j.bridge.SLF4JBridgeHandler; + +public class TestServer { + public static void main(final String[] args) throws InterruptedException { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + Server.startServer(new CountDownLatch(1)); + Thread.currentThread().join(); + } +} diff --git a/src/test/java/org/jabref/http/server/mwessl/GServer.java b/src/test/java/org/jabref/http/server/mwessl/GServer.java new file mode 100644 index 00000000000..c5b028f316a --- /dev/null +++ b/src/test/java/org/jabref/http/server/mwessl/GServer.java @@ -0,0 +1,66 @@ +package org.jabref.http.server.mwessl; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jabref.http.server.Server; + +import org.glassfish.grizzly.Grizzly; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; + +/** + * Secured standalone Java HTTP server. + */ +public class GServer { + private static final Logger LOGGER = Grizzly.logger(Server.class); + + public static void main(String[] args) { + final HttpServer server = new HttpServer(); + final ServerConfiguration config = server.getServerConfiguration(); + + // Register simple HttpHandler + config.addHttpHandler(new SimpleHttpHandler(), "/"); + + // create a network listener that listens on port 8080. + final NetworkListener networkListener = new NetworkListener("secured-listener", NetworkListener.DEFAULT_NETWORK_HOST, + NetworkListener.DEFAULT_NETWORK_PORT); + + // Enable SSL on the listener + networkListener.setSecure(true); + networkListener.setSSLEngineConfig(createSslConfiguration()); + + server.addListener(networkListener); + try { + // Start the server + server.start(); + System.out.println("The secured server is running.\nhttps://localhost:" + NetworkListener.DEFAULT_NETWORK_PORT + "\nPress enter to stop..."); + System.in.read(); + } catch (IOException ioe) { + LOGGER.log(Level.SEVERE, ioe.toString(), ioe); + } finally { + server.shutdownNow(); + } + } + + /** + * Initialize server side SSL configuration. + * + * @return server side {@link SSLEngineConfigurator}. + */ + private static SSLEngineConfigurator createSslConfiguration() { + // Initialize SSLContext configuration + SSLContextConfigurator sslContextConfig = new SSLContextConfigurator(); + + sslContextConfig.setKeyStoreFile(Path.of("C:\\users\\koppor\\.keystore").toString()); + sslContextConfig.setKeyStorePass("changeit"); + + // Create SSLEngine configurator + return new SSLEngineConfigurator(sslContextConfig.createSSLContext(), false, false, false); + } +} diff --git a/src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java b/src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java new file mode 100644 index 00000000000..58722b8cd1f --- /dev/null +++ b/src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java @@ -0,0 +1,16 @@ +package org.jabref.http.server.mwessl; + +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +/** + * Simple {@link HttpHandler} implementation. + */ +public class SimpleHttpHandler extends HttpHandler { + + public void service(final Request request, final Response response) throws Exception { + response.setContentType("text/plain"); + response.getWriter().write("Hello world!"); + } +} diff --git a/src/test/java/org/jabref/http/server/mwessl/package-info.java b/src/test/java/org/jabref/http/server/mwessl/package-info.java new file mode 100644 index 00000000000..d74acbe1011 --- /dev/null +++ b/src/test/java/org/jabref/http/server/mwessl/package-info.java @@ -0,0 +1,8 @@ +/** + * Temporary code. + * + * Code based on https://github.com/eclipse-ee4j/grizzly/tree/master/samples/http-server-samples/src/main/java/org/glassfish/grizzly/samples/httpserver/secure + * + * BSD-3-Clause license + */ +package org.jabref.http.server.mwessl; diff --git a/src/test/java/org/jabref/logic/citationstyle/JabRefItemDataProviderTest.java b/src/test/java/org/jabref/logic/citationstyle/JabRefItemDataProviderTest.java new file mode 100644 index 00000000000..33172c35434 --- /dev/null +++ b/src/test/java/org/jabref/logic/citationstyle/JabRefItemDataProviderTest.java @@ -0,0 +1,49 @@ +package org.jabref.logic.citationstyle; + +import java.util.List; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.StandardField; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class JabRefItemDataProviderTest { + + @Test + void toJsonOneEntry() { + BibDatabase bibDatabase = new BibDatabase(List.of( + new BibEntry() + .withCitationKey("key") + .withField(StandardField.AUTHOR, "Test Author") + )); + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(bibDatabase); + JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); + jabRefItemDataProvider.setData(bibDatabaseContext, new BibEntryTypesManager()); + assertEquals(""" + [{"id":"key","type":"article","author":[{"family":"Author","given":"Test"}]}]""", + jabRefItemDataProvider.toJson()); + } + + @Test + void toJsonTwoEntries() { + BibDatabase bibDatabase = new BibDatabase(List.of( + new BibEntry() + .withCitationKey("key") + .withField(StandardField.AUTHOR, "Test Author"), + new BibEntry() + .withCitationKey("key2") + .withField(StandardField.AUTHOR, "Second Author") + )); + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(bibDatabase); + JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); + jabRefItemDataProvider.setData(bibDatabaseContext, new BibEntryTypesManager()); + assertEquals(""" + [{"id":"key","type":"article","author":[{"family":"Author","given":"Test"}]},{"id":"key2","type":"article","author":[{"family":"Author","given":"Second"}]}]""", + jabRefItemDataProvider.toJson()); + } +} diff --git a/src/test/java/org/jabref/testutils/interactive/http/rest-api.http b/src/test/java/org/jabref/testutils/interactive/http/rest-api.http new file mode 100644 index 00000000000..4994efdd6e2 --- /dev/null +++ b/src/test/java/org/jabref/testutils/interactive/http/rest-api.http @@ -0,0 +1,35 @@ +// This file is for IntelliJ's HTTP Client, available in the Ultimate Edition + +GET https://localhost:6051 + +### + +GET https://localhost:6051/libraries + +### + +GET https://localhost:6051/libraries/notfound + +### + +// if you have checkout the JabRef code at c:\git-repositories\jabref, then this +// will show the contents of your first opened library as BibTeX + +GET https://localhost:6051/libraries/jabref-authors.bib-026bd7ec +Accept: application/x-bibtex + +### + +// if you have checkout the JabRef code at c:\git-repositories\jabref, then this +// will show the contents of your first opened library using CSL JSON + +GET https://localhost:6051/libraries/jabref-authors.bib-026bd7ec +Accept: application/x-bibtex-library-csl+json + +### + +// if you have checkout the JabRef code at c:\git-repositories\jabref, then this +// will show the contents of your first opened library using json + embedded BibTeX + +GET https://localhost:6051/libraries/jabref-authors.bib-026bd7ec +Accept: application/json diff --git a/src/main/java/org/jabref/styletester/StyleTester.fxml b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml similarity index 99% rename from src/main/java/org/jabref/styletester/StyleTester.fxml rename to src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml index 1f9d65669cf..2240e83d8bb 100644 --- a/src/main/java/org/jabref/styletester/StyleTester.fxml +++ b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTester.fxml @@ -37,7 +37,7 @@ + xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jabref.testutils.interactive.styletester.StyleTesterView"> diff --git a/src/main/java/org/jabref/styletester/StyleTesterMain.java b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTesterMain.java similarity index 96% rename from src/main/java/org/jabref/styletester/StyleTesterMain.java rename to src/test/java/org/jabref/testutils/interactive/styletester/StyleTesterMain.java index a6dd6abf351..9941472abcb 100644 --- a/src/main/java/org/jabref/styletester/StyleTesterMain.java +++ b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTesterMain.java @@ -1,4 +1,4 @@ -package org.jabref.styletester; +package org.jabref.testutils.interactive.styletester; import javafx.application.Application; import javafx.scene.Scene; diff --git a/src/main/java/org/jabref/styletester/StyleTesterView.java b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTesterView.java similarity index 96% rename from src/main/java/org/jabref/styletester/StyleTesterView.java rename to src/test/java/org/jabref/testutils/interactive/styletester/StyleTesterView.java index eb2db094606..c4e3c3779b0 100644 --- a/src/main/java/org/jabref/styletester/StyleTesterView.java +++ b/src/test/java/org/jabref/testutils/interactive/styletester/StyleTesterView.java @@ -1,4 +1,4 @@ -package org.jabref.styletester; +package org.jabref.testutils.interactive.styletester; import javafx.css.PseudoClass; import javafx.fxml.FXML; diff --git a/src/test/resources/org/jabref/http/server/general-server-test.bib b/src/test/resources/org/jabref/http/server/general-server-test.bib new file mode 100644 index 00000000000..c1d828316d1 --- /dev/null +++ b/src/test/resources/org/jabref/http/server/general-server-test.bib @@ -0,0 +1,7 @@ +@Misc{Author2023test, + author = {Demo Author}, + title = {Demo Title}, + year = {2023}, +} + +@Comment{jabref-meta: databaseType:bibtex;} From 37dd6b9220d1543cc351f5584b87de53dd3b1624 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 9 Apr 2023 01:15:27 +0200 Subject: [PATCH 02/22] Remove temporary code --- .../jabref/http/server/mwessl/GServer.java | 66 ------------------- .../http/server/mwessl/SimpleHttpHandler.java | 16 ----- .../http/server/mwessl/package-info.java | 8 --- 3 files changed, 90 deletions(-) delete mode 100644 src/test/java/org/jabref/http/server/mwessl/GServer.java delete mode 100644 src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java delete mode 100644 src/test/java/org/jabref/http/server/mwessl/package-info.java diff --git a/src/test/java/org/jabref/http/server/mwessl/GServer.java b/src/test/java/org/jabref/http/server/mwessl/GServer.java deleted file mode 100644 index c5b028f316a..00000000000 --- a/src/test/java/org/jabref/http/server/mwessl/GServer.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.jabref.http.server.mwessl; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.jabref.http.server.Server; - -import org.glassfish.grizzly.Grizzly; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import org.glassfish.grizzly.http.server.ServerConfiguration; -import org.glassfish.grizzly.ssl.SSLContextConfigurator; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; - -/** - * Secured standalone Java HTTP server. - */ -public class GServer { - private static final Logger LOGGER = Grizzly.logger(Server.class); - - public static void main(String[] args) { - final HttpServer server = new HttpServer(); - final ServerConfiguration config = server.getServerConfiguration(); - - // Register simple HttpHandler - config.addHttpHandler(new SimpleHttpHandler(), "/"); - - // create a network listener that listens on port 8080. - final NetworkListener networkListener = new NetworkListener("secured-listener", NetworkListener.DEFAULT_NETWORK_HOST, - NetworkListener.DEFAULT_NETWORK_PORT); - - // Enable SSL on the listener - networkListener.setSecure(true); - networkListener.setSSLEngineConfig(createSslConfiguration()); - - server.addListener(networkListener); - try { - // Start the server - server.start(); - System.out.println("The secured server is running.\nhttps://localhost:" + NetworkListener.DEFAULT_NETWORK_PORT + "\nPress enter to stop..."); - System.in.read(); - } catch (IOException ioe) { - LOGGER.log(Level.SEVERE, ioe.toString(), ioe); - } finally { - server.shutdownNow(); - } - } - - /** - * Initialize server side SSL configuration. - * - * @return server side {@link SSLEngineConfigurator}. - */ - private static SSLEngineConfigurator createSslConfiguration() { - // Initialize SSLContext configuration - SSLContextConfigurator sslContextConfig = new SSLContextConfigurator(); - - sslContextConfig.setKeyStoreFile(Path.of("C:\\users\\koppor\\.keystore").toString()); - sslContextConfig.setKeyStorePass("changeit"); - - // Create SSLEngine configurator - return new SSLEngineConfigurator(sslContextConfig.createSSLContext(), false, false, false); - } -} diff --git a/src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java b/src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java deleted file mode 100644 index 58722b8cd1f..00000000000 --- a/src/test/java/org/jabref/http/server/mwessl/SimpleHttpHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jabref.http.server.mwessl; - -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; - -/** - * Simple {@link HttpHandler} implementation. - */ -public class SimpleHttpHandler extends HttpHandler { - - public void service(final Request request, final Response response) throws Exception { - response.setContentType("text/plain"); - response.getWriter().write("Hello world!"); - } -} diff --git a/src/test/java/org/jabref/http/server/mwessl/package-info.java b/src/test/java/org/jabref/http/server/mwessl/package-info.java deleted file mode 100644 index d74acbe1011..00000000000 --- a/src/test/java/org/jabref/http/server/mwessl/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Temporary code. - * - * Code based on https://github.com/eclipse-ee4j/grizzly/tree/master/samples/http-server-samples/src/main/java/org/glassfish/grizzly/samples/httpserver/secure - * - * BSD-3-Clause license - */ -package org.jabref.http.server.mwessl; From 8f0567757182a832e628f7958981d032eaaee241 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 9 Apr 2023 01:17:10 +0200 Subject: [PATCH 03/22] Fix checkstyle --- src/main/java/org/jabref/model/entry/BibEntry.java | 2 -- src/main/java/org/jabref/preferences/PreferencesService.java | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index 3ec0822ec3e..5f5301d633a 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -918,7 +918,6 @@ public BibEntry withFields(Map content) { return this; } - public BibEntry withDate(Date date) { setDate(date); this.setChanged(false); @@ -1158,5 +1157,4 @@ public void mergeWith(BibEntry other, Set otherPrioritizedFields) { } } } - } diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index fba07fb2006..e4839db64f8 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -42,6 +42,7 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; import org.jabref.model.metadata.SaveOrderConfig; + import org.jvnet.hk2.annotations.Contract; @Contract From 057e982ec4fc60f37872c96e1fbd5b0aca59c815 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 10 Apr 2023 12:59:22 +0200 Subject: [PATCH 04/22] Create http-server.md with a how-to for the SSL certificate generation --- docs/code-howtos/http-server.md | 16 +++++++++++ .../java/org/jabref/http/server/Server.java | 28 +++++++++---------- .../org/jabref/http/server/ServerTest.java | 8 ++++++ 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 docs/code-howtos/http-server.md diff --git a/docs/code-howtos/http-server.md b/docs/code-howtos/http-server.md new file mode 100644 index 00000000000..f3fa5d91578 --- /dev/null +++ b/docs/code-howtos/http-server.md @@ -0,0 +1,16 @@ +--- +parent: Code Howtos +--- +# HTTP Server + +## Get SSL Working + +(Based on ) + +Howto vor Windows - other operating systems work similar: + +1. As admin `choco install mkcert` +2. As admin: `mkcert -install` +3. `cd %APPDATA%\..\local\org.jabref\jabref\ssl` +4. `mkcert -pkcs12 jabref.desktop jabref localhost 127.0.0.1 ::1` +5. Rename the file to `server.p12` diff --git a/src/main/java/org/jabref/http/server/Server.java b/src/main/java/org/jabref/http/server/Server.java index 92ed926ba6e..88d57869da4 100644 --- a/src/main/java/org/jabref/http/server/Server.java +++ b/src/main/java/org/jabref/http/server/Server.java @@ -1,6 +1,7 @@ package org.jabref.http.server; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.CountDownLatch; @@ -42,20 +43,19 @@ static void startServer(CountDownLatch latch) { private static SSLContext getSslContext() { SSLContextConfigurator sslContextConfig = new SSLContextConfigurator(); - - // "server.jks" Needs to be generated using following command inside that directory: - // keytool -genkey -keyalg RSA -alias selfsigned -keystore server.jks -storepass changeit -validity 365 -keysize 2048 -dname "CN=localhost, OU=YourOrganizationUnit, O=YourOrganization, L=YourCity, S=YourState, C=YourCountry" - - String keystorePath = Path.of(AppDirsFactory.getInstance() - .getUserDataDir( - OS.APP_DIR_APP_NAME, - "ssl", - OS.APP_DIR_APP_AUTHOR)) - .resolve("server.p12").toString(); - - sslContextConfig.setKeyStoreFile(keystorePath); - sslContextConfig.setKeyStorePass("changeit"); - + Path serverKeyStore = Path.of(AppDirsFactory.getInstance() + .getUserDataDir( + OS.APP_DIR_APP_NAME, + "ssl", + OS.APP_DIR_APP_AUTHOR)) + .resolve("server.p12"); + if (Files.exists(serverKeyStore)) { + sslContextConfig.setKeyStoreFile(serverKeyStore.toString()); + sslContextConfig.setKeyStorePass("changeit"); + } else { + LOGGER.error("Could not find server key store {}.", serverKeyStore); + LOGGER.error("One create one by following the steps described in [http-server.md](/docs/code-howtos/http-server.md), which is rendered at "); + } return sslContextConfig.createSSLContext(); } diff --git a/src/test/java/org/jabref/http/server/ServerTest.java b/src/test/java/org/jabref/http/server/ServerTest.java index 9395cc93171..039b0e68131 100644 --- a/src/test/java/org/jabref/http/server/ServerTest.java +++ b/src/test/java/org/jabref/http/server/ServerTest.java @@ -24,6 +24,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +/** + * Abstract test class to + *
    + *
  • Initialize the JCL to SLF4J bridge
  • + *
  • Provide injection capabilities of JabRef's preferences and Gson<./li> + *
+ *

More information on testing with Jersey is available at the Jersey's testing documentation

. + */ abstract class ServerTest extends JerseyTest { private static PreferencesService preferencesService; From be1266495086b9057b2b155a10c9bf1b4b185a10 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 20 Apr 2023 11:41:31 +0200 Subject: [PATCH 05/22] Enables passing files to a test Server --- build.gradle | 6 ++ .../java/org/jabref/http/server/Server.java | 62 ++++++++++++++++++- .../java/org/jabref/logic/git/GitHandler.java | 2 +- .../jabref/http/server/http-server-demo.bib | 14 +++++ src/main/resources/tinylog.properties | 2 + .../org/jabref/http/server/TestServer.java | 14 ----- src/test/resources/testbib/jabref-authors.bib | 13 ++++ 7 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 src/main/resources/org/jabref/http/server/http-server-demo.bib delete mode 100644 src/test/java/org/jabref/http/server/TestServer.java diff --git a/build.gradle b/build.gradle index ac0799e23ee..5c012f27903 100644 --- a/build.gradle +++ b/build.gradle @@ -401,6 +401,12 @@ run { 'javafx.base/com.sun.javafx.event' : 'com.jfoenix' ] } + + if (project.hasProperty('application')){ + if (application == 'httpserver'){ + main = 'org.jabref.http.server.Server' + } + } } javadoc { diff --git a/src/main/java/org/jabref/http/server/Server.java b/src/main/java/org/jabref/http/server/Server.java index 88d57869da4..dc8411e8aa9 100644 --- a/src/main/java/org/jabref/http/server/Server.java +++ b/src/main/java/org/jabref/http/server/Server.java @@ -1,26 +1,81 @@ package org.jabref.http.server; import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.CountDownLatch; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import javax.net.ssl.SSLContext; +import javafx.collections.ObservableList; + import org.jabref.logic.util.OS; +import org.jabref.preferences.JabRefPreferences; import jakarta.ws.rs.SeBootstrap; import net.harawata.appdirs.AppDirsFactory; import org.glassfish.grizzly.ssl.SSLContextConfigurator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; public class Server { private static final Logger LOGGER = LoggerFactory.getLogger(Server.class); private static SeBootstrap.Instance serverInstance; - static void startServer(CountDownLatch latch) { + /** + * Starts an http server serving the last files opened in JabRef
+ * More files can be provided as args. + */ + public static void main(final String[] args) throws InterruptedException, URISyntaxException { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + + final ObservableList lastFilesOpened = JabRefPreferences.getInstance().getGuiPreferences().getLastFilesOpened(); + + // The server serves the last opened files (see org.jabref.http.server.LibraryResource.getLibraryPath) + // In a testing environment, this might be difficult to handle + // This is a quick solution. The architectural fine solution would use some http context or other @Inject_ed variables in org.jabref.http.server.LibraryResource + if (args.length > 0) { + LOGGER.debug("Command line parameters passed"); + List filesToAdd = Arrays.stream(args) + .map(Path::of) + .filter(Files::exists) + .map(Path::toString) + .filter(path -> !lastFilesOpened.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 + Collections.reverse(filesToAdd); + for (String path : filesToAdd) { + lastFilesOpened.add(0, path); + } + } + + if (lastFilesOpened.isEmpty()) { + LOGGER.debug("still no library available to serve, serve 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.toString()); + } + + LOGGER.debug("Libraries served: {}", lastFilesOpened); + + Server.startServer(); + + // Keep the http server running until user kills the process (e.g., presses Ctrl+C) + Thread.currentThread().join(); + } + + private static void startServer() { SSLContext sslContext = getSslContext(); SeBootstrap.Configuration configuration = SeBootstrap.Configuration .builder() @@ -28,7 +83,9 @@ static void startServer(CountDownLatch latch) { .protocol("HTTPS") .port(6051) .build(); + LOGGER.debug("Starting server..."); SeBootstrap.start(Application.class, configuration).thenAccept(instance -> { + LOGGER.debug("Server started."); instance.stopOnShutdown(stopResult -> System.out.printf("Stop result: %s [Native stop result: %s].%n", stopResult, stopResult.unwrap(Object.class))); @@ -37,7 +94,6 @@ static void startServer(CountDownLatch latch) { instance.unwrap(Object.class)); System.out.println("Send SIGKILL to shutdown."); serverInstance = instance; - latch.countDown(); }); } diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 498e5c5f3df..e865e9b9299 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -71,7 +71,7 @@ void setupGitIgnore() { FileUtil.copyFile(Path.of(this.getClass().getResource("git.gitignore").toURI()), gitignore, false); } } catch (URISyntaxException e) { - LOGGER.error("Error occurred during copying of the gitignore file into the git repository."); + LOGGER.error("Error occurred during copying of the gitignore file into the git repository.", e); } } diff --git a/src/main/resources/org/jabref/http/server/http-server-demo.bib b/src/main/resources/org/jabref/http/server/http-server-demo.bib new file mode 100644 index 00000000000..f5374fef3ad --- /dev/null +++ b/src/main/resources/org/jabref/http/server/http-server-demo.bib @@ -0,0 +1,14 @@ +@InProceedings{Kopp2018, + author = {Kopp, Oliver and Armbruster, Anita and Zimmermann, Olaf}, + booktitle = {Proceedings of the 10th Central European Workshop on Services and their Composition ({ZEUS} 2018)}, + title = {Markdown Architectural Decision Records: Format and Tool Support}, + year = {2018}, + editor = {Nico Herzberg and Christoph Hochreiner and Oliver Kopp and J{\"{o}}rg Lenhard}, + pages = {55--62}, + publisher = {CEUR-WS.org}, + series = {{CEUR} Workshop Proceedings}, + volume = {2072}, + keywords = {ADR, MADR, architecture decision records, architectural decision records, Nygard}, +} + +@Comment{jabref-meta: databaseType:bibtex;} diff --git a/src/main/resources/tinylog.properties b/src/main/resources/tinylog.properties index b4340fa32e9..5347cea4ca5 100644 --- a/src/main/resources/tinylog.properties +++ b/src/main/resources/tinylog.properties @@ -7,3 +7,5 @@ writerAzure = application insights exception = strip: jdk.internal #level@org.jabref.model.entry.BibEntry = debug + +level@org.jabref.http.server.Server = debug diff --git a/src/test/java/org/jabref/http/server/TestServer.java b/src/test/java/org/jabref/http/server/TestServer.java deleted file mode 100644 index 6ad1edcd970..00000000000 --- a/src/test/java/org/jabref/http/server/TestServer.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.jabref.http.server; - -import java.util.concurrent.CountDownLatch; - -import org.slf4j.bridge.SLF4JBridgeHandler; - -public class TestServer { - public static void main(final String[] args) throws InterruptedException { - SLF4JBridgeHandler.removeHandlersForRootLogger(); - SLF4JBridgeHandler.install(); - Server.startServer(new CountDownLatch(1)); - Thread.currentThread().join(); - } -} diff --git a/src/test/resources/testbib/jabref-authors.bib b/src/test/resources/testbib/jabref-authors.bib index 87a7c5eeb46..8687068e425 100644 --- a/src/test/resources/testbib/jabref-authors.bib +++ b/src/test/resources/testbib/jabref-authors.bib @@ -3003,6 +3003,19 @@ @InProceedings{SimonDietzDiezEtAl2019 priority = {prio1}, } +@InProceedings{Kopp2018, + author = {Kopp, Oliver and Armbruster, Anita and Zimmermann, Olaf}, + booktitle = {Proceedings of the 10th Central European Workshop on Services and their Composition ({ZEUS} 2018)}, + date = {2018}, + title = {Markdown Architectural Decision Records: Format and Tool Support}, + editor = {Nico Herzberg and Christoph Hochreiner and Oliver Kopp and J{\"{o}}rg Lenhard}, + pages = {55--62}, + publisher = {CEUR-WS.org}, + series = {{CEUR} Workshop Proceedings}, + volume = {2072}, + keywords = {ADR, MADR, architecture decision records, architectural decision records, Nygard}, +} + @Comment{jabref-meta: databaseType:biblatex;} @Comment{jabref-meta: grouping: From 9cde0a6b283a523dc662d4f710e660bf92d08e11 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 3 Sep 2023 21:41:32 +0200 Subject: [PATCH 06/22] fix some gradle tasks --- build.gradle | 12 ++--- src/main/java/module-info.java | 1 + .../java/org/jabref/http/dto/BibEntryDTO.java | 2 +- .../citationstyle/JabRefItemDataProvider.java | 44 ++++++++++++++++--- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index c4ef64f8788..218c46a223f 100644 --- a/build.gradle +++ b/build.gradle @@ -460,7 +460,7 @@ task fetcherTest(type: Test) { } } -task guiTest(type: Test) { +tasks.register('guiTest', Test) { useJUnitPlatform { includeTags 'GUITest' } @@ -473,7 +473,7 @@ task guiTest(type: Test) { } // Test result tasks -task copyTestResources(type: Copy) { +tasks.register('copyTestResources', Copy) { from "${projectDir}/src/test/resources" into "${buildDir}/classes/test" } @@ -483,11 +483,13 @@ tasks.withType(Test) { reports.html.outputLocation.set(file("${reporting.baseDir}/${name}")) } -task jacocoPrepare() { +tasks.register('jacocoPrepare') { doFirst { // Ignore failures of tests - tasks.withType(Test) { - ignoreFailures = true + tasks.withType(Test).tap { + configureEach { + ignoreFailures = true + } } } } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index e4c66f4e4eb..ba516fee5fb 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -137,5 +137,6 @@ // other libraries requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; + requires de.saxsys.mvvmfx.validation; } diff --git a/src/main/java/org/jabref/http/dto/BibEntryDTO.java b/src/main/java/org/jabref/http/dto/BibEntryDTO.java index b9369d2566f..1e36564072d 100644 --- a/src/main/java/org/jabref/http/dto/BibEntryDTO.java +++ b/src/main/java/org/jabref/http/dto/BibEntryDTO.java @@ -4,8 +4,8 @@ import java.io.StringWriter; import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.exporter.BibWriter; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; diff --git a/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java b/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java index c4eac582e22..8cf86c8e287 100644 --- a/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java +++ b/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java @@ -31,8 +31,28 @@ import org.jbibtex.Key; /** - * Custom {@link ItemDataProvider} that allows to set the data so that we don't have to instantiate a new CSL object - * every time. + * Custom + * {@link + * ItemDataProvider} + * that + * allows + * to + * set + * the + * data + * so + * that + * we + * don't + * have + * to + * instantiate + * a + * new + * CSL + * object + * every + * time. */ public class JabRefItemDataProvider implements ItemDataProvider { @@ -51,7 +71,13 @@ public JabRefItemDataProvider() { } /** - * Converts the {@link BibEntry} into {@link CSLItemData}. + * Converts + * the + * {@link + * BibEntry} + * into + * {@link + * CSLItemData}. * *
* @@ -167,7 +193,15 @@ private CSLItemData bibEntryToCSLItemData(BibEntry originalBibEntry, BibDatabase } /** - * Fills the data with all entries in given bibDatabaseContext + * Fills + * the + * data + * with + * all + * entries + * in + * given + * bibDatabaseContext */ public void setData(BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { this.setData(bibDatabaseContext.getEntries(), bibDatabaseContext, entryTypesManager); @@ -210,6 +244,4 @@ public String toJson() { .map(String.class::cast) .collect(Collectors.joining(",", "[", "]")); } - - } From 7fb5100f83aca9dd05e7bd853fd6ff4a54ff6810 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 3 Sep 2023 22:25:13 +0200 Subject: [PATCH 07/22] fix test build --- .../java/org/jabref/http/server/ServerTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/jabref/http/server/ServerTest.java b/src/test/java/org/jabref/http/server/ServerTest.java index 039b0e68131..3785cf001d3 100644 --- a/src/test/java/org/jabref/http/server/ServerTest.java +++ b/src/test/java/org/jabref/http/server/ServerTest.java @@ -7,8 +7,7 @@ import javafx.collections.FXCollections; import org.jabref.http.dto.GsonFactory; -import org.jabref.logic.bibtex.FieldContentFormatterPreferences; -import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.preferences.BibEntryPreferences; import org.jabref.preferences.GuiPreferences; @@ -82,15 +81,14 @@ private static void initializePreferencesService() { when(importFormatPreferences.bibEntryPreferences()).thenReturn(bibEntryPreferences); when(bibEntryPreferences.getKeywordSeparator()).thenReturn(','); - FieldWriterPreferences fieldWriterPreferences = mock(FieldWriterPreferences.class); - when(preferencesService.getFieldWriterPreferences()).thenReturn(fieldWriterPreferences); - when(fieldWriterPreferences.isResolveStrings()).thenReturn(false); + FieldPreferences fieldWriterPreferences = mock(FieldPreferences.class); + when(preferencesService.getFieldPreferences()).thenReturn(fieldWriterPreferences); + when(fieldWriterPreferences.shouldResolveStrings()).thenReturn(false); // defaults are in {@link org.jabref.preferences.JabRefPreferences.NON_WRAPPABLE_FIELDS} - FieldContentFormatterPreferences fieldContentFormatterPreferences = new FieldContentFormatterPreferences(List.of()); + FieldPreferences fieldContentFormatterPreferences = new FieldPreferences(false, List.of(), List.of()); // used twice, once for reading and once for writing - when(importFormatPreferences.fieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences); - when(preferencesService.getFieldWriterPreferences().getFieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences); + when(importFormatPreferences.fieldPreferences()).thenReturn(fieldContentFormatterPreferences); guiPreferences = mock(GuiPreferences.class); when(preferencesService.getGuiPreferences()).thenReturn(guiPreferences); From 75e7de1d83d59fc446052e50e393f29bb0b5f405 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Sep 2023 22:28:13 +0200 Subject: [PATCH 08/22] Fix broken JavaDoc --- .../citationstyle/JabRefItemDataProvider.java | 42 ++----------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java b/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java index 8cf86c8e287..2310a7c6b25 100644 --- a/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java +++ b/src/main/java/org/jabref/logic/citationstyle/JabRefItemDataProvider.java @@ -31,28 +31,8 @@ import org.jbibtex.Key; /** - * Custom - * {@link - * ItemDataProvider} - * that - * allows - * to - * set - * the - * data - * so - * that - * we - * don't - * have - * to - * instantiate - * a - * new - * CSL - * object - * every - * time. + * Custom {@link ItemDataProvider} that allows to set the data so that we don't have to instantiate a new CSL object + * every time. */ public class JabRefItemDataProvider implements ItemDataProvider { @@ -71,13 +51,7 @@ public JabRefItemDataProvider() { } /** - * Converts - * the - * {@link - * BibEntry} - * into - * {@link - * CSLItemData}. + * Converts the {@link BibEntry} into {@link CSLItemData}. * *
*
@@ -193,15 +167,7 @@ private CSLItemData bibEntryToCSLItemData(BibEntry originalBibEntry, BibDatabase } /** - * Fills - * the - * data - * with - * all - * entries - * in - * given - * bibDatabaseContext + * Fills the data with all entries in given bibDatabaseContext */ public void setData(BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager) { this.setData(bibDatabaseContext.getEntries(), bibDatabaseContext, entryTypesManager); From ef1c34a1c8f9bbd2662f55cd3ac3e9b3eb3ddf95 Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 3 Sep 2023 22:29:18 +0200 Subject: [PATCH 09/22] upgrade jersey to 3.1.3 --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 218c46a223f..5694881f02f 100644 --- a/build.gradle +++ b/build.gradle @@ -222,15 +222,15 @@ dependencies { // Implementation of the API implementation 'org.glassfish.jersey.core:jersey-server:3.1.1' // injection framework - implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.1' + implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.3' implementation 'org.glassfish.hk2:hk2-api:2.6.1' // testImplementation 'org.glassfish.hk2:hk2-testing:3.0.4' // implementation 'org.glassfish.hk2:hk2-testing-jersey:3.0.4' // testImplementation 'org.glassfish.hk2:hk2-junitrunner:3.0.4' // 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.1' - testImplementation 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:3.1.1' + implementation 'org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.3' + testImplementation 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:3.1.3' // Allow objects "magically" to be mapped to JSON using GSON // implementation 'org.glassfish.jersey.media:jersey-media-json-gson:3.1.1' @@ -436,7 +436,7 @@ testlogger { showSkipped false } -task databaseTest(type: Test) { +tasks.register('databaseTest', Test) { useJUnitPlatform { includeTags 'DatabaseTest' } @@ -448,7 +448,7 @@ task databaseTest(type: Test) { } } -task fetcherTest(type: Test) { +tasks.register('fetcherTest', Test) { useJUnitPlatform { includeTags 'FetcherTest' } From 94a05ca53946f128f87b0358d438e34f2238488e Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Sep 2023 22:38:06 +0200 Subject: [PATCH 10/22] Fix checkstyle --- src/test/java/org/jabref/http/server/ServerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/jabref/http/server/ServerTest.java b/src/test/java/org/jabref/http/server/ServerTest.java index 3785cf001d3..657a340e7c3 100644 --- a/src/test/java/org/jabref/http/server/ServerTest.java +++ b/src/test/java/org/jabref/http/server/ServerTest.java @@ -86,7 +86,7 @@ private static void initializePreferencesService() { when(fieldWriterPreferences.shouldResolveStrings()).thenReturn(false); // defaults are in {@link org.jabref.preferences.JabRefPreferences.NON_WRAPPABLE_FIELDS} - FieldPreferences fieldContentFormatterPreferences = new FieldPreferences(false, List.of(), List.of()); + FieldPreferences fieldContentFormatterPreferences = new FieldPreferences(false, List.of(), List.of()); // used twice, once for reading and once for writing when(importFormatPreferences.fieldPreferences()).thenReturn(fieldContentFormatterPreferences); From e09ead0eb9c668beea406f374a1270afa9aa57c2 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Sep 2023 23:20:00 +0200 Subject: [PATCH 11/22] Update http-server.md --- docs/code-howtos/http-server.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/code-howtos/http-server.md b/docs/code-howtos/http-server.md index f3fa5d91578..99585c50814 100644 --- a/docs/code-howtos/http-server.md +++ b/docs/code-howtos/http-server.md @@ -7,10 +7,14 @@ parent: Code Howtos (Based on ) -Howto vor Windows - other operating systems work similar: +Howto for Windows - other operating systems work similar: 1. As admin `choco install mkcert` 2. As admin: `mkcert -install` 3. `cd %APPDATA%\..\local\org.jabref\jabref\ssl` 4. `mkcert -pkcs12 jabref.desktop jabref localhost 127.0.0.1 ::1` 5. Rename the file to `server.p12` + +Note: If you do not do this, you get following error message: + + Could not find server key store C:\Users\USERNAME\AppData\Local\org.jabref\jabref\ssl\server.p12. From 29072fcb00494d312337c1082205bb5a51faa47f Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 3 Sep 2023 23:21:03 +0200 Subject: [PATCH 12/22] rewrite run --- src/main/java/org/jabref/http/server/LibraryResource.java | 5 +++-- .../java/org/jabref/http/server/LibrariesResourceTest.java | 3 ++- .../java/org/jabref/http/server/LibraryResourceTest.java | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/http/server/LibraryResource.java b/src/main/java/org/jabref/http/server/LibraryResource.java index 349cd272b3a..7a13aed89ed 100644 --- a/src/main/java/org/jabref/http/server/LibraryResource.java +++ b/src/main/java/org/jabref/http/server/LibraryResource.java @@ -6,6 +6,7 @@ import java.util.Objects; import org.jabref.gui.Globals; +import org.jabref.http.MediaType; import org.jabref.http.dto.BibEntryDTO; import org.jabref.logic.citationstyle.JabRefItemDataProvider; import org.jabref.logic.importer.ParserResult; @@ -50,7 +51,7 @@ public String getJson(@PathParam("id") String id) { } @GET - @Produces(org.jabref.http.MediaType.JSON_CSL_ITEM) + @Produces(MediaType.JSON_CSL_ITEM) public String getClsItemJson(@PathParam("id") String id) { ParserResult parserResult = getParserResult(id); JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); @@ -71,7 +72,7 @@ private ParserResult getParserResult(String id) { } @GET - @Produces(org.jabref.http.MediaType.BIBTEX) + @Produces(MediaType.BIBTEX) public Response getBibtex(@PathParam("id") String id) { java.nio.file.Path library = getLibraryPath(id); String libraryAsString; diff --git a/src/test/java/org/jabref/http/server/LibrariesResourceTest.java b/src/test/java/org/jabref/http/server/LibrariesResourceTest.java index df7d7784bae..1ec937d6daf 100644 --- a/src/test/java/org/jabref/http/server/LibrariesResourceTest.java +++ b/src/test/java/org/jabref/http/server/LibrariesResourceTest.java @@ -3,6 +3,7 @@ import java.util.EnumSet; import java.util.stream.Collectors; +import jakarta.ws.rs.core.Application; import org.glassfish.jersey.server.ResourceConfig; import org.junit.jupiter.api.Test; @@ -11,7 +12,7 @@ class LibrariesResourceTest extends ServerTest { @Override - protected jakarta.ws.rs.core.Application configure() { + protected Application configure() { ResourceConfig resourceConfig = new ResourceConfig(LibrariesResource.class); addPreferencesToResourceConfig(resourceConfig); return resourceConfig.getApplication(); diff --git a/src/test/java/org/jabref/http/server/LibraryResourceTest.java b/src/test/java/org/jabref/http/server/LibraryResourceTest.java index 650999edab2..2470c210b5c 100644 --- a/src/test/java/org/jabref/http/server/LibraryResourceTest.java +++ b/src/test/java/org/jabref/http/server/LibraryResourceTest.java @@ -2,6 +2,7 @@ import org.jabref.http.MediaType; +import jakarta.ws.rs.core.Application; import org.glassfish.jersey.server.ResourceConfig; import org.junit.jupiter.api.Test; @@ -10,7 +11,7 @@ class LibraryResourceTest extends ServerTest { @Override - protected jakarta.ws.rs.core.Application configure() { + protected Application configure() { ResourceConfig resourceConfig = new ResourceConfig(LibraryResource.class, LibrariesResource.class); addPreferencesToResourceConfig(resourceConfig); addGsonToResourceConfig(resourceConfig); From 59788d03bdefbe0f342983a6652592a1854a915f Mon Sep 17 00:00:00 2001 From: Siedlerchr Date: Sun, 3 Sep 2023 23:23:36 +0200 Subject: [PATCH 13/22] rename to avoid clash --- .../jabref/http/{MediaType.java => JabrefMediaType.java} | 2 +- src/main/java/org/jabref/http/server/LibraryResource.java | 6 +++--- .../java/org/jabref/http/server/LibraryResourceTest.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/main/java/org/jabref/http/{MediaType.java => JabrefMediaType.java} (85%) diff --git a/src/main/java/org/jabref/http/MediaType.java b/src/main/java/org/jabref/http/JabrefMediaType.java similarity index 85% rename from src/main/java/org/jabref/http/MediaType.java rename to src/main/java/org/jabref/http/JabrefMediaType.java index b37297ebd71..b1d3598867e 100644 --- a/src/main/java/org/jabref/http/MediaType.java +++ b/src/main/java/org/jabref/http/JabrefMediaType.java @@ -1,6 +1,6 @@ package org.jabref.http; -public class MediaType { +public class JabrefMediaType { public static final String BIBTEX = "application/x-bibtex"; public static final String JSON_CSL_ITEM = "application/x-bibtex-library-csl+json"; } diff --git a/src/main/java/org/jabref/http/server/LibraryResource.java b/src/main/java/org/jabref/http/server/LibraryResource.java index 7a13aed89ed..2ff4b970723 100644 --- a/src/main/java/org/jabref/http/server/LibraryResource.java +++ b/src/main/java/org/jabref/http/server/LibraryResource.java @@ -6,7 +6,7 @@ import java.util.Objects; import org.jabref.gui.Globals; -import org.jabref.http.MediaType; +import org.jabref.http.JabrefMediaType; import org.jabref.http.dto.BibEntryDTO; import org.jabref.logic.citationstyle.JabRefItemDataProvider; import org.jabref.logic.importer.ParserResult; @@ -51,7 +51,7 @@ public String getJson(@PathParam("id") String id) { } @GET - @Produces(MediaType.JSON_CSL_ITEM) + @Produces(JabrefMediaType.JSON_CSL_ITEM) public String getClsItemJson(@PathParam("id") String id) { ParserResult parserResult = getParserResult(id); JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); @@ -72,7 +72,7 @@ private ParserResult getParserResult(String id) { } @GET - @Produces(MediaType.BIBTEX) + @Produces(JabrefMediaType.BIBTEX) public Response getBibtex(@PathParam("id") String id) { java.nio.file.Path library = getLibraryPath(id); String libraryAsString; diff --git a/src/test/java/org/jabref/http/server/LibraryResourceTest.java b/src/test/java/org/jabref/http/server/LibraryResourceTest.java index 2470c210b5c..f4d43c12b77 100644 --- a/src/test/java/org/jabref/http/server/LibraryResourceTest.java +++ b/src/test/java/org/jabref/http/server/LibraryResourceTest.java @@ -1,6 +1,6 @@ package org.jabref.http.server; -import org.jabref.http.MediaType; +import org.jabref.http.JabrefMediaType; import jakarta.ws.rs.core.Application; import org.glassfish.jersey.server.ResourceConfig; @@ -28,12 +28,12 @@ void getJson() { } @Comment{jabref-meta: databaseType:bibtex;} - """, target("/libraries/" + TestBibFile.GENERAL_SERVER_TEST.id).request(MediaType.BIBTEX).get(String.class)); + """, target("/libraries/" + TestBibFile.GENERAL_SERVER_TEST.id).request(JabrefMediaType.BIBTEX).get(String.class)); } @Test void getClsItemJson() { assertEquals(""" - [{"id":"Author2023test","type":"article","author":[{"family":"Author","given":"Demo"}],"event-date":{"date-parts":[[2023]]},"issued":{"date-parts":[[2023]]},"title":"Demo Title"}]""", target("/libraries/" + TestBibFile.GENERAL_SERVER_TEST.id).request(MediaType.JSON_CSL_ITEM).get(String.class)); + [{"id":"Author2023test","type":"article","author":[{"family":"Author","given":"Demo"}],"event-date":{"date-parts":[[2023]]},"issued":{"date-parts":[[2023]]},"title":"Demo Title"}]""", target("/libraries/" + TestBibFile.GENERAL_SERVER_TEST.id).request(JabrefMediaType.JSON_CSL_ITEM).get(String.class)); } } From 018277e9855e365185cd9de880f9b79dd92e053c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Sep 2023 23:28:08 +0200 Subject: [PATCH 14/22] Hints on http server --- docs/code-howtos/http-server.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/code-howtos/http-server.md b/docs/code-howtos/http-server.md index 99585c50814..62fd3dad168 100644 --- a/docs/code-howtos/http-server.md +++ b/docs/code-howtos/http-server.md @@ -18,3 +18,7 @@ Howto for Windows - other operating systems work similar: Note: If you do not do this, you get following error message: Could not find server key store C:\Users\USERNAME\AppData\Local\org.jabref\jabref\ssl\server.p12. + +## Start http server + +The class starting the server is `org.jabref.http.server.Server`. From 1c15c60321da99d5b892e91b9e0db55df4364cee Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Sep 2023 23:33:54 +0200 Subject: [PATCH 15/22] Update http-server.md --- docs/code-howtos/http-server.md | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/code-howtos/http-server.md b/docs/code-howtos/http-server.md index 62fd3dad168..4db0fdbc406 100644 --- a/docs/code-howtos/http-server.md +++ b/docs/code-howtos/http-server.md @@ -22,3 +22,41 @@ Note: If you do not do this, you get following error message: ## Start http server The class starting the server is `org.jabref.http.server.Server`. + +Test files to server can be passed as arguments. +If no files are passed, the last opened files are served. +If that list is also empty, the file `src/main/resources/org/jabref/http/server/http-server-demo.bib` is served. + +### Starting with gradle + +Does not work. + +Current try: + + ./gradlew run -Pcomment=httpserver + +However, there are with `ForkJoin` (discussion at https://discuss.gradle.org/t/is-it-ok-to-use-collection-parallelstream-or-other-potentially-multi-threaded-code-within-gradle-plugin-code/28003) + +Gradle output: + +``` +> Task :run +2023-04-22 11:30:59 [main] org.jabref.http.server.Server.main() +DEBUG: Libraries served: [C:\git-repositories\jabref-all\jabref\src\main\resources\org\jabref\http\server\http-server-demo.bib] +2023-04-22 11:30:59 [main] org.jabref.http.server.Server.startServer() +DEBUG: Starting server... +<============-> 92% EXECUTING [2m 27s] +> :run +``` + +IntelliJ output, if `org.jabref.http.server.Server#main` is executed: + +``` +DEBUG: Starting server... +2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.glassfish.grizzly.http.server.NetworkListener.start() +INFO: Started listener bound to [localhost:6051] +2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.glassfish.grizzly.http.server.HttpServer.start() +INFO: [HttpServer] Started. +2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.jabref.http.server.Server.lambda$startServer$4() +DEBUG: Server started. +``` From 2e9ae7df39f61a7d7cef691d619077eb57f3b21e Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 3 Sep 2023 23:38:56 +0200 Subject: [PATCH 16/22] Better parameter name --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5694881f02f..07a682f4139 100644 --- a/build.gradle +++ b/build.gradle @@ -393,8 +393,8 @@ run { ] } - if (project.hasProperty('application')){ - if (application == 'httpserver'){ + if (project.hasProperty('component')){ + if (component == 'httpserver'){ main = 'org.jabref.http.server.Server' } } From 2d9e8db3ad405f852a3398865d98b2eb3b17daeb Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 4 Sep 2023 00:56:34 +0200 Subject: [PATCH 17/22] Add missing "requires" --- src/main/java/module-info.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ba516fee5fb..1c2be7cf4a5 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -16,6 +16,8 @@ requires com.dlsc.gemsfx; uses com.dlsc.gemsfx.TagsField; requires de.saxsys.mvvmfx; + requires reactfx; + requires org.fxmisc.flowless; requires org.kordamp.ikonli.core; requires org.kordamp.ikonli.javafx; From bccf22d82283e657711471214c4e3727229eba9b Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 4 Sep 2023 01:09:05 +0200 Subject: [PATCH 18/22] Create resource path only if required --- src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java b/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java index 70c0bed0659..8573c968fc6 100644 --- a/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java +++ b/src/main/java/org/jabref/logic/net/ssl/TrustStoreManager.java @@ -139,9 +139,9 @@ public X509Certificate getCertificate(String alias) { public static void createTruststoreFileIfNotExist(Path storePath) { try { LOGGER.debug("Trust store path: {}", storePath.toAbsolutePath()); - Path storeResourcePath = Path.of(TrustStoreManager.class.getResource("/ssl/truststore.jks").toURI()); - Files.createDirectories(storePath.getParent()); if (Files.notExists(storePath)) { + Path storeResourcePath = Path.of(TrustStoreManager.class.getResource("/ssl/truststore.jks").toURI()); + Files.createDirectories(storePath.getParent()); Files.copy(storeResourcePath, storePath); } } catch (IOException e) { From 72d059398ccad07a5247736289a7686ad185bde3 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 4 Sep 2023 01:41:20 +0200 Subject: [PATCH 19/22] Refine howto --- .../intellij-12-build.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md index e1feb9355a7..bdd8959042e 100644 --- a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md @@ -61,6 +61,8 @@ Then double click inside the cell "Compilation options" and enter following para ```text --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref --add-exports=org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref +--add-reads org.jabref=org.fxmisc.flowless +--add-reads org.jabref=org.apache.commons.csv ``` Press Enter to have the value really stored. From 19e73e62cb5c0f1ca03d0d54fc7692701834c6fd Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 4 Sep 2023 01:45:20 +0200 Subject: [PATCH 20/22] Add IntelliJ hint --- docs/code-howtos/http-server.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/code-howtos/http-server.md b/docs/code-howtos/http-server.md index 4db0fdbc406..0652cbe70c0 100644 --- a/docs/code-howtos/http-server.md +++ b/docs/code-howtos/http-server.md @@ -60,3 +60,8 @@ INFO: [HttpServer] Started. 2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.jabref.http.server.Server.lambda$startServer$4() DEBUG: Server started. ``` + +## Developing with IntelliJ + +IntelliJ Ultimate offers a Markdown-based http-client. One has to open the file `src/test/java/org/jabref/testutils/interactive/http/rest-api.http`. +Then, there are play buttons appearing for interacting with the server. From 6f51b0764e724bbfa58e9e419e81662b7084d8de Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 4 Sep 2023 02:06:05 +0200 Subject: [PATCH 21/22] Add AllowedToUseStandardStreams --- src/main/java/org/jabref/http/server/Server.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/jabref/http/server/Server.java b/src/main/java/org/jabref/http/server/Server.java index dc8411e8aa9..e238dceb577 100644 --- a/src/main/java/org/jabref/http/server/Server.java +++ b/src/main/java/org/jabref/http/server/Server.java @@ -12,6 +12,7 @@ import javafx.collections.ObservableList; +import org.jabref.architecture.AllowedToUseStandardStreams; import org.jabref.logic.util.OS; import org.jabref.preferences.JabRefPreferences; @@ -22,6 +23,7 @@ import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; +@AllowedToUseStandardStreams("This is a CLI application. It resides in the package http.server to be close to the other http server related classes.") public class Server { private static final Logger LOGGER = LoggerFactory.getLogger(Server.class); From 8deec2448413a0589d4440bbd1538801e07b4b54 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 4 Sep 2023 02:14:28 +0200 Subject: [PATCH 22/22] Also exclude TestBibFile --- src/test/java/org/jabref/architecture/TestArchitectureTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/jabref/architecture/TestArchitectureTest.java b/src/test/java/org/jabref/architecture/TestArchitectureTest.java index e9e66c3600f..81f73e5a117 100644 --- a/src/test/java/org/jabref/architecture/TestArchitectureTest.java +++ b/src/test/java/org/jabref/architecture/TestArchitectureTest.java @@ -36,6 +36,7 @@ public void testsAreIndependent(JavaClasses classes) { public void testNaming(JavaClasses classes) { classes().that().areTopLevelClasses() .and().doNotHaveFullyQualifiedName("org.jabref.benchmarks.Benchmarks") + .and().doNotHaveFullyQualifiedName("org.jabref.http.server.TestBibFile") .and().doNotHaveFullyQualifiedName("org.jabref.gui.autocompleter.AutoCompleterUtil") .and().doNotHaveFullyQualifiedName("org.jabref.gui.search.TextFlowEqualityHelper") .and().doNotHaveFullyQualifiedName("org.jabref.logic.bibtex.BibEntryAssert")