From 08719f58d0929db6d8c162c9e95cf1effcea90fe Mon Sep 17 00:00:00 2001 From: Tilo Villwock Date: Mon, 9 Dec 2024 08:42:20 +0100 Subject: [PATCH 1/4] Implemented PID registration for variables --- README.md | 25 +- docker-compose.yml | 5 +- mdm-frontend/angular.json | 1 + .../configuration/translations-de.js | 1 + .../configuration/translations-en.js | 1 + .../monitoring/monitoring.service.js | 5 + .../configuration/translations-de.js | 23 +- .../configuration/translations-en.js | 23 +- .../project-cockpit-status.directive.js | 4 +- .../project-cockpit-status.html.tmpl | 5 + ...registrationStatusCard.directive.html.tmpl | 17 + .../registrationStatusCard.directive.js | 46 +++ .../resources/daraRelease.resource.js | 9 +- .../services/projectReleaseService.js | 34 +- .../views/project-cockpit.controller.js | 18 +- .../views/project-cockpit.html.tmpl | 2 +- .../release-project-dialog.controller.js | 47 ++- .../views/release-project-dialog.html.tmpl | 13 + .../config/MetadataManagementProperties.java | 16 + .../mailmanagement/service/MailService.java | 115 ++++++- .../rest/DaraReleaseResource.java | 60 +++- .../service/DaraPidClientService.java | 294 ++++++++++++++++++ .../service/DaraPidHealthIndicator.java | 34 ++ .../service/DaraPidRegistrationService.java | 283 +++++++++++++++++ src/main/resources/config/application-dev.yml | 4 + .../resources/config/application-local.yml | 4 + .../resources/config/application-prod.yml | 5 + .../resources/config/application-test.yml | 4 + src/main/resources/config/application.yml | 4 + .../resources/i18n/messages_de.properties | 11 + .../resources/i18n/messages_en.properties | 11 + .../daraPidRegistrationCompleteEmail.html | 35 +++ .../mails/daraPidRegistrationErrorEmail.html | 50 +++ terraform/elastic_container_service.tf | 8 +- terraform/templates/web_container.json.tpl | 12 + terraform/templates/worker_container.json.tpl | 12 + 36 files changed, 1200 insertions(+), 41 deletions(-) create mode 100644 mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.html.tmpl create mode 100644 mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.js create mode 100644 src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidClientService.java create mode 100644 src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidHealthIndicator.java create mode 100644 src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java create mode 100644 src/main/resources/mails/daraPidRegistrationCompleteEmail.html create mode 100644 src/main/resources/mails/daraPidRegistrationErrorEmail.html diff --git a/README.md b/README.md index 49e970d8ef1..fea748bb0ef 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,26 @@ mvn clean install -f maven-plugin/pom.xml mvn spring-boot:run ``` -In order for all external services to work on your local machine, you need to set the following variables in `application-local.yml`: -``` -dara: - endpoint: "https://labs.da-ra.de/dara/" - username: {see s3://metadatamanagement-private/sensitive_variables.tf} - password: {see s3://metadatamanagement-private/sensitive_variables.tf} +In order for all external services to work on your local machine, you need to set the following environment variables +when starting the Spring Boot application: +* `DARA_ENDPOINT` (regular endpoint for registering projects) +* `DARA_USERNAME` +* `DARA_PASSWORD` +* `DARA_PID_ENDPOINT` (endpoint for registering variables) +* `DARA_PID_USERNAME` +* `DARA_PID_PASSWORD` + +Starting the application from the command line would look like this: +```sh +DARA_ENDPOINT="https://dara.service/projects/" \ +DARA_USERNAME="bob" \ +DARA_PASSWORD="secret" \ +DARA_PID_ENDPOINT="https://dara.service/variables/" \ +DARA_PID_USERNAME="alice" \ +DARA_PID_PASSWORD="pid-secret" \ + mvn spring-boot:run ``` +Use `sensitive-variables.tf` to fill in the **correct credentials**. If you run the backend on your machine for the first time, or you have restored a mongodb dump, then you need to setup/reindex the elasticsearch indices. Therefore, login as admin to the application, diff --git a/docker-compose.yml b/docker-compose.yml index 06a54dec4ab..c095c8d42d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,11 +21,14 @@ services: environment: - discovery.type=single-node # ensure group has rwx on this directory - mem_limit: 512m volumes: - ./data/elasticsearch/data:/usr/share/elasticsearch/data ports: - "9200:9200" + deploy: + resources: + limits: + memory: 1024m maildev: image: djfarrelly/maildev container_name: maildev diff --git a/mdm-frontend/angular.json b/mdm-frontend/angular.json index 88c73411608..a5b0897f6a0 100644 --- a/mdm-frontend/angular.json +++ b/mdm-frontend/angular.json @@ -461,6 +461,7 @@ "src/app/legacy/dataacquisitionprojectmanagement/services/projectStatusScoringService.js", "src/app/legacy/dataacquisitionprojectmanagement/directives/projectStatusBadge.directive.js", "src/app/legacy/dataacquisitionprojectmanagement/directives/releaseStatusBadge.directive.js", + "src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.js", "src/app/legacy/dataacquisitionprojectmanagement/directives/metadata-status.js", "src/app/legacy/dataacquisitionprojectmanagement/services/outdatedVersionNotifierService.js", "src/app/legacy/dataacquisitionprojectmanagement/directives/releaseButton.directive.js", diff --git a/mdm-frontend/src/app/legacy/administration/configuration/translations-de.js b/mdm-frontend/src/app/legacy/administration/configuration/translations-de.js index a8ce48ba5d4..2171d2a228c 100644 --- a/mdm-frontend/src/app/legacy/administration/configuration/translations-de.js +++ b/mdm-frontend/src/app/legacy/administration/configuration/translations-de.js @@ -24,6 +24,7 @@ angular.module('metadatamanagementApp').config([ 'mongo': 'MongoDB', 'elasticsearch': 'Elasticsearch', 'dara': 'Dara', + 'daraPid': 'Dara PID Registrierung', 'messageBroker': 'Message Broker (für Websockets)', 'rabbit': 'RabbitMQ', 'seo4Ajax': 'Seo4Ajax (Prerender as a Service)', diff --git a/mdm-frontend/src/app/legacy/administration/configuration/translations-en.js b/mdm-frontend/src/app/legacy/administration/configuration/translations-en.js index 40321dd4978..b6791377b3e 100644 --- a/mdm-frontend/src/app/legacy/administration/configuration/translations-en.js +++ b/mdm-frontend/src/app/legacy/administration/configuration/translations-en.js @@ -24,6 +24,7 @@ angular.module('metadatamanagementApp').config([ 'mongo': 'MongoDB', 'elasticsearch': 'Elasticsearch', 'dara': 'Dara', + 'daraPid': 'Dara PID Registration', 'messageBroker': 'Message Broker (for Websockets)', 'rabbit': 'RabbitMQ', 'seo4Ajax': 'Seo4Ajax (Prerender as a Service)', diff --git a/mdm-frontend/src/app/legacy/administration/monitoring/monitoring.service.js b/mdm-frontend/src/app/legacy/administration/monitoring/monitoring.service.js index e8b955271ee..edf0abe6f50 100644 --- a/mdm-frontend/src/app/legacy/administration/monitoring/monitoring.service.js +++ b/mdm-frontend/src/app/legacy/administration/monitoring/monitoring.service.js @@ -7,6 +7,11 @@ angular.module('metadatamanagementApp').factory('MonitoringService', ['$http', return $http.get('/management/health').then(function(response) { return response.data; }); + }, + checkDaraPidHealth: function() { + return $http.get('/management/health/daraPid') + .then(response => response.data.status === 'UP') + .catch(response => response.data.status === 'UP'); } }; }]); diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-de.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-de.js index 941b59bb4e4..19517aeedac 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-de.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-de.js @@ -13,6 +13,13 @@ angular.module('metadatamanagementApp').config([ 'landing-page-de-title': 'Deutsch', 'landing-page-en-title': 'Englisch', 'landing-page-hint': 'Bitte wählen Sie die Sprache der DOI-Landingpage', + 'pid-registration': 'Registierung von persistenten Identifikatoren (PIDs) für die Variablen des Datenpakets', + 'pid-registration-hint': 'Markieren Sie dieses Kästchen, wenn sie für jede Variable des Datenpakets eine PID registrieren wollen.', + 'pid-api-not-reachable-dialog': { + 'title': 'Dienst für PID-Registrierung nicht erreichbar', + 'message': 'Der Dienst zur Registrierung von PIDs für Variablen ist aktuell nicht verfügbar. Sie können den Releaseprozess trotzdem vornehmen und die Variablen später registrieren.', + 'question': 'Soll der Releaseprozess fortgesetzt werden?' + }, 'pin-to-start-page': 'Datenpaket auf Startseite anzeigen', 'pin-to-start-page-hint': 'Markieren Sie dieses Kästchen, wenn das Datenpaket auf der Startseite des MDMs angezeigt werden soll.', 'confirmed': { @@ -56,9 +63,11 @@ angular.module('metadatamanagementApp').config([ 'deleted-successfully-project': 'Das Datenaufnahmeprojekt "{{ id }}" wurde erfolgreich gelöscht.', 'deleted-not-successfully-project': 'Das Datenaufnahmeprojekt "{{ id }}" konnte nicht gelöscht werden!', 'released-successfully': 'Die Metadaten des Projektes wurden bei da|ra gespeichert und die Daten des Projektes "{{ id }}" werden in ca. 10 Minuten für alle Benutzer:innen sichtbar sein.', + 'released-successfully-with-pids': 'Der Releaseprozess wurde angestoßen. Das Datenpaket ist in wenigen Minuten verfügbar. Die Registrierung der PIDs für Variablen wird im Hintergrund ausgeführt und kann bis zu einer Stunde in Anspruch nehmen.', 'dara-update-successfully': 'Die Metadaten des Projektes wurden bei da|ra aktualisiert.', 'released-beta-successfully': 'Die Daten des Projektes "{{ id }}" werden in ca. 10 Minuten für alle Benutzer:innen sichtbar sein. Es wurden keine Metadaten zu da|ra gesendet.', 'dara-released-not-successfully': 'Die Daten des Projektes "{{ id }}" können nicht veröffentlicht werden. Der da|ra-Service zur Registrierung der DOI ist aktuell nicht verfügbar. Kontaktieren Sie den Administrator (fdz-feedback@dzhw.eu) und versuchen Sie es später erneut.', + 'dara-pid-previous-registration': 'Es sind bereits PIDs für die Variablen in dieser Version registriert, es wird keine neue Registrierung vorgenommen. Der Release des Datenpakets wird wie gewohnt durchgeführt, es sind keine weiteren Aktionen erforderlich.', 'unreleased-successfully': 'Die Daten des Projektes "{{ id }}" können jetzt bearbeitet werden.', 'unrelease-title': 'Freigabe für Projekt "{{ id }}" zurücknehmen?', 'unrelease': 'Möchten Sie wirklich die Freigabe zurücknehmen und die Metadaten des Projektes "{{ id }}" bearbeiten?', @@ -157,6 +166,10 @@ angular.module('metadatamanagementApp').config([ 'unreleased': 'Nicht freigegeben', 'pre-released': 'Vorläufig freigegeben' }, + 'registration-status-badge': { + 'registered': 'PIDs registriert', + 'not-registered': 'Keine PIDs registriert' + }, 'project-cockpit': { 'title': 'Projekt-Cockpit ({{projectId}})', 'header': 'Projekt-Cockpit', @@ -186,7 +199,8 @@ angular.module('metadatamanagementApp').config([ 'save': 'Klicken, um die Anpassungen zu speichern.', 'save-assign': 'Klicken, um die Anpassungen zu speichern und das Projekt zuzuweisen.', 'save-takeback': 'Klicken, um die Anpassungen zu speichern und das Projekt der Gruppe Publisher zuzuweisen.', - 'remove-user': 'Nutzer:in entfernen' + 'remove-user': 'Nutzer:in entfernen', + 'register-pids': 'PIDs bei da|ra registrieren' }, 'list': { 'empty-data-provider': 'Keine Datengeber:innen sind diesem Projekt zugeteilt.', @@ -258,6 +272,13 @@ angular.module('metadatamanagementApp').config([ 'unhide-shadow': 'Diese Version ist aktuell nicht für alle Benutzer:innen sichtbar. Klicken Sie hier, um die Version wieder sichtbar zu machen!', 'pre-released': 'Diese Version unterliegt bis zum {{date}} einem Embargo durch die Datengeber:innen. Die Veröffentlichung kann erst nach diesem Datum erfolgen.' } + }, + 'pid-registration': { + 'confirm-dialog': { + 'title': 'PIDs bei da|ra registrieren', + 'message': 'Die Registrierung der PIDs für Variablen wird im Hintergrund ausgeführt und kann bis zu einer Stunde in Anspruch nehmen.', + 'question': 'Möchten Sie fortfahren?' + } } }, 'project-overview': { diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-en.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-en.js index 81d6868d4ab..67d69b16439 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-en.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/configuration/translations-en.js @@ -13,6 +13,13 @@ angular.module('metadatamanagementApp').config([ 'landing-page-de-title': 'German', 'landing-page-en-title': 'English', 'landing-page-hint': 'Please select the language of the DOI landing page', + 'pid-registration': 'Registration of Persistent Identifiers (PIDs) for the variables of the data package', + 'pid-registration-hint': 'Check this box if you want to register PIDs for each variable of the data package', + 'pid-api-not-reachable-dialog': { + 'title': 'PID registration service not available', + 'message': 'The service for registering PIDs for variables is currently not available. You can still carry out the release and register the variables later.', + 'question': 'Should the release process be continued?' + }, 'pin-to-start-page': 'Show Data Package on Start Page', 'pin-to-start-page-hint': 'Check this box, if you want to show this data package on the start page.', 'confirmed': { @@ -56,10 +63,12 @@ angular.module('metadatamanagementApp').config([ 'deleted-successfully-project': 'Successfully deleted Data Acquisition Project "{{ id }}"!', 'deleted-not-successfully-project': 'Could not delete Data Acquisition Project "{{ id }}"!', 'released-successfully': 'The projects metadata has been sent to da|ra and the data of the project "{{ id }}" will be visible to all users in about 10 minutes.', + 'released-successfully-with-pids': 'The release process has been initiated. The data package will be available in a few minutes. The registration of the PIDs for variables is carried out in the background and can take up to an hour.', 'dara-update-successfully': 'The projects metadata has been updated at da|ra.', 'released-beta-successfully': 'The data of the project "{{ id }}" will be visible to all users in about 10 minutes. No metadata has been sent to da|ra.', 'unreleased-successfully': 'The data of the project "{{ id }}" can now be edited by assigned Publishers and Data Providers.', 'dara-released-not-successfully': 'The data of the project "{{ id }}" could not be released. The da|ra service for registering the DOI is currently not available. Contact the administrator (fdz-feedback@dzhw.eu) and try again later.', + 'dara-pid-previous-registration': 'PIDs are already registered for the variables in this version, no new registration will be made. The data package is released as usual, no further action is required.', 'unrelease-title': 'Unrelease Project "{{ id }}"?', 'unrelease': 'Do you really want to unrelease the project "{{ id }}" and edit its metadata?', 'release-not-possible-title': 'Project "{{ id }}" cannot be released!', @@ -158,6 +167,10 @@ angular.module('metadatamanagementApp').config([ 'unreleased': 'Unreleased', 'pre-released': 'Preliminarily released' }, + 'registration-status-badge': { + 'registered': 'PIDs registered', + 'not-registered': 'No PIDs registered' + }, 'project-cockpit': { 'title': 'Project-Cockpit ({{projectId}})', 'header': 'Project-Cockpit', @@ -187,7 +200,8 @@ angular.module('metadatamanagementApp').config([ 'save': 'Click to save the changes.', 'save-assign': 'Click to save the changes and to assign the project.', 'save-takeback': 'Click to save the changes and to assign the project to the publishers group.', - 'remove-user': 'Remove user' + 'remove-user': 'Remove user', + 'register-pids': 'Register PIDs with da|ra' }, 'list': { 'empty-data-provider': 'No data providers are assigned to this project.', @@ -259,6 +273,13 @@ angular.module('metadatamanagementApp').config([ 'unhide-shadow': 'This version is currently not visible for all users. Click here to make it available for all users!', 'pre-released': 'This version is subject to an embargo until {{ date }}. The final release can only take place after this date.' } + }, + 'pid-registration': { + 'confirm-dialog': { + 'title': 'Register PIDs with da|ra', + 'message': 'The registration of the PIDs for variables is carried out in the background and can take up to an hour.', + 'question': 'Do you want to proceed?' + } } }, 'project-overview': { diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.directive.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.directive.js index 3b41d6cd03c..a6e76f1c6e3 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.directive.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.directive.js @@ -24,7 +24,8 @@ angular.module('metadatamanagementApp') templateUrl: 'scripts/dataacquisitionprojectmanagement/directives/' + 'project-cockpit-status.html.tmpl', scope: { - project: '=' + project: '=', + variablesCheck: '<' }, replace: true, controllerAs: 'ctrl', @@ -36,6 +37,7 @@ angular.module('metadatamanagementApp') this.isAssignedPublisher = ProjectUpdateAccessService.isAssignedToProject.bind(null, this.project, 'publishers'); + this.hasAdminRole = () => Principal.isAdmin(); }], /* jshint -W098 */ link: function($scope, elem, attrs, ctrl) { diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.html.tmpl b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.html.tmpl index 7ac13634182..c50e3624464 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.html.tmpl +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/project-cockpit-status.html.tmpl @@ -22,6 +22,11 @@ +
diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.html.tmpl b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.html.tmpl new file mode 100644 index 00000000000..ede5a8010f4 --- /dev/null +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.html.tmpl @@ -0,0 +1,17 @@ + + {{ 'data-acquisition-project-management.registration-status-badge.registered' | translate }} + {{ 'data-acquisition-project-management.registration-status-badge.not-registered' | translate }} + + app_registration + + {{ 'data-acquisition-project-management.project-cockpit.button.register-pids' | translate }} + + + diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.js new file mode 100644 index 00000000000..23487387e30 --- /dev/null +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/directives/registrationStatusCard.directive.js @@ -0,0 +1,46 @@ +'use strict'; +/** + * Directive rendering the PID registration status of variables linked to a project + */ +angular.module('metadatamanagementApp').directive('registrationStatusCard', [ + 'DaraReleaseResource', + '$translate', + '$mdDialog', + function (DaraReleaseResource, $translate, $mdDialog) { + return { + restrict: 'E', + templateUrl: 'scripts/dataacquisitionprojectmanagement/' + + 'directives/registrationStatusCard.directive.html.tmpl', + scope: { + project: '<', + status: '<' + }, + controllerAs: 'ctrl', + controller: ['$scope', function ($scope) { + /** + * Prompts for confirmation and proceeds + * to trigger the PID registration process. + */ + this.registerVariables = async () => { + const i18nPrefix = 'data-acquisition-project-management' + + '.project-cockpit.pid-registration.confirm-dialog.'; + const confirmDialog = $mdDialog.confirm() + .title($translate.instant(i18nPrefix + 'title')) + .textContent( + $translate.instant(i18nPrefix + 'message') + + '\n\n' + + $translate.instant(i18nPrefix + 'question') + ) + .ariaLabel( + $translate.instant(i18nPrefix + 'message') + + $translate.instant(i18nPrefix + 'question') + ) + .ok($translate.instant('global.common-dialogs.yes')) + .cancel($translate.instant('global.common-dialogs.no')); + $mdDialog.show(confirmDialog) + .then(() => DaraReleaseResource.variablesRegister($scope.project)); + } + }] + }; + } +]); diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/resources/daraRelease.resource.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/resources/daraRelease.resource.js index 1eb92012a7f..b5282a0baa0 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/resources/daraRelease.resource.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/resources/daraRelease.resource.js @@ -11,11 +11,18 @@ angular.module('metadatamanagementApp') }, { 'release': { method: 'POST', - url: '/api/data-acquisition-projects/:id/release' }, 'preRelease': { method: 'POST', url: '/api/data-acquisition-projects/:id/pre-release' + }, + variablesCheck: { + method: 'GET', + url: '/api/data-acquisition-projects/:id/variables-check' + }, + variablesRegister: { + method: 'POST', + url: '/api/data-acquisition-projects/:id/variables-register' } }); }]); diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/services/projectReleaseService.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/services/projectReleaseService.js index 27dfeaf55e4..07d32646967 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/services/projectReleaseService.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/services/projectReleaseService.js @@ -4,6 +4,7 @@ angular.module('metadatamanagementApp').service('ProjectReleaseService', [ 'SimpleMessageToastService', 'DataAcquisitionProjectResource', 'CurrentProjectService', + 'MonitoringService', '$mdDialog', '$translate', '$state', @@ -11,14 +12,36 @@ angular.module('metadatamanagementApp').service('ProjectReleaseService', [ SimpleMessageToastService, DataAcquisitionProjectResource, CurrentProjectService, + MonitoringService, $mdDialog, $translate, $state ) { + var i18nPrefix = 'data-acquisition-project-management.log-messages.' + 'data-acquisition-project.'; - var releaseProject = function(project) { - $mdDialog.show({ + const i18nPrefixDialog = 'data-acquisition-project-management.release.pid-api-not-reachable-dialog.'; + + var releaseProject = async function(project) { + + const pidApiAvailable = await MonitoringService.checkDaraPidHealth(); + const pidApiUnavailableConfirmDialog = $mdDialog.confirm() + .title($translate.instant(i18nPrefixDialog + 'title')) + .textContent( + $translate.instant(i18nPrefixDialog + 'message') + '\n\n' + $translate.instant(i18nPrefixDialog + 'question') + ) + .ariaLabel( + $translate.instant(i18nPrefixDialog + 'message') + $translate.instant(i18nPrefixDialog + 'question') + ) + .ok($translate.instant('global.common-dialogs.yes')) + .cancel($translate.instant('global.common-dialogs.no')); + + var continueRelease = Promise.resolve(); + if (!pidApiAvailable) { + continueRelease = $mdDialog.show(pidApiUnavailableConfirmDialog); + } + + const releaseDialogConfig = { controller: 'ReleaseProjectDialogController', templateUrl: 'scripts/dataacquisitionprojectmanagement/' + 'views/release-project-dialog.html.tmpl', @@ -27,9 +50,10 @@ angular.module('metadatamanagementApp').service('ProjectReleaseService', [ locals: { project: angular.copy(project) } - }).catch(function() { - // user cancelled - }); + }; + continueRelease + .then(() => $mdDialog.show(releaseDialogConfig).catch(() => {})) + .catch(() => {}); }; var unreleaseProject = function(project) { diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.controller.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.controller.js index c4776188ca8..d952d100895 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.controller.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.controller.js @@ -14,11 +14,12 @@ angular.module('metadatamanagementApp').controller('ProjectCockpitController', [ 'projectDeferred', 'CommonDialogsService', 'ProjectSaveService', + 'DaraReleaseResource', 'blockUI', function($scope, $state, $location, $transitions, Principal, PageMetadataService, LanguageService, BreadcrumbService, CurrentProjectService, projectDeferred, CommonDialogsService, - ProjectSaveService, blockUI) { + ProjectSaveService, DaraReleaseResource, blockUI) { blockUI.start(); var unregisterTransitionHook; var pageTitleKey = 'data-acquisition-project-management.project' + @@ -106,11 +107,18 @@ angular.module('metadatamanagementApp').controller('ProjectCockpitController', [ CurrentProjectService.setCurrentProject(project); registerConfirmOnDirtyHook(); + + return DaraReleaseResource.variablesCheck(project).$promise; + }).then(result => { + $scope.variablesCheck = { + hasVariables: result.hasVariables, + hasRegistrations: result.hasRegistrations + }; }).finally(function() { - $state.loadStarted = false; - $scope.initializing = false; - blockUI.stop(); - }); + $state.loadStarted = false; + $scope.initializing = false; + blockUI.stop(); + }); $scope.shareButtonShown = false; diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.html.tmpl b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.html.tmpl index 28286023aa6..5e6f3c1822b 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.html.tmpl +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/project-cockpit.html.tmpl @@ -18,7 +18,7 @@ {{'data-acquisition-project-management.project-cockpit.tabs.status' | translate}} - + diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.controller.js b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.controller.js index 14131e787a6..44b63a41a94 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.controller.js +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.controller.js @@ -7,6 +7,8 @@ angular.module('metadatamanagementApp') .controller('ReleaseProjectDialogController', [ '$scope', '$mdDialog', + 'blockUI', + 'MonitoringService', 'project', 'SimpleMessageToastService', 'DataAcquisitionProjectResource', @@ -24,18 +26,36 @@ angular.module('metadatamanagementApp') 'DataPackageSearchService', 'AnalysisPackageSearchService', 'DoiService', - 'ENV', function($scope, $mdDialog, + 'ENV', function($scope, $mdDialog, blockUI, MonitoringService, project, SimpleMessageToastService, DataAcquisitionProjectResource, DaraReleaseResource, $rootScope, CurrentProjectService, DataAcquisitionProjectLastReleaseResource, $state, $translate, DataAcquisitionProjectPostValidationService, PinnedDataPackagesService, DataPackageIdBuilderService, AnalysisPackageIdBuilderService, DataAcquisitionProjectTweetResource, - DataPackageSearchService, AnalysisPackageSearchService, DoiService, ENV) { + DataPackageSearchService, AnalysisPackageSearchService, DoiService, ENV + ) { $scope.bowser = $rootScope.bowser; $scope.project = project; $scope.ENV = ENV; + $scope.showPIDRegistrationOption = false; + if (ENV !== 'prod') { + blockUI.start(); + Promise.all([ + MonitoringService.checkDaraPidHealth(), + DaraReleaseResource.variablesCheck(project).$promise + ]).then(([pidServiceIsUp, result]) => { + if (pidServiceIsUp) { + $scope.variablesCheck = { + hasVariables: result.hasVariables, + hasRegistrations: result.hasRegistrations + }; + $scope.showPIDRegistrationOption = ENV !== 'prod' && result.hasVariables; + } + }).finally(() => blockUI.stop()); + } + var i18nPrefix = 'data-acquisition-project-management.log-messages.' + 'data-acquisition-project.'; $scope.cancel = function() { @@ -235,15 +255,27 @@ angular.module('metadatamanagementApp') $mdDialog.hide(); }); } else { + const registerVars = release.registerPID && !$scope.hasRegistrations; // handling for regular releases - DaraReleaseResource.release(project) + DaraReleaseResource.release({ registerVars }, project) .$promise.then(function() { + // show warning if PIDs had been registered before + if (release.registerPID && $scope.hasRegistrations) { + SimpleMessageToastService.openSimpleMessageToast(i18nPrefix + 'dara-pid-previous-registration'); + } DataAcquisitionProjectResource.save(project).$promise .then(function() { - SimpleMessageToastService.openSimpleMessageToast( - i18nPrefix + 'released-successfully', { - id: project.id - }); + if (registerVars) { + SimpleMessageToastService.openSimpleMessageToast( + i18nPrefix + 'released-successfully-with-pids', { + id: project.id + }); + } else { + SimpleMessageToastService.openSimpleMessageToast( + i18nPrefix + 'released-successfully', { + id: project.id + }); + } CurrentProjectService.setCurrentProject(project); $mdDialog.hide(); $state.forceReload(); @@ -300,7 +332,6 @@ angular.module('metadatamanagementApp') return highestMajorVersion + ".0.0"; } return '1.0.0'; - }; }]); diff --git a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.html.tmpl b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.html.tmpl index ddee53ae901..1e93451a663 100644 --- a/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.html.tmpl +++ b/mdm-frontend/src/app/legacy/dataacquisitionprojectmanagement/views/release-project-dialog.html.tmpl @@ -71,6 +71,19 @@ + + + {{ 'data-acquisition-project-management.release.pid-registration' | translate }} +
+ {{'data-acquisition-project-management.release.pid-registration-hint' | translate}} +
+
diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/common/config/MetadataManagementProperties.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/common/config/MetadataManagementProperties.java index 09dd85b71a9..f03dc9d324c 100644 --- a/src/main/java/eu/dzhw/fdz/metadatamanagement/common/config/MetadataManagementProperties.java +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/common/config/MetadataManagementProperties.java @@ -29,6 +29,8 @@ public class MetadataManagementProperties { private final Dara dara = new Dara(); + private final DaraPid daraPid = new DaraPid(); + private final Dlp dlp = new Dlp(); private final Rabbitmq rabbitmq = new Rabbitmq(); @@ -87,6 +89,20 @@ public static class Dara { private String password; } + /** + * Credentials for da|ra variables registration (PID). + */ + @Getter + @Setter + public static class DaraPid { + private boolean enabled; + private long statusPollIntervalInSeconds; + private long abortAfterMinutesElapsed; + private String endpoint; + private String username; + private String password; + } + /** * SSL CA certificate for MongoDB. */ diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/mailmanagement/service/MailService.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/mailmanagement/service/MailService.java index cb9a4ba9344..f9b39d6d562 100644 --- a/src/main/java/eu/dzhw/fdz/metadatamanagement/mailmanagement/service/MailService.java +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/mailmanagement/service/MailService.java @@ -4,12 +4,23 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; +import eu.dzhw.fdz.metadatamanagement.datapackagemanagement.domain.DataPackage; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.domain.DataAcquisitionProject; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.JobStatusException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.JobStatusResponseException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.DaraPidApiException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.RegistrationClientException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.RegistrationResponseException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.RegistrationResponseParsingException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidRegistrationService.RegistrationFailedException; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.jsoup.Jsoup; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; @@ -306,7 +317,7 @@ public void sendDataProviderAccessRevokedMail(List users, String projectId /** * Send the result of the dataset report generation to the user who has started the report * generation. - * + * * @param user The user who has started the report generation. * @param dataSetId The id of the {@link DataSet} for which the report has been generated. * @param language The language in which the report has been generated. @@ -332,7 +343,7 @@ public void sendDataSetReportGeneratedMail(User user, String dataSetId, String l /** * Send the error during dataset report generation to the user who started the task and to all * admins. - * + * * @param onBehalfUser The user who has started the report generation. * @param admins A list of admins. * @param sender The sender of the email. @@ -358,7 +369,7 @@ public void sendDataSetReportErrorMail(User onBehalfUser, List admins, /** * Send a mail to all release managers when the project is released with a new major version. - * + * * @param releaseManagers List of ROLE_RELEASE_MANAGER * @param dataAcquisitionProjectId the id of the project which has been released * @param release the release object containing the version @@ -383,7 +394,7 @@ public void sendMailOnNewMajorProjectRelease(List releaseManagers, /** * Send the result of the data package overview generation to the user who has started the * overview generation. - * + * * @param user The user who has started the overview generation. * @param dataPackageId The id of the {@link DataPackage} for which the overview has been * generated. @@ -410,7 +421,7 @@ public void sendDataPackageOverviewGeneratedMail(User user, String dataPackageId /** * Send the error during data package overview generation to the user who started the task and to * all admins. - * + * * @param onBehalfUser The user who has started the report generation. * @param admins A list of admins. * @param sender The sender of the email. @@ -433,4 +444,98 @@ public void sendDataPackageOverviewErrorMail(User onBehalfUser, List admin adminAddresses.toArray(new String[adminAddresses.size()]), null, subject, content, locale); } + + /** + * Sends an email notifying the user that the registration process completed successfully. + * @param user the user that triggered the process by releasing a new project version + * @param project the data acquisition project which got a new release + * @param dataPackage the data package that was included in the project release + */ + public void sendDaraPidRegistrationCompleteEmail(User user, DataAcquisitionProject project, + DataPackage dataPackage + ) { + // CPD-OFF + log.debug("Sending da|ra PID registration complete e-mail to '{}'", user.getEmail()); + + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + + if (user.getFirstName() != null && !user.getFirstName().isBlank() && + user.getLastName() != null && !user.getLastName().isBlank() + ) { + context.setVariable("name", user.getFirstName() + " " + user.getLastName()); + } else { + context.setVariable("name", user.getLogin()); + } + context.setVariable("projectReleaseVersion", project.getRelease().getVersion()); + context.setVariable("projectUrl", String.format("%s/%s/data-packages/%s?page=1&size=10&type=surveys", + this.baseUrl, user.getLangKey(), dataPackage.getMasterId().replace("$", ""))); + context.setVariable("dataPackageTitle", Objects.equals(user.getLangKey(), "de") + ? dataPackage.getTitle().getDe() + : dataPackage.getTitle().getEn()); + // CPD-ON + + String content = this.templateEngine.process("daraPidRegistrationCompleteEmail", context); + String subject = this.messageSource.getMessage("email.dara-pid-registration-complete.subject", null, locale); + + this.sendEmail(null, new String[] {user.getEmail()}, null, null, subject, content, locale); + } + + /** + * Sends an Email for when the PID registration of variables linked to a data acquisition project fails. + * @param user the user that triggered the process by releasing a new project version + * @param admins list of admins that should also be notified + * @param project the data acquisition project which got a new release + * @param dataPackage the data package that was included in the project release + * @param exception the exception that was thrown during the registration process + */ + public void sendDaraPidRegistrationErrorEmail(User user, List admins, DataAcquisitionProject project, + DataPackage dataPackage, DaraPidApiException exception + ) { + log.debug("Sending da|ra PID registration error e-mail to '{}'", user.getEmail()); + + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + + if (user.getFirstName() != null && !user.getFirstName().isBlank() && + user.getLastName() != null && !user.getLastName().isBlank() + ) { + context.setVariable("name", user.getFirstName() + " " + user.getLastName()); + } else { + context.setVariable("name", user.getLogin()); + } + context.setVariable("projectReleaseVersion", project.getRelease().getVersion()); + context.setVariable("projectUrl", String.format("%s/%s/data-packages/%s?page=1&size=10&type=surveys", + this.baseUrl, user.getLangKey(), dataPackage.getMasterId().replace("$", ""))); + context.setVariable("dataPackageTitle", Objects.equals(user.getLangKey(), "de") + ? dataPackage.getTitle().getDe() + : dataPackage.getTitle().getEn()); + + if (exception instanceof RegistrationResponseException rre) { + context.setVariable("errorMessage", rre.getMessage()); + context.setVariable("statusCode", rre.getStatusCode()); + context.setVariable("body", rre.getBody()); + } else if (exception instanceof RegistrationClientException rce) { + context.setVariable("stackTrace", ExceptionUtils.getStackTrace(rce)); + } else if (exception instanceof RegistrationResponseParsingException rrpe) { + context.setVariable("stackTrace", ExceptionUtils.getStackTrace(rrpe)); + } else if (exception instanceof JobStatusResponseException jsre) { + context.setVariable("errorMessage", jsre.getMessage()); + context.setVariable("statusCode", jsre.getStatusCode()); + context.setVariable("body", jsre.getBody()); + } else if (exception instanceof JobStatusException jse) { + context.setVariable("stackTrace", ExceptionUtils.getStackTrace(jse)); + } else if (exception instanceof RegistrationFailedException rfe) { + context.setVariable("errorMessage", rfe.getMessage()); + context.setVariable("failedEntries", rfe.getEntries()); + } else { + throw new RuntimeException("Unexpected exception type: " + exception.getClass()); + } + + String content = this.templateEngine.process("daraPidRegistrationErrorEmail", context); + String subject = this.messageSource.getMessage("email.dara-pid-registration-error.subject", null, locale); + + this.sendEmail(null, new String[] {user.getEmail()}, + admins.stream().map(User::getEmail).toList().toArray(new String[0]), null, subject, content, locale); + } } diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/rest/DaraReleaseResource.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/rest/DaraReleaseResource.java index 49dbd563a06..84a61374cbd 100644 --- a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/rest/DaraReleaseResource.java +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/rest/DaraReleaseResource.java @@ -6,15 +6,23 @@ import javax.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; +import eu.dzhw.fdz.metadatamanagement.common.config.MetadataManagementProperties; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.RegistrationException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidRegistrationService; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidRegistrationService.VariablesCheckResult; +import eu.dzhw.fdz.metadatamanagement.usermanagement.repository.UserRepository; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -30,7 +38,7 @@ import lombok.RequiredArgsConstructor; /** - * A Resource Class for handling releasing and unreleading of data acquisition projects to dara. + * A Resource Class for handling releasing and un-releasing of data acquisition projects to dara. * This Resource can register a DOI to dara, updates information of a dara doi or set to not * available if a project will be unreleased. * @@ -42,9 +50,11 @@ @RequiredArgsConstructor public class DaraReleaseResource { + private final MetadataManagementProperties config; private final DaraService daraService; - @Autowired - private DataAcquisitionProjectRepository projectRepository; + private final DaraPidRegistrationService daraPidRegistrationService; + private final DataAcquisitionProjectRepository projectRepository; + private final UserRepository userRepository; /** * Release a project to dara (or update it). @@ -55,10 +65,20 @@ public class DaraReleaseResource { method = RequestMethod.POST) @Secured(value = {AuthoritiesConstants.PUBLISHER}) public ResponseEntity release(@PathVariable String id, - @RequestBody @Valid DataAcquisitionProject project) throws IOException, TemplateException { + @RequestBody @Valid DataAcquisitionProject project, + @RequestParam(required = false, defaultValue = "false") boolean registerVars, + Authentication authentication + ) throws IOException, TemplateException, RegistrationException { if (project.isShadow()) { throw new ShadowCopyReleaseToDaraNotAllowed(); } + if (this.config.getDaraPid().isEnabled() && registerVars) { + // only attempt registration if bridge is enabled and the + // user checked the relevant box on the release dialog + final var user = this.userRepository.findOneByLogin(authentication.getName()) + .orElseThrow(() -> new RuntimeException("Unable to find user with login name: " + authentication.getName())); + this.daraPidRegistrationService.register(project, user); + } HttpStatus status = this.daraService.registerOrUpdateProjectToDara(project); return ResponseEntity.status(status).build(); } @@ -102,6 +122,36 @@ public ResponseEntity preRelease(@PathVariable String id, return ResponseEntity.status(status).build(); } + /** + * Checks a data acquisition project for variables and their registration status. + * @param id the data acquisition project's ID + * @return the status of the variables + */ + @GetMapping(path = "/data-acquisition-projects/{id}/variables-check") + @Secured(value = {AuthoritiesConstants.ADMIN, AuthoritiesConstants.PUBLISHER}) + VariablesCheckResult performVariablesCheck(@PathVariable String id) { + var project = this.projectRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Unable to find data acquisition project with ID: " + id)); + return this.daraPidRegistrationService.performVariablesCheckIn(project); + } + + /** + * Registers PIDs for variables linked to the given data acquisition project. + * @param id the data acqusition project ID + * @param project the data acquisition project + * @param authentication the security context user object + */ + @PostMapping(path = "/data-acquisition-projects/{id}/variables-register") + @Secured(value = {AuthoritiesConstants.ADMIN}) + void startVariablesRegistration(@PathVariable String id, + @RequestBody @Valid DataAcquisitionProject project, + Authentication authentication + ) { + var user = this.userRepository.findOneByLogin(authentication.getName()) + .orElseThrow(() -> new RuntimeException("Unable to find user with login: " + authentication.getName())); + this.daraPidRegistrationService.register(project, user); + } + @ExceptionHandler(ShadowCopyReleaseToDaraNotAllowed.class) @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidClientService.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidClientService.java new file mode 100644 index 00000000000..a7ac6449a77 --- /dev/null +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidClientService.java @@ -0,0 +1,294 @@ +package eu.dzhw.fdz.metadatamanagement.projectmanagement.service; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dzhw.fdz.metadatamanagement.common.config.MetadataManagementProperties; +import eu.dzhw.fdz.metadatamanagement.datapackagemanagement.domain.DataPackage; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.domain.DataAcquisitionProject; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidRegistrationService.VariableMetadata; +import eu.dzhw.fdz.metadatamanagement.usermanagement.domain.User; +import io.micrometer.core.instrument.MeterRegistry; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.springframework.boot.actuate.metrics.AutoTimer; +import org.springframework.boot.actuate.metrics.web.client.MetricsRestTemplateCustomizer; +import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * A client service that's solely responsible for + * communication with the da|ra PID Service. + */ +@Service +@Slf4j +public class DaraPidClientService { + + static final String PATH_REGISTER = "/variable/register"; + static final String PATH_VERIFY = "/variable/verify"; + static final String PATH_JOB_STATUS = "/variable/status/job/"; + + private final MetadataManagementProperties config; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + public DaraPidClientService( + MetadataManagementProperties config, + MeterRegistry registry, + RestTemplateExchangeTagsProvider provider + ) { + this.config = config; + this.restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); + new MetricsRestTemplateCustomizer(registry, provider, "dara.pid.client.requests", AutoTimer.ENABLED) + .customize(restTemplate); + this.objectMapper = new ObjectMapper(); + this.objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + } + + /** + * Returns the fully expanded registration Endpoint URI. + * @return the fully expanded registration Endpoint URI + */ + String getRegistationEndpoint() { + return this.config.getDaraPid().getEndpoint() + PATH_REGISTER; + } + + /** + * Polls the registration endpoint in order to + * determine whether the service is reachable. + * @return true if the service is reachable, false otherwise + * @throws IOException if an IO error occurrs, in which case the service is also likely not reachable + */ + boolean serviceIsReachable() throws IOException { + + var authHash = Base64.encodeBase64(String.format("%s:%s", + this.config.getDaraPid().getUsername(), + this.config.getDaraPid().getPassword() + ).getBytes(StandardCharsets.UTF_8)); + + var uri = this.getRegistationEndpoint(); + var request = this.restTemplate.getRequestFactory().createRequest(URI.create(uri), HttpMethod.GET); + request.getHeaders().add("Authorization", "Basic " + new String(authHash, StandardCharsets.UTF_8)); + + try (var response = request.execute()) { + return response.getRawStatusCode() == 405; + } + } + + /** + * Registers the provided list of variables with the da|ra PID service. + * @param variables the list of variables that should be registered + * @return the API response with a Job ID + * @throws RegistrationException if the registration wasn't successful (IO error or unexpected response) + * @see RegistrationResponseException + * @see RegistrationClientException + * @see RegistrationResponseParsingException + */ + RegistrationResponse register(List variables) throws RegistrationException { + + var authHash = Base64.encodeBase64(String.format("%s:%s", + this.config.getDaraPid().getUsername(), + this.config.getDaraPid().getPassword() + ).getBytes(StandardCharsets.UTF_8)); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + headers.add("Authorization", "Basic " + new String(authHash, StandardCharsets.UTF_8)); + + var uri = this.getRegistationEndpoint(); + var entity = new HttpEntity<>(new RegistrationRequestBody(variables), headers); + try { + var response = this.restTemplate.postForEntity(uri, entity, String.class); + if (!response.getStatusCode().is2xxSuccessful()) { + throw new RegistrationResponseException(response.getStatusCodeValue(), response.getBody()); + } + var responseBody = this.objectMapper.readValue(response.getBody(), RegistrationResponse.class); + log.debug("PID Registration request was successful. Status can be queried using Job-ID: " + responseBody.jobId()); + return responseBody; + } catch (RestClientException e) { + throw new RegistrationClientException(e); + } catch (JsonProcessingException e) { + throw new RegistrationResponseParsingException(e); + } + } + + /** + * Returns the status of a registration job. + * @param jobId the UUID that was returned for creating a registration job + * @return the status entry for each variable included in the registration request + * @throws JobStatusException when fetching the job status failed (IO error or unexpected response) + * @see DaraPidClientService#register(List) + */ + VariableStatus[] getJobStatus(String jobId) throws JobStatusException { + + var authHash = Base64.encodeBase64(String.format("%s:%s", + this.config.getDaraPid().getUsername(), + this.config.getDaraPid().getPassword() + ).getBytes(StandardCharsets.UTF_8)); + + var uri = this.config.getDaraPid().getEndpoint() + PATH_JOB_STATUS + jobId; + ClientHttpRequest request; + try { + request = this.restTemplate.getRequestFactory().createRequest(URI.create(uri), HttpMethod.GET); + } catch (IOException e) { + throw new JobStatusException(String.format("Unable to create job status request using this URI: '%s'", uri)); + } + request.getHeaders().add("Authorization", "Basic " + new String(authHash, StandardCharsets.UTF_8)); + + try (var response = request.execute()) { + var body = new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8); + log.debug("Job status response for {}", jobId); + log.debug(body); + if (response.getStatusCode().is2xxSuccessful()) { + return this.objectMapper.readValue(body, VariableStatus[].class); + } else { + throw new JobStatusResponseException(response.getRawStatusCode(), body); + } + } catch (JsonMappingException | JsonParseException e) { + log.error("An error occurred while parsing the JSON response", e); + throw new JobStatusException("An error occurred while parsing the JSON response", e); + } catch (IOException e) { + log.error("An IO error has occurred while performing the request", e); + throw new JobStatusException("An IO error has occurred while performing the request", e); + } + } + + /** + * A wrapper type that provides the expected request body + * JSON structure for the registration API call . + */ + private record RegistrationRequestBody(List variables) {} + + /** The expected response type when a registration API call was successful. */ + record RegistrationResponse(String jobId) {} + + /** + * A generic interface to pass all custom exceptions around with a single argument type. + * @see eu.dzhw.fdz.metadatamanagement.mailmanagement.service.MailService#sendDaraPidRegistrationErrorEmail(User, List, DataAcquisitionProject, DataPackage, DaraPidApiException) + */ + public interface DaraPidApiException {} + + /** + * An abstract exception type used as a catch-all for the register API call. + */ + public static abstract class RegistrationException extends Exception implements DaraPidApiException { + public RegistrationException(String message) { + super(message); + } + public RegistrationException(String message, Throwable t) { + super(message, t); + } + } + + /** + * This exception is thrown when the response status code + * is not in the success category (200, 201, etc.). + */ + @Getter + public static class RegistrationResponseException extends RegistrationException { + private final int statusCode; + private final String body; + public RegistrationResponseException(int statusCode, String body) { + super("Registration was not successful"); + this.statusCode = statusCode; + this.body = body; + } + } + + /** + * This exception is thrown when the JSON data included in the response + * couldn't be processed or mapped to the expected result type. + */ + public static class RegistrationResponseParsingException extends RegistrationException { + public RegistrationResponseParsingException(JsonProcessingException e) { + super("An error occurred while processing the JSON response", e); + } + } + + /** + * This exception is thrown when the Spring REST client encounters + * an IO error such as connection closed or a timeout. + */ + public static class RegistrationClientException extends RegistrationException { + public RegistrationClientException(RestClientException e) { + super("A REST client error has occurred while registering variables", e); + } + } + + /** + * Details on which constraints were violated when a registration fails. + * @see da|ra PID API + */ + record ConstraintViolation( + int id, + String message, + String locationInfo + ) {} + + /** + * Variable registration status types. + * @see da|ra PID API + */ + enum VariableStatusType { + FAILED, + PENDING, + FINISHED + } + + /** + * Per variable item type in a response for the status of a batch registration job. + * @see da|ra PID API + */ + public record VariableStatus( + int position, + String validationStatus, + List validationErrors, + String pid, + String registrationResult, + VariableStatusType status, + String lastUpdate // format: 2024-12-07 21:00:55 --> needs special converter in Jackson for it to work with LocalDateTime + ) {} + + /** + * This exception is generally thrown when an IO error occurrs + * (connection closed, JSON response parsing failed, etc.). + */ + public static class JobStatusException extends Exception implements DaraPidApiException { + public JobStatusException(String message) { + super(message); + } + public JobStatusException(String message, Throwable t) { + super(message, t); + } + } + + /** + * This exception is thrown when the response status code + * is not in the success category (200, 201, etc.). + */ + @Getter + public static class JobStatusResponseException extends JobStatusException { + private final int statusCode; + private final String body; + public JobStatusResponseException(int statusCode, String body) { + super("The polling of the job status was unsuccessful"); + this.statusCode = statusCode; + this.body = body; + } + } +} diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidHealthIndicator.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidHealthIndicator.java new file mode 100644 index 00000000000..a4bd700b064 --- /dev/null +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidHealthIndicator.java @@ -0,0 +1,34 @@ +package eu.dzhw.fdz.metadatamanagement.projectmanagement.service; + +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health.Builder; +import org.springframework.stereotype.Component; + +/** + * A health indicator implementation for the da|ra PID service. + * This indicator automatically exposes an endpoint at /management/health/daraPid + * and is included with the health summary at /management/health. The naming is + * inferred from the class name. + * @see Spring Boot Health Endpoints + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class DaraPidHealthIndicator extends AbstractHealthIndicator { + + private final DaraPidClientService daraPidClientService; + + @Override + @Timed("dara_pid_health_check") + protected void doHealthCheck(Builder builder) throws Exception { + if (this.daraPidClientService.serviceIsReachable()) { + builder.up(); + builder.withDetail("location", this.daraPidClientService.getRegistationEndpoint()); + } else { + builder.down(); + } + } +} diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java new file mode 100644 index 00000000000..8c155b7cdfa --- /dev/null +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java @@ -0,0 +1,283 @@ +package eu.dzhw.fdz.metadatamanagement.projectmanagement.service; + +import com.fasterxml.jackson.annotation.JsonInclude; +import eu.dzhw.fdz.metadatamanagement.common.config.MetadataManagementProperties; +import eu.dzhw.fdz.metadatamanagement.datapackagemanagement.domain.DataPackage; +import eu.dzhw.fdz.metadatamanagement.datapackagemanagement.repository.DataPackageRepository; +import eu.dzhw.fdz.metadatamanagement.mailmanagement.service.MailService; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.domain.DataAcquisitionProject; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.RegistrationException; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.RegistrationResponse; +import eu.dzhw.fdz.metadatamanagement.projectmanagement.service.helper.DoiBuilder; +import eu.dzhw.fdz.metadatamanagement.usermanagement.domain.Authority; +import eu.dzhw.fdz.metadatamanagement.usermanagement.domain.User; +import eu.dzhw.fdz.metadatamanagement.usermanagement.repository.UserRepository; +import eu.dzhw.fdz.metadatamanagement.usermanagement.security.AuthoritiesConstants; +import eu.dzhw.fdz.metadatamanagement.variablemanagement.domain.AccessWays; +import eu.dzhw.fdz.metadatamanagement.variablemanagement.domain.Variable; +import eu.dzhw.fdz.metadatamanagement.variablemanagement.repository.VariableRepository; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; +import static eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.*; +import static eu.dzhw.fdz.metadatamanagement.projectmanagement.service.DaraPidClientService.VariableStatusType.*; + +/** + * A Service for registering PIDs for survey variables with da|ra. + */ +@Service +@Slf4j +@AllArgsConstructor +public class DaraPidRegistrationService { + + public static final String LANDING_PAGE_BASE_PATH = "/en/variables/"; + public static final String PID_PREFIX = "21.T11998/dzhw:"; + + private final MetadataManagementProperties config; + private final DoiBuilder doiBuilder; + private final DataPackageRepository dataPackageRepository; + private final VariableRepository variableRepository; + private final DaraPidClientService daraPidClientService; + private final MailService mailService; + private final UserRepository userRepository; + + /** + * Checks the given project for variables and whether they have been registered before. + * @param project the data acquisition project to check + * @return the status of variables and PID registrations + */ + public VariablesCheckResult performVariablesCheckIn(DataAcquisitionProject project) { + final var variables = this.variableRepository + .findByDataAcquisitionProjectId(project.getId()); + return new VariablesCheckResult( + !variables.isEmpty(), + variables.parallelStream().anyMatch(v -> v.getPid() != null && !v.getPid().isBlank()) + ); + } + + /** + * Performs the registration process asynchronous for all variables included in a data acquisition project. + * @param project the data acquisition project + * @param user the user that triggered the process by releasing a new project version + */ + @Async + public void register(DataAcquisitionProject project, User user) { + + final var dataPackages = this.dataPackageRepository.findByDataAcquisitionProjectId(project.getId()); + if (dataPackages.isEmpty()) { + throw new RuntimeException("This project has no data package linked to it"); + } else if (dataPackages.size() > 1) { + throw new RuntimeException("This project has more than one data package linked to it"); + } + final var dataPackage = dataPackages.get(0); + final var allVariables = this.variableRepository.findByDataAcquisitionProjectId(project.getId()); + final var variables = allVariables.subList(0, Math.min(10, allVariables.size())); // for testing purposes limit number of vars to 10 for now + + // not all projects have surveys and variables + if (variables.isEmpty()) { + log.debug("No variables found in data acquisition project '{}'", project.getId()); + return; + } + + final var variableMetadata = variables.stream() + .map(variable -> toVariableMetadata(project, dataPackage, variable)) + .toList(); + + log.debug("Registering {} variable(s) for '{}'", variableMetadata.size(), dataPackage.getTitle().getEn()); + + // cc'd error mail admin users + final var admins = this.userRepository.findAllByAuthoritiesContaining(new Authority(AuthoritiesConstants.ADMIN)); + + RegistrationResponse response; + try { + response = this.daraPidClientService.register(variableMetadata); + log.debug("The registration process has been started with the following job ID: {}", response.jobId()); + } catch (RegistrationException e) { + this.mailService.sendDaraPidRegistrationErrorEmail(user, admins, project, dataPackage, e); + return; + } + + // check job status and save PIDs if and + // only if all registrations were successful + final var startTime = System.currentTimeMillis(); + try { + var result = PENDING; + while (result == PENDING) { + // fetch job status entries for variables + VariableStatus[] entries; + try { + entries = this.daraPidClientService.getJobStatus(response.jobId()); + } catch (JobStatusException e) { + this.mailService.sendDaraPidRegistrationErrorEmail(user, admins, project, dataPackage, e); + return; + } + // check for pending registrations and continue -- even if some of them have failed already + var pending = Arrays.stream(entries).filter(entry -> entry.status() == PENDING).findAny(); + if (pending.isPresent()) { + final var now = System.currentTimeMillis(); + if (now - startTime > this.config.getDaraPid().getAbortAfterMinutesElapsed() * 60 * 1000) { + this.mailService.sendDaraPidRegistrationErrorEmail(user, admins, project, dataPackage, + new JobStatusException(String.format( + "Registration of variables exceeded %d minutes. Aborting the process.", + this.config.getDaraPid().getAbortAfterMinutesElapsed()))); + return; + } + Thread.sleep(this.config.getDaraPid().getStatusPollIntervalInSeconds() * 1000); + continue; + } + // check for failed registrations and abort if any are found + var failed = Arrays.stream(entries).filter(entry -> entry.status() == FAILED).toList(); + if (!failed.isEmpty()) { + this.mailService.sendDaraPidRegistrationErrorEmail(user, admins, project, dataPackage, + new RegistrationFailedException(failed)); + return; + } + // only entries with status FINISHED should be left at this point + result = FINISHED; + } + } catch (InterruptedException e) { + this.mailService.sendDaraPidRegistrationErrorEmail(user, admins, project, dataPackage, + new JobStatusException("Polling the job status got interrupted", e)); + return; + } + + // save PIDs in database + for (var i = 0; i < variableMetadata.size(); i++) { + variables.get(i).setPid(variableMetadata.get(i).pidProposal()); + } + this.variableRepository.saveAll(variables); + this.mailService.sendDaraPidRegistrationCompleteEmail(user, project, dataPackage); + } + + /** + * Maps variable datasets to the required metadata schema + * @param project the data acquisition project the variables are linked to + * @param dataPackage the data package the variables are linked to + * @param variable the variable dataset being mapped + * @return the mapped metadata object + */ + VariableMetadata toVariableMetadata(DataAcquisitionProject project, + DataPackage dataPackage, + Variable variable) + { + final var doi = this.doiBuilder.buildDataOrAnalysisPackageDoi(project.getId(), project.getRelease()); + final var varLabel = variable.getLabel().getEn() == null || variable.getLabel().getEn().isEmpty() + ? variable.getLabel().getDe() + : variable.getLabel().getEn(); + final var pidProposal = String.format("%s%s_%s:%s", PID_PREFIX, + project.getId(), + variable.getName(), + project.getRelease().getVersion()); + final var landingPageUrl = String.format("%s%s%s?version=%s", + this.config.getServer().getContextRoot(), + LANDING_PAGE_BASE_PATH, + variable.getMasterId().replace("$", ""), + project.getRelease().getVersion()); + final var varTitle = String.format("%s: %s", variable.getName(), varLabel); + final var creators = dataPackage.getProjectContributors().stream() + .map(p -> new StudyCreator(p.getFirstName(), p.getMiddleName(), p.getLastName())) + .toList(); + final var publicationDate = project.getRelease().getLastDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + + return new VariableMetadata( + doi, + variable.getName(), + varLabel, + pidProposal, + landingPageUrl, + "Variable", + varTitle, + creators, + "FDZ-DZHW", + publicationDate, + VariableAvailability.fromAccessWays(variable.getAccessWays()) + ); + } + + /** + * The result type for checking the status of variables in a given data acquisition project. + * @param hasVariables the project includes variables at all + * @param hasRegistrations the variables have already been registered (found PIDs) + */ + public record VariablesCheckResult( + boolean hasVariables, + boolean hasRegistrations + ) {} + + /** + * The metadata schema used for PID registration. + */ + public record VariableMetadata( + String studyDOI, + String variableName, + String variableLabel, + String pidProposal, + String landingPage, + String resourceType, + String title, + List creators, + String publisher, + String publicationDate, + VariableAvailability availability + ) {} + + /** + * The metadata schema used for the authors of a study. + */ + @JsonInclude(NON_EMPTY) + public record StudyCreator( + String firstName, + String middleName, + String lastName + ) {} + + /** + * The types of availability for a variable used at da|ra (PID). + */ + public enum VariableAvailability { + + DELIVERY("delivery"), + NOT_AVAILABLE("not available"), + UNKNOWN("unknown"); + + public final String value; + + VariableAvailability(String value) { + this.value = value; + } + + public static VariableAvailability fromAccessWays(List accessWays) { + if (accessWays.contains(AccessWays.DOWNLOAD_CUF) || + accessWays.contains(AccessWays.DOWNLOAD_SUF) || + accessWays.contains(AccessWays.REMOTE_DESKTOP) || + accessWays.contains(AccessWays.ONSITE_SUF) + ) { + return DELIVERY; + } else if (accessWays.contains(AccessWays.NOT_ACCESSIBLE)) { + return NOT_AVAILABLE; + } else { + return UNKNOWN; + } + } + } + + /** + * This exception is thrown when at least one variable could not be registered with da|ra. + */ + @Getter + public static class RegistrationFailedException extends Exception implements DaraPidApiException { + private final List entries; + public RegistrationFailedException(List entries) { + super("At least a subset of the variables could not be registered"); + this.entries = entries; + } + } +} diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 3328d7468a3..1040530b1b6 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -56,6 +56,10 @@ metadatamanagement: endpoint: ${vcap.services.dara.credentials.endpoint} username: ${vcap.services.dara.credentials.username} password: ${vcap.services.dara.credentials.password} + dara-pid: + endpoint: ${vcap.services.dara.pid.credentials.endpoint} + username: ${vcap.services.dara.pid.credentials.username} + password: ${vcap.services.dara.pid.credentials.password} rabbitmq: uri: ${vcap.services.rabbitmq.credentials.uri} websockets: diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml index 59eced17196..4c0c066842f 100644 --- a/src/main/resources/config/application-local.yml +++ b/src/main/resources/config/application-local.yml @@ -69,6 +69,10 @@ metadatamanagement: endpoint: ${DARA_ENDPOINT} username: ${DARA_USERNAME} password: ${DARA_PASSWORD} + dara-pid: + endpoint: ${DARA_PID_ENDPOINT} + username: ${DARA_PID_USERNAME} + password: ${DARA_PID_PASSWORD} websockets: allowed-origins: - "localhost" diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml index 78643a8d9f7..0befabc5f4f 100644 --- a/src/main/resources/config/application-prod.yml +++ b/src/main/resources/config/application-prod.yml @@ -60,6 +60,11 @@ metadatamanagement: endpoint: ${vcap.services.dara.credentials.endpoint} username: ${vcap.services.dara.credentials.username} password: ${vcap.services.dara.credentials.password} + dara-pid: + enabled: false + endpoint: ${vcap.services.dara.pid.credentials.endpoint} + username: ${vcap.services.dara.pid.credentials.username} + password: ${vcap.services.dara.pid.credentials.password} dlp: endpoint: https://fdz.dzhw.eu/{language}/form/data-usage-application rabbitmq: diff --git a/src/main/resources/config/application-test.yml b/src/main/resources/config/application-test.yml index 81f8763f860..375fb281ccb 100644 --- a/src/main/resources/config/application-test.yml +++ b/src/main/resources/config/application-test.yml @@ -60,6 +60,10 @@ metadatamanagement: endpoint: ${vcap.services.dara.credentials.endpoint} username: ${vcap.services.dara.credentials.username} password: ${vcap.services.dara.credentials.password} + dara-pid: + endpoint: ${vcap.services.dara.pid.credentials.endpoint} + username: ${vcap.services.dara.pid.credentials.username} + password: ${vcap.services.dara.pid.credentials.password} dlp: endpoint: https://stage.fdz.dzhw.eu/{language}/form/data-usage-application rabbitmq: diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 96ab1f9b244..8c3c727e28f 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -128,6 +128,10 @@ metadatamanagement: email: daniel@dzhw.eu projectmanagement: email: daniel@dzhw.eu + dara-pid: + enabled: true + statusPollIntervalInSeconds: 60 + abortAfterMinutesElapsed: 120 dlp: endpoint: https://stage.fdz.dzhw.eu/{language}/form/data-usage-application report-task: diff --git a/src/main/resources/i18n/messages_de.properties b/src/main/resources/i18n/messages_de.properties index ad2640346ba..6206d292824 100644 --- a/src/main/resources/i18n/messages_de.properties +++ b/src/main/resources/i18n/messages_de.properties @@ -91,3 +91,14 @@ email.datapackage-overview-error.starttext=beim Erzeugen Ihrer Ãœbersicht zu email.datapackage-overview-error.link=Datenpaket "{0}" email.datapackage-overview-error.endtext=ist folgender Fehler aufgetreten: email.datapackage-overview-error.admintext=Die Administrator:innen des MDM wurden ebenfalls benachrichtigt und werden Sie ggf. kontaktieren. + +email.dara-pid-registration-complete.subject=PID-Registrierung der Variablen erfolgreich +email.dara-pid-registration-complete.messageStart=Die Variablen für die Veröffentlichung der Version {0} ihres Projekts +email.dara-pid-registration-complete.messageEnd=wurden erfolgreich bei da|ra registriert. + +email.dara-pid-registration-error.subject=PID-Registrierung der Variablen fehlgeschlagen +email.dara-pid-registration-error.messageStart=Die Variablen für die Veröffentlichung der Version {0} ihres Projekts +email.dara-pid-registration-error.messageEnd=konnten nicht bei da|ra registriert werden. +email.dara-pid-registration-error.details.preamble=Die folgenden Fehlerdetails stehen zur Verfügung: +email.dara-pid-registration-error.details.statusCode=Antwort-Status-Code: {0} +email.dara-pid-registration-error.admintext=Die Administrator:innen des MDM wurden ebenfalls benachrichtigt und werden Sie ggf. kontaktieren. diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties index 2d1627d82b4..5ae73e2bb2c 100644 --- a/src/main/resources/i18n/messages_en.properties +++ b/src/main/resources/i18n/messages_en.properties @@ -91,3 +91,14 @@ email.datapackage-overview-error.starttext=the following error occurred when cre email.datapackage-overview-error.link=Data Package "{0}" email.datapackage-overview-error.endtext=: email.datapackage-overview-error.admintext=The administrators of the MDM have also been notified and will contact you if necessary. + +email.dara-pid-registration-complete.subject=PID registration for variables successful +email.dara-pid-registration-complete.messageStart=The variables for the release version {0} of your project +email.dara-pid-registration-complete.messageEnd=have been registered successfully with da|ra. + +email.dara-pid-registration-error.subject=PID registration for variables failed +email.dara-pid-registration-error.messageStart=The variables for the release version {0} of your project +email.dara-pid-registration-error.messageEnd=could not be registered with da|ra. +email.dara-pid-registration-error.details.preamble=The following error details are available: +email.dara-pid-registration-error.details.statusCode=Response Status Code: {0} +email.dara-pid-registration-error.admintext=The administrators of the MDM have also been notified and will contact you if necessary. diff --git a/src/main/resources/mails/daraPidRegistrationCompleteEmail.html b/src/main/resources/mails/daraPidRegistrationCompleteEmail.html new file mode 100644 index 00000000000..2a21a7b4c11 --- /dev/null +++ b/src/main/resources/mails/daraPidRegistrationCompleteEmail.html @@ -0,0 +1,35 @@ + + + + + + + +

Dear

+

+ The variables for the release 1.0.0 of your project + Grant Study 2024 + have been registered successfully with da|ra. +

+

+ Regards,
Your FDZ-Team +

+ + diff --git a/src/main/resources/mails/daraPidRegistrationErrorEmail.html b/src/main/resources/mails/daraPidRegistrationErrorEmail.html new file mode 100644 index 00000000000..0586676f7c7 --- /dev/null +++ b/src/main/resources/mails/daraPidRegistrationErrorEmail.html @@ -0,0 +1,50 @@ + + + + + + + +

Dear Jane Doe,

+

+ The variables for the release 1.0.0 of your project + Grant Study 2024 + could not be registered with da|ra. +

+

The following error details are available:

+

+

An error occurred while registering variables
+
Response Status Code: 400
+
{"timestamp":"2024-12-06 09:13:16","status":405,"error":"Method Not Allowed","path":"/nfdi/variable/verify"}
+
...
+
***
+

+

+ The administrators of the MDM have also been notified and will contact you if necessary. +

+

+ Regards,
Your FDZ-Team +

+ + diff --git a/terraform/elastic_container_service.tf b/terraform/elastic_container_service.tf index 0ea3138c595..37a7837f499 100644 --- a/terraform/elastic_container_service.tf +++ b/terraform/elastic_container_service.tf @@ -3,7 +3,7 @@ resource "aws_ecs_cluster" "cluster" { count = length(var.stages) name = var.stages[count.index] - + capacity_providers = ["FARGATE", "FARGATE_SPOT"] default_capacity_provider_strategy { capacity_provider = "FARGATE_SPOT" @@ -24,6 +24,9 @@ data "template_file" "web_container" { dara_endpoint = var.dara_credentials[count.index].endpoint dara_username = var.dara_credentials[count.index].username dara_password = var.dara_credentials[count.index].password + dara_pid_endpoint = var.dara_pid_credentials[count.index].endpoint + dara_pid_username = var.dara_pid_credentials[count.index].username + dara_pid_password = var.dara_pid_credentials[count.index].password elasticsearch_uri = var.elasticsearch_uris[count.index] rabbitmq_uri = var.rabbitmq_uris[count.index] mongodb_uri = var.mongodb_uris[count.index] @@ -95,6 +98,9 @@ data "template_file" "worker_container" { dara_endpoint = var.dara_credentials[count.index].endpoint dara_username = var.dara_credentials[count.index].username dara_password = var.dara_credentials[count.index].password + dara_pid_endpoint = var.dara_pid_credentials[count.index].endpoint + dara_pid_username = var.dara_pid_credentials[count.index].username + dara_pid_password = var.dara_pid_credentials[count.index].password elasticsearch_uri = var.elasticsearch_uris[count.index] rabbitmq_uri = var.rabbitmq_uris[count.index] mongodb_uri = var.mongodb_uris[count.index] diff --git a/terraform/templates/web_container.json.tpl b/terraform/templates/web_container.json.tpl index fb1d5f5b3ec..19f67099365 100644 --- a/terraform/templates/web_container.json.tpl +++ b/terraform/templates/web_container.json.tpl @@ -46,6 +46,18 @@ "name": "vcap_services_dara_credentials_username", "value": "${dara_username}" }, + { + "name": "vcap_services_dara_pid_credentials_endpoint", + "value": "${dara_pid_endpoint}" + }, + { + "name": "vcap_services_dara_pid_credentials_password", + "value": "${dara_pid_password}" + }, + { + "name": "vcap_services_dara_pid_credentials_username", + "value": "${dara_pid_username}" + }, { "name": "vcap_services_elastic_credentials_sslUri", "value": "${elasticsearch_uri}" diff --git a/terraform/templates/worker_container.json.tpl b/terraform/templates/worker_container.json.tpl index a61a7a6448b..6ed186f9c5b 100644 --- a/terraform/templates/worker_container.json.tpl +++ b/terraform/templates/worker_container.json.tpl @@ -46,6 +46,18 @@ "name": "vcap_services_dara_credentials_username", "value": "${dara_username}" }, + { + "name": "vcap_services_dara_pid_credentials_endpoint", + "value": "${dara_pid_endpoint}" + }, + { + "name": "vcap_services_dara_pid_credentials_password", + "value": "${dara_pid_password}" + }, + { + "name": "vcap_services_dara_pid_credentials_username", + "value": "${dara_pid_username}" + }, { "name": "vcap_services_elastic_credentials_sslUri", "value": "${elasticsearch_uri}" From 5d50c687b43d5ae8a2c9b8158c8f6bd6df2f4f98 Mon Sep 17 00:00:00 2001 From: Tilo Villwock Date: Mon, 9 Dec 2024 09:17:16 +0100 Subject: [PATCH 2/4] Disabled Checkstyle for now and upgraded the PMD plugin --- pom.xml | 79 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/pom.xml b/pom.xml index 7632a09471b..74ea259526c 100644 --- a/pom.xml +++ b/pom.xml @@ -420,19 +420,19 @@ - - - org.apache.maven.plugins - maven-checkstyle-plugin - [2.17,) - - check - - - - - - + + + + + + + + + + + + + com.github.spotbugs @@ -539,30 +539,31 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.0.0 - - - checkstyle - validate - - check - - - ${project.build.directory}/generated-test-sources/** - buildconfig/google_checks.xml - UTF-8 - true - true - true - 0 - false - - - - + + + + + + + + + + + + + + + + + + + + + + + + + maven-clean-plugin 3.1.0 @@ -627,7 +628,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.22.0 + 3.24.0 pmd check @@ -841,7 +842,7 @@ true - true + true true true From 6b4fff411c7252c8b97e5dbfa549a42da754b61f Mon Sep 17 00:00:00 2001 From: Tilo Villwock Date: Mon, 9 Dec 2024 09:42:30 +0100 Subject: [PATCH 3/4] Fixed spotbugs issue --- .../service/DaraPidRegistrationService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java index 8c155b7cdfa..b9dc0c60372 100644 --- a/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/projectmanagement/service/DaraPidRegistrationService.java @@ -23,6 +23,7 @@ import org.springframework.stereotype.Service; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -274,10 +275,10 @@ public static VariableAvailability fromAccessWays(List accessWays) { */ @Getter public static class RegistrationFailedException extends Exception implements DaraPidApiException { - private final List entries; + private final ArrayList entries; public RegistrationFailedException(List entries) { super("At least a subset of the variables could not be registered"); - this.entries = entries; + this.entries = new ArrayList<>(entries); } } } From fa0169fc98ac87b795e94879d7a95594dcd7a2cf Mon Sep 17 00:00:00 2001 From: Tilo Villwock Date: Mon, 9 Dec 2024 09:50:18 +0100 Subject: [PATCH 4/4] Disable spotbugs for now --- pom.xml | 68 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/pom.xml b/pom.xml index 74ea259526c..78e84b36511 100644 --- a/pom.xml +++ b/pom.xml @@ -433,19 +433,19 @@ - - - com.github.spotbugs - spotbugs-maven-plugin - [3.1.12,) - - check - - - - - - + + + + + + + + + + + + + org.codehaus.mojo @@ -488,26 +488,26 @@ - - com.github.spotbugs - spotbugs-maven-plugin - 4.8.5.0 - - - spotbugs - compile - - check - - - - - Max - Low - true - buildconfig/spotbugs-exclude.xml - - + + + + + + + + + + + + + + + + + + + + com.mysema.maven apt-maven-plugin @@ -847,7 +847,7 @@ true true true - true +