Skip to content

Commit 6772596

Browse files
committed
Merge remote-tracking branch 'upstream/main'
* upstream/main: Add http endpoints for JabMap (#13519)
2 parents cb77f24 + 25ae95d commit 6772596

File tree

7 files changed

+304
-21
lines changed

7 files changed

+304
-21
lines changed

.jbang/JabSrvLauncher.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/cayw/SimpleJson.java
1313
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/GlobalExceptionMapper.java
1414
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/GsonFactory.java
15+
//SOURCES ../jabsrv/src/main/java/org/jabref/http/dto/LinkedPdfFileDTO.java
1516
//SOURCES ../jabsrv/src/main/java/org/jabref/http/JabrefMediaType.java
1617
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWResource.java
1718
//SOURCES ../jabsrv/src/main/java/org/jabref/http/server/cayw/CAYWQueryParams.java

jabsrv/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
opens org.jabref.http.server to org.glassfish.hk2.utilities, org.glassfish.hk2.locator;
99
exports org.jabref.http.server.cayw;
1010
opens org.jabref.http.server.cayw to com.google.gson, org.glassfish.hk2.locator, org.glassfish.hk2.utilities;
11+
opens org.jabref.http.dto to com.google.gson;
1112

1213
requires javafx.base;
1314

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.jabref.http.dto;
2+
3+
import org.jabref.model.entry.BibEntry;
4+
import org.jabref.model.entry.LinkedFile;
5+
6+
public class LinkedPdfFileDTO {
7+
private final String fileName;
8+
private final String parentCitationKey;
9+
private final String path;
10+
11+
public LinkedPdfFileDTO(BibEntry parentEntry, LinkedFile file) {
12+
this.parentCitationKey = parentEntry.getCitationKey().orElse("N/A");
13+
this.path = file.getLink();
14+
this.fileName = path.substring(path.lastIndexOf('/') + 1);
15+
}
16+
17+
public String getFileName() {
18+
return fileName;
19+
}
20+
21+
public String getParentCitationKey() {
22+
return parentCitationKey;
23+
}
24+
25+
public String getPath() {
26+
return path;
27+
}
28+
}

jabsrv/src/main/java/org/jabref/http/server/CORSFilter.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@
99
public class CORSFilter implements ContainerResponseFilter {
1010
@Override
1111
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
12-
String requestOrigin = requestContext.getHeaderString("Origin");
13-
if (requestOrigin == null) {
14-
// IntelliJ's rest client is calling
15-
responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
16-
} else if (requestOrigin.contains("://localhost")) {
17-
responseContext.getHeaders().add("Access-Control-Allow-Origin", requestOrigin);
18-
}
12+
responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
1913
responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
2014
responseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept");
2115
responseContext.getHeaders().add("Access-Control-Allow-Credentials", "false");

jabsrv/src/main/java/org/jabref/http/server/LibraryResource.java

Lines changed: 240 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,33 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.nio.file.Files;
6+
import java.util.ArrayList;
67
import java.util.List;
78
import java.util.Objects;
89

910
import org.jabref.http.JabrefMediaType;
1011
import org.jabref.http.dto.BibEntryDTO;
12+
import org.jabref.http.dto.LinkedPdfFileDTO;
1113
import org.jabref.http.server.services.ContextsToServe;
1214
import org.jabref.http.server.services.FilesToServe;
1315
import org.jabref.http.server.services.ServerUtils;
1416
import org.jabref.logic.citationstyle.JabRefItemDataProvider;
1517
import org.jabref.logic.preferences.CliPreferences;
1618
import org.jabref.model.database.BibDatabase;
1719
import org.jabref.model.database.BibDatabaseContext;
20+
import org.jabref.model.entry.BibEntry;
1821
import org.jabref.model.entry.BibEntryTypesManager;
22+
import org.jabref.model.entry.LinkedFile;
23+
import org.jabref.model.entry.field.StandardField;
1924

2025
import com.airhacks.afterburner.injection.Injector;
2126
import com.google.gson.Gson;
2227
import jakarta.inject.Inject;
28+
import jakarta.ws.rs.Consumes;
2329
import jakarta.ws.rs.GET;
2430
import jakarta.ws.rs.InternalServerErrorException;
31+
import jakarta.ws.rs.NotFoundException;
32+
import jakarta.ws.rs.PUT;
2533
import jakarta.ws.rs.Path;
2634
import jakarta.ws.rs.PathParam;
2735
import jakarta.ws.rs.Produces;
@@ -48,18 +56,94 @@ public class LibraryResource {
4856
@Inject
4957
Gson gson;
5058

59+
/**
60+
* At http://localhost:23119/libraries/{id}
61+
*
62+
* @param id The specified library
63+
* @return specified library in JSON format
64+
* @throws IOException
65+
*/
5166
@GET
5267
@Produces(MediaType.APPLICATION_JSON)
5368
public String getJson(@PathParam("id") String id) throws IOException {
5469
BibDatabaseContext databaseContext = getDatabaseContext(id);
5570
BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class);
5671
List<BibEntryDTO> list = databaseContext.getDatabase().getEntries().stream()
57-
.peek(bibEntry -> bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry)))
58-
.map(entry -> new BibEntryDTO(entry, databaseContext.getMode(), preferences.getFieldPreferences(), entryTypesManager))
59-
.toList();
72+
.peek(bibEntry -> bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry)))
73+
.map(entry -> new BibEntryDTO(entry, databaseContext.getMode(), preferences.getFieldPreferences(), entryTypesManager))
74+
.toList();
6075
return gson.toJson(list);
6176
}
6277

78+
/**
79+
* At http://localhost:23119/libraries/{id}/map <br><br>
80+
*
81+
* Looks for the .jmp file in the directory of the given library ({id}.bib file).
82+
*
83+
* @param id The given library
84+
* @return A JSON String containing the mindmap data. If no {id}.jmp file was found, returns the standard mindmap
85+
* @throws IOException
86+
*/
87+
@GET
88+
@Path("map")
89+
@Produces(MediaType.APPLICATION_JSON)
90+
public String getJabMapJson(@PathParam("id") String id) throws IOException {
91+
boolean isDemo = "demo".equals(id);
92+
java.nio.file.Path jabMapPath;
93+
if (isDemo) {
94+
jabMapPath = getJabMapDemoPath();
95+
} else {
96+
jabMapPath = getJabMapPath(id);
97+
}
98+
// if no file is found, return the default mindmap
99+
if (!Files.exists(jabMapPath)) {
100+
return """
101+
{
102+
"map": {
103+
"meta": {
104+
"name": "JabMap",
105+
"author": "JabMap",
106+
"version": "1.0"
107+
},
108+
"format": "node_tree",
109+
"data": {
110+
"id": "root",
111+
"topic": "JabMap",
112+
"expanded": true,
113+
"icons": [],
114+
"highlight": null,
115+
"type": "Text"
116+
}
117+
}
118+
}
119+
""";
120+
}
121+
return Files.readString(jabMapPath);
122+
}
123+
124+
/**
125+
* At http://localhost:23119/libraries/{id}/map <br><br>
126+
*
127+
* Saves the mindmap next to its associated library.
128+
*
129+
* @param id The given library
130+
*
131+
* @throws IOException
132+
*/
133+
@PUT
134+
@Path("map")
135+
@Consumes(MediaType.APPLICATION_JSON)
136+
public void updateJabMapJson(@PathParam("id") String id, String fileContent) throws IOException {
137+
boolean isDemo = "demo".equals(id);
138+
java.nio.file.Path targetPath;
139+
if (isDemo) {
140+
targetPath = getJabMapDemoPath();
141+
} else {
142+
targetPath = getJabMapPath(id);
143+
}
144+
Files.writeString(targetPath, fileContent);
145+
}
146+
63147
@GET
64148
@Produces(JabrefMediaType.JSON_CSL_ITEM)
65149
public String getClsItemJson(@PathParam("id") String id) throws IOException {
@@ -94,17 +178,165 @@ public Response getBibtex(@PathParam("id") String id) {
94178
throw new InternalServerErrorException("Could not read library " + library, e);
95179
}
96180
return Response.ok()
97-
.header("Content-Disposition", "attachment; filename=\"" + library.getFileName() + "\"")
98-
.entity(libraryAsString)
99-
.build();
181+
.header("Content-Disposition", "attachment; filename=\"" + library.getFileName() + "\"")
182+
.entity(libraryAsString)
183+
.build();
184+
}
185+
186+
private java.nio.file.Path getJabMapPath(String id) {
187+
java.nio.file.Path libraryPath = ServerUtils.getLibraryPath(id, filesToServe, contextsToServe);
188+
String newName = libraryPath.getFileName().toString().replaceFirst("\\.bib$", ".jmp");
189+
return libraryPath.getParent().resolve(newName);
190+
}
191+
192+
private java.nio.file.Path getJabMapDemoPath() {
193+
java.nio.file.Path result = java.nio.file.Path.of(System.getProperty("java.io.tmpdir")).resolve("demo.jmp");
194+
LOGGER.debug("Using temporary file for demo jmp: {}", result);
195+
return result;
100196
}
101197

102-
/// @param id - also "demo" for the Chocolate.bib file
198+
/**
199+
* @param id - also "demo" for the Chocolate.bib file
200+
*/
103201
private BibDatabaseContext getDatabaseContext(String id) throws IOException {
104202
return ServerUtils.getBibDatabaseContext(id, filesToServe, contextsToServe, preferences.getImportFormatPreferences());
105203
}
106204

107-
/// @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)
205+
/**
206+
* At http://localhost:23119/libraries/{id}/entries/{entryId} <br><br>
207+
*
208+
* Combines attributes of a given BibEntry into a basic entry preview for as plain text.
209+
*
210+
* @param id The name of the library
211+
* @param entryId The CitationKey of the BibEntry
212+
* @return a basic entry preview as plain text
213+
* @throws IOException
214+
* @throws NotFoundException
215+
*/
216+
@GET
217+
@Path("entries/{entryId}")
218+
@Produces(MediaType.TEXT_PLAIN + ";charset=UTF-8")
219+
public String getPlainRepresentation(@PathParam("id") String id, @PathParam("entryId") String entryId) throws IOException {
220+
BibDatabaseContext databaseContext = getDatabaseContext(id);
221+
List<BibEntry> entriesByCitationKey = databaseContext.getDatabase().getEntriesByCitationKey(entryId);
222+
if (entriesByCitationKey.isEmpty()) {
223+
throw new NotFoundException("Entry with citation key '" + entryId + "' not found in library " + id);
224+
}
225+
if (entriesByCitationKey.size() > 1) {
226+
LOGGER.warn("Multiple entries found with citation key '{}'. Using the first one.", entryId);
227+
}
228+
229+
// TODO: Currently, the preview preferences are in GUI package, which is not accessible here.
230+
// build the preview
231+
BibEntry entry = entriesByCitationKey.getFirst();
232+
233+
String author = entry.getField(StandardField.AUTHOR).orElse("(N/A)");
234+
String title = entry.getField(StandardField.TITLE).orElse("(N/A)");
235+
String journal = entry.getField(StandardField.JOURNAL).orElse("(N/A)");
236+
String volume = entry.getField(StandardField.VOLUME).orElse("(N/A)");
237+
String number = entry.getField(StandardField.NUMBER).orElse("(N/A)");
238+
String pages = entry.getField(StandardField.PAGES).orElse("(N/A)");
239+
String releaseDate = entry.getField(StandardField.DATE).orElse("(N/A)");
240+
241+
// the only difference to the HTML version of this method is the format of the output:
242+
String preview =
243+
"Author: " + author
244+
+ "\nTitle: " + title
245+
+ "\nJournal: " + journal
246+
+ "\nVolume: " + volume
247+
+ "\nNumber: " + number
248+
+ "\nPages: " + pages
249+
+ "\nReleased on: " + releaseDate;
250+
251+
return preview;
252+
}
253+
254+
/**
255+
* At http://localhost:23119/libraries/{id}/entries/{entryId} <br><br>
256+
*
257+
* Combines attributes of a given BibEntry into a basic entry preview for as HTML text.
258+
*
259+
* @param id The name of the library
260+
* @param entryId The CitationKey of the BibEntry
261+
* @return a basic entry preview as HTML text
262+
* @throws IOException
263+
*/
264+
@GET
265+
@Path("entries/{entryId}")
266+
@Produces(MediaType.TEXT_HTML + ";charset=UTF-8")
267+
public String getHTMLRepresentation(@PathParam("id") String id, @PathParam("entryId") String entryId) throws IOException {
268+
List<BibEntry> entriesByCitationKey = getDatabaseContext(id).getDatabase().getEntriesByCitationKey(entryId);
269+
if (entriesByCitationKey.isEmpty()) {
270+
throw new NotFoundException("Entry with citation key '" + entryId + "' not found in library " + id);
271+
}
272+
if (entriesByCitationKey.size() > 1) {
273+
LOGGER.warn("Multiple entries found with citation key '{}'. Using the first one.", entryId);
274+
}
275+
276+
// TODO: Currently, the preview preferences are in GUI package, which is not accessible here.
277+
// build the preview
278+
BibEntry entry = entriesByCitationKey.getFirst();
279+
280+
String author = entry.getField(StandardField.AUTHOR).orElse("(N/A)");
281+
String title = entry.getField(StandardField.TITLE).orElse("(N/A)");
282+
String journal = entry.getField(StandardField.JOURNAL).orElse("(N/A)");
283+
String volume = entry.getField(StandardField.VOLUME).orElse("(N/A)");
284+
String number = entry.getField(StandardField.NUMBER).orElse("(N/A)");
285+
String pages = entry.getField(StandardField.PAGES).orElse("(N/A)");
286+
String releaseDate = entry.getField(StandardField.DATE).orElse("(N/A)");
287+
288+
// the only difference to the plain text version of this method is the format of the output:
289+
String preview =
290+
"<strong>Author:</strong> " + author + "<br>" +
291+
"<strong>Title:</strong> " + title + "<br>" +
292+
"<strong>Journal:</strong> " + journal + "<br>" +
293+
"<strong>Volume:</strong> " + volume + "<br>" +
294+
"<strong>Number:</strong> " + number + "<br>" +
295+
"<strong>Pages:</strong> " + pages + "<br>" +
296+
"<strong>Released on:</strong> " + releaseDate;
297+
298+
return preview;
299+
}
300+
301+
/**
302+
* At http://localhost:23119/libraries/{id}/entries/pdffiles <br><br>
303+
*
304+
* Loops through all entries in the specified library and adds attached files of type "PDF" to
305+
* a list and JSON serialises it.
306+
*/
307+
@GET
308+
@Path("entries/pdffiles")
309+
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
310+
public String getPDFFilesAsList(@PathParam("id") String id) throws IOException {
311+
// get a list of all entries in library (specified by "id")
312+
BibDatabaseContext databaseContext = getDatabaseContext(id);
313+
List<LinkedPdfFileDTO> response = new ArrayList<>();
314+
List<BibEntry> entries = databaseContext.getDatabase().getEntries();
315+
if (entries.isEmpty()) {
316+
throw new NotFoundException("No entries found for library: " + id);
317+
}
318+
319+
// loop through all entries to extract pdfs and paths
320+
for (BibEntry entry : entries) {
321+
List<LinkedFile> pathsToFiles = entry.getFiles();
322+
if (!pathsToFiles.isEmpty()) {
323+
for (LinkedFile file : pathsToFiles) {
324+
// ignore all non pdf files and online references
325+
if (!"PDF".equals(file.getFileType()) || LinkedFile.isOnlineLink(file.getLink())) {
326+
continue;
327+
}
328+
// add file to response body
329+
LinkedPdfFileDTO localPdfFile = new LinkedPdfFileDTO(entry, file);
330+
response.add(localPdfFile);
331+
}
332+
}
333+
}
334+
return gson.toJson(response);
335+
}
336+
337+
/**
338+
* @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)
339+
*/
108340
private @Nullable InputStream getChocolateBibAsStream() {
109341
return BibDatabase.class.getResourceAsStream("/Chocolate.bib");
110342
}

jabsrv/src/main/java/org/jabref/http/server/Server.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private HttpServer startServer(ServiceLocator serviceLocator, URI uri) {
9494

9595
// see https://stackoverflow.com/a/33794265/873282
9696
final ResourceConfig resourceConfig = new ResourceConfig();
97+
resourceConfig.property("jersey.config.server.wadl.disableWadl", true);
9798
// TODO: Add SSL
9899
resourceConfig.register(RootResource.class);
99100
resourceConfig.register(LibrariesResource.class);

0 commit comments

Comments
 (0)