diff --git a/.jbang/JabSrvLauncher.java b/.jbang/JabSrvLauncher.java index 353a8c75f97..161432bcc31 100755 --- a/.jbang/JabSrvLauncher.java +++ b/.jbang/JabSrvLauncher.java @@ -34,14 +34,15 @@ //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/command/Command.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/command/CommandResource.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/command/SelectEntriesCommand.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/resources/LibrariesResource.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/resources/LibraryResource.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/resources/MapResource.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/resources/RootResource.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/services/FilesToServe.java +//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/services/ServerUtils.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/CORSFilter.java -//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/LibrariesResource.java -//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/PreferencesFactory.java -//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/RootResource.java //SOURCES ../jabsrv/src/main/java/org/jabref/http/server/Server.java -//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/services/FilesToServe.java -//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/services/ServerUtils.java // REPOS mavencentral,snapshots=https://central.sonatype.com/repository/maven-snapshots/ // REPOS mavencentral,mavencentralsnapshots=https://central.sonatype.com/repository/maven-snapshots/,s01oss=https://s01.oss.sonatype.org/content/repositories/snapshots/,oss=https://oss.sonatype.org/content/repositories,jitpack=https://jitpack.io,oss2=https://oss.sonatype.org/content/groups/public,ossrh=https://oss.sonatype.org/content/repositories/snapshots diff --git a/docs/code-howtos/http-server.md b/docs/code-howtos/http-server.md index 4444f3eeeca..6e69d197374 100644 --- a/docs/code-howtos/http-server.md +++ b/docs/code-howtos/http-server.md @@ -6,7 +6,7 @@ parent: Code Howtos JabRef has a built-in http server. The source is located in the project `jabsrv`. -The resource for a library is implemented at [`org.jabref.http.server.LibraryResource`](https://github.com/JabRef/jabref/blob/main/jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java). +The resource for a library is implemented at [`org.jabref.http.server.resources.LibraryResource`](https://github.com/JabRef/jabref/blob/main/jabsrv/src/main/java/org/jabref/http/server/resources/LibraryResource.java). ## Start http server diff --git a/jabsrv-cli/src/main/java/org/jabref/http/server/cli/ServerCli.java b/jabsrv-cli/src/main/java/org/jabref/http/server/cli/ServerCli.java index c590639aebb..64465089090 100644 --- a/jabsrv-cli/src/main/java/org/jabref/http/server/cli/ServerCli.java +++ b/jabsrv-cli/src/main/java/org/jabref/http/server/cli/ServerCli.java @@ -44,7 +44,7 @@ public static void main(final String[] args) throws InterruptedException { @Override public Void call() throws InterruptedException { - // The server serves the last opened files (see org.jabref.http.server.LibraryResource.getLibraryPath) + // The server serves the last opened files (see org.jabref.http.server.resources.LibraryResource.getLibraryPath) final List filesToServe = new ArrayList<>(JabRefCliPreferences.getInstance().getLastFilesOpenedPreferences().getLastFilesOpened()); // Additionally, files can be provided as args diff --git a/jabsrv/src/main/java/module-info.java b/jabsrv/src/main/java/module-info.java index 5f9b180b771..b94c17b39c8 100644 --- a/jabsrv/src/main/java/module-info.java +++ b/jabsrv/src/main/java/module-info.java @@ -13,6 +13,8 @@ opens org.jabref.http.server.command to com.google.gson, org.glassfish.hk2.locator, org.glassfish.hk2.utilities, com.fasterxml.jackson.databind; exports org.jabref.http.server.services; exports org.jabref.http; + opens org.jabref.http.server.resources to org.glassfish.hk2.locator, org.glassfish.hk2.utilities; + exports org.jabref.http.server.resources; requires javafx.base; @@ -58,5 +60,4 @@ requires jersey.server; requires com.fasterxml.jackson.annotation; requires com.fasterxml.jackson.databind; - } diff --git a/jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java b/jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java deleted file mode 100644 index 0db9294d281..00000000000 --- a/jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.jabref.http.server; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.jabref.http.JabrefMediaType; -import org.jabref.http.SrvStateManager; -import org.jabref.http.dto.BibEntryDTO; -import org.jabref.http.dto.LinkedPdfFileDTO; -import org.jabref.http.server.services.FilesToServe; -import org.jabref.http.server.services.ServerUtils; -import org.jabref.logic.citationstyle.JabRefItemDataProvider; -import org.jabref.logic.preferences.CliPreferences; -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.LinkedFile; -import org.jabref.model.entry.field.StandardField; - -import com.airhacks.afterburner.injection.Injector; -import com.google.gson.Gson; -import jakarta.inject.Inject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.InternalServerErrorException; -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.PUT; -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 jakarta.ws.rs.core.StreamingOutput; -import org.jspecify.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Path("libraries/{id}") -public class LibraryResource { - private static final Logger LOGGER = LoggerFactory.getLogger(LibraryResource.class); - - @Inject - CliPreferences preferences; - - @Inject - SrvStateManager srvStateManager; - - @Inject - FilesToServe filesToServe; - - @Inject - Gson gson; - - /** - * At http://localhost:23119/libraries/{id} - * - * @param id The specified library - * @return specified library in JSON format - * @throws IOException - */ - @GET - @Produces(MediaType.APPLICATION_JSON) - public String getJson(@PathParam("id") String id) throws IOException { - BibDatabaseContext databaseContext = getDatabaseContext(id); - BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); - List list = databaseContext.getDatabase().getEntries().stream() - .peek(bibEntry -> bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry))) - .map(entry -> new BibEntryDTO(entry, databaseContext.getMode(), preferences.getFieldPreferences(), entryTypesManager)) - .toList(); - return gson.toJson(list); - } - - /** - * At http://localhost:23119/libraries/{id}/map

- *

- * Looks for the .jmp file in the directory of the given library ({id}.bib file). - * - * @param id The given library - * @return A JSON String containing the mindmap data. If no {id}.jmp file was found, returns the standard mindmap - * @throws IOException - */ - @GET - @Path("map") - @Produces(MediaType.APPLICATION_JSON) - public String getJabMapJson(@PathParam("id") String id) throws IOException { - boolean isDemo = "demo".equals(id); - java.nio.file.Path jabMapPath; - if (isDemo) { - jabMapPath = getJabMapDemoPath(); - } else { - jabMapPath = getJabMapPath(id); - } - // if no file is found, return the default mindmap - if (!Files.exists(jabMapPath)) { - return """ - { - "map": { - "meta": { - "name": "JabMap", - "author": "JabMap", - "version": "1.0" - }, - "format": "node_tree", - "data": { - "id": "root", - "topic": "JabMap", - "expanded": true, - "icons": [], - "highlight": null, - "type": "Text" - } - } - } - """; - } - return Files.readString(jabMapPath); - } - - /** - * At http://localhost:23119/libraries/{id}/map

- *

- * Saves the mindmap next to its associated library. - * - * @param id The given library - * @throws IOException - */ - @PUT - @Path("map") - @Consumes(MediaType.APPLICATION_JSON) - public void updateJabMapJson(@PathParam("id") String id, String fileContent) throws IOException { - boolean isDemo = "demo".equals(id); - java.nio.file.Path targetPath; - if (isDemo) { - targetPath = getJabMapDemoPath(); - } else { - targetPath = getJabMapPath(id); - } - Files.writeString(targetPath, fileContent); - } - - @GET - @Produces(JabrefMediaType.JSON_CSL_ITEM) - public String getClsItemJson(@PathParam("id") String id) throws IOException { - BibDatabaseContext databaseContext = getDatabaseContext(id); - JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); - jabRefItemDataProvider.setData(databaseContext, new BibEntryTypesManager()); - return jabRefItemDataProvider.toJson(); - } - - @GET - @Produces(JabrefMediaType.BIBTEX) - public Response getBibtex(@PathParam("id") String id) { - if ("demo".equals(id)) { - StreamingOutput stream = output -> { - try (InputStream in = getChocolateBibAsStream()) { - in.transferTo(output); - } - }; - - return Response.ok(stream) - // org.glassfish.jersey.media would be required for a "nice" Java to create ContentDisposition; we avoid this - .header("Content-Disposition", "attachment; filename=\"Chocolate.bib\"") - .build(); - } - - java.nio.file.Path library = ServerUtils.getLibraryPath(id, filesToServe, srvStateManager); - 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() - .header("Content-Disposition", "attachment; filename=\"" + library.getFileName() + "\"") - .entity(libraryAsString) - .build(); - } - - private java.nio.file.Path getJabMapPath(String id) { - java.nio.file.Path libraryPath = ServerUtils.getLibraryPath(id, filesToServe, srvStateManager); - String newName = libraryPath.getFileName().toString().replaceFirst("\\.bib$", ".jmp"); - return libraryPath.getParent().resolve(newName); - } - - private java.nio.file.Path getJabMapDemoPath() { - java.nio.file.Path result = java.nio.file.Path.of(System.getProperty("java.io.tmpdir")).resolve("demo.jmp"); - LOGGER.debug("Using temporary file for demo jmp: {}", result); - return result; - } - - /** - * @param id - also "demo" for the Chocolate.bib file - */ - private BibDatabaseContext getDatabaseContext(String id) throws IOException { - return ServerUtils.getBibDatabaseContext(id, filesToServe, srvStateManager, preferences.getImportFormatPreferences()); - } - - /** - * At http://localhost:23119/libraries/{id}/entries/{entryId}

- *

- * Combines attributes of a given BibEntry into a basic entry preview for as plain text. - * - * @param id The name of the library - * @param entryId The CitationKey of the BibEntry - * @return a basic entry preview as plain text - * @throws IOException - * @throws NotFoundException - */ - @GET - @Path("entries/{entryId}") - @Produces(MediaType.TEXT_PLAIN + ";charset=UTF-8") - public String getPlainRepresentation(@PathParam("id") String id, @PathParam("entryId") String entryId) throws IOException { - BibDatabaseContext databaseContext = getDatabaseContext(id); - List entriesByCitationKey = databaseContext.getDatabase().getEntriesByCitationKey(entryId); - if (entriesByCitationKey.isEmpty()) { - throw new NotFoundException("Entry with citation key '" + entryId + "' not found in library " + id); - } - if (entriesByCitationKey.size() > 1) { - LOGGER.warn("Multiple entries found with citation key '{}'. Using the first one.", entryId); - } - - // TODO: Currently, the preview preferences are in GUI package, which is not accessible here. - // build the preview - BibEntry entry = entriesByCitationKey.getFirst(); - - String author = entry.getField(StandardField.AUTHOR).orElse("(N/A)"); - String title = entry.getField(StandardField.TITLE).orElse("(N/A)"); - String journal = entry.getField(StandardField.JOURNAL).orElse("(N/A)"); - String volume = entry.getField(StandardField.VOLUME).orElse("(N/A)"); - String number = entry.getField(StandardField.NUMBER).orElse("(N/A)"); - String pages = entry.getField(StandardField.PAGES).orElse("(N/A)"); - String releaseDate = entry.getField(StandardField.DATE).orElse("(N/A)"); - - // the only difference to the HTML version of this method is the format of the output: - String preview = - "Author: " + author - + "\nTitle: " + title - + "\nJournal: " + journal - + "\nVolume: " + volume - + "\nNumber: " + number - + "\nPages: " + pages - + "\nReleased on: " + releaseDate; - - return preview; - } - - /** - * At http://localhost:23119/libraries/{id}/entries/{entryId}

- *

- * Combines attributes of a given BibEntry into a basic entry preview for as HTML text. - * - * @param id The name of the library - * @param entryId The CitationKey of the BibEntry - * @return a basic entry preview as HTML text - * @throws IOException - */ - @GET - @Path("entries/{entryId}") - @Produces(MediaType.TEXT_HTML + ";charset=UTF-8") - public String getHTMLRepresentation(@PathParam("id") String id, @PathParam("entryId") String entryId) throws IOException { - List entriesByCitationKey = getDatabaseContext(id).getDatabase().getEntriesByCitationKey(entryId); - if (entriesByCitationKey.isEmpty()) { - throw new NotFoundException("Entry with citation key '" + entryId + "' not found in library " + id); - } - if (entriesByCitationKey.size() > 1) { - LOGGER.warn("Multiple entries found with citation key '{}'. Using the first one.", entryId); - } - - // TODO: Currently, the preview preferences are in GUI package, which is not accessible here. - // build the preview - BibEntry entry = entriesByCitationKey.getFirst(); - - String author = entry.getField(StandardField.AUTHOR).orElse("(N/A)"); - String title = entry.getField(StandardField.TITLE).orElse("(N/A)"); - String journal = entry.getField(StandardField.JOURNAL).orElse("(N/A)"); - String volume = entry.getField(StandardField.VOLUME).orElse("(N/A)"); - String number = entry.getField(StandardField.NUMBER).orElse("(N/A)"); - String pages = entry.getField(StandardField.PAGES).orElse("(N/A)"); - String releaseDate = entry.getField(StandardField.DATE).orElse("(N/A)"); - - // the only difference to the plain text version of this method is the format of the output: - String preview = - "Author: " + author + "
" + - "Title: " + title + "
" + - "Journal: " + journal + "
" + - "Volume: " + volume + "
" + - "Number: " + number + "
" + - "Pages: " + pages + "
" + - "Released on: " + releaseDate; - - return preview; - } - - /** - * At http://localhost:23119/libraries/{id}/entries/pdffiles

- *

- * Loops through all entries in the specified library and adds attached files of type "PDF" to - * a list and JSON serialises it. - */ - @GET - @Path("entries/pdffiles") - @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") - public String getPDFFilesAsList(@PathParam("id") String id) throws IOException { - // get a list of all entries in library (specified by "id") - BibDatabaseContext databaseContext = getDatabaseContext(id); - List response = new ArrayList<>(); - List entries = databaseContext.getDatabase().getEntries(); - if (entries.isEmpty()) { - throw new NotFoundException("No entries found for library: " + id); - } - - // loop through all entries to extract pdfs and paths - for (BibEntry entry : entries) { - List pathsToFiles = entry.getFiles(); - if (!pathsToFiles.isEmpty()) { - for (LinkedFile file : pathsToFiles) { - // ignore all non pdf files and online references - if (!"PDF".equals(file.getFileType()) || LinkedFile.isOnlineLink(file.getLink())) { - continue; - } - // add file to response body - LinkedPdfFileDTO localPdfFile = new LinkedPdfFileDTO(entry, file); - response.add(localPdfFile); - } - } - } - return gson.toJson(response); - } - - /** - * @return a stream to the Chocolate.bib file in the classpath (is null only if the file was moved or there are issues with the classpath) - */ - private @Nullable InputStream getChocolateBibAsStream() { - return BibDatabase.class.getResourceAsStream("/Chocolate.bib"); - } -} diff --git a/jabsrv/src/main/java/org/jabref/http/server/Server.java b/jabsrv/src/main/java/org/jabref/http/server/Server.java index 72a79fd707a..2a148fc2dae 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/Server.java +++ b/jabsrv/src/main/java/org/jabref/http/server/Server.java @@ -14,6 +14,10 @@ import org.jabref.http.server.cayw.CAYWResource; import org.jabref.http.server.cayw.format.FormatterService; import org.jabref.http.server.command.CommandResource; +import org.jabref.http.server.resources.LibrariesResource; +import org.jabref.http.server.resources.LibraryResource; +import org.jabref.http.server.resources.MapResource; +import org.jabref.http.server.resources.RootResource; import org.jabref.http.server.services.FilesToServe; import org.jabref.logic.os.OS; @@ -92,11 +96,18 @@ private HttpServer startServer(ServiceLocator serviceLocator, URI uri) { final ResourceConfig resourceConfig = new ResourceConfig(); resourceConfig.property("jersey.config.server.wadl.disableWadl", true); // TODO: Add SSL + + // RESTish resources resourceConfig.register(RootResource.class); resourceConfig.register(LibrariesResource.class); resourceConfig.register(LibraryResource.class); - resourceConfig.register(CAYWResource.class); + resourceConfig.register(MapResource.class); + + // Other resources resourceConfig.register(CommandResource.class); + resourceConfig.register(CAYWResource.class); + + // Supporting classes resourceConfig.register(CORSFilter.class); resourceConfig.register(GlobalExceptionMapper.class); diff --git a/jabsrv/src/main/java/org/jabref/http/server/LibrariesResource.java b/jabsrv/src/main/java/org/jabref/http/server/resources/LibrariesResource.java similarity index 97% rename from jabsrv/src/main/java/org/jabref/http/server/LibrariesResource.java rename to jabsrv/src/main/java/org/jabref/http/server/resources/LibrariesResource.java index 579f477c4f7..b8dbb9f9b5b 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/LibrariesResource.java +++ b/jabsrv/src/main/java/org/jabref/http/server/resources/LibrariesResource.java @@ -1,4 +1,4 @@ -package org.jabref.http.server; +package org.jabref.http.server.resources; import java.util.ArrayList; import java.util.List; diff --git a/jabsrv/src/main/java/org/jabref/http/server/resources/LibraryResource.java b/jabsrv/src/main/java/org/jabref/http/server/resources/LibraryResource.java new file mode 100644 index 00000000000..9a91c3628ac --- /dev/null +++ b/jabsrv/src/main/java/org/jabref/http/server/resources/LibraryResource.java @@ -0,0 +1,158 @@ +package org.jabref.http.server.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.jabref.http.JabrefMediaType; +import org.jabref.http.SrvStateManager; +import org.jabref.http.dto.BibEntryDTO; +import org.jabref.http.dto.LinkedPdfFileDTO; +import org.jabref.http.server.services.FilesToServe; +import org.jabref.http.server.services.ServerUtils; +import org.jabref.logic.citationstyle.JabRefItemDataProvider; +import org.jabref.logic.preferences.CliPreferences; +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.LinkedFile; + +import com.airhacks.afterburner.injection.Injector; +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 jakarta.ws.rs.core.StreamingOutput; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Path("libraries/{id}") +public class LibraryResource { + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryResource.class); + + @Inject + CliPreferences preferences; + + @Inject + SrvStateManager srvStateManager; + + @Inject + FilesToServe filesToServe; + + @Inject + Gson gson; + + /** + * At http://localhost:23119/libraries/{id} + * + * @param id The specified library + * @return specified library in JSON format + * @throws IOException + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public String getJson(@PathParam("id") String id) throws IOException { + BibDatabaseContext databaseContext = getDatabaseContext(id); + BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + List list = databaseContext.getDatabase().getEntries().stream() + .peek(bibEntry -> bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry))) + .map(entry -> new BibEntryDTO(entry, databaseContext.getMode(), preferences.getFieldPreferences(), entryTypesManager)) + .toList(); + return gson.toJson(list); + } + + @GET + @Produces(JabrefMediaType.JSON_CSL_ITEM) + public String getClsItemJson(@PathParam("id") String id) throws IOException { + BibDatabaseContext databaseContext = getDatabaseContext(id); + JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider(); + jabRefItemDataProvider.setData(databaseContext, new BibEntryTypesManager()); + return jabRefItemDataProvider.toJson(); + } + + @GET + @Produces(JabrefMediaType.BIBTEX) + public Response getBibtex(@PathParam("id") String id) { + if ("demo".equals(id)) { + StreamingOutput stream = output -> { + try (InputStream in = getChocolateBibAsStream()) { + in.transferTo(output); + } + }; + + return Response.ok(stream) + // org.glassfish.jersey.media would be required for a "nice" Java to create ContentDisposition; we avoid this + .header("Content-Disposition", "attachment; filename=\"Chocolate.bib\"") + .build(); + } + + java.nio.file.Path library = ServerUtils.getLibraryPath(id, filesToServe, srvStateManager); + 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() + .header("Content-Disposition", "attachment; filename=\"" + library.getFileName() + "\"") + .entity(libraryAsString) + .build(); + } + + /// Loops through all entries in the specified library and adds attached files of type "PDF" to + /// a list and JSON serialises it. + /// FIXME: JabMap should serve the files per BibEntry. See for details + @GET + @Path("entries/pdffiles") + @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") + public String getPDFFilesAsList(@PathParam("id") String id) throws IOException { + // get a list of all entries in library (specified by "id") + BibDatabaseContext databaseContext = getDatabaseContext(id); + List response = new ArrayList<>(); + List entries = databaseContext.getDatabase().getEntries(); + if (entries.isEmpty()) { + throw new NotFoundException("No entries found for library: " + id); + } + + // loop through all entries to extract pdfs and paths + for (BibEntry entry : entries) { + List pathsToFiles = entry.getFiles(); + if (!pathsToFiles.isEmpty()) { + for (LinkedFile file : pathsToFiles) { + // ignore all non pdf files and online references + if (!"PDF".equals(file.getFileType()) || LinkedFile.isOnlineLink(file.getLink())) { + continue; + } + // add file to response body + LinkedPdfFileDTO localPdfFile = new LinkedPdfFileDTO(entry, file); + response.add(localPdfFile); + } + } + } + return gson.toJson(response); + } + + /** + * @return a stream to the Chocolate.bib file in the classpath (is null only if the file was moved or there are issues with the classpath) + */ + private @Nullable InputStream getChocolateBibAsStream() { + return BibDatabase.class.getResourceAsStream("/Chocolate.bib"); + } + + /// @param id - also "demo" for the Chocolate.bib file + private BibDatabaseContext getDatabaseContext(String id) throws IOException { + return ServerUtils.getBibDatabaseContext(id, filesToServe, srvStateManager, preferences.getImportFormatPreferences()); + } +} diff --git a/jabsrv/src/main/java/org/jabref/http/server/resources/MapResource.java b/jabsrv/src/main/java/org/jabref/http/server/resources/MapResource.java new file mode 100644 index 00000000000..05f0a6f61a6 --- /dev/null +++ b/jabsrv/src/main/java/org/jabref/http/server/resources/MapResource.java @@ -0,0 +1,108 @@ +package org.jabref.http.server.resources; + +import java.io.IOException; +import java.nio.file.Files; + +import org.jabref.http.SrvStateManager; +import org.jabref.http.server.services.FilesToServe; +import org.jabref.http.server.services.ServerUtils; + +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Path("libraries/{id}/map") +public class MapResource { + private static final Logger LOGGER = LoggerFactory.getLogger(MapResource.class); + + @Inject + SrvStateManager srvStateManager; + + @Inject + FilesToServe filesToServe; + + /** + * At http://localhost:23119/libraries/{id}/map

+ *

+ * Looks for the .jmp file in the directory of the given library ({id}.bib file). + * + * @param id The given library + * @return A JSON String containing the mindmap data. If no {id}.jmp file was found, returns the standard mindmap + * @throws IOException + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public String getJabMapJson(@PathParam("id") String id) throws IOException { + boolean isDemo = "demo".equals(id); + java.nio.file.Path jabMapPath; + if (isDemo) { + jabMapPath = getJabMapDemoPath(); + } else { + jabMapPath = getJabMapPath(id); + } + // if no file is found, return the default mindmap + if (!Files.exists(jabMapPath)) { + return """ + { + "map": { + "meta": { + "name": "JabMap", + "author": "JabMap", + "version": "1.0" + }, + "format": "node_tree", + "data": { + "id": "root", + "topic": "JabMap", + "expanded": true, + "icons": [], + "highlight": null, + "type": "Text" + } + } + } + """; + } + return Files.readString(jabMapPath); + } + + /** + * At http://localhost:23119/libraries/{id}/map

+ *

+ * Saves the mindmap next to its associated library. + * + * @param id The given library + * @throws IOException + */ + @PUT + @Consumes(MediaType.APPLICATION_JSON) + public void updateJabMapJson(@PathParam("id") String id, String fileContent) throws IOException { + boolean isDemo = "demo".equals(id); + java.nio.file.Path targetPath; + if (isDemo) { + targetPath = getJabMapDemoPath(); + } else { + targetPath = getJabMapPath(id); + } + Files.writeString(targetPath, fileContent); + } + + private java.nio.file.Path getJabMapPath(String id) { + java.nio.file.Path libraryPath = ServerUtils.getLibraryPath(id, filesToServe, srvStateManager); + String newName = libraryPath.getFileName().toString().replaceFirst("\\.bib$", ".jmp"); + return libraryPath.getParent().resolve(newName); + } + + private java.nio.file.Path getJabMapDemoPath() { + java.nio.file.Path result = java.nio.file.Path.of(System.getProperty("java.io.tmpdir")).resolve("demo.jmp"); + LOGGER.debug("Using temporary file for demo jmp: {}", result); + return result; + } +} diff --git a/jabsrv/src/main/java/org/jabref/http/server/RootResource.java b/jabsrv/src/main/java/org/jabref/http/server/resources/RootResource.java similarity index 92% rename from jabsrv/src/main/java/org/jabref/http/server/RootResource.java rename to jabsrv/src/main/java/org/jabref/http/server/resources/RootResource.java index 5c3c9753ec5..8181100c0ff 100644 --- a/jabsrv/src/main/java/org/jabref/http/server/RootResource.java +++ b/jabsrv/src/main/java/org/jabref/http/server/resources/RootResource.java @@ -1,4 +1,4 @@ -package org.jabref.http.server; +package org.jabref.http.server.resources; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; diff --git a/jabsrv/src/test/java/org/jabref/http/server/EntryResource.java b/jabsrv/src/test/java/org/jabref/http/server/EntryResource.java new file mode 100644 index 00000000000..b7d516d2e19 --- /dev/null +++ b/jabsrv/src/test/java/org/jabref/http/server/EntryResource.java @@ -0,0 +1,136 @@ +package org.jabref.http.server; + +import java.io.IOException; +import java.util.List; + +import org.jabref.http.SrvStateManager; +import org.jabref.http.server.services.FilesToServe; +import org.jabref.http.server.services.ServerUtils; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Path("libraries/{id}/entries/{entryId}") +public class EntryResource { + private static final Logger LOGGER = LoggerFactory.getLogger(EntryResource.class); + + @Inject + CliPreferences preferences; + + @Inject + SrvStateManager srvStateManager; + + @Inject + FilesToServe filesToServe; + + /** + * At http://localhost:23119/libraries/{id}/entries/{entryId}

+ *

+ * Combines attributes of a given BibEntry into a basic entry preview for as plain text. + * + * @param id The name of the library + * @param entryId The CitationKey of the BibEntry + * @return a basic entry preview as plain text + * @throws IOException + * @throws NotFoundException + */ + @GET + @Produces(MediaType.TEXT_PLAIN + ";charset=UTF-8") + public String getPlainRepresentation(@PathParam("id") String id, @PathParam("entryId") String entryId) throws IOException { + BibDatabaseContext databaseContext = getDatabaseContext(id); + List entriesByCitationKey = databaseContext.getDatabase().getEntriesByCitationKey(entryId); + if (entriesByCitationKey.isEmpty()) { + throw new NotFoundException("Entry with citation key '" + entryId + "' not found in library " + id); + } + if (entriesByCitationKey.size() > 1) { + LOGGER.warn("Multiple entries found with citation key '{}'. Using the first one.", entryId); + } + + // TODO: Currently, the preview preferences are in GUI package, which is not accessible here. + // build the preview + BibEntry entry = entriesByCitationKey.getFirst(); + + String author = entry.getField(StandardField.AUTHOR).orElse("(N/A)"); + String title = entry.getField(StandardField.TITLE).orElse("(N/A)"); + String journal = entry.getField(StandardField.JOURNAL).orElse("(N/A)"); + String volume = entry.getField(StandardField.VOLUME).orElse("(N/A)"); + String number = entry.getField(StandardField.NUMBER).orElse("(N/A)"); + String pages = entry.getField(StandardField.PAGES).orElse("(N/A)"); + String releaseDate = entry.getField(StandardField.DATE).orElse("(N/A)"); + + // the only difference to the HTML version of this method is the format of the output: + String preview = + "Author: " + author + + "\nTitle: " + title + + "\nJournal: " + journal + + "\nVolume: " + volume + + "\nNumber: " + number + + "\nPages: " + pages + + "\nReleased on: " + releaseDate; + + return preview; + } + + /** + * At http://localhost:23119/libraries/{id}/entries/{entryId}

+ *

+ * Combines attributes of a given BibEntry into a basic entry preview for as HTML text. + * + * @param id The name of the library + * @param entryId The CitationKey of the BibEntry + * @return a basic entry preview as HTML text + * @throws IOException + */ + @GET + @Path("entries/{entryId}") + @Produces(MediaType.TEXT_HTML + ";charset=UTF-8") + public String getHTMLRepresentation(@PathParam("id") String id, @PathParam("entryId") String entryId) throws IOException { + List entriesByCitationKey = getDatabaseContext(id).getDatabase().getEntriesByCitationKey(entryId); + if (entriesByCitationKey.isEmpty()) { + throw new NotFoundException("Entry with citation key '" + entryId + "' not found in library " + id); + } + if (entriesByCitationKey.size() > 1) { + LOGGER.warn("Multiple entries found with citation key '{}'. Using the first one.", entryId); + } + + // TODO: Currently, the preview preferences are in GUI package, which is not accessible here. + // build the preview + BibEntry entry = entriesByCitationKey.getFirst(); + + String author = entry.getField(StandardField.AUTHOR).orElse("(N/A)"); + String title = entry.getField(StandardField.TITLE).orElse("(N/A)"); + String journal = entry.getField(StandardField.JOURNAL).orElse("(N/A)"); + String volume = entry.getField(StandardField.VOLUME).orElse("(N/A)"); + String number = entry.getField(StandardField.NUMBER).orElse("(N/A)"); + String pages = entry.getField(StandardField.PAGES).orElse("(N/A)"); + String releaseDate = entry.getField(StandardField.DATE).orElse("(N/A)"); + + // the only difference to the plain text version of this method is the format of the output: + String preview = + "Author: " + author + "
" + + "Title: " + title + "
" + + "Journal: " + journal + "
" + + "Volume: " + volume + "
" + + "Number: " + number + "
" + + "Pages: " + pages + "
" + + "Released on: " + releaseDate; + + return preview; + } + + /// @param id - also "demo" for the Chocolate.bib file + private BibDatabaseContext getDatabaseContext(String id) throws IOException { + return ServerUtils.getBibDatabaseContext(id, filesToServe, srvStateManager, preferences.getImportFormatPreferences()); + } +} diff --git a/jabsrv/src/test/java/org/jabref/http/server/LibrariesResourceTest.java b/jabsrv/src/test/java/org/jabref/http/server/LibrariesResourceTest.java index 3696fffd29e..8b43278dbe9 100644 --- a/jabsrv/src/test/java/org/jabref/http/server/LibrariesResourceTest.java +++ b/jabsrv/src/test/java/org/jabref/http/server/LibrariesResourceTest.java @@ -2,6 +2,8 @@ import java.util.EnumSet; +import org.jabref.http.server.resources.LibrariesResource; + import jakarta.ws.rs.core.Application; import org.glassfish.jersey.server.ResourceConfig; import org.junit.jupiter.api.Test; diff --git a/jabsrv/src/test/java/org/jabref/http/server/LibraryResourceTest.java b/jabsrv/src/test/java/org/jabref/http/server/LibraryResourceTest.java index 1223712bc83..61240286388 100644 --- a/jabsrv/src/test/java/org/jabref/http/server/LibraryResourceTest.java +++ b/jabsrv/src/test/java/org/jabref/http/server/LibraryResourceTest.java @@ -1,6 +1,8 @@ package org.jabref.http.server; import org.jabref.http.JabrefMediaType; +import org.jabref.http.server.resources.LibrariesResource; +import org.jabref.http.server.resources.LibraryResource; import jakarta.ws.rs.core.Application; import org.glassfish.jersey.server.ResourceConfig;