diff --git a/.github/workflows/develop-build.yml b/.github/workflows/develop-build.yml
index 15a2b59..2005d1f 100644
--- a/.github/workflows/develop-build.yml
+++ b/.github/workflows/develop-build.yml
@@ -27,6 +27,14 @@ jobs:
${{ runner.os }}-maven-
- name: Build with Maven
run: mvn clean verify -U -P snapshot-build
+ - name: Create ZIP archive for migration tool
+ uses: montudor/action-zip@v1
+ with:
+ args: zip -qq -r migration-tool.zip migration
+ - name: Create ZIP archive for setup scripts
+ uses: montudor/action-zip@v1
+ with:
+ args: zip -qq -r setup-scripts.zip install
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d %H:%M:%S %Z')"
@@ -45,5 +53,5 @@ jobs:
files: |
module-*/target/*.jar
module-core/src/main/resources/application.properties
- install/*
- migration/**
+ setup-scripts.zip
+ migration-tool.zip
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index f89d868..1611dae 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -27,6 +27,14 @@ jobs:
${{ runner.os }}-maven-
- name: Build with Maven
run: mvn clean verify -U -P release-build
+ - name: Create ZIP archive for migration tool
+ uses: montudor/action-zip@v1
+ with:
+ args: zip -qq -r migration-tool.zip migration
+ - name: Create ZIP archive for setup scripts
+ uses: montudor/action-zip@v1
+ with:
+ args: zip -qq -r setup-scripts.zip install
- name: Release
id: create_release
uses: softprops/action-gh-release@v2
@@ -38,5 +46,5 @@ jobs:
files: |
module-*/target/*.jar
module-core/src/main/resources/application.properties
- install/*
- migration/**
+ setup-scripts.zip
+ migration-tool.zip
diff --git a/docs/de/README.md b/docs/de/README.md
index 5ee57a3..750786c 100644
--- a/docs/de/README.md
+++ b/docs/de/README.md
@@ -1,8 +1,8 @@
# Dokumentation
-Dieses Dokument beschreibt alles Wichtige rund um den neuen Vokabularserver. Früher waren Vokabulare Teil von Goobi Workflow und wurden in der Datenbank `goobi` gespeichert. Jetzt ist alles, was mit Vokabularen zu tun hat, in eine neue, eigenständige Anwendung, den Vokabularserver, umgezogen. Der Vokabularserver benötigt eine eigene Datenbank, um alle Daten zu speichern, und ermöglicht den Zugriff auf die Vokabulare und Datensätze über eine REST-API. Goobi Workflow wurde aktualisiert, um den neuen Vokabularserver anstelle seiner eigenen, eingebetteten Vokabulare zu verwenden. Falls gewünscht, kann der Vokabularserver im Gegensatz zur Goobi Workflow-Instanz öffentlich zugänglich sein. Wenn Sie bereits vorher Vokabulare verwendet haben, lesen Sie bitte die Migrationsanleitung in dieser Dokumentation, um Ihre Daten auf den neuen Vokabularserver zu übertragen.
+Dieses Dokument beschreibt den neuen Vokabularserver. Bis zur Version 24.06 waren Vokabulare Teil von Goobi Workflow und wurden in der Goobi-Datenbank gespeichert. Ab Version 24.07 ist alles, was mit Vokabularen zu tun hat, in eine eigenständige Anwendung, den Vokabularserver, umgezogen. Der Vokabularserver benötigt eine eigene Datenbank, um alle Daten zu speichern, und ermöglicht den Zugriff auf die Vokabulare und Datensätze über eine REST-API. Goobi Workflow wurde aktualisiert, um den neuen Vokabularserver anstelle seiner eigenen, eingebetteten Vokabulare zu verwenden. Falls gewünscht, kann der Vokabularserver öffentlich zugänglich sein. Wenn Sie bereits vorher Vokabulare verwendet haben, lesen Sie bitte die Migrationsanleitung in dieser Dokumentation, um Ihre Daten auf den neuen Vokabularserver zu übertragen.
## Installation
-Bevor Sie den neuen Vokabularserver nutzen können, folgen Sie den [Installations Anweisungen](setup.md).
+Bevor Sie den Vokabularserver nutzen können, folgen Sie den [Installations Anweisungen](setup.md).
## Vokabularerstellung
Vokabulare und Vokabularschemata sind ein komplexes Thema für sich, daher wird die [Dokumentation der Erstellung von Vokabularen, Schemata und Feldtypen](creation.md) separat behandelt.
diff --git a/docs/de/migration.md b/docs/de/migration.md
index 1c4722e..cedb11e 100644
--- a/docs/de/migration.md
+++ b/docs/de/migration.md
@@ -9,13 +9,13 @@ Für alle folgenden Anweisungen muss der Vokabularserver bereits laufen.
Erstellen Sie zunächst eine virtuelle Python-Umgebung, aktivieren Sie diese und installieren Sie alle erforderlichen Python-Abhängigkeiten. Alle folgenden Anweisungen in dieser Dokumentation setzen immer eine aktivierte Python-Umgebung voraus, in der alle diese Abhängigkeiten vorhanden sind.
```bash
-python -m venv vmenv
+python3 -m venv vmenv
. vmenv/bin/activate
pip install requests mysql-connector-python==8.4.0 alive_progress lxml
```
## Migration der Vokabulardaten durchführen
-Laden Sie das [Vocabulary Migration Tool] herunter und entpacken Sie es (https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/migration/*zip*/migration.zip).
+Laden Sie das [Vocabulary Migration Tool](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/migration/*zip*/migration.zip) herunter und entpacken Sie es, falls nicht schon bei der Installation geschehen.
**Hinweis** Bevor Sie einen der folgenden Schritte durchführen, lesen Sie diese Dokumentation bitte zuerst vollständig durch.
Es gibt keine einfache "Nur diese Schritte ausführen"-Lösung für jeden Anwendungsfall.
@@ -32,6 +32,18 @@ Wenn Sie keine Feldtypen erstellen wollen, können Sie die Datenmigration mit de
python vocabulary-migrator.py --vocabulary-server-host localhost --vocabulary-server-port 8081 --goobi-database-host localhost --goobi-database-port 3306 --goobi-database-name goobi --goobi-database-user goobi --goobi-database-password goobi --continue-on-error --fallback-language eng
```
+### Skript
+Die obigen beiden Puntke, die virtuelle Python-Umgebung und die Migration der Vokabulardaten in einer typischen Installation:
+```bash
+cd /opt/digiverso/vocabulary/migration
+python3 -m venv vmenv
+. vmenv/bin/activate
+pip install requests mysql-connector-python==8.4.0 alive_progress lxml
+VOC_PORT=$(sudo grep -oP '^server.port=\K.*' /opt/digiverso/vocabulary/application.properties)
+DB_GOOBI_PW=$(sudo xmlstarlet sel -t -v '//Resource/@password' -n /etc/tomcat9/Catalina/localhost/goobi.xml)
+python vocabulary-migrator.py --vocabulary-server-host localhost --vocabulary-server-port "${VOC_PORT}" --goobi-database-host localhost --goobi-database-port 3306 --goobi-database-name goobi --goobi-database-user goobi --goobi-database-password "${DB_GOOBI_PW}" --continue-on-error --fallback-language ger
+```
+
**Hinweis** Ändern Sie die Parameter entsprechend Ihrer Konfiguration. Der Parameter `fallback-language` definiert die Standardsprache, die für ein mehrsprachiges Vokabularfeld verwendet wird, für das keine Standardsprache abgeleitet werden konnte. Die Option `continue-on-error` verhindert, dass das Migrationstool bei Fehlern bei der Datenmigration anhält. Diese Fehler können auftreten, wenn die Daten nicht in den neuen Vokabularserver eingefügt werden konnten. Mögliche Gründe dafür könnten sein:
- Der Vokabulardatensatz ist leer.
- Der Vokabulardatensatz enthält Daten, die mit einigen Typbeschränkungen nicht kompatibel sind.
@@ -48,7 +60,7 @@ python vocabulary-migrator.py --vocabulary-server-host localhost --vocabulary-se
- Führen Sie die Ersteinrichtung durch.
- Führen Sie die Vokabular-Migration wie oben beschrieben durch.
- Speichern Sie nach Abschluss der Migration die Datei `migration.csv` an einem sicheren Ort.
-Diese Datei enthält alle Informationen über die migrierten Datensätze mit ihren alten und neuen IDs.
+Diese Datei enthält alle Informationen über die migrierten Datensätze mit ihren alten und neuen IDs.
Diese Informationen werden benötigt, um später die Verweise auf die Vokabulardatensätze in den Metadaten-Dateien zu aktualisieren.
- Sie sollten versuchen, die Migration am Ende ohne den Parameter `--continue-on-error` durchzuführen. Wenn dies funktioniert, dann wissen Sie dass alles problemslos migriert werden konnte.
@@ -72,7 +84,7 @@ Dieses Problem zeigt an, dass eines der Vokabulardatensatzfelder den Wert `Geona
Während der Migration wurde dieses Feld (auf der Grundlage vorhandener Daten) mit einem neuen Typ konfiguriert, der die folgenden auswählbaren Werte `geonames` und `viaf` enthält.
Wie Sie sehen können, wird der aktuelle Wert mit einem großen `G` geschrieben, aber nur die klein geschriebene Version von `geonames` ist einer der auswählbaren Werte.
Daher schlägt die Validierung dieses Vokabeldatensatzes fehl und das Skript ist nicht in der Lage, diesen Datensatz zu importieren.
-In diesem speziellen Fall könnten Sie alle Vorkommen in der alten Datenbank aktualisieren und anschließend einen erneuten Import der Daten durchführen.
+In diesem speziellen Fall könnten Sie alle Vorkommen in der alten Datenbank aktualisieren und anschließend einen erneuten Import der Daten durchführen. Leeren Sie dazu zunächst die Vokabular-(SQL)-Datenbank.
Stellen Sie sicher, dass Sie die Datei `migration_issues.log` vor einem Re-Import umbenennen oder entfernen, da das Migrationsscript immer an diese Datei anhängt und so bereits gelöste Probleme enthalten würde.
Jeder Vokabulardatensatz, der in dieser Datei gemeldet wird, wurde nicht in den Vokabularserver importiert.
Wenn dies gewünscht ist (weil der Vokabeldatensatz fehlerhafte Daten enthält und nicht beibehalten werden soll), können Sie dieses Problem einfach ignorieren und weitermachen.
@@ -83,7 +95,7 @@ Mit `python vocabulary-migrator.py --help` können Sie sich alle verfügbaren Op
Wenn Sie vorhaben, bestehende Feldtypen wiederzuverwenden oder andere Vokabulare für Vokabularreferenzen während einer Migration zu verwenden, erstellen Sie die folgenden drei Dateien: `reference_type_lookup.csv`, `reference_value_lookup.csv` und `type_definition_lookup.csv`.
Legen Sie alle drei Dateien in einem neuen Verzeichnis ab und übergeben Sie diesen Verzeichnispfad als Parameter `--lookup-file-directory` an das Migrationstool.
-Um die folgende Konfiguration besser zu verstehen, geben wir ein Beispiel.
+Um die folgende Konfiguration besser zu verstehen, geben wir ein Beispiel.
Stellen Sie sich vor, Sie haben derzeit ein Vokabular mit einem Feld mit den folgenden auswählbaren Werten: `red`, `blue`.
Datensätze können jeden dieser beiden Werte enthalten.
Die Unterstützung mehrerer Sprachen wird derzeit erreicht, indem eine neue Felddefinition mit einem anderen Sprachwert erstellt wird und die gleiche Anzahl von auswählbaren Werten, diesmal in der anderen Sprache, bereitgestellt wird: `rot`, `blau` (auf Deutsch).
@@ -113,7 +125,7 @@ rot|blau,2
Diese Datei ordnet allen auswählbaren Werten (in allen Sprachen, eine Sprache pro Zeile) die ID des Vokabulars zu, welches die Farbdatensätze enthält.
Achten Sie bitte darauf, immer eine Sprache pro Zeile zu definieren.
Der Grund dafür ist, dass verschiedene Sprachen vorher als mehrere Felddefinitionen vorhanden waren und die Migrationsverarbeitung diese Art der Trennung erfordert.
-Der Wert für die Spalten `values` muss mit der Spalte `selection` in der Tabelle `vocabulary_structure` der bestehenden Datenbank übereinstimmen.
+Der Wert für die Spalten `values` muss mit der Spalte `selection` in der Tabelle `vocabulary_structure` der bestehenden Datenbank übereinstimmen.
Die Datei `reference_value_lookup.csv` sieht wie folgt aus:
```csv
@@ -125,11 +137,30 @@ blau,123
```
Diese Datei ordnet allen Datensatzwerten die entsprechende Datensatz-IDs im Referenzvokabular zu (also die ID des Farbdatensatzes im Farbvokabular).
+### Test der Vokabulardaten-Migration
+- Wenn eine Datenmigration stattgefunden hat, prüfen Sie, ob alle Vokabulare migriert wurden:
+```bash
+curl -s http://localhost:8081/api/v1/vocabularies | jq -r '._embedded.vocabularyList[] .name'
+```
+- Prüfen Sie, ob die Links korrekt aufgelöst werden (siehe Konfiguration):
+```bash
+curl http://localhost:8081/api/v1/records/1 | jq
+```
+Das JSON-Element `_links` sollte Verweise auf andere Ressourcen enthalten.
+Diese URLs sollten gültig und auflösbar sein.
+Der Host-Teil dieser URLs wird aus der Anfrage generiert.
+
## Migration der Mets-Datei
Dieser Schritt kann nur durchgeführt werden, wenn die Migration der Vokabulardaten erfolgreich abgeschlossen wurde!
Wenn die Datei `migration.csv` vorhanden ist, führen Sie den folgenden Befehl in der aktivierten Python-Umgebung aus:
```bash
+cd /opt/digiverso/vocabulary/migration
+sudo -s
+. vmenv/bin/activate
+# dry-run:
+python metadata-migrator.py --verbose --log INFO -m migration.csv -d /opt/digiverso/goobi/metadata --dry
+# metadata migration
python metadata-migrator.py -m migration.csv -d /opt/digiverso/goobi/metadata
```
@@ -138,7 +169,7 @@ Wann immer das Script eine Vokabularreferenz in der Mets-Datei findet, wird es v
Wenn etwas geändert wird, wird zuvor ein Backup der Mets-Datei erstellt.
Wenn die Mets-Dateien zusätzliche Referenzen auf Datensätze in separaten XML-Elementen enthalten (z. B. `5661`), kann das `metadata-migrator.py` Script diese Referenzen auch mit dem zusätzlichen Parameter `--manual-id-fix SourceID` aktualisieren. Der Wert des Parameters muss mit dem Attribut `Name` eines `Metadaten`-Elements übereinstimmen, damit dessen Datensatz-ID durch die neue Datensatz-ID ersetzt werden kann. Dieser Schritt darf nicht zweimal ausgeführt werden, da dies die IDs verfälschen würde!
-
+
## Datenbereinigung
Wenn die Datenmigration erfolgreich abgeschlossen ist und Sie sicher sind, dass Sie die alten Daten nicht mehr benötigen, können Sie alle Vokabulartabellen manuell aus der Datenbank `goobi` Ihrer Goobi-Instanz entfernen:
- `vocabulary`
@@ -146,7 +177,7 @@ Wenn die Datenmigration erfolgreich abgeschlossen ist und Sie sicher sind, dass
- `vocabulary_record_data`
- `vocabulary_structure`
-**Achtung** Die Datenbereinigung kann nicht rückgängig gemacht werden.
-Wenn Sie sich nicht sicher sind, führen Sie die Bereinigungsschritte nicht durch.
-Die alten Vokabulardaten haben keinen Einfluss auf neuere Versionen von Goobi Workflow.
+**Achtung** Die Datenbereinigung kann nicht rückgängig gemacht werden.
+Wenn Sie sich nicht sicher sind, führen Sie die Bereinigungsschritte nicht durch.
+Die alten Vokabulardaten haben keinen Einfluss auf neuere Versionen von Goobi Workflow.
Wir empfehlen, diese Daten für den Fall der Fälle für einige Zeit aufzubewahren.
diff --git a/docs/de/setup.md b/docs/de/setup.md
index 733109d..d7c0909 100644
--- a/docs/de/setup.md
+++ b/docs/de/setup.md
@@ -2,21 +2,13 @@
Diese Dokumentation beschreibt den Prozess der Installation und Ersteinrichtung des Vokabularservers.
## Download und Installation
-- Laden Sie die [Neuste Version](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/target/) des Vokabularservers herunter.
-- Laden Sie die [Konfigurationsdatei](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/src/main/resources/application.properties) des Vokabularservers herunter.
+- Laden Sie die [Neuste Version](https://github.com/intranda/goobi-vocabulary-server/releases/latest) des Vokabularservers herunter.
+- Laden Sie die [Konfigurationsdatei](https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties) des Vokabularservers herunter.
- Passen Sie die Konfigurationsdatei entsprechend Ihrer Konfiguration an und entfernen Sie nicht geänderte Zeilen.
- Datenbankanmeldeinformationen und Datenbankname.
- Basis-URL und Port.
-- **TODO** *Installieren Sie die `vocabulary-server.jar` und die Konfigurationsdatei `application.properties` direkt in einen neuen Ordner (z. B. `/opt/digiverso/vocabulary/`)*
-
-## Als systemd-Dienst starten
-- **TODO** *Erstellen einer systemd Service Unit für den Vokabularserver (Die Anwendung sollte bei SIGTERM korrekt herunterfahren können)*
-- **TODO** *Admin-Dokumentation hier*
-- Führen Sie `java -jar vocabulary-server-VERSION.jar` aus.
-- Wenn der Start erfolgreich war, werden Sie nach ein paar Sekunden eine Zeile wie diese sehen:
-```bash
-Started VocabularyServerApplication in 4.244 seconds (process running for 4.581)
-```
+ - Sicherheitstoken (dieses muss identisch auch in Goobi konfiguriert werden).
+- Erstellen Sie ein Systemd-Service, um den Dienst automatisch zu starten.
## Einrichtung von Goobi Workflow zur Kommunikation mit dem Vokabularserver
- Goobi Workflow verwendet seit Version `24.07` den neuen Vokabularserver.
@@ -32,9 +24,92 @@ Started VocabularyServerApplication in 4.244 seconds (process running for 4.581)
- Ändern Sie die Variable `HOST` am Anfang entsprechend der Konfiguration des Vokabularservers, lassen Sie das Suffix `/api/v1` unverändert.
- Führen Sie das Skript aus.
-## Sicherheit
-- Sie können Apache-URL-Beschränkungen einrichten, um den Vokabularserver vor unberechtigtem Zugriff zu schützen.
-- **TODO** *Admins, bitte finden Sie heraus, was und wie man es im Detail macht.*
+## Installationsskript
+Für die obigen drei Punkte unter Ubuntu:
+```bash
+export VOC_PORT=8081
+export VOC_TOKEN=supersecret
+export VOC_PATH=/opt/digiverso/vocabulary
+export VOC_USER=vocabulary
+export VOC_SQL_USER=${VOC_USER}
+export VOC_SQL_DB=${VOC_USER}
+export PW_SQL_VOC=$(/dev/null
+sudo ln -rs ${VOC_PATH}/vocabulary-server*.jar ${VOC_PATH}/vocabulary-server.jar
+
+# create system user which will run the service
+sudo adduser --system --home ${VOC_PATH}/home --shell /usr/sbin/nologin --no-create-home --disabled-login ${VOC_USER}
+
+# download the vocabulary migration tools
+git clone --depth=1 https://github.com/intranda/goobi-vocabulary-server.git /tmp/goobi-vocabulary-server
+sudo cp -ait ${VOC_PATH} /tmp/goobi-vocabulary-server/migration
+
+# download and set up the config file
+wget https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties -O - | sudo tee ${VOC_PATH}/application.properties >/dev/null
+sudo sed -re "s|^(server.port=).*|\1${VOC_PORT}|" \
+ -e "s|^(security.token=).*|\1${VOC_TOKEN}|" \
+ -e "s|^(spring.datasource.username=).*|\1${VOC_SQL_USER}|" \
+ -e "s|^(spring.datasource.password=).*|\1${PW_SQL_VOC}|" \
+ -e "s|^(spring.datasource.url=).*|\1jdbc:mariadb://localhost:3306/${VOC_SQL_DB}|" \
+ -i ${VOC_PATH}/application.properties
+sudo chown ${VOC_USER}: ${VOC_PATH}/application.properties
+sudo chmod 600 ${VOC_PATH}/application.properties
+
+# install a systemd service unit file
+cat << EOF | sudo tee /etc/systemd/system/vocabulary.service
+[Unit]
+Description=Goobi Vocabulary Server
+After=mysql.service remote-fs.target
+Requires=mysql.service remote-fs.target
+
+[Service]
+WorkingDirectory=${VOC_PATH}
+Restart=always
+RestartSec=20s
+StartLimitInterval=100s
+StartLimitBurst=4
+ExecStart=/usr/bin/java -jar vocabulary-server.jar
+User=${VOC_USER}
+NoNewPrivileges=true
+ProtectSystem=true
+PrivateTmp=yes
+
+[Install]
+WantedBy=default.target tomcat9.service
+EOF
+sudo systemctl daemon-reload
+sudo systemctl enable vocabulary.service
+
+# create and configure the database
+sudo mysql -e "CREATE DATABASE ${VOC_SQL_DB};
+ CREATE USER '${VOC_SQL_USER}'@'localhost' IDENTIFIED BY '${PW_SQL_VOC}';
+ GRANT ALL PRIVILEGES ON ${VOC_SQL_DB}.* TO '${VOC_SQL_USER}'@'localhost' WITH GRANT OPTION;
+ FLUSH PRIVILEGES;"
+
+# append vocabulary server address to the Goobi workflow config
+grep ^vocabularyServerHost= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerHost=localhost" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties
+grep ^vocabularyServerPort= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerPort=${VOC_PORT}" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties
+grep ^vocabularyServerToken= /opt/digiverso/goobi/config/goobi_config.properties || echo "vocabularyServerToken=${VOC_TOKEN}" | sudo tee -a /opt/digiverso/goobi/config/goobi_config.properties
+
+# start the vocabulary server
+sudo systemctl start vocabulary.service
+## check startup
+journalctl -u vocabulary.service -f
+
+# initial set up
+wget https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/default_setup.sh -O - | sudo tee ${VOC_PATH}/default_setup.sh >/dev/null
+bash ${VOC_PATH}/default_setup.sh
+## test
+curl -s http://localhost:${VOC_PORT}/api/v1/types | jq -r '._embedded.fieldTypeList[] .name'
+```
+
+
+## Erreichbarkeit
+- Sie können den Vokabularserver von außen erreichbar machen, indem Sie einen Proxy samt Zugriffskontrolle davorschalten.
## Installationstest
- Ändern Sie für alle Befehle Host und Port entsprechend.
@@ -56,16 +131,3 @@ skos:related
skos:closeMatch
skos:exactMatch
```
-- Wenn eine Datenmigration stattgefunden hat, prüfen Sie, ob alle Vokabulare migriert wurden:
-```bash
-curl http://localhost:8081/api/v1/vocabularies/all | jq -r '._embedded.vocabularyList[] .name'
-```
-- Prüfen Sie, ob die Links korrekt aufgelöst werden (siehe Konfiguration):
-```bash
-curl http://localhost:8081/api/v1/records/1 | jq
-```
-Das JSON-Element `_links` sollte Verweise auf andere Ressourcen enthalten.
-Diese URLs sollten gültig und auflösbar sein.
-Wenn Sie keinen dieser Verweise öffnen können, überprüfen Sie die Konfiguration des Vokabularservers (Konfigurationsoption `vocabulary-server.base-url`).
-Bei Problemen mit diesen URLs müssen die Daten nicht neu importiert werden.
-Aktualisieren Sie einfach die Konfigurationsdatei und starten Sie den Vokabularserver neu, damit die Änderungen wirksam werden.
diff --git a/docs/en/README.md b/docs/en/README.md
index f26dd6f..4bb1a9c 100644
--- a/docs/en/README.md
+++ b/docs/en/README.md
@@ -1,8 +1,8 @@
# Documentation
-This document describes everything important regarding the new vocabulary server. Earlier, vocabularies were part of Goobi Workflow and saved in the `goobi` database. Now, everything related to vocabularies moved to a new stand-alone application, the vocabulary server. The vocabulary server requires its own database to store all its data and provides access to the vocabularies and records through a REST API. Goobi Workflow has been updated to use the new vocabulary server instead of its own, embedded vocabularies. If desired, the vocabulary server could be publicly available in contrast to the Goobi Workflow instance. If you already used vocabularies before, check out the migration guide in this documentation to transfer your data to the new vocabulary server.
+This document describes the new vocabulary server. Until version 24.06, vocabularies were part of Goobi Workflow and saved in Goobi's database. Since version 24.07, everything related to vocabularies moved to a stand-alone application, the vocabulary server. The vocabulary server requires its own database to store all its data and provides access to the vocabularies and records through a REST API. Goobi Workflow has been updated to use the new vocabulary server instead of its own, embedded vocabularies. If desired, the vocabulary server could be publicly available. If you already used vocabularies before, check out the migration guide in this documentation to transfer your data to the new vocabulary server.
## Setup
-Before you can start using the new vocabulary server, follow the [setup instructions](setup.md).
+Before you can start using the vocabulary server, follow the [setup instructions](setup.md).
## Vocabulary Creation
Vocabularies and vocabulary schemas are a complex topic on their own, therefore the [documentation of the creation of vocabularies, schemas and field types](creation.md) is covered separately.
diff --git a/docs/en/setup.md b/docs/en/setup.md
index 3a1683b..a769a46 100644
--- a/docs/en/setup.md
+++ b/docs/en/setup.md
@@ -2,8 +2,8 @@
This documentation describes the process of bootstrapping the vocabulary server.
## Download and Installation
-- Download [Latest Build](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/target/) of vocabulary server.
-- Download [Configuration File](https://jenkins.intranda.com/job/intranda/job/vocabulary-server/job/develop/lastSuccessfulBuild/artifact/module-core/src/main/resources/application.properties) of the vocabulary server.
+- Download [Latest Build](https://github.com/intranda/goobi-vocabulary-server/releases/latest) of vocabulary server.
+- Download [Configuration File](https://github.com/intranda/goobi-vocabulary-server/releases/latest/download/application.properties) of the vocabulary server.
- Adapt configuration file properly and remove unmodified lines.
- Database credentials and database name.
- Base URL and port.
@@ -66,6 +66,4 @@ curl http://localhost:8081/api/v1/records/1 | jq
```
The `_links` JSON element should contain references to other resources.
These URLs should be valid and resolvable.
-If you are unable to open any of these references, check the configuration of the vocabulary server (`vocabulary-server.base-url` configuration option).
-Any issues regarding these URLs doesn't require a re-import of the data.
-Just update the configuration file and restart the vocabulary server for the changes to take effect.
+The host part of these URLs is generated from the request.
diff --git a/install/default_setup.sh b/install/default_setup.sh
index a32b4ee..93d8967 100755
--- a/install/default_setup.sh
+++ b/install/default_setup.sh
@@ -1,8 +1,9 @@
#!/usr/bin/env bash
HOST='localhost:8081/api/v1'
+TOKEN='secret'
curl_call() {
- curl --location "$HOST/$1" --header 'Content-Type: application/json' --data "$2"
+ curl --location "$HOST/$1" --header 'Content-Type: application/json' --header "Authorization: Bearer $TOKEN" --data "$2"
}
create_language() {
diff --git a/migration/lib/api.py b/migration/lib/api.py
index 9dcfa9c..fd8cd69 100644
--- a/migration/lib/api.py
+++ b/migration/lib/api.py
@@ -30,7 +30,7 @@
SCHEMA_LOOKUP = 9
class API:
- def __init__(self, host, port):
+ def __init__(self, host, port, token):
self.urls = {}
self.urls[SCHEMA_INSERTION] = SCHEMA_INSERTION_URL
self.urls[VOCABULARY_INSERTION] = VOCABULARY_INSERTION_URL
@@ -45,6 +45,8 @@ def __init__(self, host, port):
self.type_counter = 1
for u in self.urls:
self.urls[u] = self.urls[u].replace('{{HOST}}', host).replace('{{PORT}}', port)
+ if token != None:
+ HEADERS['Authorization'] = f'Bearer {token}'
def set_context(self, ctx):
self.ctx = ctx
diff --git a/migration/lib/mets_context.py b/migration/lib/mets_context.py
index c8be2e7..56a4f02 100644
--- a/migration/lib/mets_context.py
+++ b/migration/lib/mets_context.py
@@ -6,8 +6,9 @@
RECORD_PATTERN = re.compile('^(\\d+).*$')
class Context:
- def __init__(self, api, verbose, continue_on_error, metadata_directory, mapping_file, preferred_mets_main_value_language, manual_id_fix):
+ def __init__(self, api, dry, verbose, continue_on_error, metadata_directory, mapping_file, preferred_mets_main_value_language, manual_id_fix):
self.api = api
+ self.dry = dry
self.verbose = verbose
self.continue_on_error = continue_on_error
self.metadata_directory = metadata_directory
diff --git a/migration/lib/mets_manipulator.py b/migration/lib/mets_manipulator.py
index 25ab52b..ca3dcb0 100644
--- a/migration/lib/mets_manipulator.py
+++ b/migration/lib/mets_manipulator.py
@@ -32,7 +32,7 @@ def process_mets_file(self):
root = tree.getroot()
self.process_node(root)
- if self.changed:
+ if self.changed and not self.ctx.dry:
self.create_backup()
tree.write(self.file_path, encoding='utf-8', xml_declaration=True)
self.ctx.log_processed(self.file_path)
@@ -40,8 +40,12 @@ def process_mets_file(self):
def process_node(self, node):
if self.is_vocabulary_reference(node) and not self.is_already_migrated(node):
self.process_vocabulary_reference(node)
+ if self.ctx.dry:
+ dump_node(node)
if self.is_manual_id_reference(node):
self.process_manual_id_reference(node)
+ if self.ctx.dry:
+ dump_node(node)
for child in node:
self.process_node(child)
@@ -129,4 +133,8 @@ def generate_vocabulary_uri(vocabulary_id):
return VOCABULARY_ENDPOINT.replace('{{ID}}', str(vocabulary_id))
def generate_record_uri(record_id):
- return RECORD_ENDPOINT.replace('{{ID}}', str(record_id))
\ No newline at end of file
+ return RECORD_ENDPOINT.replace('{{ID}}', str(record_id))
+
+def dump_node(node):
+ attributes = ' '.join(f'{k}="{v}"' for k, v in node.attrib.items())
+ logging.info(f'<{node.tag} {attributes} />')
\ No newline at end of file
diff --git a/migration/metadata-migrator.py b/migration/metadata-migrator.py
index ea9fd55..643ba90 100644
--- a/migration/metadata-migrator.py
+++ b/migration/metadata-migrator.py
@@ -11,9 +11,10 @@ def main():
api = API(
args.vocabulary_server_host,
- args.vocabulary_server_port
+ args.vocabulary_server_port,
+ args.vocabulary_server_token
)
- ctx = Context(api, args.verbose, args.continue_on_error, args.metadata_directory, args.mapping_file, args.preferred_mets_main_value_language, args.manual_id_fix)
+ ctx = Context(api, args.dry, args.verbose, args.continue_on_error, args.metadata_directory, args.mapping_file, args.preferred_mets_main_value_language, args.manual_id_fix)
try:
migrator = MetsMigrator(ctx)
@@ -31,10 +32,12 @@ class RawTextDefaultsHelpFormatter(argparse.RawTextHelpFormatter, argparse.Argum
def parse_args():
parser = argparse.ArgumentParser(prog='metadata-migrator.py', formatter_class=RawTextDefaultsHelpFormatter, description='Metadata migration tool.')
+ parser.add_argument('--dry', required=False, default=False, action='store_const', const=True, help='Don\'t persist changes but only print replacements to the console')
parser.add_argument('--metadata-directory', '-d', required=True, help='directory to recursively scan for metadata to update')
parser.add_argument('--mapping-file', '-m', required=True, help='vocabulary and record mapping file')
parser.add_argument('--vocabulary-server-host', type=str, default='localhost', help='vocabulary server host')
parser.add_argument('--vocabulary-server-port', type=str, default='8081', help='vocabulary server port')
+ parser.add_argument('--vocabulary-server-token', type=str, default=None, help='vocabulary server security token')
parser.add_argument('--preferred-mets-main-value-language', type=str, default='eng', help='Default language to use for mets value writing, if present and prior value invalid')
parser.add_argument('--manual-id-fix', type=str, default=None, help='Manually fix the record ID of elements whose name attribute matches this parameter. Caution, this must not be executed twice!')
parser.add_argument('--log', required=False, default='INFO', help='logger level (possible values are: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL)')
diff --git a/migration/vocabulary-migrator.py b/migration/vocabulary-migrator.py
index 1fffe11..99c8d9a 100644
--- a/migration/vocabulary-migrator.py
+++ b/migration/vocabulary-migrator.py
@@ -19,7 +19,8 @@ def main():
)
api = API(
args.vocabulary_server_host,
- args.vocabulary_server_port
+ args.vocabulary_server_port,
+ args.vocabulary_server_token
)
ctx = Context(db, api, args.dry, args.fallback_language, args.continue_on_error, args.lookup_file_directory)
api.set_context(ctx)
@@ -45,6 +46,7 @@ def parse_args():
parser.add_argument('--dry', type=str, required=False, help='Don\'t call the API but instead write the API calls to a file, provide the filename as a parameter')
parser.add_argument('--vocabulary-server-host', type=str, default='localhost', help='vocabulary server host')
parser.add_argument('--vocabulary-server-port', type=str, default='8081', help='vocabulary server port')
+ parser.add_argument('--vocabulary-server-token', type=str, default=None, help='vocabulary server security token')
parser.add_argument('--goobi-database-host', type=str, default='localhost', help='Goobi database host')
parser.add_argument('--goobi-database-port', type=str, default='3306', help='Goobi database port')
parser.add_argument('--goobi-database-name', type=str, default='goobi', help='Goobi database name')
diff --git a/module-core/pom.xml b/module-core/pom.xml
index afa69e7..20563fb 100644
--- a/module-core/pom.xml
+++ b/module-core/pom.xml
@@ -5,18 +5,16 @@
org.springframework.boot
spring-boot-starter-parent
- 3.3.2
+ 3.3.3
io.goobi.vocabulary
vocabulary-server-core
- 1.0.0
+ 1.1.0
Vocabulary-Server-Core
Spring Boot based RESTful web service for vocabulary management
jar
- 17
- 17
17
UTF-8
@@ -27,7 +25,7 @@
io.goobi.vocabulary
vocabulary-server-exchange
- 1.0.0
+ 1.1.0
compile
@@ -46,6 +44,15 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
com.fasterxml.jackson.dataformat
jackson-dataformat-xml
@@ -139,6 +146,7 @@
+ ${project.artifactId}
org.springframework.boot
diff --git a/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java b/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java
index 7bc067b..cd13f3d 100644
--- a/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java
+++ b/module-core/src/main/java/io/goobi/vocabulary/api/LanguageController.java
@@ -7,14 +7,11 @@
import io.goobi.vocabulary.model.jpa.LanguageEntity;
import io.goobi.vocabulary.service.manager.LanguageDTOManager;
import io.goobi.vocabulary.service.manager.Manager;
-import io.goobi.vocabulary.service.rdf.RDFMapper;
-import org.apache.commons.io.IOUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.PagedModel;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -23,7 +20,6 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@@ -33,13 +29,11 @@ public class LanguageController {
private final LanguageDTOManager manager;
private final Manager managerEntity;
private final LanguageAssembler assembler;
- private final RDFMapper rdfMapper;
- public LanguageController(LanguageDTOManager managerDTO, Manager managerEntity, LanguageAssembler assembler, RDFMapper rdfMapper) {
+ public LanguageController(LanguageDTOManager managerDTO, Manager managerEntity, LanguageAssembler assembler) {
this.manager = managerDTO;
this.managerEntity = managerEntity;
this.assembler = assembler;
- this.rdfMapper = rdfMapper;
}
@GetMapping("/languages")
@@ -57,38 +51,6 @@ public EntityModel findByAbbreviation(@PathVariable String abbreviatio
return assembler.toModel(manager.find(abbreviation));
}
- @GetMapping(value = "/languages/{id}", produces = {"application/rdf+xml"})
- public String oneAsRdfXml(@PathVariable long id) {
- return rdfMapper.toRDFXML(managerEntity.get(id));
- }
-
- @GetMapping(
- value = "/languages/{id}/export/rdfxml",
- produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
- )
- public @ResponseBody ResponseEntity> exportAsRdfXml(@PathVariable long id) {
- return ResponseEntity.ok()
- .contentType(MediaType.parseMediaType("application/octet-stream"))
- .header("Content-disposition", "attachment; filename=\"language_" + id + ".rdf\"")
- .body(IOUtils.toByteArray(rdfMapper.toRDFXML(managerEntity.get(id))));
- }
-
- @GetMapping(value = "/languages/{id}", produces = {"application/n-triples", "text/turtle"})
- public String oneAsRdfTurtle(@PathVariable long id) {
- return rdfMapper.toRDFTurtle(managerEntity.get(id));
- }
-
- @GetMapping(
- value = "/languages/{id}/export/turtle",
- produces = MediaType.APPLICATION_OCTET_STREAM_VALUE
- )
- public @ResponseBody ResponseEntity> exportAsRdfTurtle(@PathVariable long id) {
- return ResponseEntity.ok()
- .contentType(MediaType.parseMediaType("application/octet-stream"))
- .header("Content-disposition", "attachment; filename=\"language_" + id + ".ttl\"")
- .body(IOUtils.toByteArray(rdfMapper.toRDFTurtle(managerEntity.get(id))));
- }
-
@PostMapping("/languages")
@ResponseStatus(HttpStatus.CREATED)
public EntityModel create(@RequestBody Language newLanguage) throws VocabularyException {
diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java
new file mode 100644
index 0000000..0ef7193
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/security/BearerTokenAuthFilter.java
@@ -0,0 +1,73 @@
+package io.goobi.vocabulary.security;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.Optional;
+
+@Component
+public class BearerTokenAuthFilter extends OncePerRequestFilter {
+ @Value("${security.token:#{null}}")
+ private String secretToken;
+
+ @Value("${security.anonymous.read-allowed:false}")
+ private boolean anonymousReadAllowed;
+
+ @Bean
+ public FilterRegistrationBean bearerTokenAuthFilterFilterRegistrationBean(BearerTokenAuthFilter filter) {
+ FilterRegistrationBean registration = new FilterRegistrationBean<>(filter);
+ registration.setEnabled(false);
+ return registration;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ final String authHeader = request.getHeader("Authorization");
+
+ final String token = authHeader != null && authHeader.startsWith("Bearer ") ? authHeader.substring(7) : null;
+ final Optional jwt = Optional.ofNullable(token);
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication == null) {
+ User user = new User();
+
+ if (isPublic(request) || (jwt.isPresent() && isTokenValid(jwt.get()))) {
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+ user,
+ null,
+ user.getAuthorities()
+ );
+
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ }
+ }
+ filterChain.doFilter(request, response);
+ }
+
+ private boolean isPublic(HttpServletRequest request) {
+ // TODO: Vocabulary filtering
+ return anonymousReadAllowed && "GET".equals(request.getMethod());
+ }
+
+ private boolean isTokenValid(String accessToken) {
+ // If secret token is not set, deny
+ if (secretToken == null) {
+ return false;
+ }
+ return secretToken.equals(accessToken);
+ }
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java b/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java
new file mode 100644
index 0000000..b1027cc
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/security/SecurityConfiguration.java
@@ -0,0 +1,38 @@
+package io.goobi.vocabulary.security;
+
+import jakarta.servlet.DispatcherType;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfiguration {
+ private final BearerTokenAuthFilter bearerTokenAuthFilter;
+
+ public SecurityConfiguration(BearerTokenAuthFilter bearerTokenAuthFilter) {
+ this.bearerTokenAuthFilter = bearerTokenAuthFilter;
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(auth -> auth
+ .dispatcherTypeMatchers(DispatcherType.ASYNC)
+ .permitAll()
+ .anyRequest()
+ .authenticated()
+ )
+ .sessionManagement(sess -> sess
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .addFilterBefore(bearerTokenAuthFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/security/User.java b/module-core/src/main/java/io/goobi/vocabulary/security/User.java
new file mode 100644
index 0000000..1a71e90
--- /dev/null
+++ b/module-core/src/main/java/io/goobi/vocabulary/security/User.java
@@ -0,0 +1,19 @@
+package io.goobi.vocabulary.security;
+
+import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+
+@Data
+public class User implements UserDetails {
+ private String username;
+ private String password;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return List.of();
+ }
+}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java
index e50c3b3..d473fae 100644
--- a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java
+++ b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapper.java
@@ -1,16 +1,11 @@
package io.goobi.vocabulary.service.rdf;
-import io.goobi.vocabulary.model.jpa.LanguageEntity;
import io.goobi.vocabulary.model.jpa.VocabularyEntity;
public interface RDFMapper {
boolean isRDFCompatible(VocabularyEntity entity);
- String toRDFXML(LanguageEntity entity);
-
String toRDFXML(VocabularyEntity entity);
- String toRDFTurtle(LanguageEntity entity);
-
String toRDFTurtle(VocabularyEntity entity);
}
diff --git a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java
index d2669c9..99dc8d9 100644
--- a/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java
+++ b/module-core/src/main/java/io/goobi/vocabulary/service/rdf/RDFMapperImpl.java
@@ -1,6 +1,5 @@
package io.goobi.vocabulary.service.rdf;
-import io.goobi.vocabulary.api.LanguageController;
import io.goobi.vocabulary.api.VocabularyController;
import io.goobi.vocabulary.api.VocabularyRecordController;
import io.goobi.vocabulary.exception.MappingException;
@@ -9,10 +8,8 @@
import io.goobi.vocabulary.model.jpa.FieldTranslationEntity;
import io.goobi.vocabulary.model.jpa.FieldTypeEntity;
import io.goobi.vocabulary.model.jpa.FieldValueEntity;
-import io.goobi.vocabulary.model.jpa.LanguageEntity;
import io.goobi.vocabulary.model.jpa.VocabularyEntity;
import io.goobi.vocabulary.model.jpa.VocabularyRecordEntity;
-import io.goobi.vocabulary.service.rdf.vocabulary.LANGUAGE;
import org.apache.jena.datatypes.xsd.XSDDatatype;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
@@ -25,10 +22,11 @@
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.SKOS;
import org.apache.jena.vocabulary.XSD;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import org.springframework.web.util.UriComponents;
import java.io.StringWriter;
import java.lang.reflect.Method;
@@ -39,12 +37,6 @@
@Service
public class RDFMapperImpl implements RDFMapper {
- @Value("${vocabulary-server.base-url}")
- private String host;
-
- @Value("${server.port}")
- private int port;
-
public static final RDFFormat RDF_XML_SYNTAX = RDFFormat.RDFXML;
public static final RDFFormat RDF_TURTLE_SYNTAX = RDFFormat.TURTLE_BLOCKS;
@@ -56,15 +48,24 @@ private String transform(T entity, EntityToModelMappingFunction function,
private String generateURIForId(Class> clazz, long id) {
try {
+ String baseUrl = extractBaseURI();
String classRoute = extractClassEndpoint(clazz);
String methodRoute = extractMethodEndpoint(clazz.getMethod("one", long.class));
String endpoint = classRoute + methodRoute.replace("{id}", Long.toString(id));
- return host + ':' + port + endpoint;
+ return baseUrl + endpoint;
} catch (NoSuchMethodException e) {
throw new MappingException(clazz, String.class, e);
}
}
+ private String extractBaseURI() {
+ UriComponents uriComponents = ServletUriComponentsBuilder.fromCurrentRequest().build();
+ String scheme = uriComponents.getScheme();
+ String host = uriComponents.getHost();
+ String port = String.valueOf(uriComponents.getPort());
+ return scheme + "://" + host + ':' + port;
+ }
+
private static String extractClassEndpoint(Class> clazz) throws NoSuchMethodException {
String[] values = clazz.getAnnotation(RequestMapping.class).value();
assert (values.length == 1);
@@ -104,21 +105,11 @@ public boolean isRDFCompatible(VocabularyEntity entity) {
}
}
- @Override
- public String toRDFXML(LanguageEntity entity) {
- return transform(entity, this::generateLanguageModel, RDF_XML_SYNTAX);
- }
-
@Override
public String toRDFXML(VocabularyEntity entity) {
return transform(entity, this::generateVocabularyModel, RDF_XML_SYNTAX);
}
- @Override
- public String toRDFTurtle(LanguageEntity entity) {
- return transform(entity, this::generateLanguageModel, RDF_TURTLE_SYNTAX);
- }
-
@Override
public String toRDFTurtle(VocabularyEntity entity) {
return transform(entity, this::generateVocabularyModel, RDF_TURTLE_SYNTAX);
@@ -292,16 +283,4 @@ private void createSkosInSchemePropertyIfApplicable(Model model, List !r.isMetadata() && r.getId() != topRecordId)
.forEach(r -> recordMap.get(r.getId()).addProperty(SKOS.inScheme, topElement));
}
-
- private Model generateLanguageModel(LanguageEntity entity) {
- String uri = generateURIForId(LanguageController.class, entity.getId());
-
- Model model = ModelFactory.createDefaultModel();
-
- Resource resource = model.createResource(uri)
- .addProperty(LANGUAGE.NAME, entity.getName())
- .addProperty(LANGUAGE.ABBREVIATION, entity.getAbbreviation());
-
- return model;
- }
}
diff --git a/module-core/src/main/resources/application.properties b/module-core/src/main/resources/application.properties
index b4d56cc..d368c74 100644
--- a/module-core/src/main/resources/application.properties
+++ b/module-core/src/main/resources/application.properties
@@ -3,12 +3,14 @@
# Security
# Only listen on local address. Remove this line, if you want to open the vocabulary server to the public.
-# ATTENTION: Currently, there is no security in the vocabulary server. Every caller of the API can do anything!
-server.address=127.0.0.1
+#server.address=127.0.0.1
+
+# Set a security token! If not set, you won't be able to make modifying API calls
+#security.token=secret
+# Control anonymous read operations. If set to false or not set, anonymous readers will not get access
+#security.anonymous.read-allowed=true
# Basic configuration
-# The base url of the vocabulary server (which depends on external factors), it is used it generate valid API reference links
-vocabulary-server.base-url=http://localhost
# The port the vocabulary server should listen on
server.port=8081
# The name of the application, can be customized but shouldn't affect anything
@@ -32,6 +34,7 @@ springdoc.swagger-ui.path=/docs/ui
#spring.jpa.show-sql=true
#logging.level.org.hibernate.orm.jdbc.bind=TRACE
#logging.level.org.hibernate.SQL=TRACE
+#logging.level.org.springframework.security=TRACE
# DO NOT TOUCH THESE UNLESS YOU KNOW WHAT TO DO
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
diff --git a/module-core/src/test/resources/application.properties b/module-core/src/test/resources/application.properties
index 58223d9..2bed3c5 100644
--- a/module-core/src/test/resources/application.properties
+++ b/module-core/src/test/resources/application.properties
@@ -1,4 +1,5 @@
-vocabulary-server.base-url=http://localhost
+security.token=secret
+security.anonymous.read-allowed=false
spring.application.name=Vocabulary-Server
server.port=8081
spring.datasource.username=goobi
diff --git a/module-exchange/pom.xml b/module-exchange/pom.xml
index 3d63b5b..47af30c 100644
--- a/module-exchange/pom.xml
+++ b/module-exchange/pom.xml
@@ -4,14 +4,12 @@
4.0.0
io.goobi.vocabulary
vocabulary-server-exchange
- 1.0.0
+ 1.1.0
Vocabulary Exchange
Vocabulary data exchange classes
jar
- 11
- 11
- 11
+ 17
UTF-8
@@ -40,6 +38,7 @@
+ ${project.artifactId}
diff --git a/pom.xml b/pom.xml
index 878b5c8..5e51df1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
io.goobi.vocabulary
vocabulary-server
- 1.0.0
+ 1.1.0
Vocabulary-Server
pom
RESTful webservice for vocabulary management