33import java .io .IOException ;
44import java .io .InputStream ;
55import java .nio .file .Files ;
6+ import java .util .ArrayList ;
67import java .util .List ;
78import java .util .Objects ;
89
910import org .jabref .http .JabrefMediaType ;
1011import org .jabref .http .dto .BibEntryDTO ;
12+ import org .jabref .http .dto .LinkedPdfFileDTO ;
1113import org .jabref .http .server .services .ContextsToServe ;
1214import org .jabref .http .server .services .FilesToServe ;
1315import org .jabref .http .server .services .ServerUtils ;
1416import org .jabref .logic .citationstyle .JabRefItemDataProvider ;
1517import org .jabref .logic .preferences .CliPreferences ;
1618import org .jabref .model .database .BibDatabase ;
1719import org .jabref .model .database .BibDatabaseContext ;
20+ import org .jabref .model .entry .BibEntry ;
1821import org .jabref .model .entry .BibEntryTypesManager ;
22+ import org .jabref .model .entry .LinkedFile ;
23+ import org .jabref .model .entry .field .StandardField ;
1924
2025import com .airhacks .afterburner .injection .Injector ;
2126import com .google .gson .Gson ;
2227import jakarta .inject .Inject ;
28+ import jakarta .ws .rs .Consumes ;
2329import jakarta .ws .rs .GET ;
2430import jakarta .ws .rs .InternalServerErrorException ;
31+ import jakarta .ws .rs .NotFoundException ;
32+ import jakarta .ws .rs .PUT ;
2533import jakarta .ws .rs .Path ;
2634import jakarta .ws .rs .PathParam ;
2735import 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+ + "\n Title: " + title
245+ + "\n Journal: " + journal
246+ + "\n Volume: " + volume
247+ + "\n Number: " + number
248+ + "\n Pages: " + pages
249+ + "\n Released 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 }
0 commit comments