Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

WIP Restapi #628

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b29aaea
Add jaxrs copy over code
Siedlerchr Sep 21, 2022
c951839
try with jetty
Siedlerchr Sep 21, 2022
96d917a
fcking inject (
Siedlerchr Sep 21, 2022
4b38bd1
Update afterburner to jakarta
Siedlerchr Sep 21, 2022
8f8dff9
Merge remote-tracking branch 'upstream/afterburner' into restApi
Siedlerchr Sep 21, 2022
03bcdd3
add another fcking injection stuff
Siedlerchr Sep 21, 2022
7ce929b
test again
Siedlerchr Sep 22, 2022
809a579
fix afterburner
Siedlerchr Sep 22, 2022
607cb2a
Merge remote-tracking branch 'upstream/main' into restapi
Siedlerchr Oct 13, 2022
9293da1
Fix conflicts
Siedlerchr Oct 13, 2022
698e88f
cleanup
Siedlerchr Oct 14, 2022
4638d77
Merge remote-tracking branch 'upstream/main' into restapi
Siedlerchr Jan 5, 2023
bcca5cc
fix compile errors
Siedlerchr Jan 5, 2023
dd77dce
Switch to JBOSS RestEasy (and Jackson instead of Glassfish)
koppor Jan 5, 2023
4640087
Switch back to Glassfish for JAXB (instead of Jackson)
koppor Jan 6, 2023
a6ee546
Switch from Glassfish to Jackson2 for JSON (but keep it for XML)
koppor Jan 6, 2023
1b4c30d
Remove one dependency
koppor Jan 6, 2023
67280b0
Merge remote-tracking branch 'origin/main' into restapi
koppor Mar 17, 2023
1bbb1db
Remove two "nice-to-have" Markdown plugins (strikethrough, tasklist)
koppor Mar 17, 2023
5c448de
Fix checkstyle
koppor Mar 17, 2023
6095223
Remove duoble dependency entry
koppor Mar 17, 2023
82492fd
Really remove Markdown extension
koppor Mar 17, 2023
bf659ea
Endless startup...
koppor Mar 17, 2023
636873d
Get server to run
koppor Mar 17, 2023
2f8302b
Make use of JabRefDesktop for default directory
koppor Mar 17, 2023
1b1fa9d
Streamline code of ServerPropertyService
koppor Mar 17, 2023
cf14659
Fix checkstyle
koppor Mar 18, 2023
ba80857
Merge remote-tracking branch 'upstream/main' into restapi
Siedlerchr Mar 18, 2023
0c2c30c
fix starting error
Siedlerchr Mar 18, 2023
306d731
fix jlink dupliate module
Siedlerchr Mar 18, 2023
a0799b0
run in background thread
Siedlerchr Mar 18, 2023
21b20f1
Merge remote-tracking branch 'upstream/main' into restapi
Siedlerchr Mar 20, 2023
3b14255
remove tika
Siedlerchr Mar 20, 2023
e734892
Fix checkstyle
koppor Mar 20, 2023
1aacc96
Merge branch 'main' into restapi
koppor Apr 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,16 @@ dependencies {
implementation "org.tinylog:tinylog-api:2.5.0"
implementation "org.tinylog:slf4j-tinylog:2.5.0"
implementation "org.tinylog:tinylog-impl:2.5.0"

implementation 'de.undercouch:citeproc-java:3.0.0-alpha.6'

// jakarta.activation is already dependency of glassfish
implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '3.0.1'
implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '3.0.2'
implementation 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
implementation group: 'org.glassfish.jersey.containers', name: 'jersey-container-jetty-http', version: '3.1.0-M8'
implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.0-M8'
implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '11.0.12'

implementation ('com.github.tomtung:latex2unicode_2.13:0.3.2') {
exclude module: 'fastparse_2.13'
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@
requires com.sun.jna.platform;

requires org.eclipse.jgit;
requires jakarta.ws.rs;
requires jakarta.validation;
requires jersey.server;
requires jersey.container.jetty.http;
requires org.eclipse.jetty.server;
uses org.eclipse.jgit.transport.SshSessionFactory;
uses org.eclipse.jgit.lib.GpgSigner;
}
14 changes: 14 additions & 0 deletions src/main/java/org/jabref/cli/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -21,15 +22,20 @@
import org.jabref.logic.protectedterms.ProtectedTermsLoader;
import org.jabref.logic.remote.RemotePreferences;
import org.jabref.logic.remote.client.RemoteClient;
import org.jabref.logic.shared.restserver.rest.Root;
import org.jabref.logic.util.BuildInfo;
import org.jabref.migrations.PreferencesMigrations;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.preferences.JabRefPreferences;
import org.jabref.preferences.PreferencesService;

import jakarta.ws.rs.core.UriBuilder;
import net.harawata.appdirs.AppDirsFactory;
import org.apache.commons.cli.ParseException;
import org.eclipse.jetty.server.Server;
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinylog.configuration.Configuration;
Expand Down Expand Up @@ -82,6 +88,14 @@ public static void main(String[] args) {
} catch (Exception ex) {
LOGGER.error("Unexpected exception", ex);
}

startServer();
}

private static void startServer() {
URI baseUri = UriBuilder.fromUri("http://localhost/").port(9998).build();
ResourceConfig config = new ResourceConfig(Root.class);
Server server = JettyHttpContainerFactory.createServer(baseUri, config);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jabref.logic.shared.restserver.core.properties;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ServerPropertyService {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerPropertyService.class);
private static ServerPropertyService instance;
private final Properties serverProperties;

private ServerPropertyService() {
serverProperties = loadProperties();
}

public static ServerPropertyService getInstance() {
if (instance == null) {
instance = new ServerPropertyService();
}
return instance;
}

/**
* Tries to determine the working directory of the library.
* Uses the first path it finds when resolving in this order:
* 1. Environment variable LIBRARY_WORKSPACE
* 2. Default User home with a new directory for the library
*/
private Properties loadProperties() {
Properties properties = new Properties();
if (!((System.getenv("LIBRARY_WORKSPACE") == null) || System.getenv("LIBRARY_WORKSPACE").isBlank())) {
LOGGER.info("Environment Variable found, using defined directory: {}", System.getenv("LIBRARY_WORKSPACE"));
properties.setProperty("workingDirectory", System.getenv("LIBRARY_WORKSPACE"));
} else {
LOGGER.info("Working directory was not found in either the properties or the environment variables, falling back to default location: {}", System.getProperty("user.home") + "/planqk-library");
properties.setProperty("workingDirectory", System.getProperty("user.home") + "/planqk-library");
}
return properties;
}

public Path getWorkingDirectory() {
return Paths.get(serverProperties.getProperty("workingDirectory"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.jabref.logic.shared.restserver.core.repository;

import java.io.IOException;

import org.jabref.logic.crawler.Crawler;
import org.jabref.logic.exporter.SaveException;

import org.eclipse.jgit.api.errors.GitAPIException;

public class CrawlTask implements Runnable {
private final Crawler crawler;
private TaskStatus status;

public CrawlTask(Crawler crawler) {
this.crawler = crawler;
}

public TaskStatus getStatus() {
return status;
}

@Override
public void run() {
status = TaskStatus.RUNNING;
try {
crawler.performCrawl();
} catch (IOException | GitAPIException | SaveException e) {
status = TaskStatus.FAILED;
throw new RuntimeException(e);
}
status = TaskStatus.DONE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package org.jabref.logic.shared.restserver.core.repository;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.jabref.gui.Globals;
import org.jabref.logic.database.DatabaseMerger;
import org.jabref.logic.exporter.AtomicFileWriter;
import org.jabref.logic.exporter.BibWriter;
import org.jabref.logic.exporter.BibtexDatabaseWriter;
import org.jabref.logic.exporter.SavePreferences;
import org.jabref.logic.importer.OpenDatabase;
import org.jabref.logic.shared.restserver.rest.model.NewLibraryDTO;
import org.jabref.logic.util.OS;
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.util.DummyFileUpdateMonitor;
import org.jabref.model.util.FileUpdateMonitor;
import org.jabref.preferences.GeneralPreferences;
import org.jabref.preferences.JabRefPreferences;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LibraryService {

private static final Map<Path, LibraryService> instances = new HashMap<>();
koppor marked this conversation as resolved.
Show resolved Hide resolved
private static final Logger LOGGER = LoggerFactory.getLogger(LibraryService.class);
private final Path workingDirectory;

private LibraryService(Path workingDirectory) {
this.workingDirectory = workingDirectory;
if (Files.notExists(workingDirectory)) {
try {
Files.createDirectories(workingDirectory);
} catch (IOException e) {
LOGGER.error("Could not create working directory.", e);
System.exit(1);
}
}
}

public static LibraryService getInstance(Path workingDirectory) {
return instances.computeIfAbsent(workingDirectory, LibraryService::new);
}

public List<String> getLibraryNames() throws IOException {
return Files.list(workingDirectory) // Alternatively walk(Path start, int depth) for recursive aggregation
.filter(file -> !Files.isDirectory(file))
.map(Path::getFileName)
.map(Path::toString)
.filter(file -> file.endsWith(".bib"))
.collect(Collectors.toList());
}

public void createLibrary(NewLibraryDTO newLibraryConfiguration) throws IOException {
Files.createFile(getLibraryPath(newLibraryConfiguration.getLibraryName()));
}

public Boolean deleteLibrary(String libraryName) throws IOException {
return Files.deleteIfExists(getLibraryPath(libraryName));
}

public boolean libraryExists(String libraryName) {
return Files.exists(getLibraryPath(libraryName));
}

public List<BibEntry> getLibraryEntries(String libraryName) throws IOException {
Path libraryPath = getLibraryPath(libraryName);
if (!Files.exists(libraryPath)) {
throw new FileNotFoundException();
}
// We do not need any update monitoring
return new ArrayList<>(OpenDatabase.loadDatabase(libraryPath, Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor())
.getDatabase()
.getEntries());
}

public Optional<BibEntry> getLibraryEntryMatchingCiteKey(String libraryName, String citeKey) throws IOException {
Path libraryPath = getLibraryPath(libraryName);
if (!Files.exists(libraryPath)) {
throw new FileNotFoundException();
}

// Note that this might lead to issues if multiple entries have the same cite key!
return OpenDatabase.loadDatabase(libraryPath, Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor())
.getDatabase()
.getEntryByCitationKey(citeKey);
}

public synchronized void addEntryToLibrary(String libraryName, BibEntry newEntry) throws IOException {
// Enforce that a citation key is provided and that is is not part of the library already.
if (newEntry.getCitationKey().isEmpty()) {
throw new IllegalArgumentException("Entry does not contain a citation key");
}
Path libraryPath = getLibraryPath(libraryName);
BibDatabaseContext context;
if (!Files.exists(libraryPath)) {
throw new FileNotFoundException();
} else {
context = OpenDatabase.loadDatabase(libraryPath, Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor())
koppor marked this conversation as resolved.
Show resolved Hide resolved
.getDatabaseContext();
}
// Required to get serialized
newEntry.setChanged(true);
if (this.citationKeyAlreadyExists(libraryName, newEntry.getCitationKey().get())) {
throw new IllegalArgumentException("Library already contains an entry with that citation key.");
}
context.getDatabase().insertEntry(newEntry);
GeneralPreferences generalPreferences = JabRefPreferences.getInstance().getGeneralPreferences();
SavePreferences savePreferences = JabRefPreferences.getInstance().getSavePreferences();

try (AtomicFileWriter fileWriter = new AtomicFileWriter(libraryPath, context.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8), savePreferences.shouldMakeBackup())) {
BibWriter writer = new BibWriter(fileWriter, OS.NEWLINE);
BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(writer, JabRefPreferences.getInstance().getGeneralPreferences(), savePreferences, new BibEntryTypesManager());
databaseWriter.saveDatabase(context);
}
}

public synchronized void updateEntry(String libraryName, String citeKey, BibEntry updatedEntry) throws IOException {
// Enforce that a citation key is provided and that is is not part of the library already.
if (updatedEntry.getCitationKey().isEmpty()) {
throw new IllegalArgumentException("Entry does not contain a citation key");
}
this.deleteEntryByCiteKey(libraryName, citeKey);
updatedEntry.setChanged(true);
this.addEntryToLibrary(libraryName, updatedEntry);
}

public synchronized boolean deleteEntryByCiteKey(String libraryName, String citeKey) throws IOException {
Path libraryPath = getLibraryPath(libraryName);
BibDatabaseContext context;
if (!Files.exists(libraryPath)) {
return false;
} else {
context = OpenDatabase.loadDatabase(libraryPath, Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor())
.getDatabaseContext();
}
Optional<BibEntry> entry = context.getDatabase().getEntryByCitationKey(citeKey);
if (entry.isEmpty()) {
return false;
}

context.getDatabase().removeEntry(entry.get());
GeneralPreferences generalPreferences = JabRefPreferences.getInstance().getGeneralPreferences();
SavePreferences savePreferences = JabRefPreferences.getInstance().getSavePreferences();

try (AtomicFileWriter fileWriter = new AtomicFileWriter(libraryPath, context.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8), savePreferences.shouldMakeBackup())) {
BibWriter writer = new BibWriter(fileWriter, OS.NEWLINE);
BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(writer, generalPreferences, savePreferences, new BibEntryTypesManager());
databaseWriter.saveDatabase(context);
return true;
}
}

public List<BibEntry> getAllEntries() throws IOException {
List<String> libraryNames = getLibraryNames();
BibDatabase result = new BibDatabase();
DatabaseMerger merger = new DatabaseMerger(JabRefPreferences.getInstance().getImportFormatPreferences().getKeywordSeparator());
FileUpdateMonitor dummy = new DummyFileUpdateMonitor();
libraryNames.stream()
.map(this::getLibraryPath)
.map(path -> {
try {
return OpenDatabase.loadDatabase(path, Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor()).getDatabase();
} catch (IOException e) {
// Just return an empty database, a.k.a if opening fails, ignore it
return new BibDatabase();
}
})
.forEach(database -> merger.merge(result, database));
return new ArrayList<>(result.getEntries());
}

private boolean citationKeyAlreadyExists(String libraryName, String citationKey) throws IOException {
return this.getLibraryEntryMatchingCiteKey(libraryName, citationKey).isPresent();
}

private Path getLibraryPath(String libraryName) {
libraryName = addBibExtensionIfMissing(libraryName);
LOGGER.info("Resolved path: {}", workingDirectory.resolve(libraryName));
// For now make assumption that the directory is flat:
return workingDirectory.resolve(libraryName);
}

private String addBibExtensionIfMissing(String libraryName) {
return libraryName.endsWith(".bib") ? libraryName : libraryName + ".bib";
}
}
Loading