From 9dcf878f8f322496bd75f731299ddb5c547fc64e Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 30 Apr 2024 12:00:00 +0200 Subject: [PATCH 01/45] Return instead of throw exception --- .../java/org/highmed/numportal/service/ProjectService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/highmed/numportal/service/ProjectService.java b/src/main/java/org/highmed/numportal/service/ProjectService.java index 80660ed6..6d7c064a 100644 --- a/src/main/java/org/highmed/numportal/service/ProjectService.java +++ b/src/main/java/org/highmed/numportal/service/ProjectService.java @@ -1250,10 +1250,7 @@ public byte[] getInfoDocBytes(Long id, String userId, Locale locale) { Project project = projectRepository .findById(id) - .orElseThrow( - () -> { - throw new BadRequestException(ProjectService.class, PROJECT_NOT_FOUND); - }); + .orElseThrow(() -> new BadRequestException(ProjectService.class, PROJECT_NOT_FOUND)); ProjectDto projectDto = projectMapper.convertToDto(project); try { return projectDocCreator.getDocBytesOfProject(projectDto, locale); From 43fd42de34ad917cefd237d9136e08d838358945 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 16 Sep 2024 09:03:39 +0000 Subject: [PATCH 02/45] updated development version to 1.22.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cbda4009..57276e96 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.highmed.num-portal num-portal - 1.21.0-SNAPSHOT + 1.22.0-SNAPSHOT org.springframework.boot From 7872603775fe8b45b2c204168e1aa1ca70425bee Mon Sep 17 00:00:00 2001 From: Marcus Fabarius Date: Mon, 16 Sep 2024 11:43:39 +0200 Subject: [PATCH 03/45] add clamAV to local env; make delete attachment idempotent --- local-env/docker-compose.yaml | 19 +++++++++++++++---- .../attachment/service/AttachmentService.java | 3 ++- .../service/AttachmentServiceTest.java | 5 ----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/local-env/docker-compose.yaml b/local-env/docker-compose.yaml index 5ee391b9..9319c886 100644 --- a/local-env/docker-compose.yaml +++ b/local-env/docker-compose.yaml @@ -14,7 +14,7 @@ services: volumes: - ./keycloak.conf:/opt/keycloak/conf/keycloak.conf ports: - - 8180:8080 + - "8180:8080" networks: - num-portal depends_on: @@ -23,7 +23,7 @@ services: image: postgres:16.2 restart: always ports: - - 5432:5432 + - "5432:5432" networks: - num-portal environment: @@ -35,7 +35,7 @@ services: image: dpage/pgadmin4:8.9 restart: always ports: - - 8888:80 + - "8888:80" networks: - num-portal environment: @@ -48,7 +48,7 @@ services: ehrbase: image: ehrbase/ehrbase:2.5.0 ports: - - 8080:8080 + - "8080:8080" networks: - num-portal env_file: @@ -62,10 +62,21 @@ services: DB_PASS: ${DB_PASS} depends_on: - postgres + clamav: + image: clamav/clamav:1.3.2 + container_name: clamav + ports: + - "3310:3310" + volumes: + - clamav-db:/var/lib/clamav + environment: + - CLAMD_CONF_FILE=/etc/clamav/clamd.conf + - FRESHCLAM_CONF_FILE=/etc/clamav/freshclam.conf volumes: pgdata: pgadmin-data: + clamav-db: networks: num-portal: driver: bridge diff --git a/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java b/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java index 8e5ac1cc..2c93ed33 100644 --- a/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java +++ b/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java @@ -141,7 +141,8 @@ public void deleteAttachments(Set attachmentsId, Long projectId, String lo for(Long attachmentId : attachmentsId) { Optional attachment = attachmentRepository.findByIdAndProjectId(attachmentId, projectId); if(attachment.isEmpty()) { - throw new ResourceNotFound(AttachmentService.class, "Attachment not found", String.format("Attachment not found: %s", attachmentId)); + log.info("Attachment is already deleted, attachment: {}, project: {}", attachmentId, projectId); + return; } Attachment currentAttachment = attachment.get(); if (Boolean.FALSE.equals(userIsApprover) && currentAttachment.getReviewCounter() > 1) { diff --git a/src/test/java/org/highmed/numportal/attachment/service/AttachmentServiceTest.java b/src/test/java/org/highmed/numportal/attachment/service/AttachmentServiceTest.java index 2f8f7b76..705fd1ad 100644 --- a/src/test/java/org/highmed/numportal/attachment/service/AttachmentServiceTest.java +++ b/src/test/java/org/highmed/numportal/attachment/service/AttachmentServiceTest.java @@ -153,11 +153,6 @@ public void deleteAttachmentsTest() { Mockito.verify(attachmentRepository, Mockito.times(2)).deleteAttachment(Mockito.anyLong()); } - @Test(expected = ResourceNotFound.class) - public void deleteAttachmentsAndExpectResourceNotFoundTest() { - attachmentService.deleteAttachments(Set.of(3L), 9L, "loggedInUser", true); - } - @Test(expected = ForbiddenException.class) public void deleteAttachmentsHandleReviewCounterExceededTest() { Attachment one = Attachment.builder() From ba00d24355d868436d19295200bfdbe941d583ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 04:43:41 +0000 Subject: [PATCH 04/45] Bump commons-io:commons-io from 2.16.1 to 2.17.0 Bumps commons-io:commons-io from 2.16.1 to 2.17.0. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 57276e96..d7d24db1 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 4.4 - 2.16.1 + 2.17.0 2.17.0 1.0.5 From 58d051bcb9181ce61b08bc008228d5be5a499ab6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 04:47:32 +0000 Subject: [PATCH 05/45] Bump org.springframework.boot:spring-boot-starter-parent Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.3.3 to 3.3.4. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.3.3...v3.3.4) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 57276e96..bbb02484 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.3 + 3.3.4 From bf63cfe7636dd1db6677e60e61c222d5e9bbb3ca Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:27:38 +0200 Subject: [PATCH 06/45] Update build-for-development.yml --- .github/workflows/build-for-development.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index 8a18628d..a609eca0 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -2,10 +2,15 @@ name: Build for development on: push: - branches: [ 'feature/**', 'dependabot/**' ] + branches: [ 'feature/**' ] # Removed dependabot from here + pull_request: + branches: [ develop, main ] + types: [ ready_for_review, closed ] pull_request_target: branches: [ develop, main ] types: [ ready_for_review, closed ] + # Add pull_request trigger for Dependabot + workflow_dispatch: jobs: test_and_scan: @@ -54,6 +59,7 @@ jobs: output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results to GitHub Security tab + if: github.event_name == 'pull_request' # Ensure this runs only on PRs uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' From 7bb4123cb6d612523b3f7e91bb2c2886092dd4eb Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:28:09 +0200 Subject: [PATCH 07/45] Update build-for-development.yml --- .github/workflows/build-for-development.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index a609eca0..cc7a26a1 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -2,14 +2,13 @@ name: Build for development on: push: - branches: [ 'feature/**' ] # Removed dependabot from here + branches: [ 'feature/**' ] pull_request: branches: [ develop, main ] types: [ ready_for_review, closed ] pull_request_target: branches: [ develop, main ] types: [ ready_for_review, closed ] - # Add pull_request trigger for Dependabot workflow_dispatch: jobs: @@ -59,7 +58,7 @@ jobs: output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results to GitHub Security tab - if: github.event_name == 'pull_request' # Ensure this runs only on PRs + if: github.event_name == 'pull_request' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' From a5fc53714e99019c0ed8a3b12f5dfcc3e331e0a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 05:10:27 +0000 Subject: [PATCH 08/45] Bump org.apache.commons:commons-csv from 1.11.0 to 1.12.0 Bumps [org.apache.commons:commons-csv](https://github.com/apache/commons-csv) from 1.11.0 to 1.12.0. - [Changelog](https://github.com/apache/commons-csv/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-csv/compare/rel/commons-csv-1.11.0...rel/commons-csv-1.12.0) --- updated-dependencies: - dependency-name: org.apache.commons:commons-csv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 81f62d14..14a88163 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 3.2.1 2.6.0 17 - 1.11.0 + 1.12.0 0.8.12 3.5.0 3.5.0 From 5ca6a683f2997f3acd844e6cb16574fadb39086a Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:54:44 +0200 Subject: [PATCH 09/45] Update build-for-development.yml --- .github/workflows/build-for-development.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index cc7a26a1..2f4ddf38 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -5,10 +5,7 @@ on: branches: [ 'feature/**' ] pull_request: branches: [ develop, main ] - types: [ ready_for_review, closed ] - pull_request_target: - branches: [ develop, main ] - types: [ ready_for_review, closed ] + types: [ open, ready_for_review, closed ] workflow_dispatch: jobs: From 7c20060811a0907144c18d1621b51d4039ad5aa0 Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:19:41 +0200 Subject: [PATCH 10/45] Update build-for-development.yml --- .github/workflows/build-for-development.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index 2f4ddf38..06adeafd 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ develop, main ] types: [ open, ready_for_review, closed ] + pull_request_target: + branches: [ develop, main ] + types: [ closed ] workflow_dispatch: jobs: From a0ddbca6ea3a23e81adea1a147d3c5fc72f8dfce Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:20:25 +0200 Subject: [PATCH 11/45] Update build-for-development.yml --- .github/workflows/build-for-development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index 06adeafd..7a8a9144 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -5,7 +5,7 @@ on: branches: [ 'feature/**' ] pull_request: branches: [ develop, main ] - types: [ open, ready_for_review, closed ] + types: [ open, ready_for_review ] pull_request_target: branches: [ develop, main ] types: [ closed ] From a898d31c7d27c8d22950f97e08c2fa4ac029ba62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 04:31:04 +0000 Subject: [PATCH 12/45] Bump testcontainers.version from 1.20.1 to 1.20.2 Bumps `testcontainers.version` from 1.20.1 to 1.20.2. Updates `org.testcontainers:postgresql` from 1.20.1 to 1.20.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.20.1...1.20.2) Updates `org.testcontainers:mockserver` from 1.20.1 to 1.20.2 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.20.1...1.20.2) --- updated-dependencies: - dependency-name: org.testcontainers:postgresql dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.testcontainers:mockserver dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 14a88163..11972195 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ false 42.7.4 6.8.3 - 1.20.1 + 1.20.2 5.15.0 false 2023.0.3 From 203650a02a3e33bb106d9d024373f9c6df195ca5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 04:31:17 +0000 Subject: [PATCH 13/45] Bump ehr-sdk.version from 2.17.0 to 2.18.0 Bumps `ehr-sdk.version` from 2.17.0 to 2.18.0. Updates `org.ehrbase.openehr.sdk:client` from 2.17.0 to 2.18.0 Updates `org.ehrbase.openehr.sdk:aql` from 2.17.0 to 2.18.0 --- updated-dependencies: - dependency-name: org.ehrbase.openehr.sdk:client dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.ehrbase.openehr.sdk:aql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 14a88163..caa577b5 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 4.4 2.17.0 - 2.17.0 + 2.18.0 1.0.5 8.0 From 3ee8e5ed2f482cfae8f76b9b36f5204adbe72b2d Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:18:09 +0200 Subject: [PATCH 14/45] Update build-for-development.yml --- .github/workflows/build-for-development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index 7a8a9144..62768bb3 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -5,7 +5,7 @@ on: branches: [ 'feature/**' ] pull_request: branches: [ develop, main ] - types: [ open, ready_for_review ] + types: [ opened, ready_for_review ] pull_request_target: branches: [ develop, main ] types: [ closed ] From 8dfb4b677a35b0ab864a8699fb70ba4137641033 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:35:48 +0000 Subject: [PATCH 15/45] Bump surefire.version from 3.5.0 to 3.5.1 Bumps `surefire.version` from 3.5.0 to 3.5.1. Updates `org.apache.maven.surefire:surefire-junit47` from 3.5.0 to 3.5.1 Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.0 to 3.5.1 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.0...surefire-3.5.1) --- updated-dependencies: - dependency-name: org.apache.maven.surefire:surefire-junit47 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 14a88163..c6b822aa 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 1.12.0 0.8.12 3.5.0 - 3.5.0 + 3.5.1 3.10.8 4.9.10 false From e93a9606f854ff7432682baed70f57e74f0db375 Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:17:41 +0200 Subject: [PATCH 16/45] Feature/project info metrics (#625) * Add a projectsMetrics class * Add all project metrics --- .../domain/repository/ProjectRepository.java | 2 + .../numportal/service/ProjectService.java | 15 ++-- .../service/metric/ProjectsMetrics.java | 79 +++++++++++++++++++ .../tests/ProjectControllerIT.java | 1 + .../numportal/service/ProjectServiceTest.java | 6 +- 5 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java diff --git a/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java b/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java index d17a0338..dec1c717 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java @@ -23,4 +23,6 @@ List findLatestProjects( @Query(value = "SELECT new Project(pr.id, pr.name, pr.createDate, pr.coordinator) FROM Project pr " + "WHERE pr.status IN (:statuses) ORDER BY pr.createDate DESC ") List findByStatusInOrderByCreateDateDesc(List statuses, Pageable pageable); + + long countByStatus(ProjectStatus projectStatus); } diff --git a/src/main/java/org/highmed/numportal/service/ProjectService.java b/src/main/java/org/highmed/numportal/service/ProjectService.java index 6d7c064a..21f0d7a8 100644 --- a/src/main/java/org/highmed/numportal/service/ProjectService.java +++ b/src/main/java/org/highmed/numportal/service/ProjectService.java @@ -31,6 +31,7 @@ import org.highmed.numportal.service.email.ZarsService; import org.highmed.numportal.service.exception.*; import org.highmed.numportal.service.executors.CohortQueryLister; +import org.highmed.numportal.service.metric.ProjectsMetrics; import org.highmed.numportal.service.notification.NotificationService; import org.highmed.numportal.service.notification.dto.*; import org.highmed.numportal.service.policy.*; @@ -127,6 +128,8 @@ public class ProjectService { private final AttachmentService attachmentService; + private final ProjectsMetrics projectsMetrics; + @Transactional public boolean deleteProject(Long projectId, String userId, List roles) throws ForbiddenException { @@ -144,6 +147,7 @@ public boolean deleteProject(Long projectId, String userId, List roles) if (project.isDeletable()) { attachmentService.deleteAllProjectAttachments(projectId, userId); projectRepository.deleteById(projectId); + projectsMetrics.decreaseTotalNumber(); log.info("Project {} was deleted by {}", projectId, userId); } else { throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_PROJECT_INVALID_STATUS, @@ -164,12 +168,13 @@ public boolean archiveProject(Long projectId, String userId, List roles) if (project.hasEmptyOrDifferentOwner(userId) && !roles.contains(Roles.SUPER_ADMIN)) { throw new ForbiddenException(ProjectService.class, CANNOT_ARCHIVE_PROJECT, String.format(CANNOT_ARCHIVE_PROJECT, projectId)); } - + ProjectStatus before = project.getStatus(); validateStatus(project.getStatus(), ProjectStatus.ARCHIVED, roles); persistTransition(project, project.getStatus(), ProjectStatus.ARCHIVED, user); project.setStatus(ProjectStatus.ARCHIVED); projectRepository.save(project); + projectsMetrics.changeStatus(before, ProjectStatus.ARCHIVED); return true; } @@ -488,7 +493,7 @@ public Project createProject(ProjectDto projectDto, String userId, List project.setFinanced(projectDto.isFinanced()); var savedProject = projectRepository.save(project); - + projectsMetrics.increaseTotalNumber(); if (savedProject.getStatus() == ProjectStatus.PENDING) { registerToZars(project); } @@ -582,9 +587,8 @@ private Project updateProjectStatus(ProjectDto projectDto, List roles, U validateStatus(projectToEdit.getStatus(), projectDto.getStatus(), roles); persistTransition(projectToEdit, projectToEdit.getStatus(), projectDto.getStatus(), user); projectToEdit.setStatus(projectDto.getStatus()); - var savedProject = projectRepository.save(projectToEdit); - + projectsMetrics.changeStatus(oldProjectStatus, savedProject.getStatus()); registerToZarsIfNecessary( savedProject, oldProjectStatus, @@ -624,7 +628,7 @@ private Project updateProjectAllFields( || ProjectStatus.PUBLISHED.equals(projectToEdit.getStatus())) { projectToEdit.setStatus(projectDto.getStatus()); var savedProject = projectRepository.save(projectToEdit); - + projectsMetrics.changeStatus(oldStatus, savedProject.getStatus()); registerToZarsIfNecessary(savedProject, oldStatus, oldResearchers, newResearchers); List notifications = @@ -660,6 +664,7 @@ private Project updateProjectAllFields( projectToEdit.setFinanced(projectDto.isFinanced()); var savedProject = projectRepository.save(projectToEdit); + projectsMetrics.changeStatus(oldStatus, savedProject.getStatus()); if (ProjectStatus.REVIEWING.equals(savedProject.getStatus())) { attachmentService.updateStatusChangeCounter(projectToEdit.getId()); } diff --git a/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java b/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java new file mode 100644 index 00000000..1b5c7c9b --- /dev/null +++ b/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java @@ -0,0 +1,79 @@ +package org.highmed.numportal.service.metric; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.repository.ProjectRepository; +import org.springframework.stereotype.Component; + +/** + * Custom prometheus metric, to detect number of projects in different states, approved, ongoing, finished or archived. + */ +@Getter +@Component +@Slf4j +public class ProjectsMetrics { + private double totalNumberOfProjects; + private double approvedProjects; + private double publishedProjects; + private double closedProjects; + private double archivedProjects; + + public ProjectsMetrics(MeterRegistry registry, ProjectRepository projectRepository) { + Gauge.builder("custom.metric.project.totalNumber.counter", this::getTotalNumberOfProjects) + .description("Total number of projects") + .register(registry); + Gauge.builder("custom.metric.project.approved.counter", this::getApprovedProjects) + .description("Approved projects") + .register(registry); + Gauge.builder("custom.metric.project.published.counter", this::getPublishedProjects) + .description("Published projects") + .register(registry); + Gauge.builder("custom.metric.project.closed.counter", this::getClosedProjects) + .description("Closed projects") + .register(registry); + Gauge.builder("custom.metric.project.archived.counter", this::getArchivedProjects) + .description("Archived projects") + .register(registry); + + totalNumberOfProjects = projectRepository.count(); + approvedProjects = projectRepository.countByStatus(ProjectStatus.APPROVED); + publishedProjects = projectRepository.countByStatus(ProjectStatus.PUBLISHED); + closedProjects = projectRepository.countByStatus(ProjectStatus.CLOSED); + archivedProjects = projectRepository.countByStatus(ProjectStatus.ARCHIVED); + } + + public void increaseTotalNumber(){ + totalNumberOfProjects++; + } + + public void decreaseTotalNumber(){ + totalNumberOfProjects--; + } + + public void changeStatus(ProjectStatus before, ProjectStatus after){ + if(before == null){ + log.error("The project status cannot be null!"); + return; + } + if(before.equals(after)){ + return; + } + switch (before){ + case APPROVED -> approvedProjects --; + case PUBLISHED -> publishedProjects --; + case CLOSED -> closedProjects --; + case ARCHIVED -> archivedProjects --; + } + + switch (after){ + case APPROVED -> approvedProjects ++; + case PUBLISHED -> publishedProjects ++; + case CLOSED -> closedProjects ++; + case ARCHIVED -> archivedProjects ++; + } + } + +} diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java index 78b17321..6963186c 100644 --- a/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java @@ -435,6 +435,7 @@ public void shouldUpdateProjectSuccessfully() { .endDate(LocalDate.now()) .coordinator(user1) .researchers(Lists.newArrayList(user1)) + .status(ProjectStatus.DRAFT) .build(); Project project = projectRepository.save(createProject); diff --git a/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java b/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java index 7fa8c4aa..b16af735 100644 --- a/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java +++ b/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java @@ -25,6 +25,7 @@ import org.highmed.numportal.service.ehrbase.EhrBaseService; import org.highmed.numportal.service.ehrbase.ResponseFilter; import org.highmed.numportal.service.exception.*; +import org.highmed.numportal.service.metric.ProjectsMetrics; import org.highmed.numportal.service.notification.NotificationService; import org.highmed.numportal.service.notification.dto.Notification; import org.highmed.numportal.service.notification.dto.ProjectCloseNotification; @@ -122,7 +123,10 @@ public class ProjectServiceTest { @Mock private PrivacyProperties privacyProperties; - @Mock + @Mock + private ProjectsMetrics projectsMetrics; + + @Mock private ConsentProperties consentProperties; @Mock private UserService userService; From ae544b23409975709e93897f9ac4ffd9c4adbeeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 05:07:31 +0000 Subject: [PATCH 17/45] Bump ch.qos.logback:logback-classic from 1.5.7 to 1.5.9 Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.7 to 1.5.9. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.7...v_1.5.9) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 37e02f82..e0a6bf93 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.0.5 8.0 - 1.5.7 + 1.5.9 3.2.1 2.6.0 17 From 7a1796916fd90af6f70d5db67f3fc15393dac369 Mon Sep 17 00:00:00 2001 From: mafasva <156201303+mafasva@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:52:25 +0200 Subject: [PATCH 18/45] Feature/checkstyle (#627) * add checkstyle config * Add checkstyle step in pipeline * checkstyle: fail on violation * Expand readme, added contributing requirementss --- .config/checkstyle.xml | 400 +++ .config/intellij-codestyle.xml | 168 ++ .github/workflows/build-for-development.yml | 2 + README.md | 37 +- pom.xml | 27 + .../numportal/NumPortalApplication.java | 2 +- .../attachment/AttachmentRepository.java | 16 +- .../attachment/domain/dto/AttachmentDto.java | 24 +- .../domain/dto/LightAttachmentDto.java | 9 +- .../attachment/domain/model/Attachment.java | 50 +- .../repository/AttachmentRepositoryImpl.java | 95 +- .../repository/AttachmentRepositoryJpa.java | 28 +- .../attachment/service/AttachmentService.java | 394 +-- .../attachment/service/ClamAVService.java | 170 +- .../attachment/service/FileScanService.java | 41 +- .../numportal/config/EhrBaseConfig.java | 1 + .../numportal/config/FttpClientConfig.java | 11 +- .../config/MessageSourceConfiguration.java | 3 +- .../database/NumAttachmentFlywayConfig.java | 31 +- ...rtalAttachmentDatasourceConfiguration.java | 71 +- .../NumPortalDatasourceConfiguration.java | 110 +- .../database/NumPortalFlywayConfig.java | 31 +- .../numportal/domain/dto/AqlCategoryDto.java | 8 +- .../highmed/numportal/domain/dto/AqlDto.java | 3 +- .../highmed/numportal/domain/dto/CardDto.java | 39 +- .../numportal/domain/dto/CohortAqlDto.java | 5 +- .../numportal/domain/dto/CohortDto.java | 3 +- .../numportal/domain/dto/CohortGroupDto.java | 9 +- .../numportal/domain/dto/CommentDto.java | 3 +- .../numportal/domain/dto/Language.java | 3 +- .../domain/dto/ManagerProjectDto.java | 7 +- .../domain/dto/NavigationItemDto.java | 2 +- .../numportal/domain/dto/ProjectDto.java | 15 +- ...ProjectViewTO.java => ProjectViewDto.java} | 21 +- .../numportal/domain/dto/SearchCriteria.java | 60 +- .../numportal/domain/dto/TemplateInfoDto.java | 4 +- .../domain/dto/TemplateMetadataDto.java | 4 +- .../domain/dto/TemplateSizeRequestDto.java | 7 +- .../numportal/domain/dto/ZarsInfoDto.java | 4 +- .../highmed/numportal/domain/model/Aql.java | 10 +- .../numportal/domain/model/AqlCategory.java | 14 +- .../numportal/domain/model/Cohort.java | 11 +- .../numportal/domain/model/CohortGroup.java | 24 +- .../numportal/domain/model/Comment.java | 15 +- .../numportal/domain/model/Content.java | 12 +- .../numportal/domain/model/EntityGroup.java | 4 +- .../numportal/domain/model/MailDomain.java | 16 +- .../numportal/domain/model/Operator.java | 4 +- .../numportal/domain/model/Organization.java | 17 +- .../numportal/domain/model/Project.java | 33 +- .../numportal/domain/model/ProjectStatus.java | 58 +- .../domain/model/ProjectTransition.java | 17 +- .../highmed/numportal/domain/model/Roles.java | 4 +- .../numportal/domain/model/Translation.java | 42 +- .../numportal/domain/model/admin/User.java | 8 +- .../domain/model/admin/UserDetails.java | 20 +- .../repository/AqlCategoryRepository.java | 11 +- .../domain/repository/AqlConverter.java | 4 +- .../domain/repository/AqlRepository.java | 1 + .../repository/CategorySetConverter.java | 9 +- .../repository/CohortGroupRepository.java | 1 + .../domain/repository/CohortRepository.java | 2 + .../domain/repository/CommentRepository.java | 4 +- .../repository/ContentItemRepository.java | 6 +- .../repository/CustomProjectRepository.java | 3 +- .../repository/MailDomainRepository.java | 11 +- .../repository/OrganizationRepository.java | 1 + .../domain/repository/ProjectRepository.java | 5 +- .../repository/ProjectRepositoryImpl.java | 121 +- .../ProjectTransitionRepository.java | 6 +- .../domain/repository/StringSetConverter.java | 6 +- .../repository/TranslationRepository.java | 5 +- .../repository/UserDetailsRepository.java | 1 + .../specification/AqlSpecification.java | 133 +- .../specification/BaseSpecification.java | 17 +- .../OrganizationSpecification.java | 55 +- .../specification/ProjectSpecification.java | 178 +- .../UserDetailsSpecification.java | 48 +- .../domain/templates/ExceptionsTemplate.java | 639 ++--- .../domain/validation/CohortValidator.java | 7 +- .../validation/TranslatedStringValidator.java | 4 +- .../domain/validation/ValidCohort.java | 9 +- .../validation/ValidTranslatedString.java | 16 +- .../numportal/events/DeactivateUserEvent.java | 26 +- .../listeners/DeactivateUserListener.java | 15 +- .../numportal/listeners/UserCacheInit.java | 17 +- .../highmed/numportal/mapper/AqlMapper.java | 3 +- .../numportal/mapper/CohortMapper.java | 7 +- .../numportal/mapper/CommentMapper.java | 1 + .../numportal/mapper/OrganizationMapper.java | 5 +- .../numportal/mapper/ProjectMapper.java | 1 + .../numportal/mapper/ProjectViewMapper.java | 20 +- .../numportal/mapper/TemplateMapper.java | 7 +- .../numportal/mapper/UserDetailsMapper.java | 1 + .../properties/ClamAVProperties.java | 47 +- .../PseudonymsPsnWorkflowProperties.java | 10 +- .../highmed/numportal/service/AqlService.java | 94 +- .../numportal/service/CohortService.java | 111 +- .../numportal/service/CommentService.java | 10 +- .../numportal/service/ContentService.java | 162 +- .../service/OrganizationService.java | 259 +- .../numportal/service/ProjectDocCreator.java | 18 +- .../numportal/service/ProjectService.java | 2190 +++++++++-------- .../numportal/service/TemplateService.java | 17 +- .../numportal/service/UserDetailsService.java | 128 +- .../numportal/service/UserService.java | 402 +-- .../numportal/service/atna/AtnaService.java | 13 +- .../service/ehrbase/CompositionFlattener.java | 24 +- .../CompositionResponseDataBuilder.java | 11 +- .../service/ehrbase/EhrBaseService.java | 58 +- .../service/ehrbase/ParameterService.java | 33 +- .../service/ehrbase/Pseudonymity.java | 31 +- .../service/ehrbase/ResponseFilter.java | 15 +- .../numportal/service/email/ZarsService.java | 15 +- .../exception/BadCredentialsException.java | 6 +- .../exception/BadRequestException.java | 30 +- .../service/exception/CustomException.java | 2 +- .../exception/CustomizedExceptionHandler.java | 661 ++--- .../exception/EntityNotFoundException.java | 2 +- .../service/exception/ExceptionDto.java | 5 +- .../service/exception/ForbiddenException.java | 12 +- .../exception/IllegalArgumentException.java | 18 +- .../exception/SameEntityExistsException.java | 2 +- .../exception/TokenIsNotValidException.java | 2 +- .../exception/UserUnauthorizedException.java | 29 +- ...nameNotFoundOrNoLongerActiveException.java | 6 +- .../service/exception/dto/ExceptionDto.java | 5 +- .../BadCredentialsException.java | 6 +- .../implementation/CustomException.java | 2 +- .../EntityNotFoundException.java | 2 +- .../SameEntityExistsException.java | 2 +- .../TokenIsNotValidException.java | 2 +- ...nameNotFoundOrNoLongerActiveException.java | 6 +- .../service/executors/AqlExecutor.java | 17 +- .../service/executors/CohortExecutor.java | 9 +- .../service/executors/CohortQueryLister.java | 1 + .../executors/SetOperationsService.java | 3 +- .../numportal/service/logger/ContextLog.java | 8 +- .../service/logger/ContextLogger.java | 100 +- .../service/metric/ProjectsMetrics.java | 118 +- .../service/metric/UsersMetrics.java | 85 +- .../notification/NotificationService.java | 7 +- .../notification/dto/NewUserNotification.java | 17 +- ...ewUserWithoutOrganizationNotification.java | 5 +- .../notification/dto/Notification.java | 3 +- .../ProjectApprovalRequestNotification.java | 3 +- .../dto/ProjectCloseNotification.java | 3 +- .../dto/ProjectStartNotification.java | 3 +- .../dto/ProjectStatusChangeNotification.java | 6 +- ...rojectStatusChangeRequestNotification.java | 55 +- .../account/AccountApprovalNotification.java | 4 +- .../AccountStatusChangedNotification.java | 91 +- .../OrganizationUpdateNotification.java | 3 +- .../dto/account/RolesUpdateNotification.java | 5 +- .../account/UserNameUpdateNotification.java | 3 +- .../numportal/service/policy/EhrPolicy.java | 11 +- .../service/policy/EuropeanConsentPolicy.java | 12 +- .../numportal/service/policy/Policy.java | 45 +- .../service/policy/ProjectPolicyService.java | 46 +- .../service/policy/TemplatesPolicy.java | 13 +- .../service/util/AqlQueryConstants.java | 14 +- .../web/config/ApplicationSecurity.java | 41 +- .../web/config/AuthorizationConverter.java | 15 +- .../numportal/web/config/CorsConfig.java | 4 +- .../web/config/FeignClientConfig.java | 1 + .../numportal/web/config/SwaggerConfig.java | 29 +- .../web/controller/AdminController.java | 91 +- .../web/controller/AqlController.java | 61 +- .../web/controller/CohortController.java | 35 +- .../web/controller/ContentController.java | 43 +- .../controller/NumAttachmentController.java | 141 +- .../controller/OrganizationController.java | 56 +- .../web/controller/ProfileController.java | 4 +- .../web/controller/ProjectController.java | 79 +- .../web/controller/TemplateController.java | 3 +- .../numportal/web/feign/KeycloakFeign.java | 14 +- .../web/feign/config/ClientTokenProvider.java | 4 +- .../web/feign/config/OAuth2Configuration.java | 5 +- .../service/FileScanServiceTest.java | 2 - .../specification/AqlSpecificationTest.java | 1 - .../ProjectSpecificationTest.java | 1 - .../tests/ProjectControllerIT.java | 18 +- .../mapper/ProjectViewMapperTest.java | 19 +- 183 files changed, 5319 insertions(+), 4119 deletions(-) create mode 100644 .config/checkstyle.xml create mode 100644 .config/intellij-codestyle.xml rename src/main/java/org/highmed/numportal/domain/dto/{ProjectViewTO.java => ProjectViewDto.java} (66%) diff --git a/.config/checkstyle.xml b/.config/checkstyle.xml new file mode 100644 index 00000000..6fdd771e --- /dev/null +++ b/.config/checkstyle.xml @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.config/intellij-codestyle.xml b/.config/intellij-codestyle.xml new file mode 100644 index 00000000..683b954c --- /dev/null +++ b/.config/intellij-codestyle.xml @@ -0,0 +1,168 @@ + + + + + \ No newline at end of file diff --git a/.github/workflows/build-for-development.yml b/.github/workflows/build-for-development.yml index 62768bb3..20989c93 100644 --- a/.github/workflows/build-for-development.yml +++ b/.github/workflows/build-for-development.yml @@ -24,6 +24,8 @@ jobs: java-version: '17' distribution: 'temurin' cache: 'maven' + - name: Run Checkstyle + run: mvn validate - name: Run unit tests run: mvn clean test - name: Run integration tests diff --git a/README.md b/README.md index 1d17ac07..3cbce225 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,46 @@ # Num-portal +The num-portal repository includes the backend for the Routine Data Platform (RDP). ## Building and running locally +To get the backend running first a postgres instance needs to be running. -1. Postgres should be up and running, instructions below +To start a local instance of PostgreSQL: + +``` +docker run --name postgres -e POSTGRES_PASSWORD=postgres -d -p 5432:5432 postgres +``` +Then you can start the project with the setting for the application-local.yml: In the root folder of the project, open cmd and run: 1. Build app: `mvn clean install` 2. Run: `spring_profiles_active=local mvn spring-boot:run` -## Database +After that you can visit the swagger website: -Start a local instance of PostgreSQL: +### Swagger -``` -docker run --name postgres -e POSTGRES_PASSWORD=postgres -d -p 5432:5432 postgres -``` +http://localhost:8090/swagger-ui/index.html -## Swagger +## Contributing -http://localhost:8090/swagger-ui/index.html +Pull requests are welcome. +For major changes, please open an issue first to discuss what you would like to change. +After that and the approval of HiGHmed e.V. (rdp-support@highmed.org) you can add the code in a Branch. +1. Create a branch named 'feature/name-of-branch' because of pipeline requirements +2. Check you code with our provided [checkstyle](/.config/checkstyle.xml) +3. Update the previously tests +4. PRs can only be merged once the [build_for_development pipeline](/.github/workflows/build-for-development.yml) has been successfully completed + +### Checkstyle + +To integrate checkstyle in your IDE (IntelliJ) you need the checkstyle-plugin. +And for intelliJ we include a [intellij-codestyle.xml](/.config/intellij-codestyle.xml) file + +#### IntelliJ Steps + +1. Add the file in Settings -> Editor -> Code Style -> Java +2. For the checkstyle-plugin you can add the file [checkstyle](/.config/checkstyle.xml) under Settings -> Tools -> Checkstyle -> Configuration File ## License diff --git a/pom.xml b/pom.xml index 37e02f82..45c62fa1 100644 --- a/pom.xml +++ b/pom.xml @@ -466,6 +466,33 @@ full + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.5.0 + + + com.puppycrawl.tools + checkstyle + 10.18.2 + + + + .config/checkstyle.xml + true + true + true + + + + validate + validate + + check + + + + diff --git a/src/main/java/org/highmed/numportal/NumPortalApplication.java b/src/main/java/org/highmed/numportal/NumPortalApplication.java index d2e46fd7..04be6b67 100644 --- a/src/main/java/org/highmed/numportal/NumPortalApplication.java +++ b/src/main/java/org/highmed/numportal/NumPortalApplication.java @@ -1,8 +1,8 @@ package org.highmed.numportal; - import org.highmed.numportal.service.atna.AtnaProperties; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; diff --git a/src/main/java/org/highmed/numportal/attachment/AttachmentRepository.java b/src/main/java/org/highmed/numportal/attachment/AttachmentRepository.java index da735264..23a87ec9 100644 --- a/src/main/java/org/highmed/numportal/attachment/AttachmentRepository.java +++ b/src/main/java/org/highmed/numportal/attachment/AttachmentRepository.java @@ -8,19 +8,19 @@ public interface AttachmentRepository { - List getAttachments(); + List getAttachments(); - void saveAttachment(AttachmentDto model); + void saveAttachment(AttachmentDto model); - void deleteAttachment(Long id); + void deleteAttachment(Long id); - Optional findById(Long id); + Optional findById(Long id); - void updateReviewCounterByProjectId(Long projectId); + void updateReviewCounterByProjectId(Long projectId); - Optional findByIdAndProjectId(Long id, Long projectId); + Optional findByIdAndProjectId(Long id, Long projectId); - List findAttachmentsByProjectId(Long projectId); + List findAttachmentsByProjectId(Long projectId); - void deleteByProjectId(Long projectId); + void deleteByProjectId(Long projectId); } diff --git a/src/main/java/org/highmed/numportal/attachment/domain/dto/AttachmentDto.java b/src/main/java/org/highmed/numportal/attachment/domain/dto/AttachmentDto.java index 19da15ea..65e4a7a8 100644 --- a/src/main/java/org/highmed/numportal/attachment/domain/dto/AttachmentDto.java +++ b/src/main/java/org/highmed/numportal/attachment/domain/dto/AttachmentDto.java @@ -16,24 +16,24 @@ @AllArgsConstructor public class AttachmentDto { - @Schema(accessMode = Schema.AccessMode.READ_ONLY) - private Long id; + @Schema(accessMode = Schema.AccessMode.READ_ONLY) + private Long id; - private String name; + private String name; - private String description; + private String description; - private String type; + private String type; - private byte[] content; + private byte[] content; - private Long projectId; + private Long projectId; - @Schema(accessMode = Schema.AccessMode.READ_ONLY) - private OffsetDateTime uploadDate; + @Schema(accessMode = Schema.AccessMode.READ_ONLY) + private OffsetDateTime uploadDate; - private String authorId; + private String authorId; - @Schema(accessMode = Schema.AccessMode.READ_ONLY) - private int reviewCounter; + @Schema(accessMode = Schema.AccessMode.READ_ONLY) + private int reviewCounter; } diff --git a/src/main/java/org/highmed/numportal/attachment/domain/dto/LightAttachmentDto.java b/src/main/java/org/highmed/numportal/attachment/domain/dto/LightAttachmentDto.java index a7fe2821..6a9d3575 100644 --- a/src/main/java/org/highmed/numportal/attachment/domain/dto/LightAttachmentDto.java +++ b/src/main/java/org/highmed/numportal/attachment/domain/dto/LightAttachmentDto.java @@ -1,15 +1,14 @@ package org.highmed.numportal.attachment.domain.dto; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonInclude; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.web.multipart.MultipartFile; +import java.util.List; + @Data @Builder @JsonInclude(JsonInclude.Include.NON_NULL) @@ -17,8 +16,8 @@ @AllArgsConstructor public class LightAttachmentDto { - private List description; + private List description; - private MultipartFile[] files; + private MultipartFile[] files; } diff --git a/src/main/java/org/highmed/numportal/attachment/domain/model/Attachment.java b/src/main/java/org/highmed/numportal/attachment/domain/model/Attachment.java index 3cfee68a..c79c1d26 100644 --- a/src/main/java/org/highmed/numportal/attachment/domain/model/Attachment.java +++ b/src/main/java/org/highmed/numportal/attachment/domain/model/Attachment.java @@ -1,6 +1,10 @@ package org.highmed.numportal.attachment.domain.model; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -16,36 +20,36 @@ @AllArgsConstructor public class Attachment implements Serializable { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - private String name; + private String name; - private String description; + private String description; - @Column(name = "upload_date", nullable = false) - private OffsetDateTime uploadDate; + @Column(name = "upload_date", nullable = false) + private OffsetDateTime uploadDate; - private String type; + private String type; - private byte[] content; + private byte[] content; - @Column(name = "author_id") - private String authorId; + @Column(name = "author_id") + private String authorId; - @Column(name = "review_counter", nullable = false, columnDefinition = "INTEGER DEFAULT 0") - private int reviewCounter; + @Column(name = "review_counter", nullable = false, columnDefinition = "INTEGER DEFAULT 0") + private int reviewCounter; - @Column(name = "project_id", nullable = false, columnDefinition = "BIGINT DEFAULT 0") - private Long projectId; + @Column(name = "project_id", nullable = false, columnDefinition = "BIGINT DEFAULT 0") + private Long projectId; - public Attachment(Long id, String name, String description, OffsetDateTime uploadDate, int reviewCounter) { - this.id = id; - this.name = name; - this.description = description; - this.uploadDate = uploadDate; - this.reviewCounter = reviewCounter; - } + public Attachment(Long id, String name, String description, OffsetDateTime uploadDate, int reviewCounter) { + this.id = id; + this.name = name; + this.description = description; + this.uploadDate = uploadDate; + this.reviewCounter = reviewCounter; + } } diff --git a/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryImpl.java b/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryImpl.java index be29b808..fc59916c 100644 --- a/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryImpl.java +++ b/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryImpl.java @@ -1,10 +1,11 @@ package org.highmed.numportal.attachment.domain.repository; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.highmed.numportal.attachment.AttachmentRepository; import org.highmed.numportal.attachment.domain.dto.AttachmentDto; import org.highmed.numportal.attachment.domain.model.Attachment; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -19,59 +20,59 @@ @ConditionalOnProperty(prefix = "num", name = "enableAttachmentDatabase", havingValue = "true") public class AttachmentRepositoryImpl implements AttachmentRepository { - private final AttachmentRepositoryJpa attachmentRepositoryJpa; + private final AttachmentRepositoryJpa attachmentRepositoryJpa; - @Override - public List getAttachments() { - return attachmentRepositoryJpa.getAttachments(); - } + @Override + public List getAttachments() { + return attachmentRepositoryJpa.getAttachments(); + } - @Override - public void saveAttachment(AttachmentDto model) { - Attachment entity = Attachment.builder() - .name(model.getName()) - .description(model.getDescription()) - .authorId(model.getAuthorId()) - .projectId(model.getProjectId()) - .uploadDate(OffsetDateTime.now()) - .type(model.getType()) - .content(model.getContent()) - .projectId(model.getProjectId()) - .build(); - entity = attachmentRepositoryJpa.save(entity); - log.info("New attachment with id {} and name {} saved by {} ", entity.getId(), entity.getName(), entity.getAuthorId()); - } + @Override + public void saveAttachment(AttachmentDto model) { + Attachment entity = Attachment.builder() + .name(model.getName()) + .description(model.getDescription()) + .authorId(model.getAuthorId()) + .projectId(model.getProjectId()) + .uploadDate(OffsetDateTime.now()) + .type(model.getType()) + .content(model.getContent()) + .projectId(model.getProjectId()) + .build(); + entity = attachmentRepositoryJpa.save(entity); + log.info("New attachment with id {} and name {} saved by {} ", entity.getId(), entity.getName(), entity.getAuthorId()); + } - @Override - public void deleteAttachment(Long id) { - attachmentRepositoryJpa.deleteById(id); - } + @Override + public void deleteAttachment(Long id) { + attachmentRepositoryJpa.deleteById(id); + } - @Override - public Optional findById(Long id) { - return attachmentRepositoryJpa.findById(id); - } + @Override + public Optional findById(Long id) { + return attachmentRepositoryJpa.findById(id); + } - @Override - @Transactional(transactionManager = "attachmentTransactionManager") - public void updateReviewCounterByProjectId(Long projectId) { - attachmentRepositoryJpa.updateReviewCounterByProjectId(projectId); - } + @Override + @Transactional(transactionManager = "attachmentTransactionManager") + public void updateReviewCounterByProjectId(Long projectId) { + attachmentRepositoryJpa.updateReviewCounterByProjectId(projectId); + } - @Override - public Optional findByIdAndProjectId(Long id, Long projectId) { - return attachmentRepositoryJpa.findByIdAndProjectId(id, projectId); - } + @Override + public Optional findByIdAndProjectId(Long id, Long projectId) { + return attachmentRepositoryJpa.findByIdAndProjectId(id, projectId); + } - @Override - public List findAttachmentsByProjectId(Long projectId) { - return attachmentRepositoryJpa.findAttachmentsByProjectId(projectId); - } + @Override + public List findAttachmentsByProjectId(Long projectId) { + return attachmentRepositoryJpa.findAttachmentsByProjectId(projectId); + } - @Override - public void deleteByProjectId(Long projectId) { - attachmentRepositoryJpa.deleteByProjectId(projectId); - } + @Override + public void deleteByProjectId(Long projectId) { + attachmentRepositoryJpa.deleteByProjectId(projectId); + } } diff --git a/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryJpa.java b/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryJpa.java index 93aec8d7..0fc050ce 100644 --- a/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryJpa.java +++ b/src/main/java/org/highmed/numportal/attachment/domain/repository/AttachmentRepositoryJpa.java @@ -1,6 +1,7 @@ package org.highmed.numportal.attachment.domain.repository; import org.highmed.numportal.attachment.domain.model.Attachment; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -13,22 +14,21 @@ @Repository public interface AttachmentRepositoryJpa extends JpaRepository { - @Query("SELECT new Attachment (atc.id, atc.name, atc.description, atc.uploadDate, atc.reviewCounter) " + - "FROM Attachment atc ") - List getAttachments(); + @Query("SELECT new Attachment (atc.id, atc.name, atc.description, atc.uploadDate, atc.reviewCounter) FROM Attachment atc ") + List getAttachments(); - @Modifying - @Query("UPDATE Attachment atch SET atch.reviewCounter = atch.reviewCounter + 1 WHERE atch.projectId = :projectId") - void updateReviewCounterByProjectId(@Param("projectId") Long projectId); + @Modifying + @Query("UPDATE Attachment atch SET atch.reviewCounter = atch.reviewCounter + 1 WHERE atch.projectId = :projectId") + void updateReviewCounterByProjectId(@Param("projectId") Long projectId); - Optional findByIdAndProjectId(Long id, Long projectId); + Optional findByIdAndProjectId(Long id, Long projectId); - @Query("SELECT new Attachment (atc.id, atc.name, atc.description, atc.uploadDate, atc.reviewCounter) " + - "FROM Attachment atc " + - "WHERE atc.projectId = :projectId") - List findAttachmentsByProjectId(@Param("projectId") Long projectId); + @Query("SELECT new Attachment (atc.id, atc.name, atc.description, atc.uploadDate, atc.reviewCounter) " + + "FROM Attachment atc " + + "WHERE atc.projectId = :projectId") + List findAttachmentsByProjectId(@Param("projectId") Long projectId); - @Modifying - @Query("DELETE FROM Attachment atch WHERE atch.projectId = :projectId") - void deleteByProjectId(@Param("projectId") Long projectId); + @Modifying + @Query("DELETE FROM Attachment atch WHERE atch.projectId = :projectId") + void deleteByProjectId(@Param("projectId") Long projectId); } diff --git a/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java b/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java index 2c93ed33..253c7db0 100644 --- a/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java +++ b/src/main/java/org/highmed/numportal/attachment/service/AttachmentService.java @@ -1,17 +1,19 @@ package org.highmed.numportal.attachment.service; -import org.highmed.numportal.domain.model.Project; -import org.highmed.numportal.domain.model.ProjectStatus; -import org.highmed.numportal.domain.templates.ExceptionsTemplate; import org.highmed.numportal.attachment.AttachmentRepository; import org.highmed.numportal.attachment.domain.dto.AttachmentDto; import org.highmed.numportal.attachment.domain.dto.LightAttachmentDto; import org.highmed.numportal.attachment.domain.model.Attachment; +import org.highmed.numportal.domain.model.Project; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.templates.ExceptionsTemplate; import org.highmed.numportal.service.ProjectService; import org.highmed.numportal.service.exception.BadRequestException; import org.highmed.numportal.service.exception.ForbiddenException; import org.highmed.numportal.service.exception.ResourceNotFound; import org.highmed.numportal.web.controller.NumAttachmentController; + +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; import org.modelmapper.ModelMapper; @@ -22,7 +24,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import jakarta.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Objects; @@ -32,7 +33,15 @@ import static java.util.Objects.isNull; import static java.util.Objects.nonNull; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ATTACHMENT_LIMIT_REACHED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.DESCRIPTION_TOO_LONG; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.DOCUMENT_TYPE_MISMATCH; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_FILE_MISSING_CONTENT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PDF_FILES_ARE_NOT_ATTACHED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PDF_FILE_SIZE_EXCEEDED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.WRONG_PROJECT_STATUS; @Service @Transactional(value = "attachmentTransactionManager") @@ -40,192 +49,193 @@ @ConditionalOnProperty(prefix = "num", name = "enableAttachmentDatabase", havingValue = "true") public class AttachmentService { - private final AttachmentRepository attachmentRepository; - private final ProjectService projectService; - - @Value("${num.pdfFileSize:10485760}") - private long pdfFileSize; - - @Value("${num.fileVirusScanEnabled}") - private boolean fileVirusScanEnabled; - private final FileScanService fileScanService; - - private final ModelMapper modelMapper; - - public AttachmentService(AttachmentRepository attachmentRepository, @Lazy ProjectService projectService, FileScanService fileScanService, ModelMapper modelMapper) { - this.attachmentRepository = attachmentRepository; - this.projectService = projectService; - this.fileScanService = fileScanService; - this.modelMapper = modelMapper; - } - - public List listAttachments() { - return attachmentRepository.getAttachments(); - } - - public Attachment getAttachmentById(Long id) { - return attachmentRepository - .findById(id) - .orElseThrow(() -> new ResourceNotFound(AttachmentService.class, ExceptionsTemplate.ATTACHMENT_NOT_FOUND, - String.format(ExceptionsTemplate.ATTACHMENT_NOT_FOUND, id))); - } - - private AttachmentDto buildModel(MultipartFile file, String description, String loggedInUserId, Long projectId) throws IOException { - return AttachmentDto.builder() - .name(file.getOriginalFilename()) - .description(description) - .authorId(loggedInUserId) - .projectId(projectId) - .type(file.getContentType()) - .content(file.getBytes()) - .build(); - } - - private void validate(MultipartFile file) throws IOException { - if (file.isEmpty()) { - log.error("File content is missing for uploaded file {}", file.getOriginalFilename()); - throw new BadRequestException(AttachmentService.class, INVALID_FILE_MISSING_CONTENT); - } - if (Boolean.TRUE.equals(fileVirusScanEnabled)) { - fileScanService.virusScan(file); - } else { - log.warn("File scan for virus/malware is not enabled"); - } - - if (!Objects.requireNonNull(file.getOriginalFilename()).toLowerCase().endsWith(".pdf") || !checkIsPDFContent(file.getBytes())){ - log.error("Invalid document type received for {}", file.getOriginalFilename()); - throw new BadRequestException(NumAttachmentController.class, DOCUMENT_TYPE_MISMATCH); - } - if (file.getSize() > pdfFileSize){ - throw new BadRequestException(NumAttachmentController.class, - String.format(PDF_FILE_SIZE_EXCEEDED, pdfFileSize/1048576, file.getSize()/1048576)); - } - } - - private boolean checkIsPDFContent(byte[] data) { - if(data.length < 5) { - return false; - } - - //%PDF- - if(!((data[0] == 0x25) &&(data[1] == 0x50)&&(data[2] == 0x44)&&(data[3] == 0x46)&&(data[4] == 0x2D))){ - return false; - } - - // version is 1. - if(!(data[5]==0x31 && data[6]==0x2E )){ - return false; - } - return true; - } - - public void deleteById(Long id, String loggedInUserId) { - Optional attachment = attachmentRepository.findById(id); - if (attachment.isPresent()) { - attachmentRepository.deleteAttachment(id); - log.info("Attachment {} was deleted by user {}", id, loggedInUserId); - } else { - log.error("Could not delete attachment {} because was not found", id); - throw new ResourceNotFound(AttachmentService.class, ExceptionsTemplate.ATTACHMENT_NOT_FOUND, - String.format(ExceptionsTemplate.ATTACHMENT_NOT_FOUND, id)); - } - } - @Transactional(rollbackFor = ForbiddenException.class) - public void updateStatusChangeCounter(Long projectId) { - log.info("Update counter for project's {} attachments ", projectId); - attachmentRepository.updateReviewCounterByProjectId(projectId); - } - - public void deleteAttachments(Set attachmentsId, Long projectId, String loggedInUser, Boolean userIsApprover) { - log.info("Receive request to delete attachments {} from project {} by loggedInUser {} ", attachmentsId, projectId, loggedInUser); - for(Long attachmentId : attachmentsId) { - Optional attachment = attachmentRepository.findByIdAndProjectId(attachmentId, projectId); - if(attachment.isEmpty()) { - log.info("Attachment is already deleted, attachment: {}, project: {}", attachmentId, projectId); - return; - } - Attachment currentAttachment = attachment.get(); - if (Boolean.FALSE.equals(userIsApprover) && currentAttachment.getReviewCounter() > 1) { - log.error("Not allowed to delete attachment with id {} and name {} because it was at least once in review {}", attachmentId, currentAttachment.getName(), currentAttachment.getReviewCounter()); - throw new ForbiddenException(AttachmentService.class, CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER, - String.format(CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER, currentAttachment.getName())); - } - attachmentRepository.deleteAttachment(attachmentId); - } - log.info("Attachments with id {} from project {} deleted by user {} ", attachmentsId, projectId, loggedInUser); - } - - public List findAttachmentsByProjectId(Long projectId) { - log.info("Retrieve attachments for project {}", projectId); - List attachments = attachmentRepository.findAttachmentsByProjectId(projectId); - return attachments.stream() - .map(attachment -> modelMapper.map(attachment, AttachmentDto.class)) - .collect(Collectors.toList()); - } - - public void saveAttachments(Long projectId, String loggedInUserId, @Valid LightAttachmentDto lightDto, boolean isNewProject) throws IOException { - if(isNewProject) { - checkConsistency(lightDto, projectId, ProjectStatus.DRAFT); - } else { - Project project = projectService.getProjectById(loggedInUserId, projectId) - .orElseThrow(() -> new ResourceNotFound(AttachmentService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); - - checkConsistency(lightDto, projectId, project.getStatus()); - } - - int i = 0; - for (MultipartFile file: lightDto.getFiles()) { - if(nonNull(lightDto.getDescription()) && (lightDto.getDescription().size() > i)) { - saveAttachment(file, lightDto.getDescription().get(i++), loggedInUserId, projectId); - } else { - saveAttachment(file, null, loggedInUserId, projectId); - } - } - } - - public List getAttachmentsBy(Long projectId) { - return attachmentRepository.findAttachmentsByProjectId(projectId); - } - - public void deleteAllProjectAttachments(Long projectId, String loggedInUser) { - log.info("Receive request to delete all attachments from project {} by loggedInUser {} ", projectId, loggedInUser); - if (!projectService.exists(projectId)) { - log.error("Project not found {} ", projectId); - throw new ResourceNotFound(AttachmentService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId)); - } - attachmentRepository.deleteByProjectId(projectId); - } - - private boolean isInsertable(ProjectStatus status) { - return (ProjectStatus.DRAFT.equals(status) || ProjectStatus.CHANGE_REQUEST.equals(status)); - } - - private void checkConsistency(LightAttachmentDto lightDto, Long projectId, ProjectStatus status) { - if(isNull(lightDto.getFiles()) || lightDto.getFiles().length == 1 && Objects.equals(lightDto.getFiles()[0].getOriginalFilename(), Strings.EMPTY)){ - throw new ResourceNotFound(AttachmentService.class, PDF_FILES_ARE_NOT_ATTACHED); - } - - if(nonNull(lightDto.getDescription())) { - for (String description : lightDto.getDescription()) { - if (description.length() > 255) { - throw new BadRequestException(AttachmentService.class, DESCRIPTION_TOO_LONG, String.format(DESCRIPTION_TOO_LONG, description)); - } - } - } - - if(!isInsertable(status)){ - throw new BadRequestException(AttachmentService.class, WRONG_PROJECT_STATUS, String.format(WRONG_PROJECT_STATUS, status)); - } - - if(attachmentRepository.findAttachmentsByProjectId(projectId).size() + lightDto.getFiles().length > 10){ - throw new BadRequestException(AttachmentService.class, ATTACHMENT_LIMIT_REACHED); - } - } - - public void saveAttachment(MultipartFile file, String description, String loggedInUserId, Long projectId) throws IOException { - validate(file); - AttachmentDto model = buildModel(file, description, loggedInUserId, projectId); - attachmentRepository.saveAttachment(model); - } + private final AttachmentRepository attachmentRepository; + private final ProjectService projectService; + private final FileScanService fileScanService; + private final ModelMapper modelMapper; + @Value("${num.pdfFileSize:10485760}") + private long pdfFileSize; + @Value("${num.fileVirusScanEnabled}") + private boolean fileVirusScanEnabled; + + public AttachmentService(AttachmentRepository attachmentRepository, @Lazy ProjectService projectService, FileScanService fileScanService, + ModelMapper modelMapper) { + this.attachmentRepository = attachmentRepository; + this.projectService = projectService; + this.fileScanService = fileScanService; + this.modelMapper = modelMapper; + } + + public List listAttachments() { + return attachmentRepository.getAttachments(); + } + + public Attachment getAttachmentById(Long id) { + return attachmentRepository + .findById(id) + .orElseThrow(() -> new ResourceNotFound(AttachmentService.class, ExceptionsTemplate.ATTACHMENT_NOT_FOUND, + String.format(ExceptionsTemplate.ATTACHMENT_NOT_FOUND, id))); + } + + private AttachmentDto buildModel(MultipartFile file, String description, String loggedInUserId, Long projectId) throws IOException { + return AttachmentDto.builder() + .name(file.getOriginalFilename()) + .description(description) + .authorId(loggedInUserId) + .projectId(projectId) + .type(file.getContentType()) + .content(file.getBytes()) + .build(); + } + + private void validate(MultipartFile file) throws IOException { + if (file.isEmpty()) { + log.error("File content is missing for uploaded file {}", file.getOriginalFilename()); + throw new BadRequestException(AttachmentService.class, INVALID_FILE_MISSING_CONTENT); + } + if (Boolean.TRUE.equals(fileVirusScanEnabled)) { + fileScanService.virusScan(file); + } else { + log.warn("File scan for virus/malware is not enabled"); + } + + if (!Objects.requireNonNull(file.getOriginalFilename()).toLowerCase().endsWith(".pdf") || !checkIsPdfContent(file.getBytes())) { + log.error("Invalid document type received for {}", file.getOriginalFilename()); + throw new BadRequestException(NumAttachmentController.class, DOCUMENT_TYPE_MISMATCH); + } + if (file.getSize() > pdfFileSize) { + throw new BadRequestException( + NumAttachmentController.class, + String.format(PDF_FILE_SIZE_EXCEEDED, pdfFileSize / 1048576, file.getSize() / 1048576)); + } + } + + private boolean checkIsPdfContent(byte[] data) { + if (data.length < 5) { + return false; + } + + //%PDF- + if (!((data[0] == 0x25) && (data[1] == 0x50) && (data[2] == 0x44) && (data[3] == 0x46) && (data[4] == 0x2D))) { + return false; + } + + // version is 1. + return data[5] == 0x31 && data[6] == 0x2E; + } + + public void deleteById(Long id, String loggedInUserId) { + Optional attachment = attachmentRepository.findById(id); + if (attachment.isPresent()) { + attachmentRepository.deleteAttachment(id); + log.info("Attachment {} was deleted by user {}", id, loggedInUserId); + } else { + log.error("Could not delete attachment {} because was not found", id); + throw new ResourceNotFound(AttachmentService.class, ExceptionsTemplate.ATTACHMENT_NOT_FOUND, + String.format(ExceptionsTemplate.ATTACHMENT_NOT_FOUND, id)); + } + } + + @Transactional(rollbackFor = ForbiddenException.class) + public void updateStatusChangeCounter(Long projectId) { + log.info("Update counter for project's {} attachments ", projectId); + attachmentRepository.updateReviewCounterByProjectId(projectId); + } + + public void deleteAttachments(Set attachmentsId, Long projectId, String loggedInUser, Boolean userIsApprover) { + log.info("Receive request to delete attachments {} from project {} by loggedInUser {} ", attachmentsId, projectId, loggedInUser); + for (Long attachmentId : attachmentsId) { + Optional attachment = attachmentRepository.findByIdAndProjectId(attachmentId, projectId); + if (attachment.isEmpty()) { + log.info("Attachment is already deleted, attachment: {}, project: {}", attachmentId, projectId); + return; + } + Attachment currentAttachment = attachment.get(); + if (Boolean.FALSE.equals(userIsApprover) && currentAttachment.getReviewCounter() > 1) { + log.error( + "Not allowed to delete attachment with id {} and name {} because it was at least once in review {}", attachmentId, + currentAttachment.getName(), currentAttachment.getReviewCounter()); + throw new ForbiddenException(AttachmentService.class, CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER, + String.format(CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER, currentAttachment.getName())); + } + attachmentRepository.deleteAttachment(attachmentId); + } + log.info("Attachments with id {} from project {} deleted by user {} ", attachmentsId, projectId, loggedInUser); + } + + public List findAttachmentsByProjectId(Long projectId) { + log.info("Retrieve attachments for project {}", projectId); + List attachments = attachmentRepository.findAttachmentsByProjectId(projectId); + return attachments.stream() + .map(attachment -> modelMapper.map(attachment, AttachmentDto.class)) + .collect(Collectors.toList()); + } + + public void saveAttachments(Long projectId, String loggedInUserId, @Valid LightAttachmentDto lightDto, boolean isNewProject) throws IOException { + if (isNewProject) { + checkConsistency(lightDto, projectId, ProjectStatus.DRAFT); + } else { + Project project = projectService.getProjectById(loggedInUserId, projectId) + .orElseThrow(() -> new ResourceNotFound(AttachmentService.class, PROJECT_NOT_FOUND, + String.format(PROJECT_NOT_FOUND, projectId))); + + checkConsistency(lightDto, projectId, project.getStatus()); + } + + int i = 0; + for (MultipartFile file : lightDto.getFiles()) { + if (nonNull(lightDto.getDescription()) && (lightDto.getDescription().size() > i)) { + saveAttachment(file, lightDto.getDescription().get(i++), loggedInUserId, projectId); + } else { + saveAttachment(file, null, loggedInUserId, projectId); + } + } + } + + public List getAttachmentsBy(Long projectId) { + return attachmentRepository.findAttachmentsByProjectId(projectId); + } + + public void deleteAllProjectAttachments(Long projectId, String loggedInUser) { + log.info("Receive request to delete all attachments from project {} by loggedInUser {} ", projectId, loggedInUser); + if (!projectService.exists(projectId)) { + log.error("Project not found {} ", projectId); + throw new ResourceNotFound(AttachmentService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId)); + } + attachmentRepository.deleteByProjectId(projectId); + } + + private boolean isInsertable(ProjectStatus status) { + return (ProjectStatus.DRAFT.equals(status) || ProjectStatus.CHANGE_REQUEST.equals(status)); + } + + private void checkConsistency(LightAttachmentDto lightDto, Long projectId, ProjectStatus status) { + if (isNull(lightDto.getFiles()) || lightDto.getFiles().length == 1 && Objects.equals( + lightDto.getFiles()[0].getOriginalFilename(), Strings.EMPTY)) { + throw new ResourceNotFound(AttachmentService.class, PDF_FILES_ARE_NOT_ATTACHED); + } + + if (nonNull(lightDto.getDescription())) { + for (String description : lightDto.getDescription()) { + if (description.length() > 255) { + throw new BadRequestException(AttachmentService.class, DESCRIPTION_TOO_LONG, String.format(DESCRIPTION_TOO_LONG, description)); + } + } + } + + if (!isInsertable(status)) { + throw new BadRequestException(AttachmentService.class, WRONG_PROJECT_STATUS, String.format(WRONG_PROJECT_STATUS, status)); + } + + if (attachmentRepository.findAttachmentsByProjectId(projectId).size() + lightDto.getFiles().length > 10) { + throw new BadRequestException(AttachmentService.class, ATTACHMENT_LIMIT_REACHED); + } + } + + public void saveAttachment(MultipartFile file, String description, String loggedInUserId, Long projectId) throws IOException { + validate(file); + AttachmentDto model = buildModel(file, description, loggedInUserId, projectId); + attachmentRepository.saveAttachment(model); + } } diff --git a/src/main/java/org/highmed/numportal/attachment/service/ClamAVService.java b/src/main/java/org/highmed/numportal/attachment/service/ClamAVService.java index da18004d..396d607c 100644 --- a/src/main/java/org/highmed/numportal/attachment/service/ClamAVService.java +++ b/src/main/java/org/highmed/numportal/attachment/service/ClamAVService.java @@ -2,12 +2,17 @@ import org.highmed.numportal.properties.ClamAVProperties; import org.highmed.numportal.service.exception.SystemException; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Component; -import java.io.*; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; @@ -18,91 +23,90 @@ @Component public class ClamAVService { - private final ClamAVProperties clamAVProperties; - - private static final int CHUNK_SIZE = 4096; - private static final String INSTREAM = "zINSTREAM\0"; - - private static final String INSTREAM_SIZE_LIMIT_RESPONSE = "INSTREAM size limit exceeded."; - - public boolean ping() { - try (Socket s = new Socket()) { - s.connect(new InetSocketAddress(clamAVProperties.getHost(), clamAVProperties.getPort()), clamAVProperties.getConnectionTimeout()); - log.info("ClamAV is up"); - return true; - } catch (IOException e) { - log.error("Failed to ping ClamAV at {}:{}", clamAVProperties.getHost(), clamAVProperties.getPort(), e); - return false; - } + private static final int CHUNK_SIZE = 4096; + private static final String INSTREAM = "zINSTREAM\0"; + private static final String INSTREAM_SIZE_LIMIT_RESPONSE = "INSTREAM size limit exceeded."; + private final ClamAVProperties clamAVProperties; + + public boolean ping() { + try (Socket s = new Socket()) { + s.connect(new InetSocketAddress(clamAVProperties.getHost(), clamAVProperties.getPort()), clamAVProperties.getConnectionTimeout()); + log.info("ClamAV is up"); + return true; + } catch (IOException e) { + log.error("Failed to ping ClamAV at {}:{}", clamAVProperties.getHost(), clamAVProperties.getPort(), e); + return false; } - - public String scan(InputStream fileContent) { - try (Socket socket = new Socket(clamAVProperties.getHost(), clamAVProperties.getPort())) { - socket.setSoTimeout(clamAVProperties.getReadTimeout()); - - try (OutputStream out = new BufferedOutputStream(socket.getOutputStream())) { - //send INSTREAM command - out.write(INSTREAM.getBytes(StandardCharsets.UTF_8)); - out.flush(); - log.debug("Socket information = {} connected = {} ", socket, socket.isConnected()); - - byte[] buffer = new byte[CHUNK_SIZE]; - try (InputStream ins = socket.getInputStream()) { - int read = fileContent.read(buffer); - - while (read >= 0) { - byte[] chunkSize = ByteBuffer.allocate(4).putInt(read).array(); - // send chunk length - out.write(chunkSize); - // send the chunk of data corresponding to chunk length - out.write(buffer, 0, read); - - if (ins.available() > 0) { - byte[] reply = readAll(ins); - String replyResponse = new String(reply, StandardCharsets.UTF_8); - log.debug("Scan aborted. Server reply response {} ", replyResponse); - if (isSizeLimitException(replyResponse)) { - throw new SystemException(ClamAVService.class, "Scan aborted due to INSTREAM size limit exceeded. Check the value for ClamAV property 'StreamMaxLength'", - "Scan aborted because data size limit exceeded"); - } - throw new IOException("ClamAV scan aborted. Reply from server: " + replyResponse); - } - read = fileContent.read(buffer); - } - - // send 0 to mark end of stream - out.write(new byte[]{0, 0, 0, 0}); - out.flush(); - - String scanResult = new String(IOUtils.toByteArray(ins)); - log.debug("scan result {}", scanResult); - return scanResult; - } + } + + public String scan(InputStream fileContent) { + try (Socket socket = new Socket(clamAVProperties.getHost(), clamAVProperties.getPort())) { + socket.setSoTimeout(clamAVProperties.getReadTimeout()); + + try (OutputStream out = new BufferedOutputStream(socket.getOutputStream())) { + //send INSTREAM command + out.write(INSTREAM.getBytes(StandardCharsets.UTF_8)); + out.flush(); + log.debug("Socket information = {} connected = {} ", socket, socket.isConnected()); + + byte[] buffer = new byte[CHUNK_SIZE]; + try (InputStream ins = socket.getInputStream()) { + int read = fileContent.read(buffer); + + while (read >= 0) { + byte[] chunkSize = ByteBuffer.allocate(4).putInt(read).array(); + // send chunk length + out.write(chunkSize); + // send the chunk of data corresponding to chunk length + out.write(buffer, 0, read); + + if (ins.available() > 0) { + byte[] reply = readAll(ins); + String replyResponse = new String(reply, StandardCharsets.UTF_8); + log.debug("Scan aborted. Server reply response {} ", replyResponse); + if (isSizeLimitException(replyResponse)) { + throw new SystemException( + ClamAVService.class, "Scan aborted due to INSTREAM size limit exceeded. Check the value for ClamAV property 'StreamMaxLength'", + "Scan aborted because data size limit exceeded"); + } + throw new IOException("ClamAV scan aborted. Reply from server: " + replyResponse); } - } catch (IOException e) { - log.error("Error while scanning ", e); - throw new SystemException(ClamAVService.class, "Error during scanning", "Error during scanning"); - } - } - - private byte[] readAll(InputStream is) throws IOException { - ByteArrayOutputStream temp = new ByteArrayOutputStream(); - byte[] buf = new byte[1024]; - int read; - do { - read = is.read(buf); - temp.write(buf, 0, read); - } while ((read > 0) && (is.available() > 0)); - return temp.toByteArray(); - } + read = fileContent.read(buffer); + } - public boolean isScannedFileSafe(String reply) { - return reply.contains("stream: OK") && - !reply.contains("FOUND") && - !reply.contains("ERROR"); - } + // send 0 to mark end of stream + out.write(new byte[]{0, 0, 0, 0}); + out.flush(); - public boolean isSizeLimitException(String reply) { - return reply.startsWith(INSTREAM_SIZE_LIMIT_RESPONSE); + String scanResult = new String(IOUtils.toByteArray(ins)); + log.debug("scan result {}", scanResult); + return scanResult; + } + } + } catch (IOException e) { + log.error("Error while scanning ", e); + throw new SystemException(ClamAVService.class, "Error during scanning", "Error during scanning"); } + } + + private byte[] readAll(InputStream is) throws IOException { + ByteArrayOutputStream temp = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int read; + do { + read = is.read(buf); + temp.write(buf, 0, read); + } while ((read > 0) && (is.available() > 0)); + return temp.toByteArray(); + } + + public boolean isScannedFileSafe(String reply) { + return reply.contains("stream: OK") + && !reply.contains("FOUND") + && !reply.contains("ERROR"); + } + + public boolean isSizeLimitException(String reply) { + return reply.startsWith(INSTREAM_SIZE_LIMIT_RESPONSE); + } } diff --git a/src/main/java/org/highmed/numportal/attachment/service/FileScanService.java b/src/main/java/org/highmed/numportal/attachment/service/FileScanService.java index 7d268e2e..d6584949 100644 --- a/src/main/java/org/highmed/numportal/attachment/service/FileScanService.java +++ b/src/main/java/org/highmed/numportal/attachment/service/FileScanService.java @@ -3,6 +3,7 @@ import org.highmed.numportal.domain.templates.ExceptionsTemplate; import org.highmed.numportal.service.exception.BadRequestException; import org.highmed.numportal.service.exception.SystemException; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -16,27 +17,27 @@ @Slf4j public class FileScanService { - private final ClamAVService clamAVService; + private final ClamAVService clamAvService; - public void virusScan(MultipartFile file) { - log.info("Start scanning file {}", file.getOriginalFilename()); - if (clamAVService.ping()) { - try { - String scanResult = clamAVService.scan(new ByteArrayInputStream(file.getBytes())); - if (!clamAVService.isScannedFileSafe(scanResult)) { - log.error("File {} rejected by ClamAV", file.getOriginalFilename()); - throw new BadRequestException(FileScanService.class, "File rejected", "File rejected"); - } - } catch (IOException e) { - log.error("Error occurred when scanning file {} ", file.getOriginalFilename(), e); - throw new SystemException(FileScanService.class, ExceptionsTemplate.CLAMAV_SCAN_FAILED, - String.format(ExceptionsTemplate.CLAMAV_SCAN_FAILED, file.getOriginalFilename())); - } - } else { - log.error("ClamAV service did not respond to ping request"); - throw new SystemException(FileScanService.class, ExceptionsTemplate.CLAMAV_PING_FAILED, - ExceptionsTemplate.CLAMAV_PING_FAILED); + public void virusScan(MultipartFile file) { + log.info("Start scanning file {}", file.getOriginalFilename()); + if (clamAvService.ping()) { + try { + String scanResult = clamAvService.scan(new ByteArrayInputStream(file.getBytes())); + if (!clamAvService.isScannedFileSafe(scanResult)) { + log.error("File {} rejected by ClamAV", file.getOriginalFilename()); + throw new BadRequestException(FileScanService.class, "File rejected", "File rejected"); } - log.info("End scanning file {}", file.getOriginalFilename()); + } catch (IOException e) { + log.error("Error occurred when scanning file {} ", file.getOriginalFilename(), e); + throw new SystemException(FileScanService.class, ExceptionsTemplate.CLAMAV_SCAN_FAILED, + String.format(ExceptionsTemplate.CLAMAV_SCAN_FAILED, file.getOriginalFilename())); + } + } else { + log.error("ClamAV service did not respond to ping request"); + throw new SystemException(FileScanService.class, ExceptionsTemplate.CLAMAV_PING_FAILED, + ExceptionsTemplate.CLAMAV_PING_FAILED); } + log.info("End scanning file {}", file.getOriginalFilename()); + } } diff --git a/src/main/java/org/highmed/numportal/config/EhrBaseConfig.java b/src/main/java/org/highmed/numportal/config/EhrBaseConfig.java index 76b782b7..28fe1862 100644 --- a/src/main/java/org/highmed/numportal/config/EhrBaseConfig.java +++ b/src/main/java/org/highmed/numportal/config/EhrBaseConfig.java @@ -1,6 +1,7 @@ package org.highmed.numportal.config; import org.highmed.numportal.properties.EhrBaseProperties; + import lombok.RequiredArgsConstructor; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; diff --git a/src/main/java/org/highmed/numportal/config/FttpClientConfig.java b/src/main/java/org/highmed/numportal/config/FttpClientConfig.java index 82871e52..80da343c 100644 --- a/src/main/java/org/highmed/numportal/config/FttpClientConfig.java +++ b/src/main/java/org/highmed/numportal/config/FttpClientConfig.java @@ -1,7 +1,8 @@ package org.highmed.numportal.config; -import ca.uhn.fhir.context.FhirContext; import org.highmed.numportal.properties.FttpProperties; + +import ca.uhn.fhir.context.FhirContext; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.http.auth.AuthScope; @@ -15,9 +16,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.security.KeyStore; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import java.security.KeyStore; @Slf4j @Configuration @@ -38,7 +39,8 @@ public CloseableHttpClient httpClient() { try { var keyStore = KeyStore.getInstance(CERTIFICATE_TYPE); - keyStore.load(getClass().getResourceAsStream(fttpProperties.getCertificatePath()), + keyStore.load( + getClass().getResourceAsStream(fttpProperties.getCertificatePath()), fttpProperties.getCertificateKey().toCharArray()); SSLContext sslContext = @@ -52,7 +54,8 @@ public CloseableHttpClient httpClient() { if (fttpProperties.isUseBasicAuth()) { var provider = new BasicCredentialsProvider(); - provider.setCredentials(AuthScope.ANY, + provider.setCredentials( + AuthScope.ANY, new UsernamePasswordCredentials(fttpProperties.getUsername(), fttpProperties.getPassword()) ); diff --git a/src/main/java/org/highmed/numportal/config/MessageSourceConfiguration.java b/src/main/java/org/highmed/numportal/config/MessageSourceConfiguration.java index 0aba9ff0..9c56166f 100644 --- a/src/main/java/org/highmed/numportal/config/MessageSourceConfiguration.java +++ b/src/main/java/org/highmed/numportal/config/MessageSourceConfiguration.java @@ -1,6 +1,7 @@ package org.highmed.numportal.config; import org.highmed.numportal.properties.NumProperties; + import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.context.MessageSource; @@ -8,9 +9,9 @@ import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import java.util.Locale; +import javax.annotation.PostConstruct; @Component @AllArgsConstructor diff --git a/src/main/java/org/highmed/numportal/config/database/NumAttachmentFlywayConfig.java b/src/main/java/org/highmed/numportal/config/database/NumAttachmentFlywayConfig.java index 47a4f869..b8a69349 100644 --- a/src/main/java/org/highmed/numportal/config/database/NumAttachmentFlywayConfig.java +++ b/src/main/java/org/highmed/numportal/config/database/NumAttachmentFlywayConfig.java @@ -13,22 +13,21 @@ @Configuration public class NumAttachmentFlywayConfig { - @Value("${spring.flyway.numportal-attachment.locations}") - private String flywayLocations; + private final DataSource dataSource; + @Value("${spring.flyway.numportal-attachment.locations}") + private String flywayLocations; - private final DataSource dataSource; + public NumAttachmentFlywayConfig(@Qualifier("numAttachmentDatasource") DataSource dataSource) { + this.dataSource = dataSource; + } - public NumAttachmentFlywayConfig(@Qualifier("numAttachmentDatasource") DataSource dataSource) { - this.dataSource = dataSource; - } - - @PostConstruct - public void executeMigration() { - Flyway.configure() - .dataSource(dataSource) - .locations(flywayLocations) - .baselineOnMigrate(true) - .load() - .migrate(); - } + @PostConstruct + public void executeMigration() { + Flyway.configure() + .dataSource(dataSource) + .locations(flywayLocations) + .baselineOnMigrate(true) + .load() + .migrate(); + } } diff --git a/src/main/java/org/highmed/numportal/config/database/NumPortalAttachmentDatasourceConfiguration.java b/src/main/java/org/highmed/numportal/config/database/NumPortalAttachmentDatasourceConfiguration.java index 9eb38e64..11b8ce2f 100644 --- a/src/main/java/org/highmed/numportal/config/database/NumPortalAttachmentDatasourceConfiguration.java +++ b/src/main/java/org/highmed/numportal/config/database/NumPortalAttachmentDatasourceConfiguration.java @@ -19,53 +19,54 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; -import javax.sql.DataSource; import java.util.Objects; +import javax.sql.DataSource; @ConditionalOnProperty(prefix = "num", name = "enableAttachmentDatabase", havingValue = "true") @Configuration @EnableJpaRepositories(basePackages = "org.highmed.numportal.attachment", - entityManagerFactoryRef = "attachmentEntityManagerFactory", - transactionManagerRef = "attachmentTransactionManager") + entityManagerFactoryRef = "attachmentEntityManagerFactory", + transactionManagerRef = "attachmentTransactionManager") @EnableTransactionManagement public class NumPortalAttachmentDatasourceConfiguration { - @Value("${spring.jpa.show-sql}") - private boolean showSql; + @Value("${spring.jpa.show-sql}") + private boolean showSql; - @Bean(name = "numAttachmentProperties") - @ConfigurationProperties(prefix = "spring.datasource.numportal-attachment") - public DataSourceProperties dataSourceProperties(){ - return new DataSourceProperties(); - } + @Bean(name = "numAttachmentProperties") + @ConfigurationProperties(prefix = "spring.datasource.numportal-attachment") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } - @Bean("numAttachmentDatasource") - public DataSource dataSource(@Qualifier("numAttachmentProperties") DataSourceProperties dataSourceProperties) { - return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); - } + @Bean("numAttachmentDatasource") + public DataSource dataSource(@Qualifier("numAttachmentProperties") DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); + } - @Bean - public LocalContainerEntityManagerFactoryBean attachmentEntityManagerFactory(EntityManagerFactoryBuilder builder, - @Qualifier("numAttachmentDatasource") DataSource dataSource) { - LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = builder - .dataSource(dataSource) - .packages("org.highmed.numportal.attachment") - .persistenceUnit("numAttachment") - .build(); - localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, new SpringImplicitNamingStrategy()); - localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, new CamelCaseToUnderscoresNamingStrategy()); - HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); - hibernateJpaVendorAdapter.setGenerateDdl(false); - hibernateJpaVendorAdapter.setShowSql(showSql); + @Bean + public LocalContainerEntityManagerFactoryBean attachmentEntityManagerFactory(EntityManagerFactoryBuilder builder, + @Qualifier("numAttachmentDatasource") DataSource dataSource) { + LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = builder + .dataSource(dataSource) + .packages("org.highmed.numportal.attachment") + .persistenceUnit("numAttachment") + .build(); + localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, new SpringImplicitNamingStrategy()); + localContainerEntityManagerFactoryBean.getJpaPropertyMap() + .put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, new CamelCaseToUnderscoresNamingStrategy()); + HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); + hibernateJpaVendorAdapter.setGenerateDdl(false); + hibernateJpaVendorAdapter.setShowSql(showSql); - localContainerEntityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter); - return localContainerEntityManagerFactoryBean; - } + localContainerEntityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter); + return localContainerEntityManagerFactoryBean; + } - @Bean - public PlatformTransactionManager attachmentTransactionManager( - @Qualifier("attachmentEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { - return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryBean.getObject())); - } + @Bean + public PlatformTransactionManager attachmentTransactionManager( + @Qualifier("attachmentEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { + return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryBean.getObject())); + } } diff --git a/src/main/java/org/highmed/numportal/config/database/NumPortalDatasourceConfiguration.java b/src/main/java/org/highmed/numportal/config/database/NumPortalDatasourceConfiguration.java index 50bdaf43..2c26a924 100644 --- a/src/main/java/org/highmed/numportal/config/database/NumPortalDatasourceConfiguration.java +++ b/src/main/java/org/highmed/numportal/config/database/NumPortalDatasourceConfiguration.java @@ -22,73 +22,75 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; -import javax.sql.DataSource; import java.util.Objects; import java.util.Properties; +import javax.sql.DataSource; @Configuration @EnableJpaRepositories(basePackages = {"org.highmed.numportal.domain", "org.highmed.numportal.service"}, - entityManagerFactoryRef = "numEntityManagerFactory", - transactionManagerRef = "numTransactionManager") + entityManagerFactoryRef = "numEntityManagerFactory", + transactionManagerRef = "numTransactionManager") @EnableTransactionManagement public class NumPortalDatasourceConfiguration { - @Value("${spring.jpa.show-sql}") - private boolean showSql; + @Value("${spring.jpa.show-sql}") + private boolean showSql; - @Primary - @Bean(name = "numPortalProperties") - @ConfigurationProperties(prefix = "spring.datasource.numportal") - public DataSourceProperties dataSourceProperties(){ - return new DataSourceProperties(); - } + @Primary + @Bean(name = "numPortalProperties") + @ConfigurationProperties(prefix = "spring.datasource.numportal") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } - @Primary - @ConfigurationProperties(prefix = "spring.datasource.hikari.data-source-properties") - @Bean(name = "hikariProps") - public Properties hikaryProperties() { - return new Properties(); - } + @Primary + @ConfigurationProperties(prefix = "spring.datasource.hikari.data-source-properties") + @Bean(name = "hikariProps") + public Properties hikaryProperties() { + return new Properties(); + } - @Primary - @Bean("numPortalDatasource") - @FlywayDataSource - public DataSource dataSource(@Qualifier("numPortalProperties") DataSourceProperties dataSourceProperties, @Qualifier("hikariProps") Properties hikaryProps) { - HikariConfig hikariConfig = new HikariConfig(); - hikariConfig.setDriverClassName(dataSourceProperties().getDriverClassName()); - hikariConfig.setJdbcUrl(dataSourceProperties.getUrl()); - hikariConfig.setUsername(dataSourceProperties.getUsername()); - hikariConfig.setPassword(dataSourceProperties.getPassword()); - hikariConfig.getDataSourceProperties().putAll(hikaryProps); - return new HikariDataSource(hikariConfig); - } + @Primary + @Bean("numPortalDatasource") + @FlywayDataSource + public DataSource dataSource(@Qualifier("numPortalProperties") DataSourceProperties dataSourceProperties, + @Qualifier("hikariProps") Properties hikaryProps) { + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDriverClassName(dataSourceProperties().getDriverClassName()); + hikariConfig.setJdbcUrl(dataSourceProperties.getUrl()); + hikariConfig.setUsername(dataSourceProperties.getUsername()); + hikariConfig.setPassword(dataSourceProperties.getPassword()); + hikariConfig.getDataSourceProperties().putAll(hikaryProps); + return new HikariDataSource(hikariConfig); + } - @Primary - @Bean - public LocalContainerEntityManagerFactoryBean numEntityManagerFactory(ConfigurableListableBeanFactory beanFactory, - @Qualifier("numPortalDatasource") DataSource dataSource) { - LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); - localContainerEntityManagerFactoryBean.setDataSource(dataSource); - localContainerEntityManagerFactoryBean.setPackagesToScan("org.highmed.numportal.domain", "org.highmed.numportal.service"); - localContainerEntityManagerFactoryBean.setPersistenceUnitName("numPortal"); - localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)); - localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, new SpringImplicitNamingStrategy()); - localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, new CamelCaseToUnderscoresNamingStrategy()); - localContainerEntityManagerFactoryBean.getJpaPropertyMap().put("hibernate.order_by.default_null_ordering", "last"); - HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); - hibernateJpaVendorAdapter.setGenerateDdl(false); - hibernateJpaVendorAdapter.setShowSql(showSql); + @Primary + @Bean + public LocalContainerEntityManagerFactoryBean numEntityManagerFactory(ConfigurableListableBeanFactory beanFactory, + @Qualifier("numPortalDatasource") DataSource dataSource) { + LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); + localContainerEntityManagerFactoryBean.setDataSource(dataSource); + localContainerEntityManagerFactoryBean.setPackagesToScan("org.highmed.numportal.domain", "org.highmed.numportal.service"); + localContainerEntityManagerFactoryBean.setPersistenceUnitName("numPortal"); + localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)); + localContainerEntityManagerFactoryBean.getJpaPropertyMap().put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, new SpringImplicitNamingStrategy()); + localContainerEntityManagerFactoryBean.getJpaPropertyMap() + .put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, new CamelCaseToUnderscoresNamingStrategy()); + localContainerEntityManagerFactoryBean.getJpaPropertyMap().put("hibernate.order_by.default_null_ordering", "last"); + HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); + hibernateJpaVendorAdapter.setGenerateDdl(false); + hibernateJpaVendorAdapter.setShowSql(showSql); - localContainerEntityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter); - return localContainerEntityManagerFactoryBean; - } + localContainerEntityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter); + return localContainerEntityManagerFactoryBean; + } - @Primary - @Bean - @ConfigurationProperties("spring.jpa") - public PlatformTransactionManager numTransactionManager( - @Qualifier("numEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { - return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryBean.getObject())); - } + @Primary + @Bean + @ConfigurationProperties("spring.jpa") + public PlatformTransactionManager numTransactionManager( + @Qualifier("numEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { + return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryBean.getObject())); + } } diff --git a/src/main/java/org/highmed/numportal/config/database/NumPortalFlywayConfig.java b/src/main/java/org/highmed/numportal/config/database/NumPortalFlywayConfig.java index 359535f9..b308c821 100644 --- a/src/main/java/org/highmed/numportal/config/database/NumPortalFlywayConfig.java +++ b/src/main/java/org/highmed/numportal/config/database/NumPortalFlywayConfig.java @@ -11,22 +11,21 @@ @Configuration public class NumPortalFlywayConfig { - @Value("${spring.flyway.numportal.locations}") - private String flywayLocations; + private final DataSource dataSource; + @Value("${spring.flyway.numportal.locations}") + private String flywayLocations; - private final DataSource dataSource; + public NumPortalFlywayConfig(@Qualifier("numPortalDatasource") DataSource dataSource) { + this.dataSource = dataSource; + } - public NumPortalFlywayConfig(@Qualifier("numPortalDatasource") DataSource dataSource) { - this.dataSource = dataSource; - } - - @PostConstruct - public void executeMigration() { - Flyway.configure() - .dataSource(dataSource) - .locations(flywayLocations) - .baselineOnMigrate(true) - .load() - .migrate(); - } + @PostConstruct + public void executeMigration() { + Flyway.configure() + .dataSource(dataSource) + .locations(flywayLocations) + .baselineOnMigrate(true) + .load() + .migrate(); + } } diff --git a/src/main/java/org/highmed/numportal/domain/dto/AqlCategoryDto.java b/src/main/java/org/highmed/numportal/domain/dto/AqlCategoryDto.java index 20bb92ca..d964dd96 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/AqlCategoryDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/AqlCategoryDto.java @@ -1,12 +1,13 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.domain.validation.ValidTranslatedString; + import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.domain.validation.ValidTranslatedString; import java.util.Map; @@ -16,13 +17,12 @@ @AllArgsConstructor @NoArgsConstructor public class AqlCategoryDto { - @Schema(description = "The unique identifier", example = "1", accessMode = Schema.AccessMode.READ_ONLY) - private Long id; @Schema(description = "The map of language text pairs", example = "{'en':'text in english','de':'text auf deutsch'}") @ValidTranslatedString(message = "Translated string must contain at lest 'en' and 'de' translations.") Map name; - + @Schema(description = "The unique identifier", example = "1", accessMode = Schema.AccessMode.READ_ONLY) + private Long id; @Schema(description = "Flag used to mark if category can be deleted if no aqls assigned", accessMode = Schema.AccessMode.READ_ONLY) @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean allowedToBeDeleted; diff --git a/src/main/java/org/highmed/numportal/domain/dto/AqlDto.java b/src/main/java/org/highmed/numportal/domain/dto/AqlDto.java index 4c768815..d73062e3 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/AqlDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/AqlDto.java @@ -1,5 +1,7 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.domain.model.admin.User; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -7,7 +9,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.domain.model.admin.User; import java.time.OffsetDateTime; diff --git a/src/main/java/org/highmed/numportal/domain/dto/CardDto.java b/src/main/java/org/highmed/numportal/domain/dto/CardDto.java index 2fcff4f7..b8ae6fff 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/CardDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/CardDto.java @@ -1,14 +1,14 @@ package org.highmed.numportal.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; import java.net.URL; @Data @@ -18,14 +18,27 @@ @AllArgsConstructor public class CardDto { + @Schema(description = "The english content of the card") + @NotNull + private LocalizedPart en; + @Schema(description = "The german content of the card") + @NotNull + private LocalizedPart de; + @Schema(description = "The id of image of the card", example = "image-4") + @NotBlank + private String imageId; + @Schema(description = "The URL of the card", example = "https://www.highmed.org/") + private URL url; + @Data @Builder @NoArgsConstructor @AllArgsConstructor - public static class LocalizedPart{ + public static class LocalizedPart { + @Schema(description = "The localized title of the card", example = "image-4") @NotBlank - @Size(min=1, max = 65) + @Size(min = 1, max = 65) private String title; @Schema(description = "The localized text of the card", example = "This is the best card ever!") @@ -33,20 +46,4 @@ public static class LocalizedPart{ private String text; } - - @Schema(description = "The english content of the card") - @NotNull - private LocalizedPart en; - - @Schema(description = "The german content of the card") - @NotNull - private LocalizedPart de; - - - @Schema(description = "The id of image of the card", example = "image-4") - @NotBlank - private String imageId; - - @Schema(description = "The URL of the card", example = "https://www.highmed.org/") - private URL url; } diff --git a/src/main/java/org/highmed/numportal/domain/dto/CohortAqlDto.java b/src/main/java/org/highmed/numportal/domain/dto/CohortAqlDto.java index 6496e4cf..51ba6d67 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/CohortAqlDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/CohortAqlDto.java @@ -1,14 +1,13 @@ package org.highmed.numportal.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - @Schema @Data @Builder diff --git a/src/main/java/org/highmed/numportal/domain/dto/CohortDto.java b/src/main/java/org/highmed/numportal/domain/dto/CohortDto.java index c88103ce..30b654d3 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/CohortDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/CohortDto.java @@ -1,5 +1,7 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.domain.validation.ValidCohort; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -7,7 +9,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.domain.validation.ValidCohort; @Schema @Data diff --git a/src/main/java/org/highmed/numportal/domain/dto/CohortGroupDto.java b/src/main/java/org/highmed/numportal/domain/dto/CohortGroupDto.java index 5b57b193..d43a3ce0 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/CohortGroupDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/CohortGroupDto.java @@ -1,5 +1,9 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.domain.model.Operator; +import org.highmed.numportal.domain.model.Type; +import org.highmed.numportal.domain.repository.AqlConverter; + import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Convert; @@ -8,9 +12,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.domain.model.Operator; -import org.highmed.numportal.domain.model.Type; -import org.highmed.numportal.domain.repository.AqlConverter; import java.util.List; import java.util.Map; @@ -38,7 +39,7 @@ public class CohortGroupDto { private Type type; @Schema(description = - "Children of the cohort group in case the type of the group is: GROUP; can be other groups or aqls") + "Children of the cohort group in case the type of the group is: GROUP; can be other groups or aqls") private List children; @Convert(converter = AqlConverter.class) diff --git a/src/main/java/org/highmed/numportal/domain/dto/CommentDto.java b/src/main/java/org/highmed/numportal/domain/dto/CommentDto.java index 361ce52f..ac1f3f47 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/CommentDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/CommentDto.java @@ -1,5 +1,7 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.domain.model.admin.User; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -7,7 +9,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.domain.model.admin.User; import java.time.OffsetDateTime; diff --git a/src/main/java/org/highmed/numportal/domain/dto/Language.java b/src/main/java/org/highmed/numportal/domain/dto/Language.java index c1ed62a4..34ffc739 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/Language.java +++ b/src/main/java/org/highmed/numportal/domain/dto/Language.java @@ -1,5 +1,6 @@ package org.highmed.numportal.domain.dto; public enum Language { - de, en + de, + en } diff --git a/src/main/java/org/highmed/numportal/domain/dto/ManagerProjectDto.java b/src/main/java/org/highmed/numportal/domain/dto/ManagerProjectDto.java index 4f895724..e22a56c0 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/ManagerProjectDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/ManagerProjectDto.java @@ -11,7 +11,10 @@ @Schema public class ManagerProjectDto { - @NotNull private CohortDto cohort; + @NotNull + private CohortDto cohort; - @NotNull @NotEmpty private List templates; + @NotNull + @NotEmpty + private List templates; } diff --git a/src/main/java/org/highmed/numportal/domain/dto/NavigationItemDto.java b/src/main/java/org/highmed/numportal/domain/dto/NavigationItemDto.java index 29532b8a..33aeebc2 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/NavigationItemDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/NavigationItemDto.java @@ -20,7 +20,7 @@ public class NavigationItemDto { @Schema(description = "The tile of the navigation item", example = "Link 4") @NotBlank - @Size(min=1, max = 20) + @Size(min = 1, max = 20) private String title; @Schema(description = "The URL of the navigation item", example = "https://www.google.de/") diff --git a/src/main/java/org/highmed/numportal/domain/dto/ProjectDto.java b/src/main/java/org/highmed/numportal/domain/dto/ProjectDto.java index 9f1ff23a..49a79274 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/ProjectDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/ProjectDto.java @@ -1,5 +1,10 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.attachment.domain.dto.AttachmentDto; +import org.highmed.numportal.domain.model.ProjectCategories; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.model.admin.User; + import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; @@ -9,10 +14,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.attachment.domain.dto.AttachmentDto; -import org.highmed.numportal.domain.model.ProjectCategories; -import org.highmed.numportal.domain.model.ProjectStatus; -import org.highmed.numportal.domain.model.admin.User; import java.time.LocalDate; import java.time.OffsetDateTime; @@ -40,7 +41,8 @@ public class ProjectDto { private boolean usedOutsideEu; - @Valid private List templates; + @Valid + private List templates; private Long cohortId; @@ -77,7 +79,8 @@ public class ProjectDto { @NotNull(message = "Project endDate cannot be null") private LocalDate endDate; - @Builder.Default private boolean financed = false; + @Builder.Default + private boolean financed = false; @Schema(description = "attachment's id to be deleted") private Set attachmentsToBeDeleted; diff --git a/src/main/java/org/highmed/numportal/domain/dto/ProjectViewTO.java b/src/main/java/org/highmed/numportal/domain/dto/ProjectViewDto.java similarity index 66% rename from src/main/java/org/highmed/numportal/domain/dto/ProjectViewTO.java rename to src/main/java/org/highmed/numportal/domain/dto/ProjectViewDto.java index 8ebf2514..5725ef0f 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/ProjectViewTO.java +++ b/src/main/java/org/highmed/numportal/domain/dto/ProjectViewDto.java @@ -1,12 +1,13 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.model.admin.User; + import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.highmed.numportal.domain.model.ProjectStatus; -import org.highmed.numportal.domain.model.admin.User; import java.time.LocalDate; import java.time.OffsetDateTime; @@ -16,20 +17,20 @@ @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) -public class ProjectViewTO { +public class ProjectViewDto { - private Long id; + private Long id; - private String name; + private String name; - private User coordinator; + private User coordinator; - private ProjectStatus status; + private ProjectStatus status; - private LocalDate startDate; + private LocalDate startDate; - private LocalDate endDate; + private LocalDate endDate; - private OffsetDateTime createDate; + private OffsetDateTime createDate; } diff --git a/src/main/java/org/highmed/numportal/domain/dto/SearchCriteria.java b/src/main/java/org/highmed/numportal/domain/dto/SearchCriteria.java index 59c2e1ed..315a1615 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/SearchCriteria.java +++ b/src/main/java/org/highmed/numportal/domain/dto/SearchCriteria.java @@ -1,12 +1,13 @@ package org.highmed.numportal.domain.dto; +import org.highmed.numportal.service.exception.BadRequestException; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; -import org.highmed.numportal.service.exception.BadRequestException; import java.util.Map; @@ -17,44 +18,39 @@ @AllArgsConstructor public class SearchCriteria { - public static final String FILTER_SEARCH_BY_KEY = "search"; - - public static final String FILTER_BY_TYPE_KEY = "type"; - - // boolean flag used for users search/filter (optional -> omitting it returns both) - public static final String FILTER_APPROVED_KEY = "approved"; + public static final String FILTER_SEARCH_BY_KEY = "search"; - // boolean flag used for users search/filter (optional) - public static final String FILTER_USER_WITH_ROLES_KEY = "withRoles"; + public static final String FILTER_BY_TYPE_KEY = "type"; - private static final String AUTHOR_NAME = "author"; + // boolean flag used for users search/filter (optional -> omitting it returns both) + public static final String FILTER_APPROVED_KEY = "approved"; - public static final String FILTER_BY_STATUS = "status"; + // boolean flag used for users search/filter (optional) + public static final String FILTER_USER_WITH_ROLES_KEY = "withRoles"; + public static final String FILTER_BY_STATUS = "status"; + public static final String FILTER_BY_ROLES = "roles"; + public static final String FILTER_BY_ACTIVE = "enabled"; + public static final String FILTER_BY_ACTIVE_ORGANIZATION = "active"; + private static final String AUTHOR_NAME = "author"; + private Map filter; - public static final String FILTER_BY_ROLES = "roles"; + private String sort; - public static final String FILTER_BY_ACTIVE = "enabled"; + private String sortBy; - public static final String FILTER_BY_ACTIVE_ORGANIZATION = "active"; + private Language language; - private Map filter; - - private String sort; - - private String sortBy; - - private Language language; - - public boolean isValid() { - if (StringUtils.isEmpty(this.sort) && StringUtils.isNotEmpty(this.sortBy)) { - throw new BadRequestException(SearchCriteria.class, "sort field is required when sortBy is provided"); - } - if (StringUtils.isNotEmpty(this.sort) && StringUtils.isEmpty(this.sortBy)) { - throw new BadRequestException(SearchCriteria.class, "sortBy field is required when sort is provided"); - } - return true; + public boolean isValid() { + if (StringUtils.isEmpty(this.sort) && StringUtils.isNotEmpty(this.sortBy)) { + throw new BadRequestException(SearchCriteria.class, "sort field is required when sortBy is provided"); } - public boolean isSortByAuthor() { - return AUTHOR_NAME.equals(this.getSortBy()); + if (StringUtils.isNotEmpty(this.sort) && StringUtils.isEmpty(this.sortBy)) { + throw new BadRequestException(SearchCriteria.class, "sortBy field is required when sort is provided"); } + return true; + } + + public boolean isSortByAuthor() { + return AUTHOR_NAME.equals(this.getSortBy()); + } } diff --git a/src/main/java/org/highmed/numportal/domain/dto/TemplateInfoDto.java b/src/main/java/org/highmed/numportal/domain/dto/TemplateInfoDto.java index d85606ee..7d8fabb2 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/TemplateInfoDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/TemplateInfoDto.java @@ -9,7 +9,9 @@ import lombok.Data; import lombok.NoArgsConstructor; -/** Dto for template information linked to a project */ +/** + * Dto for template information linked to a project + */ @Data @Builder @Schema diff --git a/src/main/java/org/highmed/numportal/domain/dto/TemplateMetadataDto.java b/src/main/java/org/highmed/numportal/domain/dto/TemplateMetadataDto.java index 0a5c6755..17fb4d76 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/TemplateMetadataDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/TemplateMetadataDto.java @@ -8,7 +8,9 @@ import java.time.OffsetDateTime; -/** Dto for template metadata retrieved from ehr base */ +/** + * Dto for template metadata retrieved from ehr base + */ @Data @Builder @Schema diff --git a/src/main/java/org/highmed/numportal/domain/dto/TemplateSizeRequestDto.java b/src/main/java/org/highmed/numportal/domain/dto/TemplateSizeRequestDto.java index ea560105..94fe86ff 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/TemplateSizeRequestDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/TemplateSizeRequestDto.java @@ -18,8 +18,11 @@ @AllArgsConstructor public class TemplateSizeRequestDto { - @NotNull @Valid public CohortDto cohortDto; + @NotNull + @Valid + public CohortDto cohortDto; - @NotNull @NotEmpty + @NotNull + @NotEmpty public List templateIds; } diff --git a/src/main/java/org/highmed/numportal/domain/dto/ZarsInfoDto.java b/src/main/java/org/highmed/numportal/domain/dto/ZarsInfoDto.java index e7fcec08..f658b905 100644 --- a/src/main/java/org/highmed/numportal/domain/dto/ZarsInfoDto.java +++ b/src/main/java/org/highmed/numportal/domain/dto/ZarsInfoDto.java @@ -1,14 +1,16 @@ package org.highmed.numportal.domain.dto; -import lombok.Data; import org.highmed.numportal.domain.model.ProjectCategories; import org.highmed.numportal.domain.model.ProjectStatus; +import lombok.Data; + import java.time.LocalDate; import java.util.Set; @Data public class ZarsInfoDto { + private int id; private String name; private ProjectStatus status; diff --git a/src/main/java/org/highmed/numportal/domain/model/Aql.java b/src/main/java/org/highmed/numportal/domain/model/Aql.java index d9ea0be4..a20f12e8 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Aql.java +++ b/src/main/java/org/highmed/numportal/domain/model/Aql.java @@ -1,8 +1,14 @@ package org.highmed.numportal.domain.model; -import com.fasterxml.jackson.annotation.JsonBackReference; import org.highmed.numportal.domain.model.admin.UserDetails; -import jakarta.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/org/highmed/numportal/domain/model/AqlCategory.java b/src/main/java/org/highmed/numportal/domain/model/AqlCategory.java index 7f6d6dc0..97cd2041 100644 --- a/src/main/java/org/highmed/numportal/domain/model/AqlCategory.java +++ b/src/main/java/org/highmed/numportal/domain/model/AqlCategory.java @@ -2,13 +2,17 @@ import org.highmed.numportal.domain.repository.MapConverter; import org.highmed.numportal.domain.validation.ValidTranslatedString; + +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.persistence.*; - import java.util.Map; @Entity @@ -17,11 +21,11 @@ @AllArgsConstructor @Builder public class AqlCategory { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; @Convert(converter = MapConverter.class) @ValidTranslatedString Map name; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; } diff --git a/src/main/java/org/highmed/numportal/domain/model/Cohort.java b/src/main/java/org/highmed/numportal/domain/model/Cohort.java index ba5a225c..9aa30bbb 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Cohort.java +++ b/src/main/java/org/highmed/numportal/domain/model/Cohort.java @@ -1,13 +1,18 @@ package org.highmed.numportal.domain.model; -import java.io.Serializable; - +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.persistence.*; +import java.io.Serializable; @Entity @Data diff --git a/src/main/java/org/highmed/numportal/domain/model/CohortGroup.java b/src/main/java/org/highmed/numportal/domain/model/CohortGroup.java index 99fa7cc4..5b12a130 100644 --- a/src/main/java/org/highmed/numportal/domain/model/CohortGroup.java +++ b/src/main/java/org/highmed/numportal/domain/model/CohortGroup.java @@ -1,20 +1,30 @@ package org.highmed.numportal.domain.model; -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonManagedReference; import org.highmed.numportal.domain.repository.AqlConverter; import org.highmed.numportal.domain.repository.MapConverter; -import java.io.Serializable; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import jakarta.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + @Entity @Data @NoArgsConstructor diff --git a/src/main/java/org/highmed/numportal/domain/model/Comment.java b/src/main/java/org/highmed/numportal/domain/model/Comment.java index 6593fad3..65149bd8 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Comment.java +++ b/src/main/java/org/highmed/numportal/domain/model/Comment.java @@ -1,16 +1,23 @@ package org.highmed.numportal.domain.model; -import com.fasterxml.jackson.annotation.JsonBackReference; import org.highmed.numportal.domain.model.admin.UserDetails; -import java.io.Serializable; -import java.time.OffsetDateTime; -import jakarta.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.apache.commons.lang3.ObjectUtils; +import java.io.Serializable; +import java.time.OffsetDateTime; + @Entity @Builder @Data diff --git a/src/main/java/org/highmed/numportal/domain/model/Content.java b/src/main/java/org/highmed/numportal/domain/model/Content.java index e75a644b..25106f78 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Content.java +++ b/src/main/java/org/highmed/numportal/domain/model/Content.java @@ -1,14 +1,20 @@ package org.highmed.numportal.domain.model; -import java.io.Serializable; - -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Entity @Builder @Data diff --git a/src/main/java/org/highmed/numportal/domain/model/EntityGroup.java b/src/main/java/org/highmed/numportal/domain/model/EntityGroup.java index b735c131..d463676e 100644 --- a/src/main/java/org/highmed/numportal/domain/model/EntityGroup.java +++ b/src/main/java/org/highmed/numportal/domain/model/EntityGroup.java @@ -1,6 +1,6 @@ package org.highmed.numportal.domain.model; public enum EntityGroup { - PROJECT_STATUS, - ROLE_NAME + PROJECT_STATUS, + ROLE_NAME } diff --git a/src/main/java/org/highmed/numportal/domain/model/MailDomain.java b/src/main/java/org/highmed/numportal/domain/model/MailDomain.java index 04b38260..3aa39114 100644 --- a/src/main/java/org/highmed/numportal/domain/model/MailDomain.java +++ b/src/main/java/org/highmed/numportal/domain/model/MailDomain.java @@ -1,20 +1,26 @@ package org.highmed.numportal.domain.model; import com.fasterxml.jackson.annotation.JsonBackReference; -import java.io.Serializable; - -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Entity @Builder @Data @NoArgsConstructor @AllArgsConstructor -@Table(name="maildomain") +@Table(name = "maildomain") public class MailDomain implements Serializable { @Id @@ -25,7 +31,7 @@ public class MailDomain implements Serializable { @ManyToOne @JsonBackReference - @JoinColumn(name="organization_id") + @JoinColumn(name = "organization_id") private Organization organization; } diff --git a/src/main/java/org/highmed/numportal/domain/model/Operator.java b/src/main/java/org/highmed/numportal/domain/model/Operator.java index a993f5df..bbd6c9c7 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Operator.java +++ b/src/main/java/org/highmed/numportal/domain/model/Operator.java @@ -1,5 +1,7 @@ package org.highmed.numportal.domain.model; public enum Operator { - AND, OR, NOT + AND, + OR, + NOT } diff --git a/src/main/java/org/highmed/numportal/domain/model/Organization.java b/src/main/java/org/highmed/numportal/domain/model/Organization.java index ab327ba0..697b51b1 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Organization.java +++ b/src/main/java/org/highmed/numportal/domain/model/Organization.java @@ -1,12 +1,13 @@ package org.highmed.numportal.domain.model; import com.fasterxml.jackson.annotation.JsonManagedReference; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; - -import jakarta.persistence.*; - +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,6 +15,10 @@ import lombok.NoArgsConstructor; import lombok.ToString; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + @Entity @Builder @Data diff --git a/src/main/java/org/highmed/numportal/domain/model/Project.java b/src/main/java/org/highmed/numportal/domain/model/Project.java index 8997868f..349548fe 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Project.java +++ b/src/main/java/org/highmed/numportal/domain/model/Project.java @@ -1,19 +1,38 @@ package org.highmed.numportal.domain.model; -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonManagedReference; import org.highmed.numportal.domain.model.admin.UserDetails; import org.highmed.numportal.domain.repository.CategorySetConverter; import org.highmed.numportal.domain.repository.MapConverter; import org.highmed.numportal.domain.repository.StringSetConverter; -import lombok.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.hibernate.annotations.JoinColumnOrFormula; -import jakarta.persistence.*; - import java.io.Serializable; import java.time.LocalDate; import java.time.OffsetDateTime; @@ -27,7 +46,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@EqualsAndHashCode(exclude = {"cohort", "transitions","translations"}) +@EqualsAndHashCode(exclude = {"cohort", "transitions", "translations"}) public class Project implements Serializable { @Id diff --git a/src/main/java/org/highmed/numportal/domain/model/ProjectStatus.java b/src/main/java/org/highmed/numportal/domain/model/ProjectStatus.java index c3fc97b1..a0197d94 100644 --- a/src/main/java/org/highmed/numportal/domain/model/ProjectStatus.java +++ b/src/main/java/org/highmed/numportal/domain/model/ProjectStatus.java @@ -1,17 +1,19 @@ package org.highmed.numportal.domain.model; -import static org.highmed.numportal.domain.model.Roles.STUDY_APPROVER; -import static org.highmed.numportal.domain.model.Roles.STUDY_COORDINATOR; -import static org.highmed.numportal.domain.model.Roles.SUPER_ADMIN; - import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.highmed.numportal.domain.model.Roles.STUDY_APPROVER; +import static org.highmed.numportal.domain.model.Roles.STUDY_COORDINATOR; +import static org.highmed.numportal.domain.model.Roles.SUPER_ADMIN; + public enum ProjectStatus { - /** Project creation in progress */ + /** + * Project creation in progress + */ DRAFT { @Override public Map> nextStatusesAndRoles() { @@ -21,7 +23,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is finalized and send for approval but still editable */ + /** + * Project is finalized and send for approval but still editable + */ PENDING { @Override public Map> nextStatusesAndRoles() { @@ -32,7 +36,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is being review and cannot be edited */ + /** + * Project is being review and cannot be edited + */ REVIEWING { @Override public Map> nextStatusesAndRoles() { @@ -44,7 +50,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Pending requests from the reviewer */ + /** + * Pending requests from the reviewer + */ CHANGE_REQUEST { @Override public Map> nextStatusesAndRoles() { @@ -55,7 +63,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is denied */ + /** + * Project is denied + */ DENIED { @Override public Map> nextStatusesAndRoles() { @@ -65,7 +75,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is approved */ + /** + * Project is approved + */ APPROVED { @Override public Map> nextStatusesAndRoles() { @@ -75,7 +87,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is published and cannot be edited anymore */ + /** + * Project is published and cannot be edited anymore + */ PUBLISHED { @Override public Map> nextStatusesAndRoles() { @@ -85,7 +99,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is finished */ + /** + * Project is finished + */ CLOSED { @Override public Map> nextStatusesAndRoles() { @@ -95,7 +111,9 @@ public Map> nextStatusesAndRoles() { } }, - /** Project is archived */ + /** + * Project is archived + */ ARCHIVED { @Override public Map> nextStatusesAndRoles() { @@ -103,8 +121,6 @@ public Map> nextStatusesAndRoles() { } }; - public abstract Map> nextStatusesAndRoles(); - public static List getAllProjectStatusToViewAsCoordinator() { return Arrays.asList(ProjectStatus.APPROVED, ProjectStatus.PUBLISHED, ProjectStatus.CLOSED); } @@ -115,10 +131,12 @@ public static List getAllProjectStatusToViewAsResearcher() { public static List getAllProjectStatusToViewAsApprover() { return Stream.of(ProjectStatus.values()) - .filter( - projectStatus -> - projectStatus != ProjectStatus.DRAFT - && projectStatus != ProjectStatus.ARCHIVED) - .collect(Collectors.toList()); + .filter( + projectStatus -> + projectStatus != ProjectStatus.DRAFT + && projectStatus != ProjectStatus.ARCHIVED) + .collect(Collectors.toList()); } + + public abstract Map> nextStatusesAndRoles(); } diff --git a/src/main/java/org/highmed/numportal/domain/model/ProjectTransition.java b/src/main/java/org/highmed/numportal/domain/model/ProjectTransition.java index 428a2faa..2ca73b41 100644 --- a/src/main/java/org/highmed/numportal/domain/model/ProjectTransition.java +++ b/src/main/java/org/highmed/numportal/domain/model/ProjectTransition.java @@ -1,15 +1,24 @@ package org.highmed.numportal.domain.model; -import com.fasterxml.jackson.annotation.JsonBackReference; import org.highmed.numportal.domain.model.admin.UserDetails; -import java.io.Serializable; -import java.time.OffsetDateTime; -import jakarta.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; +import java.time.OffsetDateTime; + @Entity @Data @NoArgsConstructor diff --git a/src/main/java/org/highmed/numportal/domain/model/Roles.java b/src/main/java/org/highmed/numportal/domain/model/Roles.java index 9b536b43..c06561c4 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Roles.java +++ b/src/main/java/org/highmed/numportal/domain/model/Roles.java @@ -8,6 +8,7 @@ import java.util.Map; public class Roles { + public static final String SUPER_ADMIN = "SUPER_ADMIN"; public static final String CONTENT_ADMIN = "CONTENT_ADMIN"; public static final String ORGANIZATION_ADMIN = "ORGANIZATION_ADMIN"; @@ -33,7 +34,8 @@ public class Roles { private static final String REALM_ACCESS_CLAIM = "realm_access"; private static final String ROLES_CLAIM = "roles"; - private Roles() {} + private Roles() { + } public static List extractRoles(Jwt principal) { Map access = principal.getClaimAsMap(REALM_ACCESS_CLAIM); diff --git a/src/main/java/org/highmed/numportal/domain/model/Translation.java b/src/main/java/org/highmed/numportal/domain/model/Translation.java index adf38894..ca08c615 100644 --- a/src/main/java/org/highmed/numportal/domain/model/Translation.java +++ b/src/main/java/org/highmed/numportal/domain/model/Translation.java @@ -1,6 +1,15 @@ package org.highmed.numportal.domain.model; import org.highmed.numportal.domain.dto.Language; + +import jakarta.persistence.Cacheable; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -8,8 +17,6 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import jakarta.persistence.*; - @Entity @Data @Builder @@ -18,24 +25,25 @@ @Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) public class Translation { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @Column(name = "entity_group", nullable = false) - @Enumerated(EnumType.STRING) - private EntityGroup entityGroup; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "entity_group", nullable = false) + @Enumerated(EnumType.STRING) + private EntityGroup entityGroup; - @Column(name = "entity_id") - private Long entityId; + @Column(name = "entity_id") + private Long entityId; - @Column(nullable = false) - private String property; + @Column(nullable = false) + private String property; - @Column(nullable = false) - private String value; + @Column(nullable = false) + private String value; - @Column(name = "language_code", nullable = false) - @Enumerated(EnumType.STRING) - private Language language; + @Column(name = "language_code", nullable = false) + @Enumerated(EnumType.STRING) + private Language language; } diff --git a/src/main/java/org/highmed/numportal/domain/model/admin/User.java b/src/main/java/org/highmed/numportal/domain/model/admin/User.java index cf702fc5..44e5bf91 100644 --- a/src/main/java/org/highmed/numportal/domain/model/admin/User.java +++ b/src/main/java/org/highmed/numportal/domain/model/admin/User.java @@ -1,17 +1,17 @@ package org.highmed.numportal.domain.model.admin; +import org.highmed.numportal.domain.dto.OrganizationDto; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import org.highmed.numportal.domain.dto.OrganizationDto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - import java.util.Map; import java.util.Set; diff --git a/src/main/java/org/highmed/numportal/domain/model/admin/UserDetails.java b/src/main/java/org/highmed/numportal/domain/model/admin/UserDetails.java index 8efad292..25d45db3 100644 --- a/src/main/java/org/highmed/numportal/domain/model/admin/UserDetails.java +++ b/src/main/java/org/highmed/numportal/domain/model/admin/UserDetails.java @@ -1,16 +1,20 @@ package org.highmed.numportal.domain.model.admin; -import com.fasterxml.jackson.annotation.JsonBackReference; import org.highmed.numportal.domain.model.Organization; -import java.io.Serializable; -import java.time.LocalDateTime; -import jakarta.persistence.*; +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; +import java.time.LocalDateTime; + @Data @Entity @Builder @@ -31,14 +35,14 @@ public class UserDetails implements Serializable { private LocalDateTime createdDate; - public boolean isNotApproved() { - return !approved; - } - public UserDetails(String userId, Organization organization, boolean approved) { this.userId = userId; this.organization = organization; this.approved = approved; } + public boolean isNotApproved() { + return !approved; + } + } diff --git a/src/main/java/org/highmed/numportal/domain/repository/AqlCategoryRepository.java b/src/main/java/org/highmed/numportal/domain/repository/AqlCategoryRepository.java index ae8bc25b..085412ab 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/AqlCategoryRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/AqlCategoryRepository.java @@ -1,6 +1,7 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.AqlCategory; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -13,13 +14,13 @@ public interface AqlCategoryRepository extends JpaRepository { @Query( - value = "SELECT * FROM aql_category ", - countQuery = "SELECT COUNT (*) FROM aql_category", - nativeQuery = true) + value = "SELECT * FROM aql_category ", + countQuery = "SELECT COUNT (*) FROM aql_category", + nativeQuery = true) Page findAllCategories(Pageable pageable); @Query( - value = "SELECT * FROM aql_category ORDER BY aql_category.name->>'de' ASC", - nativeQuery = true) + value = "SELECT * FROM aql_category ORDER BY aql_category.name->>'de' ASC", + nativeQuery = true) List findAllCategories(); } diff --git a/src/main/java/org/highmed/numportal/domain/repository/AqlConverter.java b/src/main/java/org/highmed/numportal/domain/repository/AqlConverter.java index c8bfe8bf..8bdcef56 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/AqlConverter.java +++ b/src/main/java/org/highmed/numportal/domain/repository/AqlConverter.java @@ -1,9 +1,9 @@ package org.highmed.numportal.domain.repository; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.highmed.numportal.domain.model.CohortAql; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.persistence.AttributeConverter; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/org/highmed/numportal/domain/repository/AqlRepository.java b/src/main/java/org/highmed/numportal/domain/repository/AqlRepository.java index 9f72f682..31726749 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/AqlRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/AqlRepository.java @@ -1,6 +1,7 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.Aql; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/org/highmed/numportal/domain/repository/CategorySetConverter.java b/src/main/java/org/highmed/numportal/domain/repository/CategorySetConverter.java index 9cb02c6a..04c0594f 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/CategorySetConverter.java +++ b/src/main/java/org/highmed/numportal/domain/repository/CategorySetConverter.java @@ -1,17 +1,18 @@ package org.highmed.numportal.domain.repository; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.highmed.numportal.domain.model.ProjectCategories; -import java.io.IOException; -import java.util.Set; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.persistence.AttributeConverter; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import java.io.IOException; +import java.util.Set; + @Slf4j @AllArgsConstructor public class CategorySetConverter implements AttributeConverter, String> { diff --git a/src/main/java/org/highmed/numportal/domain/repository/CohortGroupRepository.java b/src/main/java/org/highmed/numportal/domain/repository/CohortGroupRepository.java index 26511092..2c8e3440 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/CohortGroupRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/CohortGroupRepository.java @@ -1,6 +1,7 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.CohortGroup; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/org/highmed/numportal/domain/repository/CohortRepository.java b/src/main/java/org/highmed/numportal/domain/repository/CohortRepository.java index ca521f62..4e8a67b3 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/CohortRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/CohortRepository.java @@ -1,9 +1,11 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.Cohort; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface CohortRepository extends JpaRepository { + } diff --git a/src/main/java/org/highmed/numportal/domain/repository/CommentRepository.java b/src/main/java/org/highmed/numportal/domain/repository/CommentRepository.java index 2bd375ab..7477966c 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/CommentRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/CommentRepository.java @@ -1,10 +1,12 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.Comment; -import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface CommentRepository extends JpaRepository { diff --git a/src/main/java/org/highmed/numportal/domain/repository/ContentItemRepository.java b/src/main/java/org/highmed/numportal/domain/repository/ContentItemRepository.java index a43ebfa5..ccae8d58 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/ContentItemRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/ContentItemRepository.java @@ -2,11 +2,13 @@ import org.highmed.numportal.domain.model.Content; import org.highmed.numportal.domain.model.ContentType; -import java.util.List; -import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface ContentItemRepository extends JpaRepository { diff --git a/src/main/java/org/highmed/numportal/domain/repository/CustomProjectRepository.java b/src/main/java/org/highmed/numportal/domain/repository/CustomProjectRepository.java index b13903b9..b8fb3f2d 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/CustomProjectRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/CustomProjectRepository.java @@ -2,10 +2,11 @@ import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.specification.ProjectSpecification; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface CustomProjectRepository { - Page findProjects(ProjectSpecification projectSpecification, Pageable pageable); + Page findProjects(ProjectSpecification projectSpecification, Pageable pageable); } diff --git a/src/main/java/org/highmed/numportal/domain/repository/MailDomainRepository.java b/src/main/java/org/highmed/numportal/domain/repository/MailDomainRepository.java index 826a650f..95489962 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/MailDomainRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/MailDomainRepository.java @@ -2,20 +2,21 @@ import org.highmed.numportal.domain.model.MailDomain; -import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface MailDomainRepository extends JpaRepository { Optional findByName(String name); - @Query("SELECT md FROM MailDomain md " + - "INNER JOIN md.organization org " + - "WHERE org.active = true") + @Query("SELECT md FROM MailDomain md " + + "INNER JOIN md.organization org " + + "WHERE org.active = true") List findAllByActiveOrganization(); } diff --git a/src/main/java/org/highmed/numportal/domain/repository/OrganizationRepository.java b/src/main/java/org/highmed/numportal/domain/repository/OrganizationRepository.java index 1f23af1d..b31c514e 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/OrganizationRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/OrganizationRepository.java @@ -1,6 +1,7 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.Organization; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java b/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java index dec1c717..ba6fa22f 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/ProjectRepository.java @@ -2,6 +2,7 @@ import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.ProjectStatus; + import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -20,8 +21,8 @@ List findLatestProjects( int count, String status1, String status2, String status3); - @Query(value = "SELECT new Project(pr.id, pr.name, pr.createDate, pr.coordinator) FROM Project pr " + - "WHERE pr.status IN (:statuses) ORDER BY pr.createDate DESC ") + @Query(value = "SELECT new Project(pr.id, pr.name, pr.createDate, pr.coordinator) FROM Project pr " + + "WHERE pr.status IN (:statuses) ORDER BY pr.createDate DESC ") List findByStatusInOrderByCreateDateDesc(List statuses, Pageable pageable); long countByStatus(ProjectStatus projectStatus); diff --git a/src/main/java/org/highmed/numportal/domain/repository/ProjectRepositoryImpl.java b/src/main/java/org/highmed/numportal/domain/repository/ProjectRepositoryImpl.java index ab95b008..04fb67b7 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/ProjectRepositoryImpl.java +++ b/src/main/java/org/highmed/numportal/domain/repository/ProjectRepositoryImpl.java @@ -5,9 +5,17 @@ import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.admin.UserDetails; import org.highmed.numportal.domain.specification.ProjectSpecification; + import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import jakarta.persistence.criteria.*; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Fetch; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -21,66 +29,65 @@ @Component public class ProjectRepositoryImpl implements CustomProjectRepository { - @PersistenceContext - private EntityManager entityManager; - - @Override - public Page findProjects(ProjectSpecification projectSpecification, Pageable pageable) { - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + @PersistenceContext + private EntityManager entityManager; - CriteriaQuery selectProjectQuery = cb.createQuery(Project.class); - CriteriaQuery countQuery = cb.createQuery(Long.class); - Root countRoot = countQuery.from(selectProjectQuery.getResultType()); - Root root = selectProjectQuery.from(Project.class); + @Override + public Page findProjects(ProjectSpecification projectSpecification, Pageable pageable) { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery selectProjectQuery = cb.createQuery(Project.class); + CriteriaQuery countQuery = cb.createQuery(Long.class); + Root countRoot = countQuery.from(selectProjectQuery.getResultType()); + Root root = selectProjectQuery.from(Project.class); - Predicate selectProjectPredicate = projectSpecification.toPredicate(root, cb); - Predicate selectCountProjectPredicate = projectSpecification.toPredicate(countRoot, cb); - countQuery.select(cb.countDistinct(countRoot.get("id"))) - .where(selectCountProjectPredicate); - Long count = entityManager.createQuery(countQuery).getSingleResult(); + Predicate selectProjectPredicate = projectSpecification.toPredicate(root, cb); + Predicate selectCountProjectPredicate = projectSpecification.toPredicate(countRoot, cb); + countQuery.select(cb.countDistinct(countRoot.get("id"))) + .where(selectCountProjectPredicate); + Long count = entityManager.createQuery(countQuery).getSingleResult(); - List> groupByExpressions = new ArrayList<>(); - groupByExpressions.add(root.get("id")); - if (projectSpecification.getSortOrder() != null) { - Sort.Order sortOrder = projectSpecification.getSortOrder(); - Expression orderByExpression = null; - if (ProjectSpecification.COORDINATOR_ORGANIZATION.equals(projectSpecification.getSortOrder().getProperty())) { - Fetch coordinator = root.fetch("coordinator"); - Fetch organization = coordinator.fetch("organization", JoinType.LEFT); - Join coordinatorJoin = (Join) coordinator; - Join organizationJoin = (Join) organization; - groupByExpressions.add(coordinatorJoin.get("userId")); - groupByExpressions.add(organizationJoin.get("id")); - orderByExpression = organizationJoin.get("name"); - } else { - if (ProjectSpecification.COLUMN_PROJECT_STATUS.equals(projectSpecification.getSortOrder().getProperty())) { - Join translationJoin = root.join("translations", JoinType.LEFT); - translationJoin.on(cb.and( - cb.equal(translationJoin.get("entityGroup"), EntityGroup.PROJECT_STATUS), - cb.equal(translationJoin.get("language"), projectSpecification.getLanguage()), - cb.equal(translationJoin.get("property"), root.get("status")) - )); - groupByExpressions.add(translationJoin.get("value")); - orderByExpression = translationJoin.get("value"); - } else if (!"author".equals(sortOrder.getProperty())) { - orderByExpression = root.get(sortOrder.getProperty()); - } - } - if (Objects.nonNull(orderByExpression)) { - if (sortOrder.getDirection().isAscending()) { - selectProjectQuery.orderBy(cb.asc(orderByExpression)); - } else { - selectProjectQuery.orderBy(cb.desc(orderByExpression)); - } - } + List> groupByExpressions = new ArrayList<>(); + groupByExpressions.add(root.get("id")); + if (projectSpecification.getSortOrder() != null) { + Sort.Order sortOrder = projectSpecification.getSortOrder(); + Expression orderByExpression = null; + if (ProjectSpecification.COORDINATOR_ORGANIZATION.equals(projectSpecification.getSortOrder().getProperty())) { + Fetch coordinator = root.fetch("coordinator"); + Fetch organization = coordinator.fetch("organization", JoinType.LEFT); + Join coordinatorJoin = (Join) coordinator; + Join organizationJoin = (Join) organization; + groupByExpressions.add(coordinatorJoin.get("userId")); + groupByExpressions.add(organizationJoin.get("id")); + orderByExpression = organizationJoin.get("name"); + } else { + if (ProjectSpecification.COLUMN_PROJECT_STATUS.equals(projectSpecification.getSortOrder().getProperty())) { + Join translationJoin = root.join("translations", JoinType.LEFT); + translationJoin.on(cb.and( + cb.equal(translationJoin.get("entityGroup"), EntityGroup.PROJECT_STATUS), + cb.equal(translationJoin.get("language"), projectSpecification.getLanguage()), + cb.equal(translationJoin.get("property"), root.get("status")) + )); + groupByExpressions.add(translationJoin.get("value")); + orderByExpression = translationJoin.get("value"); + } else if (!"author".equals(sortOrder.getProperty())) { + orderByExpression = root.get(sortOrder.getProperty()); + } + } + if (Objects.nonNull(orderByExpression)) { + if (sortOrder.getDirection().isAscending()) { + selectProjectQuery.orderBy(cb.asc(orderByExpression)); + } else { + selectProjectQuery.orderBy(cb.desc(orderByExpression)); } - selectProjectQuery.select(root) - .where(selectProjectPredicate) - .groupBy(groupByExpressions); - List result = entityManager.createQuery(selectProjectQuery) - .setFirstResult((int) pageable.getOffset()) - .setMaxResults(pageable.getPageSize()).getResultList(); - return new PageImpl<>(result, pageable, count); + } } + selectProjectQuery.select(root) + .where(selectProjectPredicate) + .groupBy(groupByExpressions); + List result = entityManager.createQuery(selectProjectQuery) + .setFirstResult((int) pageable.getOffset()) + .setMaxResults(pageable.getPageSize()).getResultList(); + return new PageImpl<>(result, pageable, count); + } } diff --git a/src/main/java/org/highmed/numportal/domain/repository/ProjectTransitionRepository.java b/src/main/java/org/highmed/numportal/domain/repository/ProjectTransitionRepository.java index 9e477088..d7b5a811 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/ProjectTransitionRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/ProjectTransitionRepository.java @@ -2,11 +2,13 @@ import org.highmed.numportal.domain.model.ProjectStatus; import org.highmed.numportal.domain.model.ProjectTransition; -import java.util.List; -import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface ProjectTransitionRepository extends JpaRepository { diff --git a/src/main/java/org/highmed/numportal/domain/repository/StringSetConverter.java b/src/main/java/org/highmed/numportal/domain/repository/StringSetConverter.java index 02ebbcb6..64ce0813 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/StringSetConverter.java +++ b/src/main/java/org/highmed/numportal/domain/repository/StringSetConverter.java @@ -2,15 +2,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.util.Set; - import jakarta.persistence.AttributeConverter; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import java.io.IOException; +import java.util.Set; + @Slf4j @AllArgsConstructor public class StringSetConverter implements AttributeConverter, String> { diff --git a/src/main/java/org/highmed/numportal/domain/repository/TranslationRepository.java b/src/main/java/org/highmed/numportal/domain/repository/TranslationRepository.java index 614da576..e84ec226 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/TranslationRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/TranslationRepository.java @@ -1,10 +1,11 @@ package org.highmed.numportal.domain.repository; +import org.highmed.numportal.domain.model.Translation; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.highmed.numportal.domain.model.Translation; - @Repository public interface TranslationRepository extends JpaRepository { + } diff --git a/src/main/java/org/highmed/numportal/domain/repository/UserDetailsRepository.java b/src/main/java/org/highmed/numportal/domain/repository/UserDetailsRepository.java index 13aa7d00..4d0cb7a5 100644 --- a/src/main/java/org/highmed/numportal/domain/repository/UserDetailsRepository.java +++ b/src/main/java/org/highmed/numportal/domain/repository/UserDetailsRepository.java @@ -1,6 +1,7 @@ package org.highmed.numportal.domain.repository; import org.highmed.numportal.domain.model.admin.UserDetails; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/org/highmed/numportal/domain/specification/AqlSpecification.java b/src/main/java/org/highmed/numportal/domain/specification/AqlSpecification.java index 1863ada9..b657a313 100644 --- a/src/main/java/org/highmed/numportal/domain/specification/AqlSpecification.java +++ b/src/main/java/org/highmed/numportal/domain/specification/AqlSpecification.java @@ -1,19 +1,26 @@ package org.highmed.numportal.domain.specification; +import org.highmed.numportal.domain.dto.Language; +import org.highmed.numportal.domain.dto.SearchCriteria; +import org.highmed.numportal.domain.dto.SearchFilter; import org.highmed.numportal.domain.model.Aql; import org.highmed.numportal.domain.model.AqlCategory; import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.domain.model.admin.UserDetails; -import org.highmed.numportal.domain.dto.Language; -import org.highmed.numportal.domain.dto.SearchCriteria; -import org.highmed.numportal.domain.dto.SearchFilter; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import lombok.Getter; import lombok.experimental.SuperBuilder; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.data.jpa.domain.Specification; -import jakarta.persistence.criteria.*; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -22,68 +29,72 @@ @Getter public class AqlSpecification extends BaseSpecification implements Specification { - private static final String AQL_CATEGORY = "category"; + private static final String AQL_CATEGORY = "category"; - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { - Join owner = root.join("owner", JoinType.INNER); - Predicate ownedPred = criteriaBuilder.equal(owner.get("userId"), loggedInUserId); - Predicate publicAql = criteriaBuilder.equal(root.get("publicAql"), Boolean.TRUE); - Predicate ownedOrPublic = criteriaBuilder.or(ownedPred, publicAql); - if (sortOrder != null && sortOrder.getProperty().equals(AQL_CATEGORY)) { - Join aqlCategory = root.join(AQL_CATEGORY, JoinType.LEFT); - Expression aqlCategoryName = criteriaBuilder.function("json_extract_path_text", String.class, aqlCategory.get("name"), - criteriaBuilder.literal(language.name())); - if (sortOrder.getDirection().isAscending()) { - query.orderBy(criteriaBuilder.asc(aqlCategoryName)); - } else { - query.orderBy(criteriaBuilder.desc(aqlCategoryName)); - } - } + Join owner = root.join("owner", JoinType.INNER); + Predicate ownedPred = criteriaBuilder.equal(owner.get("userId"), loggedInUserId); + Predicate publicAql = criteriaBuilder.equal(root.get("publicAql"), Boolean.TRUE); + Predicate ownedOrPublic = criteriaBuilder.or(ownedPred, publicAql); + if (sortOrder != null && sortOrder.getProperty().equals(AQL_CATEGORY)) { + Join aqlCategory = root.join(AQL_CATEGORY, JoinType.LEFT); + Expression aqlCategoryName = criteriaBuilder.function("json_extract_path_text", String.class, aqlCategory.get("name"), + criteriaBuilder.literal(language.name())); + if (sortOrder.getDirection().isAscending()) { + query.orderBy(criteriaBuilder.asc(aqlCategoryName)); + } else { + query.orderBy(criteriaBuilder.desc(aqlCategoryName)); + } + } - if (Objects.nonNull(filter)) { - List predicates = new ArrayList<>(); - List nameLikePredicates = new ArrayList<>(); - if (StringUtils.isNotEmpty((String) filter.get(SearchCriteria.FILTER_SEARCH_BY_KEY))) { - String searchInput = "%" + ((String) filter.get(SearchCriteria.FILTER_SEARCH_BY_KEY)).toUpperCase() + "%"; - Predicate alqNameLike = Language.de.equals(language) ? criteriaBuilder.like(criteriaBuilder.upper(root.get("name")), searchInput) : - criteriaBuilder.like(criteriaBuilder.upper(root.get("nameTranslated")), searchInput); - nameLikePredicates.add(alqNameLike); + if (Objects.nonNull(filter)) { + List predicates = new ArrayList<>(); + List nameLikePredicates = new ArrayList<>(); + if (StringUtils.isNotEmpty((String) filter.get(SearchCriteria.FILTER_SEARCH_BY_KEY))) { + String searchInput = "%" + ((String) filter.get(SearchCriteria.FILTER_SEARCH_BY_KEY)).toUpperCase() + "%"; + Predicate alqNameLike = Language.de.equals(language) ? criteriaBuilder.like(criteriaBuilder.upper(root.get("name")), searchInput) + : criteriaBuilder.like(criteriaBuilder.upper(root.get("nameTranslated")), searchInput); + nameLikePredicates.add(alqNameLike); - Join aqlCategory = root.join(AQL_CATEGORY, JoinType.LEFT); - Predicate aqlCategoryNameLike = criteriaBuilder.like(criteriaBuilder.upper( - criteriaBuilder.function("json_extract_path_text", String.class, aqlCategory.get("name"), - criteriaBuilder.literal(language.name())) - ), searchInput); + Join aqlCategory = root.join(AQL_CATEGORY, JoinType.LEFT); + Predicate aqlCategoryNameLike = criteriaBuilder.like( + criteriaBuilder.upper( + criteriaBuilder.function("json_extract_path_text", String.class, aqlCategory.get("name"), criteriaBuilder.literal(language.name()))), + searchInput); - nameLikePredicates.add(aqlCategoryNameLike); - if (CollectionUtils.isNotEmpty(ownersUUID)) { - Predicate ownerNameLike = owner.get("userId").in(ownersUUID); - nameLikePredicates.add(ownerNameLike); - } - } - SearchFilter filterType = filter.containsKey(SearchCriteria.FILTER_BY_TYPE_KEY) ? - SearchFilter.valueOf((String) filter.get(SearchCriteria.FILTER_BY_TYPE_KEY)) : - SearchFilter.ALL; - switch (filterType) { - case ALL -> predicates.add(ownedOrPublic); - case OWNED -> predicates.add(ownedPred); - case ORGANIZATION -> { - Join ownerOrganization = owner.join("organization", JoinType.INNER); - Predicate sameOrganization = criteriaBuilder.equal(ownerOrganization.get("id"), loggedInUserOrganizationId); - predicates.add(sameOrganization); - predicates.add(ownedOrPublic); - } - } - if (CollectionUtils.isNotEmpty(nameLikePredicates)) { - Predicate finaleNameLike = criteriaBuilder.or(nameLikePredicates.toArray(Predicate[]::new)); - predicates.add(finaleNameLike); - } - return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); - } else { - //all owned or public - return ownedOrPublic; + nameLikePredicates.add(aqlCategoryNameLike); + if (CollectionUtils.isNotEmpty(ownersUUID)) { + Predicate ownerNameLike = owner.get("userId").in(ownersUUID); + nameLikePredicates.add(ownerNameLike); + } + } + SearchFilter filterType = + filter.containsKey(SearchCriteria.FILTER_BY_TYPE_KEY) + ? SearchFilter.valueOf((String) filter.get(SearchCriteria.FILTER_BY_TYPE_KEY)) + : SearchFilter.ALL; + switch (filterType) { + case ALL -> predicates.add(ownedOrPublic); + case OWNED -> predicates.add(ownedPred); + case ORGANIZATION -> { + Join ownerOrganization = owner.join("organization", JoinType.INNER); + Predicate sameOrganization = criteriaBuilder.equal(ownerOrganization.get("id"), loggedInUserOrganizationId); + predicates.add(sameOrganization); + predicates.add(ownedOrPublic); + } + default -> { + // Nothing to do } + } + if (CollectionUtils.isNotEmpty(nameLikePredicates)) { + Predicate finaleNameLike = criteriaBuilder.or(nameLikePredicates.toArray(Predicate[]::new)); + predicates.add(finaleNameLike); + } + return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); + } else { + //all owned or public + return ownedOrPublic; } + } } diff --git a/src/main/java/org/highmed/numportal/domain/specification/BaseSpecification.java b/src/main/java/org/highmed/numportal/domain/specification/BaseSpecification.java index 629d1c36..8c2b3bba 100644 --- a/src/main/java/org/highmed/numportal/domain/specification/BaseSpecification.java +++ b/src/main/java/org/highmed/numportal/domain/specification/BaseSpecification.java @@ -1,15 +1,16 @@ package org.highmed.numportal.domain.specification; import org.highmed.numportal.domain.dto.Language; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import org.springframework.data.domain.Sort; -import javax.annotation.Nonnull; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; @SuperBuilder @Getter @@ -17,16 +18,16 @@ @NoArgsConstructor public class BaseSpecification { - protected Map filter; + protected Map filter; - @Nonnull - protected String loggedInUserId; + @Nonnull + protected String loggedInUserId; - protected Long loggedInUserOrganizationId; + protected Long loggedInUserOrganizationId; - protected Language language; + protected Language language; - protected Sort.Order sortOrder; + protected Sort.Order sortOrder; - protected Set ownersUUID; + protected Set ownersUUID; } diff --git a/src/main/java/org/highmed/numportal/domain/specification/OrganizationSpecification.java b/src/main/java/org/highmed/numportal/domain/specification/OrganizationSpecification.java index badef16a..2a6396ea 100644 --- a/src/main/java/org/highmed/numportal/domain/specification/OrganizationSpecification.java +++ b/src/main/java/org/highmed/numportal/domain/specification/OrganizationSpecification.java @@ -1,7 +1,12 @@ package org.highmed.numportal.domain.specification; -import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.domain.dto.SearchCriteria; +import org.highmed.numportal.domain.model.Organization; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -9,7 +14,6 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.data.jpa.domain.Specification; -import jakarta.persistence.criteria.*; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -21,30 +25,31 @@ @Getter public class OrganizationSpecification implements Specification { - private static final String WILDCARD_PERCENTAGE_SIGN = "%"; - private Map filter; + private static final String WILDCARD_PERCENTAGE_SIGN = "%"; + private Map filter; - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { - if (Objects.nonNull(filter)) { - List predicates = new ArrayList<>(); - if (filter.containsKey(SearchCriteria.FILTER_SEARCH_BY_KEY)) { - String entry = (String) filter.get(SearchCriteria.FILTER_SEARCH_BY_KEY); - if (StringUtils.isNotEmpty(entry)) { - predicates.add(criteriaBuilder.like( - criteriaBuilder.upper(root.get("name")), - WILDCARD_PERCENTAGE_SIGN + entry.toUpperCase() + WILDCARD_PERCENTAGE_SIGN)); - } - } - if (filter.containsKey(SearchCriteria.FILTER_BY_ACTIVE_ORGANIZATION)) { - String entry = (String) filter.get(SearchCriteria.FILTER_BY_ACTIVE_ORGANIZATION); - if (StringUtils.isNotEmpty(entry)) { - predicates.add(criteriaBuilder.equal(root.get("active"), - Boolean.valueOf(entry))); - } - } - return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { + if (Objects.nonNull(filter)) { + List predicates = new ArrayList<>(); + if (filter.containsKey(SearchCriteria.FILTER_SEARCH_BY_KEY)) { + String entry = (String) filter.get(SearchCriteria.FILTER_SEARCH_BY_KEY); + if (StringUtils.isNotEmpty(entry)) { + predicates.add(criteriaBuilder.like( + criteriaBuilder.upper(root.get("name")), + WILDCARD_PERCENTAGE_SIGN + entry.toUpperCase() + WILDCARD_PERCENTAGE_SIGN)); + } + } + if (filter.containsKey(SearchCriteria.FILTER_BY_ACTIVE_ORGANIZATION)) { + String entry = (String) filter.get(SearchCriteria.FILTER_BY_ACTIVE_ORGANIZATION); + if (StringUtils.isNotEmpty(entry)) { + predicates.add(criteriaBuilder.equal( + root.get("active"), + Boolean.valueOf(entry))); } - return null; + } + return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); } + return null; + } } diff --git a/src/main/java/org/highmed/numportal/domain/specification/ProjectSpecification.java b/src/main/java/org/highmed/numportal/domain/specification/ProjectSpecification.java index efb5980d..8cecfc0a 100644 --- a/src/main/java/org/highmed/numportal/domain/specification/ProjectSpecification.java +++ b/src/main/java/org/highmed/numportal/domain/specification/ProjectSpecification.java @@ -1,114 +1,126 @@ package org.highmed.numportal.domain.specification; +import org.highmed.numportal.domain.dto.SearchCriteria; +import org.highmed.numportal.domain.dto.SearchFilter; import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.ProjectStatus; import org.highmed.numportal.domain.model.Roles; import org.highmed.numportal.domain.model.admin.UserDetails; -import org.highmed.numportal.domain.dto.SearchCriteria; -import org.highmed.numportal.domain.dto.SearchFilter; -import jakarta.persistence.criteria.*; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import lombok.Getter; import lombok.experimental.SuperBuilder; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @SuperBuilder @Getter public class ProjectSpecification extends BaseSpecification { - public static final String COLUMN_PROJECT_STATUS = "status"; + public static final String COLUMN_PROJECT_STATUS = "status"; - public static final String COORDINATOR_ORGANIZATION = "organization"; + public static final String COORDINATOR_ORGANIZATION = "organization"; - private static final String WILDCARD_PERCENTAGE_SIGN = "%"; + private static final String WILDCARD_PERCENTAGE_SIGN = "%"; - private List roles; + private List roles; - public Predicate toPredicate(Root root, CriteriaBuilder criteriaBuilder) { - List roleBasedPredicates = new ArrayList<>(); - Join coordinator = root.join("coordinator", JoinType.INNER); + public Predicate toPredicate(Root root, CriteriaBuilder criteriaBuilder) { + List roleBasedPredicates = new ArrayList<>(); + Join coordinator = root.join("coordinator", JoinType.INNER); - if (roles.contains(Roles.STUDY_COORDINATOR)) { - Predicate coordinatorPredicate = criteriaBuilder.equal(coordinator.get("userId"), loggedInUserId); - Predicate coordinatorStatuses = searchByStatus(root, ProjectStatus.getAllProjectStatusToViewAsCoordinator()); - Predicate combined = criteriaBuilder.or(coordinatorPredicate, coordinatorStatuses); - roleBasedPredicates.add(combined); - } - if (roles.contains(Roles.RESEARCHER)) { - Predicate researcherPredicate = criteriaBuilder.equal(root.join("researchers", JoinType.LEFT).get("userId"), loggedInUserId); - Predicate researcherStatuses = searchByStatus(root, ProjectStatus.getAllProjectStatusToViewAsResearcher()); - Predicate combined = criteriaBuilder.and(researcherPredicate, researcherStatuses); - roleBasedPredicates.add(combined); - } - if (roles.contains(Roles.STUDY_APPROVER)) { - roleBasedPredicates.add(searchByStatus(root, ProjectStatus.getAllProjectStatusToViewAsApprover())); - } - Predicate finalRoleBasedPredicate = criteriaBuilder.or(roleBasedPredicates.toArray(Predicate[]::new)); - Predicate filterPredicate; - if (Objects.nonNull(filter)) { - List predicates = new ArrayList<>(); - for (Map.Entry entry : filter.entrySet()) { - if (SearchCriteria.FILTER_BY_TYPE_KEY.equals(entry.getKey()) && StringUtils.isNotEmpty((String) entry.getValue())) { - SearchFilter typeValue = SearchFilter.valueOf((String) entry.getValue()); - switch (typeValue) { - case OWNED -> { - predicates.add(criteriaBuilder.equal(coordinator.get("userId"), loggedInUserId)); - predicates.add(criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED)); - } - case ORGANIZATION -> { - Join coordinatorOrganization = coordinator.join(COORDINATOR_ORGANIZATION, JoinType.INNER); - predicates.add(criteriaBuilder.equal(coordinatorOrganization.get("id"), loggedInUserOrganizationId)); - predicates.add(criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED)); - } - case ARCHIVED -> predicates.add(searchByStatus(root, List.of(ProjectStatus.ARCHIVED))); - case ALL -> // IN FE default ALL tag shows all projects based on roles except archived ones - predicates.add(criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED)); - } - } - if (SearchCriteria.FILTER_SEARCH_BY_KEY.equals(entry.getKey()) && StringUtils.isNotEmpty((String) entry.getValue())) { - String searchValue = WILDCARD_PERCENTAGE_SIGN + ((String) entry.getValue()).toUpperCase() + WILDCARD_PERCENTAGE_SIGN; - Predicate projectNameLike = criteriaBuilder.like(criteriaBuilder.upper(root.get("name")), searchValue); - Predicate authorNameLike = null; - if (CollectionUtils.isNotEmpty(ownersUUID)) { - authorNameLike = coordinator.get("userId").in(ownersUUID); - } - if (Objects.nonNull(authorNameLike)) { - predicates.add(criteriaBuilder.or(projectNameLike, authorNameLike)); - } else { - predicates.add(projectNameLike); - } - } - if (SearchCriteria.FILTER_BY_STATUS.equals(entry.getKey()) && StringUtils.isNotEmpty((String) entry.getValue())) { - predicates.add(searchByStatus(root, getStatusFilter((String) entry.getValue()))); - } + if (roles.contains(Roles.STUDY_COORDINATOR)) { + Predicate coordinatorPredicate = criteriaBuilder.equal(coordinator.get("userId"), loggedInUserId); + Predicate coordinatorStatuses = searchByStatus(root, ProjectStatus.getAllProjectStatusToViewAsCoordinator()); + Predicate combined = criteriaBuilder.or(coordinatorPredicate, coordinatorStatuses); + roleBasedPredicates.add(combined); + } + if (roles.contains(Roles.RESEARCHER)) { + Predicate researcherPredicate = criteriaBuilder.equal(root.join("researchers", JoinType.LEFT).get("userId"), loggedInUserId); + Predicate researcherStatuses = searchByStatus(root, ProjectStatus.getAllProjectStatusToViewAsResearcher()); + Predicate combined = criteriaBuilder.and(researcherPredicate, researcherStatuses); + roleBasedPredicates.add(combined); + } + if (roles.contains(Roles.STUDY_APPROVER)) { + roleBasedPredicates.add(searchByStatus(root, ProjectStatus.getAllProjectStatusToViewAsApprover())); + } + Predicate finalRoleBasedPredicate = criteriaBuilder.or(roleBasedPredicates.toArray(Predicate[]::new)); + Predicate filterPredicate; + if (Objects.nonNull(filter)) { + List predicates = new ArrayList<>(); + for (Map.Entry entry : filter.entrySet()) { + if (SearchCriteria.FILTER_BY_TYPE_KEY.equals(entry.getKey()) && StringUtils.isNotEmpty((String) entry.getValue())) { + SearchFilter typeValue = SearchFilter.valueOf((String) entry.getValue()); + switch (typeValue) { + case OWNED -> { + predicates.add(criteriaBuilder.equal(coordinator.get("userId"), loggedInUserId)); + predicates.add(criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED)); + } + case ORGANIZATION -> { + Join coordinatorOrganization = coordinator.join(COORDINATOR_ORGANIZATION, JoinType.INNER); + predicates.add(criteriaBuilder.equal(coordinatorOrganization.get("id"), loggedInUserOrganizationId)); + predicates.add(criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED)); + } + case ARCHIVED -> predicates.add(searchByStatus(root, List.of(ProjectStatus.ARCHIVED))); + case ALL -> // IN FE default ALL tag shows all projects based on roles except archived ones + predicates.add(criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED)); + default -> { + // Nothing to do } - filterPredicate = criteriaBuilder.and(predicates.toArray(Predicate[]::new)); - } else { - // IN FE all tag shows all projects based on roles except archived ones - filterPredicate = criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED); + } } - if (Objects.nonNull(filterPredicate)) { - return criteriaBuilder.and(finalRoleBasedPredicate, filterPredicate); - } else { - return finalRoleBasedPredicate; + if (SearchCriteria.FILTER_SEARCH_BY_KEY.equals(entry.getKey()) && StringUtils.isNotEmpty((String) entry.getValue())) { + String searchValue = WILDCARD_PERCENTAGE_SIGN + ((String) entry.getValue()).toUpperCase() + WILDCARD_PERCENTAGE_SIGN; + Predicate projectNameLike = criteriaBuilder.like(criteriaBuilder.upper(root.get("name")), searchValue); + Predicate authorNameLike = null; + if (CollectionUtils.isNotEmpty(ownersUUID)) { + authorNameLike = coordinator.get("userId").in(ownersUUID); + } + if (Objects.nonNull(authorNameLike)) { + predicates.add(criteriaBuilder.or(projectNameLike, authorNameLike)); + } else { + predicates.add(projectNameLike); + } } - } - - private Predicate searchByStatus(Root root, - List statuses) { - if (CollectionUtils.isEmpty(statuses)) { - throw new IllegalArgumentException("status cannot be null"); + if (SearchCriteria.FILTER_BY_STATUS.equals(entry.getKey()) && StringUtils.isNotEmpty((String) entry.getValue())) { + predicates.add(searchByStatus(root, getStatusFilter((String) entry.getValue()))); } - return root.get(COLUMN_PROJECT_STATUS).in(statuses); + } + filterPredicate = criteriaBuilder.and(predicates.toArray(Predicate[]::new)); + } else { + // IN FE all tag shows all projects based on roles except archived ones + filterPredicate = criteriaBuilder.notEqual(root.get(COLUMN_PROJECT_STATUS), ProjectStatus.ARCHIVED); } + if (Objects.nonNull(filterPredicate)) { + return criteriaBuilder.and(finalRoleBasedPredicate, filterPredicate); + } else { + return finalRoleBasedPredicate; + } + } - private List getStatusFilter(String status) { - return Arrays.stream(status.split(",")) - .map(ProjectStatus::valueOf) - .collect(Collectors.toList()); + private Predicate searchByStatus(Root root, + List statuses) { + if (CollectionUtils.isEmpty(statuses)) { + throw new IllegalArgumentException("status cannot be null"); } + return root.get(COLUMN_PROJECT_STATUS).in(statuses); + } + + private List getStatusFilter(String status) { + return Arrays.stream(status.split(",")) + .map(ProjectStatus::valueOf) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/highmed/numportal/domain/specification/UserDetailsSpecification.java b/src/main/java/org/highmed/numportal/domain/specification/UserDetailsSpecification.java index 95e387e8..37620bf6 100644 --- a/src/main/java/org/highmed/numportal/domain/specification/UserDetailsSpecification.java +++ b/src/main/java/org/highmed/numportal/domain/specification/UserDetailsSpecification.java @@ -2,7 +2,13 @@ import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.domain.model.admin.UserDetails; -import jakarta.persistence.criteria.*; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -18,25 +24,25 @@ @Getter public class UserDetailsSpecification implements Specification { - private Long loggedInUserOrganizationId; - - private Set usersUUID; - - private Boolean approved; - - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { - List predicates = new ArrayList<>(); - if (CollectionUtils.isNotEmpty(usersUUID)) { - predicates.add(root.get("userId").in(usersUUID)); - } - if (approved != null) { - predicates.add(criteriaBuilder.equal(root.get("approved"), approved)); - } - if (loggedInUserOrganizationId != null) { - Join userOrganization = root.join("organization", JoinType.INNER); - predicates.add(criteriaBuilder.equal(userOrganization.get("id"), loggedInUserOrganizationId)); - } - return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); + private Long loggedInUserOrganizationId; + + private Set usersUUID; + + private Boolean approved; + + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { + List predicates = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(usersUUID)) { + predicates.add(root.get("userId").in(usersUUID)); + } + if (approved != null) { + predicates.add(criteriaBuilder.equal(root.get("approved"), approved)); + } + if (loggedInUserOrganizationId != null) { + Join userOrganization = root.join("organization", JoinType.INNER); + predicates.add(criteriaBuilder.equal(userOrganization.get("id"), loggedInUserOrganizationId)); } + return criteriaBuilder.and(predicates.toArray(Predicate[]::new)); + } } diff --git a/src/main/java/org/highmed/numportal/domain/templates/ExceptionsTemplate.java b/src/main/java/org/highmed/numportal/domain/templates/ExceptionsTemplate.java index f07c9f9b..63d65a48 100644 --- a/src/main/java/org/highmed/numportal/domain/templates/ExceptionsTemplate.java +++ b/src/main/java/org/highmed/numportal/domain/templates/ExceptionsTemplate.java @@ -1,346 +1,347 @@ package org.highmed.numportal.domain.templates; +import org.highmed.numportal.service.exception.dto.ExceptionDto; + import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import org.highmed.numportal.service.exception.dto.ExceptionDto; - public class ExceptionsTemplate { - public final static String TOKEN_IS_NOT_VALID_MSG = "Token is not valid"; - public final static String RECORD_NOT_FOUND_MSG = "Record not found"; - public final static String RECORD_ALREADY_EXISTS = "Record already exists"; - public final static String PASS_NOT_MATCHING = "Password not match"; - - public final static String USER_UNAUTHORISED_EXCEPTION = "User %s is unauthorized to access this profile!"; - public final static String USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE = "Username not found or no longer active"; - - //ForbiddenException - AqlService - public final static String CANNOT_ACCESS_THIS_AQL = "Cannot access this aql."; - public final static String CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED = "Cannot access this resource. Logged in user is not approved."; - public final static String AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER = "Aql edit for aql with id: %s is not allowed. Aql has different owner."; - public final static String CANNOT_DELETE_AQL = "Cannot delete aql: %s"; - public final static String CHANGING_COHORT_ONLY_ALLOWED_BY_THE_OWNER_OF_THE_PROJECT = "Changing cohort only allowed by the owner of the project"; - public final static String COHORT_CHANGE_ONLY_ALLOWED_ON_PROJECT_STATUS_DRAFT_OR_PENDING = "Cohort change only allowed on project status draft or pending"; - //CommentService - public final static String COMMENT_EDIT_FOR_COMMENT_WITH_ID_IS_NOT_ALLOWED_COMMENT_HAS_DIFFERENT_AUTHOR = "Comment edit for comment with id: %s not allowed. Comment has different author"; - public final static String CANNOT_DELETE_COMMENT = "Cannot delete comment: %s"; - //ResourceNotFound - public final static String AQL_NOT_FOUND = "Aql not found: %s"; - public final static String CANNOT_FIND_AQL = "Cannot find aql: %s"; - public final static String CATEGORY_BY_ID_NOT_FOUND = "Category by id %s Not found"; - public final static String CATEGORY_WITH_ID_DOES_NOT_EXIST = "Category with id %s does not exist."; - - //BadRequestException - Organization Service - public final static String CATEGORY_ID_CANT_BE_NULL = "Category id can't be null"; - public final static String THE_CATEGORY_IS_NOT_EMPTY_CANT_DELETE_IT = "The category is not empty, can't delete it."; - public final static String COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE = "Could not serialize aql validation response: %s"; - public final static String INVALID_AQL_ID = "Invalid aql id"; - public final static String COHORT_NOT_FOUND = "Cohort not found: %s"; - public final static String COHORT_GROUP_CANNOT_BE_EMPTY = "Cohort group cannot be empty"; - public final static String INVALID_COHORT_GROUP_AQL_MISSING = "Invalid cohort group. Aql missing."; - public static final String INVALID_COHORT_GROUP_CHILDREN_MISSING = "The query is invalid. Please select at least one criterion."; - public static final String INVALID_COHORT_GROUP_AQL_MISSING_PARAMETERS = "The query is invalid. The value of at least one criterion is missing."; - public final static String INVALID_COMMENT_ID = "Invalid commentId id"; - public final static String ORGANIZATION_NAME_MUST_BE_UNIQUE = "Organization name must be unique: %s"; - public final static String ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS = "Organization mail domain already exists: %s"; - public final static String ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY = "Organization mail domain cannot be null or empty"; - public final static String INVALID_MAIL_DOMAIN = "Invalid mail domain: %s"; - public final static String ORGANIZATION_NOT_FOUND = "Organization not found: %s"; - - public static final String ORGANIZATION_IS_NOT_EMPTY_CANT_DELETE_IT = "The organization :%s is not empty, can't delete it."; - public static final String NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS = "Not allowed to update own's organization status"; - public static final String ATTACHMENT_NOT_FOUND = "Attachment with id %s not found"; - //ForbiddenException - public final static String CANNOT_UPDATE_ORGANIZATION = "Cannot update organization: %s"; - public final static String CANNOT_ACCESS_THIS_RESOURCE = "Cannot access this resource"; - public final static String CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION = "Cannot assign user to deactivated organization: %s"; + public static final String TOKEN_IS_NOT_VALID_MSG = "Token is not valid"; + public static final String RECORD_NOT_FOUND_MSG = "Record not found"; + public static final String RECORD_ALREADY_EXISTS = "Record already exists"; + public static final String PASS_NOT_MATCHING = "Password not match"; + + public static final String USER_UNAUTHORISED_EXCEPTION = "User %s is unauthorized to access this profile!"; + public static final String USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE = "Username not found or no longer active"; + + //ForbiddenException - AqlService + public static final String CANNOT_ACCESS_THIS_AQL = "Cannot access this aql."; + public static final String CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED = "Cannot access this resource. Logged in user is not approved."; + public static final String AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER = "Aql edit for aql with id: %s is not allowed. Aql has different owner."; + public static final String CANNOT_DELETE_AQL = "Cannot delete aql: %s"; + public static final String CHANGING_COHORT_ONLY_ALLOWED_BY_THE_OWNER_OF_THE_PROJECT = "Changing cohort only allowed by the owner of the project"; + public static final String COHORT_CHANGE_ONLY_ALLOWED_ON_PROJECT_STATUS_DRAFT_OR_PENDING = "Cohort change only allowed on project status draft or pending"; + //CommentService + public static final String COMMENT_EDIT_FOR_COMMENT_WITH_ID_IS_NOT_ALLOWED_COMMENT_HAS_DIFFERENT_AUTHOR = "Comment edit for comment with id: %s not allowed. Comment has different author"; + public static final String CANNOT_DELETE_COMMENT = "Cannot delete comment: %s"; + + //ResourceNotFound + public static final String AQL_NOT_FOUND = "Aql not found: %s"; + public static final String CANNOT_FIND_AQL = "Cannot find aql: %s"; + public static final String CATEGORY_BY_ID_NOT_FOUND = "Category by id %s Not found"; + public static final String CATEGORY_WITH_ID_DOES_NOT_EXIST = "Category with id %s does not exist."; + + //BadRequestException - Organization Service + public static final String CATEGORY_ID_CANT_BE_NULL = "Category id can't be null"; + public static final String THE_CATEGORY_IS_NOT_EMPTY_CANT_DELETE_IT = "The category is not empty, can't delete it."; + public static final String COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE = "Could not serialize aql validation response: %s"; + public static final String INVALID_AQL_ID = "Invalid aql id"; + public static final String COHORT_NOT_FOUND = "Cohort not found: %s"; + public static final String COHORT_GROUP_CANNOT_BE_EMPTY = "Cohort group cannot be empty"; + public static final String INVALID_COHORT_GROUP_AQL_MISSING = "Invalid cohort group. Aql missing."; + public static final String INVALID_COHORT_GROUP_CHILDREN_MISSING = "The query is invalid. Please select at least one criterion."; + public static final String INVALID_COHORT_GROUP_AQL_MISSING_PARAMETERS = "The query is invalid. The value of at least one criterion is missing."; + public static final String INVALID_COMMENT_ID = "Invalid commentId id"; + public static final String ORGANIZATION_NAME_MUST_BE_UNIQUE = "Organization name must be unique: %s"; + public static final String ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS = "Organization mail domain already exists: %s"; + public static final String ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY = "Organization mail domain cannot be null or empty"; + public static final String INVALID_MAIL_DOMAIN = "Invalid mail domain: %s"; + public static final String ORGANIZATION_NOT_FOUND = "Organization not found: %s"; + + public static final String ORGANIZATION_IS_NOT_EMPTY_CANT_DELETE_IT = "The organization :%s is not empty, can't delete it."; + public static final String NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS = "Not allowed to update own's organization status"; + public static final String ATTACHMENT_NOT_FOUND = "Attachment with id %s not found"; + //ForbiddenException + public static final String CANNOT_UPDATE_ORGANIZATION = "Cannot update organization: %s"; + public static final String CANNOT_ACCESS_THIS_RESOURCE = "Cannot access this resource"; + public static final String CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION = "Cannot assign user to deactivated organization: %s"; + + //Project service + public static final String RESEARCHER_NOT_FOUND = "Researcher not found."; + public static final String RESEARCHER_NOT_APPROVED = "Researcher not approved."; + public static final String INVALID_PROJECT_STATUS = "Invalid project status"; + public static final String INVALID_PROJECT_STATUS_PARAM = "Invalid project status: %s"; + public static final String PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED = "Project status transition from %s to %s is not allowed"; + public static final String PROJECT_COHORT_CANNOT_BE_NULL = "Project: %s cohort cannot be null"; + public static final String PROJECT_TEMPLATES_CANNOT_BE_NULL = "Project: %s templates cannot be null"; + public static final String PROJECT_NOT_FOUND = "Project not found: %s"; + public static final String CAN_T_FIND_THE_COHORT_BY_ID = "Can't find the cohort by id: %s"; + public static final String AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL = "An issue has occurred, cannot execute aql"; + public static final String ERROR_WHILE_RETRIEVING_DATA = "Error while retrieving data: %s"; + public static final String ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT = "Error creating a zip file for data export: %s"; + public static final String ERROR_WHILE_CREATING_THE_CSV_FILE = "Error while creating the CSV file: %s"; + public static final String MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT = "More than one transition from PUBLISHED to CLOSED for project: %s"; + public static final String ERROR_CREATING_THE_PROJECT_PDF = "Error creating the project PDF: %s"; + + //CommentService + public static final String PROJECT_DOES_NOT_EXIST = "Project does not exist"; + public static final String COMMENT_NOT_FOUND = "Comment not found: %s"; + + //ForbiddenException + public static final String CANNOT_ARCHIVE_PROJECT = "Cannot archive project: %s"; + public static final String CANNOT_DELETE_PROJECT = "Cannot delete project: %s"; + public static final String CANNOT_DELETE_PROJECT_INVALID_STATUS = "Cannot delete project: %s, invalid status: %s"; + public static final String CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS = "Cannot update project: %s, invalid project status: %s"; + public static final String NO_PERMISSIONS_TO_EDIT_THIS_PROJECT = "No permissions to edit this project"; + public static final String CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER = "Cannot access this resource. User is not owner."; + public static final String DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY = "Data explorer available for published projects only"; + public static final String CANNOT_ACCESS_THIS_PROJECT = "Cannot access this project"; + + public static final String NO_PERMISSIONS_TO_DELETE_ATTACHMENTS = "No permissions to delete attachments"; + + public static final String CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS = "Not allowed to delete attachments , invalid project status: %s"; + + public static final String CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER = "Not allowed to delete attachment %s because was already reviewed"; + + + //Template service + public static final String CANNOT_FIND_TEMPLATE = "Cannot find template: %s"; + public static final String CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID = "Cannot create query for template with id: %s"; + + //User service + public static final String CANNOT_DELETE_APPROVED_USER = "Cannot delete user: %s; user is approved"; + public static final String UNKNOWN_ROLE = "Unknown Role"; + public static final String CANNOT_DELETE_ENABLED_USER = "Cannot delete user. User is enabled and email address is verified"; + public static final String USER_NOT_FOUND = "User not found: %s"; + public static final String ROLE_OR_USER_NOT_FOUND = "Role or user not found"; + public static final String NO_ROLES_FOUND = "No roles found"; + public static final String AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER = "An error has occurred, cannot retrieve users, please try again later: %s"; + public static final String AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER = "An error has occurred, please try again later: %s"; + public static final String AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER = "An error has occurred, cannot retrieve user roles, please try again later: %s"; + public static final String AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER = "An error has occurred while deleting user. Please try again later: %s"; + public static final String FETCHING_USER_FROM_KEYCLOAK_FAILED = "Fetching user from Keycloak failed"; + //ForbiddenException + public static final String ORGANIZATION_ADMIN_CAN_ONLY_MANAGE_USERS_IN_THEIR_OWN_ORGANIZATION = "Organization admin can only manage users in their own organization."; + public static final String NOT_ALLOWED_TO_REMOVE_THAT_ROLE = "Not allowed to remove that role"; + public static final String NOT_ALLOWED_TO_SET_THAT_ROLE = "Not allowed to set that role"; + public static final String CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES = "Can only change own name, org admin names of the people in the organization and superuser all names."; + public static final String NOT_ALLOWED_TO_UPDATE_OWN_STATUS = "Not allowed to update own status"; + + //EhrBaseService service + public static final String NO_DATA_COLUMNS_IN_THE_QUERY_RESULT = "No data columns in the query result"; + public static final String AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL = "An error has occurred, cannot execute aql: %s"; + public static final String AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES = "An error has occurred, cannot retrieve templates: %s"; + public static final String QUERY_RESULT_DOESN_T_CONTAIN_EHR_STATUS_COLUMN = "query result doesn't contain ehr_status column"; + + //PrivacyException + public static final String TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS = "Too few matches, results withheld for privacy reasons."; + public static final String RESULTS_WITHHELD_FOR_PRIVACY_REASONS = "Number of matches below threshold, results withheld for privacy reasons."; + + //Pseudonymity + public static final String EHR_ID_MATCHING_THE_PSEUDONYM_WAS_NOT_FOUND = "Ehr Id matching the pseudonym was not found"; + public static final String PSEUDONYMITY_SECRET_IS_NOT_CONFIGURED = "Pseudonymity secret is not configured"; + + //ContentService + public static final String COULDN_T_PARSE_NAVIGATION_CONTENT = "Couldn't parse navigation content: %s"; + public static final String COULDN_T_SAVE_NAVIGATION_CONTENT = "Couldn't save navigation content: %s"; + public static final String COULDN_T_PARSE_CARD = "Couldn't parse card: %s"; + public static final String COULDN_T_SAVE_CARD = "Couldn't save card: %s"; + + //CompositionFlattener + public static final String CANNOT_PARSE_RESULTS = "Cannot parse results: %s"; + public static final String CANNOT_PARSE_RESULTS_COMPOSITION_MISSING_TEMPLATE_ID = "Cannot parse results, composition missing template id"; + + //Policy + public static final String INVALID_AQL = "Invalid aql"; + public static final String COHORT_SIZE_CANNOT_BE_0 = "Cohort size cannot be 0"; + public static final String NO_TEMPLATES_ATTACHED_TO_THE_PROJECT = "No templates attached to the project"; + + //IllegalArgumentException + public static final String CANNOT_EXECUTE_AN_EMPTY_COHORT = "Cannot execute an empty cohort"; + public static final String RELATIVE_COMPLEMENT_REQUIRES_TWO_VALID_SETS = "Relative complement requires two valid sets"; + + public static final String INVALID_AQL_QUERY = "EhrBase - Malformed query exception: {}"; + public static final String ERROR_MESSAGE = "EhrBase - An error has occurred while calling EhrBase: {} "; + public static final String CANNOT_CHECK_CONSENT_FOR_DATA_USAGE_OUTSIDE_THE_EUROPEAN_UNION_OID_NOT_CONFIGURED = "Cannot check consent for data usage outside the European Union, oid not configured"; + public static final String CACHE_IS_NOT_REACHABLE = "Cache is not reachable"; + public static final String EXCEPTION_HAPPENED_IN_CLASS_FOR_ENVIRONMENT = "Exception happened in %s class for %s environment. Link %s is not accessible"; //3 parameters + + //document + public static final String DOCUMENT_TYPE_MISMATCH = "Document type mismatch. Only PDF type is allowed to be uploaded."; + public static final String INVALID_FILE_MISSING_CONTENT = "Invalid file. Missing content"; + public static final String PDF_FILE_SIZE_EXCEEDED = "PDF File Size Exceeded. Maximum file size is [%s] MB. Current file size is [%s]+ MB."; + public static final String PDF_FILES_ARE_NOT_ATTACHED = "PDF Files are not attached to the project."; + public static final String ATTACHMENT_LIMIT_REACHED = "Attachment limit reached. Maximum of 10 attachments can be assigned to a project."; + public static final String WRONG_PROJECT_STATUS = "Wrong project status [%s]. Only projects with status 'Draft' or 'Change Request' can accept attachments."; + public static final String DESCRIPTION_TOO_LONG = "Description is too long. Only 255 characters are accepted for description. [%s]"; + + public static final String CLAMAV_PING_FAILED = "Could not ping ClamAV service"; + public static final String CLAMAV_SCAN_FAILED = "Could not scan file %s"; + + public static final Map errorMap = new HashMap<>(); + + static { + errorMap.put(TOKEN_IS_NOT_VALID_MSG, new ExceptionDto(1, new ArrayList<>())); + errorMap.put(RECORD_NOT_FOUND_MSG, new ExceptionDto(2, new ArrayList<>())); + + errorMap.put(RECORD_ALREADY_EXISTS, new ExceptionDto(3, new ArrayList<>())); + errorMap.put(PASS_NOT_MATCHING, new ExceptionDto(4, new ArrayList<>())); + + errorMap.put(USER_UNAUTHORISED_EXCEPTION, new ExceptionDto(5, new ArrayList<>())); //1 parameter + errorMap.put(USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE, new ExceptionDto(6, new ArrayList<>())); + + //Organization Service + errorMap.put(CATEGORY_ID_CANT_BE_NULL, new ExceptionDto(7, new ArrayList<>())); + errorMap.put(THE_CATEGORY_IS_NOT_EMPTY_CANT_DELETE_IT, new ExceptionDto(8, new ArrayList<>())); + errorMap.put(COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE, new ExceptionDto(9, new ArrayList<>())); + errorMap.put(INVALID_AQL_ID, new ExceptionDto(10, new ArrayList<>())); + errorMap.put(COHORT_NOT_FOUND, new ExceptionDto(11, new ArrayList<>())); //1 parameter + errorMap.put(COHORT_GROUP_CANNOT_BE_EMPTY, new ExceptionDto(12, new ArrayList<>())); + errorMap.put(INVALID_COHORT_GROUP_AQL_MISSING, new ExceptionDto(13, new ArrayList<>())); + errorMap.put(INVALID_COMMENT_ID, new ExceptionDto(14, new ArrayList<>())); + errorMap.put(ORGANIZATION_NAME_MUST_BE_UNIQUE, new ExceptionDto(15, new ArrayList<>())); //1 parameter + errorMap.put(ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, new ExceptionDto(16, new ArrayList<>())); //1 parameter + errorMap.put(ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY, new ExceptionDto(17, new ArrayList<>())); + errorMap.put(INVALID_MAIL_DOMAIN, new ExceptionDto(18, new ArrayList<>())); //1 parameter //Project service - public final static String RESEARCHER_NOT_FOUND = "Researcher not found."; - public final static String RESEARCHER_NOT_APPROVED = "Researcher not approved."; - public final static String INVALID_PROJECT_STATUS = "Invalid project status"; - public final static String INVALID_PROJECT_STATUS_PARAM = "Invalid project status: %s"; - public final static String PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED = "Project status transition from %s to %s is not allowed"; - public final static String PROJECT_COHORT_CANNOT_BE_NULL = "Project: %s cohort cannot be null"; - public final static String PROJECT_TEMPLATES_CANNOT_BE_NULL = "Project: %s templates cannot be null"; - public final static String PROJECT_NOT_FOUND = "Project not found: %s"; - public final static String CAN_T_FIND_THE_COHORT_BY_ID = "Can't find the cohort by id: %s"; - public final static String AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL = "An issue has occurred, cannot execute aql"; - public final static String ERROR_WHILE_RETRIEVING_DATA = "Error while retrieving data: %s"; - public final static String ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT = "Error creating a zip file for data export: %s"; - public final static String ERROR_WHILE_CREATING_THE_CSV_FILE = "Error while creating the CSV file: %s"; - public final static String MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT = "More than one transition from PUBLISHED to CLOSED for project: %s"; - public final static String ERROR_CREATING_THE_PROJECT_PDF = "Error creating the project PDF: %s"; - - //CommentService - public final static String PROJECT_DOES_NOT_EXIST = "Project does not exist"; - public final static String COMMENT_NOT_FOUND = "Comment not found: %s"; - - //ForbiddenException - public final static String CANNOT_ARCHIVE_PROJECT = "Cannot archive project: %s"; - public final static String CANNOT_DELETE_PROJECT = "Cannot delete project: %s"; - public final static String CANNOT_DELETE_PROJECT_INVALID_STATUS = "Cannot delete project: %s, invalid status: %s"; - public final static String CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS = "Cannot update project: %s, invalid project status: %s"; - public final static String NO_PERMISSIONS_TO_EDIT_THIS_PROJECT = "No permissions to edit this project"; - public final static String CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER = "Cannot access this resource. User is not owner."; - public final static String DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY = "Data explorer available for published projects only"; - public final static String CANNOT_ACCESS_THIS_PROJECT = "Cannot access this project"; - - public static final String NO_PERMISSIONS_TO_DELETE_ATTACHMENTS = "No permissions to delete attachments"; - - public static final String CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS = "Not allowed to delete attachments , invalid project status: %s"; - - public static final String CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER = "Not allowed to delete attachment %s because was already reviewed"; - + errorMap.put(RESEARCHER_NOT_FOUND, new ExceptionDto(19, new ArrayList<>())); + errorMap.put(RESEARCHER_NOT_APPROVED, new ExceptionDto(20, new ArrayList<>())); + errorMap.put(INVALID_PROJECT_STATUS, new ExceptionDto(21, new ArrayList<>())); + errorMap.put(INVALID_PROJECT_STATUS_PARAM, new ExceptionDto(22, new ArrayList<>())); //1 parameter + errorMap.put(PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, new ExceptionDto(23, new ArrayList<>())); //2 parameters + errorMap.put(PROJECT_COHORT_CANNOT_BE_NULL, new ExceptionDto(24, new ArrayList<>())); //1 parameter + errorMap.put(PROJECT_TEMPLATES_CANNOT_BE_NULL, new ExceptionDto(25, new ArrayList<>())); //1 parameter + errorMap.put(PROJECT_NOT_FOUND, new ExceptionDto(26, new ArrayList<>())); //1 parameter //Template service - public final static String CANNOT_FIND_TEMPLATE = "Cannot find template: %s"; - public final static String CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID = "Cannot create query for template with id: %s"; + errorMap.put(CANNOT_FIND_TEMPLATE, new ExceptionDto(27, new ArrayList<>())); //1 parameter //User service - public final static String CANNOT_DELETE_APPROVED_USER = "Cannot delete user: %s; user is approved"; - public final static String UNKNOWN_ROLE = "Unknown Role"; - public final static String CANNOT_DELETE_ENABLED_USER = "Cannot delete user. User is enabled and email address is verified"; - public final static String USER_NOT_FOUND = "User not found: %s"; - public final static String ROLE_OR_USER_NOT_FOUND = "Role or user not found"; - public final static String NO_ROLES_FOUND = "No roles found"; - public final static String AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER = "An error has occurred, cannot retrieve users, please try again later: %s"; - public final static String AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER = "An error has occurred, please try again later: %s"; - public final static String AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER = "An error has occurred, cannot retrieve user roles, please try again later: %s"; - public final static String AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER = "An error has occurred while deleting user. Please try again later: %s"; - public final static String FETCHING_USER_FROM_KEYCLOAK_FAILED = "Fetching user from Keycloak failed"; - //ForbiddenException - public final static String ORGANIZATION_ADMIN_CAN_ONLY_MANAGE_USERS_IN_THEIR_OWN_ORGANIZATION = "Organization admin can only manage users in their own organization."; - public final static String NOT_ALLOWED_TO_REMOVE_THAT_ROLE = "Not allowed to remove that role"; - public final static String NOT_ALLOWED_TO_SET_THAT_ROLE = "Not allowed to set that role"; - public final static String CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES = "Can only change own name, org admin names of the people in the organization and superuser all names."; - public static final String NOT_ALLOWED_TO_UPDATE_OWN_STATUS = "Not allowed to update own status"; + errorMap.put(CANNOT_DELETE_APPROVED_USER, new ExceptionDto(28, new ArrayList<>())); //1 parameter + errorMap.put(UNKNOWN_ROLE, new ExceptionDto(29, new ArrayList<>())); + errorMap.put(CANNOT_DELETE_ENABLED_USER, new ExceptionDto(30, new ArrayList<>())); //EhrBaseService service - public final static String NO_DATA_COLUMNS_IN_THE_QUERY_RESULT = "No data columns in the query result"; - public static final String AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL = "An error has occurred, cannot execute aql: %s"; - public static final String AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES = "An error has occurred, cannot retrieve templates: %s"; - public static final String QUERY_RESULT_DOESN_T_CONTAIN_EHR_STATUS_COLUMN = "query result doesn't contain ehr_status column"; + errorMap.put(NO_DATA_COLUMNS_IN_THE_QUERY_RESULT, new ExceptionDto(31, new ArrayList<>())); + + //ForbiddenException - AqlService + errorMap.put(CANNOT_ACCESS_THIS_AQL, new ExceptionDto(32, new ArrayList<>())); + errorMap.put(CANNOT_ARCHIVE_PROJECT, new ExceptionDto(33, new ArrayList<>())); //1 parameter + errorMap.put(CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED, new ExceptionDto(34, new ArrayList<>())); + errorMap.put(AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER, new ExceptionDto(35, new ArrayList<>())); + errorMap.put(CANNOT_DELETE_AQL, new ExceptionDto(36, new ArrayList<>())); + errorMap.put(CHANGING_COHORT_ONLY_ALLOWED_BY_THE_OWNER_OF_THE_PROJECT, new ExceptionDto(37, new ArrayList<>())); + errorMap.put(COHORT_CHANGE_ONLY_ALLOWED_ON_PROJECT_STATUS_DRAFT_OR_PENDING, new ExceptionDto(38, new ArrayList<>())); + errorMap.put(COMMENT_EDIT_FOR_COMMENT_WITH_ID_IS_NOT_ALLOWED_COMMENT_HAS_DIFFERENT_AUTHOR, new ExceptionDto(39, new ArrayList<>())); + errorMap.put(CANNOT_DELETE_COMMENT, new ExceptionDto(40, new ArrayList<>())); //1 parameter + //organization + errorMap.put(CANNOT_UPDATE_ORGANIZATION, new ExceptionDto(41, new ArrayList<>())); //1 parameter + errorMap.put(CANNOT_ACCESS_THIS_RESOURCE, new ExceptionDto(42, new ArrayList<>())); + //project + errorMap.put(CANNOT_DELETE_PROJECT, new ExceptionDto(43, new ArrayList<>())); //1 parameter + errorMap.put(CANNOT_DELETE_PROJECT_INVALID_STATUS, new ExceptionDto(44, new ArrayList<>())); //2 parameters + errorMap.put(CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS, new ExceptionDto(45, new ArrayList<>())); //2 parameters + errorMap.put(NO_PERMISSIONS_TO_EDIT_THIS_PROJECT, new ExceptionDto(46, new ArrayList<>())); + errorMap.put(CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER, new ExceptionDto(47, new ArrayList<>())); + errorMap.put(DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY, new ExceptionDto(48, new ArrayList<>())); + errorMap.put(CANNOT_ACCESS_THIS_PROJECT, new ExceptionDto(49, new ArrayList<>())); + //user-service + errorMap.put(ORGANIZATION_ADMIN_CAN_ONLY_MANAGE_USERS_IN_THEIR_OWN_ORGANIZATION, new ExceptionDto(50, new ArrayList<>())); + errorMap.put(NOT_ALLOWED_TO_REMOVE_THAT_ROLE, new ExceptionDto(51, new ArrayList<>())); + errorMap.put(NOT_ALLOWED_TO_SET_THAT_ROLE, new ExceptionDto(52, new ArrayList<>())); + errorMap.put( + CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES, new ExceptionDto(53, new ArrayList<>())); //PrivacyException - public final static String TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS = "Too few matches, results withheld for privacy reasons."; - public final static String RESULTS_WITHHELD_FOR_PRIVACY_REASONS = "Number of matches below threshold, results withheld for privacy reasons."; + errorMap.put(TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS, new ExceptionDto(54, new ArrayList<>())); - //Pseudonymity - public final static String EHR_ID_MATCHING_THE_PSEUDONYM_WAS_NOT_FOUND = "Ehr Id matching the pseudonym was not found"; - public final static String PSEUDONYMITY_SECRET_IS_NOT_CONFIGURED = "Pseudonymity secret is not configured"; + //ResourceNotFound + errorMap.put(AQL_NOT_FOUND, new ExceptionDto(55, new ArrayList<>())); //1 parameter + errorMap.put(CANNOT_FIND_AQL, new ExceptionDto(56, new ArrayList<>())); //1 parameter + errorMap.put(CATEGORY_BY_ID_NOT_FOUND, new ExceptionDto(57, new ArrayList<>())); //1 parameter + errorMap.put(CATEGORY_WITH_ID_DOES_NOT_EXIST, new ExceptionDto(58, new ArrayList<>())); //1 parameter - //ContentService - public final static String COULDN_T_PARSE_NAVIGATION_CONTENT = "Couldn't parse navigation content: %s"; - public final static String COULDN_T_SAVE_NAVIGATION_CONTENT = "Couldn't save navigation content: %s"; - public final static String COULDN_T_PARSE_CARD = "Couldn't parse card: %s"; - public final static String COULDN_T_SAVE_CARD = "Couldn't save card: %s"; + //CommentService + errorMap.put(PROJECT_DOES_NOT_EXIST, new ExceptionDto(59, new ArrayList<>())); + errorMap.put(COMMENT_NOT_FOUND, new ExceptionDto(60, new ArrayList<>())); + //OrganizationService + errorMap.put(ORGANIZATION_NOT_FOUND, new ExceptionDto(61, new ArrayList<>())); //1 parameter + errorMap.put(ORGANIZATION_IS_NOT_EMPTY_CANT_DELETE_IT, new ExceptionDto(98, new ArrayList<>())); + //UserDetailsService + errorMap.put(USER_NOT_FOUND, new ExceptionDto(62, new ArrayList<>())); //1 parameter + //UserService + errorMap.put(ROLE_OR_USER_NOT_FOUND, new ExceptionDto(63, new ArrayList<>())); + errorMap.put(NO_ROLES_FOUND, new ExceptionDto(64, new ArrayList<>())); + //Pseudonymity + errorMap.put(EHR_ID_MATCHING_THE_PSEUDONYM_WAS_NOT_FOUND, new ExceptionDto(65, new ArrayList<>())); + //SystemException + errorMap.put(COULDN_T_PARSE_NAVIGATION_CONTENT, new ExceptionDto(66, new ArrayList<>())); //1 parameter + errorMap.put(COULDN_T_SAVE_NAVIGATION_CONTENT, new ExceptionDto(67, new ArrayList<>())); //1 parameter + errorMap.put(COULDN_T_PARSE_CARD, new ExceptionDto(68, new ArrayList<>())); //1 parameter + errorMap.put(COULDN_T_SAVE_CARD, new ExceptionDto(69, new ArrayList<>())); //1 parameter //CompositionFlattener - public final static String CANNOT_PARSE_RESULTS = "Cannot parse results: %s"; - public final static String CANNOT_PARSE_RESULTS_COMPOSITION_MISSING_TEMPLATE_ID = "Cannot parse results, composition missing template id"; + errorMap.put(CANNOT_PARSE_RESULTS, new ExceptionDto(70, new ArrayList<>())); //1 parameter + errorMap.put(CANNOT_PARSE_RESULTS_COMPOSITION_MISSING_TEMPLATE_ID, new ExceptionDto(71, new ArrayList<>())); + errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, new ExceptionDto(72, new ArrayList<>())); //1 parameter + errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES, new ExceptionDto(102, new ArrayList<>())); //1 parameter + //CohortService + errorMap.put(CAN_T_FIND_THE_COHORT_BY_ID, new ExceptionDto(73, new ArrayList<>())); //1 parameter + //ProjectService + errorMap.put(AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL, new ExceptionDto(74, new ArrayList<>())); + errorMap.put(ERROR_WHILE_RETRIEVING_DATA, new ExceptionDto(75, new ArrayList<>())); //1 parameter + errorMap.put(ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, new ExceptionDto(76, new ArrayList<>())); //1 parameter + errorMap.put(ERROR_WHILE_CREATING_THE_CSV_FILE, new ExceptionDto(77, new ArrayList<>())); //1 parameter + errorMap.put(MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT, new ExceptionDto(78, new ArrayList<>())); //1 parameter + errorMap.put(ERROR_CREATING_THE_PROJECT_PDF, new ExceptionDto(79, new ArrayList<>())); //1 parameter + //TemplateService + errorMap.put(CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID, new ExceptionDto(80, new ArrayList<>())); //1 parameter + //UserService + errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, new ExceptionDto(81, new ArrayList<>())); //1 parameter + errorMap.put(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, new ExceptionDto(82, new ArrayList<>())); //1 parameter + errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER, new ExceptionDto(83, new ArrayList<>())); //1 parameter + errorMap.put(AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER, new ExceptionDto(84, new ArrayList<>())); //1 parameter + errorMap.put(FETCHING_USER_FROM_KEYCLOAK_FAILED, new ExceptionDto(85, new ArrayList<>())); + //EhrBaseService + errorMap.put(QUERY_RESULT_DOESN_T_CONTAIN_EHR_STATUS_COLUMN, new ExceptionDto(86, new ArrayList<>())); + //Pseudonymity + errorMap.put(PSEUDONYMITY_SECRET_IS_NOT_CONFIGURED, new ExceptionDto(87, new ArrayList<>())); //Policy - public final static String INVALID_AQL = "Invalid aql"; - public final static String COHORT_SIZE_CANNOT_BE_0 = "Cohort size cannot be 0"; - public final static String NO_TEMPLATES_ATTACHED_TO_THE_PROJECT = "No templates attached to the project"; + errorMap.put(INVALID_AQL, new ExceptionDto(88, new ArrayList<>())); + //EhrPolicy + errorMap.put(COHORT_SIZE_CANNOT_BE_0, new ExceptionDto(89, new ArrayList<>())); //IllegalArgumentException - public final static String CANNOT_EXECUTE_AN_EMPTY_COHORT = "Cannot execute an empty cohort"; - public final static String RELATIVE_COMPLEMENT_REQUIRES_TWO_VALID_SETS = "Relative complement requires two valid sets"; - - public final static String INVALID_AQL_QUERY = "EhrBase - Malformed query exception: {}"; - public static final String ERROR_MESSAGE = "EhrBase - An error has occurred while calling EhrBase: {} "; - public final static String CANNOT_CHECK_CONSENT_FOR_DATA_USAGE_OUTSIDE_THE_EUROPEAN_UNION_OID_NOT_CONFIGURED = "Cannot check consent for data usage outside the European Union, oid not configured"; - public final static String CACHE_IS_NOT_REACHABLE = "Cache is not reachable"; - public final static String EXCEPTION_HAPPENED_IN_CLASS_FOR_ENVIRONMENT = "Exception happened in %s class for %s environment. Link %s is not accessible";//3 parameters - - //document - public final static String DOCUMENT_TYPE_MISMATCH = "Document type mismatch. Only PDF type is allowed to be uploaded."; - public final static String INVALID_FILE_MISSING_CONTENT = "Invalid file. Missing content"; - public final static String PDF_FILE_SIZE_EXCEEDED = "PDF File Size Exceeded. Maximum file size is [%s] MB. Current file size is [%s]+ MB."; - public final static String PDF_FILES_ARE_NOT_ATTACHED = "PDF Files are not attached to the project."; - public final static String ATTACHMENT_LIMIT_REACHED = "Attachment limit reached. Maximum of 10 attachments can be assigned to a project."; - public final static String WRONG_PROJECT_STATUS = "Wrong project status [%s]. Only projects with status 'Draft' or 'Change Request' can accept attachments."; - public final static String DESCRIPTION_TOO_LONG = "Description is too long. Only 255 characters are accepted for description. [%s]"; - - public static final String CLAMAV_PING_FAILED = "Could not ping ClamAV service"; - public static final String CLAMAV_SCAN_FAILED = "Could not scan file %s"; - - public static final Map errorMap = new HashMap<>(); - - static { - errorMap.put( TOKEN_IS_NOT_VALID_MSG, new ExceptionDto( 1, new ArrayList<>() ) ); - errorMap.put( RECORD_NOT_FOUND_MSG, new ExceptionDto( 2, new ArrayList<>() ) ); - - errorMap.put( RECORD_ALREADY_EXISTS, new ExceptionDto( 3, new ArrayList<>() ) ); - errorMap.put( PASS_NOT_MATCHING, new ExceptionDto( 4, new ArrayList<>() ) ); - - errorMap.put( USER_UNAUTHORISED_EXCEPTION, new ExceptionDto( 5, new ArrayList<>() ) );//1 parameter - errorMap.put( USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE, new ExceptionDto( 6, new ArrayList<>() ) ); - - //Organization Service - errorMap.put( CATEGORY_ID_CANT_BE_NULL, new ExceptionDto( 7, new ArrayList<>() ) ); - errorMap.put( THE_CATEGORY_IS_NOT_EMPTY_CANT_DELETE_IT, new ExceptionDto( 8, new ArrayList<>() ) ); - errorMap.put( COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE, new ExceptionDto( 9, new ArrayList<>() ) ); - errorMap.put( INVALID_AQL_ID, new ExceptionDto( 10, new ArrayList<>() ) ); - errorMap.put( COHORT_NOT_FOUND, new ExceptionDto( 11, new ArrayList<>() ) );//1 parameter - errorMap.put( COHORT_GROUP_CANNOT_BE_EMPTY, new ExceptionDto( 12, new ArrayList<>() ) ); - errorMap.put( INVALID_COHORT_GROUP_AQL_MISSING, new ExceptionDto( 13, new ArrayList<>() ) ); - errorMap.put(INVALID_COMMENT_ID, new ExceptionDto( 14, new ArrayList<>() ) ); - errorMap.put( ORGANIZATION_NAME_MUST_BE_UNIQUE, new ExceptionDto( 15, new ArrayList<>() ) );//1 parameter - errorMap.put( ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, new ExceptionDto( 16, new ArrayList<>() ) );//1 parameter - errorMap.put( ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY, new ExceptionDto( 17, new ArrayList<>() ) ); - errorMap.put( INVALID_MAIL_DOMAIN, new ExceptionDto( 18, new ArrayList<>() ) );//1 parameter - - //Project service - errorMap.put( RESEARCHER_NOT_FOUND, new ExceptionDto( 19, new ArrayList<>() ) ); - errorMap.put( RESEARCHER_NOT_APPROVED, new ExceptionDto( 20, new ArrayList<>() ) ); - errorMap.put( INVALID_PROJECT_STATUS, new ExceptionDto( 21, new ArrayList<>() ) ); - errorMap.put( INVALID_PROJECT_STATUS_PARAM, new ExceptionDto( 22, new ArrayList<>() ) );//1 parameter - errorMap.put( PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, new ExceptionDto( 23, new ArrayList<>() ) );//2 parameters - errorMap.put( PROJECT_COHORT_CANNOT_BE_NULL, new ExceptionDto( 24, new ArrayList<>() ) );//1 parameter - errorMap.put( PROJECT_TEMPLATES_CANNOT_BE_NULL, new ExceptionDto( 25, new ArrayList<>() ) );//1 parameter - errorMap.put( PROJECT_NOT_FOUND, new ExceptionDto( 26, new ArrayList<>() ) );//1 parameter - - - //Template service - errorMap.put( CANNOT_FIND_TEMPLATE, new ExceptionDto( 27, new ArrayList<>() ) );//1 parameter - - //User service - errorMap.put(CANNOT_DELETE_APPROVED_USER, new ExceptionDto( 28, new ArrayList<>() ) );//1 parameter - errorMap.put( UNKNOWN_ROLE, new ExceptionDto( 29, new ArrayList<>() ) ); - errorMap.put( CANNOT_DELETE_ENABLED_USER, new ExceptionDto( 30, new ArrayList<>() ) ); - - //EhrBaseService service - errorMap.put( NO_DATA_COLUMNS_IN_THE_QUERY_RESULT, new ExceptionDto( 31, new ArrayList<>() ) ); - - //ForbiddenException - AqlService - errorMap.put( CANNOT_ACCESS_THIS_AQL, new ExceptionDto( 32, new ArrayList<>() ) ); - errorMap.put( CANNOT_ARCHIVE_PROJECT, new ExceptionDto( 33, new ArrayList<>() ) );//1 parameter - errorMap.put( CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED, new ExceptionDto( 34, new ArrayList<>() ) ); - errorMap.put( AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER, new ExceptionDto( 35, new ArrayList<>() ) ); - errorMap.put( CANNOT_DELETE_AQL, new ExceptionDto( 36, new ArrayList<>() ) ); - errorMap.put( CHANGING_COHORT_ONLY_ALLOWED_BY_THE_OWNER_OF_THE_PROJECT, new ExceptionDto( 37, new ArrayList<>() ) ); - errorMap.put( COHORT_CHANGE_ONLY_ALLOWED_ON_PROJECT_STATUS_DRAFT_OR_PENDING, new ExceptionDto( 38, new ArrayList<>() ) ); - errorMap.put( COMMENT_EDIT_FOR_COMMENT_WITH_ID_IS_NOT_ALLOWED_COMMENT_HAS_DIFFERENT_AUTHOR, new ExceptionDto( 39, new ArrayList<>() ) ); - errorMap.put( CANNOT_DELETE_COMMENT, new ExceptionDto( 40, new ArrayList<>() ) );//1 parameter - //organization - errorMap.put( CANNOT_UPDATE_ORGANIZATION, new ExceptionDto( 41, new ArrayList<>() ) );//1 parameter - errorMap.put( CANNOT_ACCESS_THIS_RESOURCE, new ExceptionDto( 42, new ArrayList<>() ) ); - //project - errorMap.put( CANNOT_DELETE_PROJECT, new ExceptionDto( 43, new ArrayList<>() ) );//1 parameter - errorMap.put( CANNOT_DELETE_PROJECT_INVALID_STATUS, new ExceptionDto( 44, new ArrayList<>() ) );//2 parameters - errorMap.put( CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS, new ExceptionDto( 45, new ArrayList<>() ) );//2 parameters - errorMap.put( NO_PERMISSIONS_TO_EDIT_THIS_PROJECT, new ExceptionDto( 46, new ArrayList<>() ) ); - errorMap.put( CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER, new ExceptionDto( 47, new ArrayList<>() ) ); - errorMap.put( DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY, new ExceptionDto( 48, new ArrayList<>() ) ); - errorMap.put( CANNOT_ACCESS_THIS_PROJECT, new ExceptionDto( 49, new ArrayList<>() ) ); - //user-service - errorMap.put( ORGANIZATION_ADMIN_CAN_ONLY_MANAGE_USERS_IN_THEIR_OWN_ORGANIZATION, new ExceptionDto( 50, new ArrayList<>() ) ); - errorMap.put( NOT_ALLOWED_TO_REMOVE_THAT_ROLE, new ExceptionDto( 51, new ArrayList<>() ) ); - errorMap.put( NOT_ALLOWED_TO_SET_THAT_ROLE, new ExceptionDto( 52, new ArrayList<>() ) ); - errorMap.put( CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES, new ExceptionDto( 53, new ArrayList<>() ) ); - - //PrivacyException - errorMap.put( TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS, new ExceptionDto( 54, new ArrayList<>() ) ); - - //ResourceNotFound - errorMap.put( AQL_NOT_FOUND, new ExceptionDto( 55, new ArrayList<>() ) );//1 parameter - errorMap.put( CANNOT_FIND_AQL, new ExceptionDto( 56, new ArrayList<>() ) );//1 parameter - errorMap.put(CATEGORY_BY_ID_NOT_FOUND, new ExceptionDto( 57, new ArrayList<>() ) );//1 parameter - errorMap.put(CATEGORY_WITH_ID_DOES_NOT_EXIST, new ExceptionDto( 58, new ArrayList<>() ) );//1 parameter - - //CommentService - errorMap.put(PROJECT_DOES_NOT_EXIST, new ExceptionDto( 59, new ArrayList<>() ) ); - errorMap.put(COMMENT_NOT_FOUND, new ExceptionDto( 60, new ArrayList<>() ) ); - //OrganizationService - errorMap.put(ORGANIZATION_NOT_FOUND, new ExceptionDto( 61, new ArrayList<>() ) );//1 parameter - errorMap.put(ORGANIZATION_IS_NOT_EMPTY_CANT_DELETE_IT, new ExceptionDto(98, new ArrayList<>())); - //UserDetailsService - errorMap.put(USER_NOT_FOUND, new ExceptionDto( 62, new ArrayList<>() ) );//1 parameter - //UserService - errorMap.put(ROLE_OR_USER_NOT_FOUND, new ExceptionDto( 63, new ArrayList<>() ) ); - errorMap.put(NO_ROLES_FOUND, new ExceptionDto( 64, new ArrayList<>() ) ); - //Pseudonymity - errorMap.put(EHR_ID_MATCHING_THE_PSEUDONYM_WAS_NOT_FOUND, new ExceptionDto( 65, new ArrayList<>() ) ); - - //SystemException - errorMap.put(COULDN_T_PARSE_NAVIGATION_CONTENT, new ExceptionDto( 66, new ArrayList<>() ) );//1 parameter - errorMap.put(COULDN_T_SAVE_NAVIGATION_CONTENT, new ExceptionDto( 67, new ArrayList<>() ) );//1 parameter - errorMap.put(COULDN_T_PARSE_CARD, new ExceptionDto( 68, new ArrayList<>() ) );//1 parameter - errorMap.put(COULDN_T_SAVE_CARD, new ExceptionDto( 69, new ArrayList<>() ) );//1 parameter - //CompositionFlattener - errorMap.put(CANNOT_PARSE_RESULTS, new ExceptionDto( 70, new ArrayList<>() ) );//1 parameter - errorMap.put(CANNOT_PARSE_RESULTS_COMPOSITION_MISSING_TEMPLATE_ID, new ExceptionDto( 71, new ArrayList<>() ) ); - errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, new ExceptionDto( 72, new ArrayList<>() ) );//1 parameter - errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES, new ExceptionDto( 102, new ArrayList<>() ) );//1 parameter - //CohortService - errorMap.put(CAN_T_FIND_THE_COHORT_BY_ID, new ExceptionDto( 73, new ArrayList<>() ) );//1 parameter - //ProjectService - errorMap.put(AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL, new ExceptionDto( 74, new ArrayList<>() ) ); - errorMap.put(ERROR_WHILE_RETRIEVING_DATA, new ExceptionDto( 75, new ArrayList<>() ) );//1 parameter - errorMap.put(ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, new ExceptionDto( 76, new ArrayList<>() ) );//1 parameter - errorMap.put(ERROR_WHILE_CREATING_THE_CSV_FILE, new ExceptionDto( 77, new ArrayList<>() ) );//1 parameter - errorMap.put(MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT, new ExceptionDto( 78, new ArrayList<>() ) );//1 parameter - errorMap.put(ERROR_CREATING_THE_PROJECT_PDF, new ExceptionDto( 79, new ArrayList<>() ) );//1 parameter - //TemplateService - errorMap.put(CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID, new ExceptionDto( 80, new ArrayList<>() ) );//1 parameter - //UserService - errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, new ExceptionDto( 81, new ArrayList<>() ) );//1 parameter - errorMap.put(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, new ExceptionDto( 82, new ArrayList<>() ) );//1 parameter - errorMap.put(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER, new ExceptionDto( 83, new ArrayList<>() ) );//1 parameter - errorMap.put(AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER, new ExceptionDto( 84, new ArrayList<>() ) );//1 parameter - errorMap.put(FETCHING_USER_FROM_KEYCLOAK_FAILED, new ExceptionDto( 85, new ArrayList<>() ) ); - //EhrBaseService - errorMap.put(QUERY_RESULT_DOESN_T_CONTAIN_EHR_STATUS_COLUMN, new ExceptionDto( 86, new ArrayList<>() ) ); - //Pseudonymity - errorMap.put(PSEUDONYMITY_SECRET_IS_NOT_CONFIGURED, new ExceptionDto( 87, new ArrayList<>() ) ); - - //Policy - errorMap.put(INVALID_AQL, new ExceptionDto( 88, new ArrayList<>() ) ); - //EhrPolicy - errorMap.put(COHORT_SIZE_CANNOT_BE_0, new ExceptionDto( 89, new ArrayList<>() ) ); - - //IllegalArgumentException - errorMap.put(CANNOT_EXECUTE_AN_EMPTY_COHORT, new ExceptionDto( 90, new ArrayList<>() ) ); - errorMap.put(RELATIVE_COMPLEMENT_REQUIRES_TWO_VALID_SETS, new ExceptionDto( 91, new ArrayList<>() ) ); - - //PrivacyException - errorMap.put(RESULTS_WITHHELD_FOR_PRIVACY_REASONS, new ExceptionDto( 92, new ArrayList<>() ) ); - errorMap.put(INVALID_AQL_QUERY, new ExceptionDto( 93, new ArrayList<>() ) ); - errorMap.put(CANNOT_CHECK_CONSENT_FOR_DATA_USAGE_OUTSIDE_THE_EUROPEAN_UNION_OID_NOT_CONFIGURED, new ExceptionDto( 95, new ArrayList<>() ) ); - - //Policy - errorMap.put(NO_TEMPLATES_ATTACHED_TO_THE_PROJECT, new ExceptionDto( 96, new ArrayList<>() ) ); - errorMap.put(NOT_ALLOWED_TO_UPDATE_OWN_STATUS, new ExceptionDto(97, new ArrayList<>())); - errorMap.put(CACHE_IS_NOT_REACHABLE, new ExceptionDto(98, new ArrayList<>())); - - errorMap.put(NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS, new ExceptionDto(99, new ArrayList<>())); - errorMap.put(CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION, new ExceptionDto(100, new ArrayList<>()));//1 parameter - errorMap.put(EXCEPTION_HAPPENED_IN_CLASS_FOR_ENVIRONMENT, new ExceptionDto(101, new ArrayList<>()));//3 parameter - errorMap.put(ATTACHMENT_NOT_FOUND, new ExceptionDto( 104, new ArrayList<>() ) );//1 parameter - - //Document - errorMap.put(DOCUMENT_TYPE_MISMATCH, new ExceptionDto( 105, new ArrayList<>() ) ); - errorMap.put(INVALID_FILE_MISSING_CONTENT, new ExceptionDto( 106, new ArrayList<>() ) ); - errorMap.put(PDF_FILE_SIZE_EXCEEDED, new ExceptionDto( 107, new ArrayList<>() ) );//2 parameters - - errorMap.put(CLAMAV_PING_FAILED, new ExceptionDto( 108, new ArrayList<>() ) ); - errorMap.put(CLAMAV_SCAN_FAILED, new ExceptionDto( 109, new ArrayList<>() ) );//1 parameter - errorMap.put(NO_PERMISSIONS_TO_DELETE_ATTACHMENTS, new ExceptionDto( 110, new ArrayList<>() ) ); - errorMap.put(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, new ExceptionDto( 111, new ArrayList<>() ) ); //1 parameter - errorMap.put(CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER, new ExceptionDto( 112, new ArrayList<>() ) ); - - errorMap.put(PDF_FILES_ARE_NOT_ATTACHED, new ExceptionDto( 110, new ArrayList<>() ) ); - errorMap.put(ATTACHMENT_LIMIT_REACHED, new ExceptionDto( 111, new ArrayList<>() ) ); - errorMap.put(WRONG_PROJECT_STATUS, new ExceptionDto( 112, new ArrayList<>() ) ); - errorMap.put(DESCRIPTION_TOO_LONG, new ExceptionDto( 113, new ArrayList<>() ) );//1 parameter - } - - private ExceptionsTemplate() { - - } + errorMap.put(CANNOT_EXECUTE_AN_EMPTY_COHORT, new ExceptionDto(90, new ArrayList<>())); + errorMap.put(RELATIVE_COMPLEMENT_REQUIRES_TWO_VALID_SETS, new ExceptionDto(91, new ArrayList<>())); + + //PrivacyException + errorMap.put(RESULTS_WITHHELD_FOR_PRIVACY_REASONS, new ExceptionDto(92, new ArrayList<>())); + errorMap.put(INVALID_AQL_QUERY, new ExceptionDto(93, new ArrayList<>())); + errorMap.put(CANNOT_CHECK_CONSENT_FOR_DATA_USAGE_OUTSIDE_THE_EUROPEAN_UNION_OID_NOT_CONFIGURED, new ExceptionDto(95, new ArrayList<>())); + + //Policy + errorMap.put(NO_TEMPLATES_ATTACHED_TO_THE_PROJECT, new ExceptionDto(96, new ArrayList<>())); + errorMap.put(NOT_ALLOWED_TO_UPDATE_OWN_STATUS, new ExceptionDto(97, new ArrayList<>())); + errorMap.put(CACHE_IS_NOT_REACHABLE, new ExceptionDto(98, new ArrayList<>())); + + errorMap.put(NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS, new ExceptionDto(99, new ArrayList<>())); + errorMap.put(CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION, new ExceptionDto(100, new ArrayList<>())); //1 parameter + errorMap.put(EXCEPTION_HAPPENED_IN_CLASS_FOR_ENVIRONMENT, new ExceptionDto(101, new ArrayList<>())); //3 parameter + errorMap.put(ATTACHMENT_NOT_FOUND, new ExceptionDto(104, new ArrayList<>())); //1 parameter + + //Document + errorMap.put(DOCUMENT_TYPE_MISMATCH, new ExceptionDto(105, new ArrayList<>())); + errorMap.put(INVALID_FILE_MISSING_CONTENT, new ExceptionDto(106, new ArrayList<>())); + errorMap.put(PDF_FILE_SIZE_EXCEEDED, new ExceptionDto(107, new ArrayList<>())); //2 parameters + + errorMap.put(CLAMAV_PING_FAILED, new ExceptionDto(108, new ArrayList<>())); + errorMap.put(CLAMAV_SCAN_FAILED, new ExceptionDto(109, new ArrayList<>())); //1 parameter + errorMap.put(NO_PERMISSIONS_TO_DELETE_ATTACHMENTS, new ExceptionDto(110, new ArrayList<>())); + errorMap.put(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, new ExceptionDto(111, new ArrayList<>())); //1 parameter + errorMap.put(CANNOT_DELETE_ATTACHMENT_INVALID_REVIEW_STATUS_COUNTER, new ExceptionDto(112, new ArrayList<>())); + + errorMap.put(PDF_FILES_ARE_NOT_ATTACHED, new ExceptionDto(110, new ArrayList<>())); + errorMap.put(ATTACHMENT_LIMIT_REACHED, new ExceptionDto(111, new ArrayList<>())); + errorMap.put(WRONG_PROJECT_STATUS, new ExceptionDto(112, new ArrayList<>())); + errorMap.put(DESCRIPTION_TOO_LONG, new ExceptionDto(113, new ArrayList<>())); //1 parameter + } + + private ExceptionsTemplate() { + + } } diff --git a/src/main/java/org/highmed/numportal/domain/validation/CohortValidator.java b/src/main/java/org/highmed/numportal/domain/validation/CohortValidator.java index 77ca66de..68ecd0d1 100644 --- a/src/main/java/org/highmed/numportal/domain/validation/CohortValidator.java +++ b/src/main/java/org/highmed/numportal/domain/validation/CohortValidator.java @@ -1,12 +1,13 @@ package org.highmed.numportal.domain.validation; -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; -import org.apache.commons.collections4.CollectionUtils; import org.highmed.numportal.domain.dto.CohortGroupDto; import org.highmed.numportal.domain.model.Operator; import org.highmed.numportal.domain.model.Type; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import org.apache.commons.collections4.CollectionUtils; + import java.util.ArrayDeque; import java.util.Queue; diff --git a/src/main/java/org/highmed/numportal/domain/validation/TranslatedStringValidator.java b/src/main/java/org/highmed/numportal/domain/validation/TranslatedStringValidator.java index 1b975e5b..a6426488 100644 --- a/src/main/java/org/highmed/numportal/domain/validation/TranslatedStringValidator.java +++ b/src/main/java/org/highmed/numportal/domain/validation/TranslatedStringValidator.java @@ -1,11 +1,11 @@ package org.highmed.numportal.domain.validation; -import java.util.Map; - import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import org.apache.logging.log4j.util.Strings; +import java.util.Map; + public class TranslatedStringValidator implements ConstraintValidator> { diff --git a/src/main/java/org/highmed/numportal/domain/validation/ValidCohort.java b/src/main/java/org/highmed/numportal/domain/validation/ValidCohort.java index c72a0f8a..5da857be 100644 --- a/src/main/java/org/highmed/numportal/domain/validation/ValidCohort.java +++ b/src/main/java/org/highmed/numportal/domain/validation/ValidCohort.java @@ -2,6 +2,7 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -10,14 +11,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Constraint(validatedBy = CohortValidator.class) -@Target({ FIELD }) +@Target({FIELD}) @Retention(RUNTIME) @Documented public @interface ValidCohort { - String message() default "Cohort must be a valid one"; + String message() default "Cohort must be a valid one"; - Class[] groups() default {}; + Class[] groups() default {}; - Class[] payload() default {}; + Class[] payload() default {}; } diff --git a/src/main/java/org/highmed/numportal/domain/validation/ValidTranslatedString.java b/src/main/java/org/highmed/numportal/domain/validation/ValidTranslatedString.java index a8583077..ce93cf4f 100644 --- a/src/main/java/org/highmed/numportal/domain/validation/ValidTranslatedString.java +++ b/src/main/java/org/highmed/numportal/domain/validation/ValidTranslatedString.java @@ -1,24 +1,24 @@ package org.highmed.numportal.domain.validation; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import jakarta.validation.Constraint; -import jakarta.validation.Payload; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; @Constraint(validatedBy = TranslatedStringValidator.class) -@Target({ FIELD }) +@Target({FIELD}) @Retention(RUNTIME) @Documented public @interface ValidTranslatedString { - String message() default "Translated string must be a valid one"; + String message() default "Translated string must be a valid one"; - Class[] groups() default {}; + Class[] groups() default {}; - Class[] payload() default {}; + Class[] payload() default {}; } diff --git a/src/main/java/org/highmed/numportal/events/DeactivateUserEvent.java b/src/main/java/org/highmed/numportal/events/DeactivateUserEvent.java index f2e5b4bd..8db78b50 100644 --- a/src/main/java/org/highmed/numportal/events/DeactivateUserEvent.java +++ b/src/main/java/org/highmed/numportal/events/DeactivateUserEvent.java @@ -4,21 +4,21 @@ public class DeactivateUserEvent extends ApplicationEvent { - private Long organizationId; + private final Long organizationId; - private String loggedInUserId; + private final String loggedInUserId; - public DeactivateUserEvent(Object source, Long organizationId, String loggedInUserId) { - super(source); - this.organizationId = organizationId; - this.loggedInUserId = loggedInUserId; - } + public DeactivateUserEvent(Object source, Long organizationId, String loggedInUserId) { + super(source); + this.organizationId = organizationId; + this.loggedInUserId = loggedInUserId; + } - public Long getOrganizationId() { - return organizationId; - } + public Long getOrganizationId() { + return organizationId; + } - public String getLoggedInUserId() { - return loggedInUserId; - } + public String getLoggedInUserId() { + return loggedInUserId; + } } diff --git a/src/main/java/org/highmed/numportal/listeners/DeactivateUserListener.java b/src/main/java/org/highmed/numportal/listeners/DeactivateUserListener.java index 6da5a284..915a7c83 100644 --- a/src/main/java/org/highmed/numportal/listeners/DeactivateUserListener.java +++ b/src/main/java/org/highmed/numportal/listeners/DeactivateUserListener.java @@ -2,6 +2,7 @@ import org.highmed.numportal.events.DeactivateUserEvent; import org.highmed.numportal.service.UserDetailsService; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; @@ -13,12 +14,12 @@ @Component public class DeactivateUserListener implements ApplicationListener { - private final UserDetailsService userDetailsService; + private final UserDetailsService userDetailsService; - @Async - @Override - public void onApplicationEvent(DeactivateUserEvent event) { - log.info("Deactivate users for organization {} was triggered by {} ", event.getOrganizationId(), event.getLoggedInUserId()); - userDetailsService.deactivateUsers(event.getLoggedInUserId(), event.getOrganizationId()); - } + @Async + @Override + public void onApplicationEvent(DeactivateUserEvent event) { + log.info("Deactivate users for organization {} was triggered by {} ", event.getOrganizationId(), event.getLoggedInUserId()); + userDetailsService.deactivateUsers(event.getLoggedInUserId(), event.getOrganizationId()); + } } diff --git a/src/main/java/org/highmed/numportal/listeners/UserCacheInit.java b/src/main/java/org/highmed/numportal/listeners/UserCacheInit.java index 51f9101b..2df9cf82 100644 --- a/src/main/java/org/highmed/numportal/listeners/UserCacheInit.java +++ b/src/main/java/org/highmed/numportal/listeners/UserCacheInit.java @@ -1,6 +1,7 @@ package org.highmed.numportal.listeners; import org.highmed.numportal.service.UserService; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -12,13 +13,13 @@ @Component public class UserCacheInit implements ApplicationListener { - private final UserService userService; + private final UserService userService; - @Override - public void onApplicationEvent(ApplicationReadyEvent event) { - log.info("---- start load existing users into cache ----- "); - userService.initializeUsersCache(); - log.info("---- end load existing users into cache ----- "); - userService.initializeTranslationCache(); - } + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + log.info("---- start load existing users into cache ----- "); + userService.initializeUsersCache(); + log.info("---- end load existing users into cache ----- "); + userService.initializeTranslationCache(); + } } diff --git a/src/main/java/org/highmed/numportal/mapper/AqlMapper.java b/src/main/java/org/highmed/numportal/mapper/AqlMapper.java index 3fa9c3e4..7bf658c3 100644 --- a/src/main/java/org/highmed/numportal/mapper/AqlMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/AqlMapper.java @@ -4,13 +4,14 @@ import org.highmed.numportal.domain.dto.AqlDto; import org.highmed.numportal.domain.model.Aql; import org.highmed.numportal.service.UserService; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.Objects; +import javax.annotation.PostConstruct; @Slf4j @Component diff --git a/src/main/java/org/highmed/numportal/mapper/CohortMapper.java b/src/main/java/org/highmed/numportal/mapper/CohortMapper.java index 42a54de3..0593a92e 100644 --- a/src/main/java/org/highmed/numportal/mapper/CohortMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/CohortMapper.java @@ -5,13 +5,14 @@ import org.highmed.numportal.domain.model.Cohort; import org.highmed.numportal.domain.model.CohortGroup; import org.highmed.numportal.domain.model.Type; + import lombok.AllArgsConstructor; import org.modelmapper.ModelMapper; import org.modelmapper.PropertyMap; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; @Component @AllArgsConstructor @@ -43,8 +44,8 @@ private CohortGroupDto convertToCohortGroupDto(CohortGroup cohortGroup) { if (cohortGroup.getType().equals(Type.GROUP)) { dto.setChildren( cohortGroup.getChildren().stream() - .map(this::convertToCohortGroupDto) - .collect(Collectors.toList())); + .map(this::convertToCohortGroupDto) + .collect(Collectors.toList())); } return dto; } diff --git a/src/main/java/org/highmed/numportal/mapper/CommentMapper.java b/src/main/java/org/highmed/numportal/mapper/CommentMapper.java index b2c409e9..942a644f 100644 --- a/src/main/java/org/highmed/numportal/mapper/CommentMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/CommentMapper.java @@ -4,6 +4,7 @@ import org.highmed.numportal.domain.model.Comment; import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.service.UserService; + import lombok.AllArgsConstructor; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; diff --git a/src/main/java/org/highmed/numportal/mapper/OrganizationMapper.java b/src/main/java/org/highmed/numportal/mapper/OrganizationMapper.java index 8afe9b45..dd601825 100644 --- a/src/main/java/org/highmed/numportal/mapper/OrganizationMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/OrganizationMapper.java @@ -4,6 +4,7 @@ import org.highmed.numportal.domain.model.MailDomain; import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.service.OrganizationService; + import lombok.AllArgsConstructor; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; @@ -22,8 +23,8 @@ public OrganizationDto convertToDto(Organization organization) { OrganizationDto organizationDto = modelMapper.map(organization, OrganizationDto.class); organizationDto.setMailDomains( organization.getDomains().stream() - .map(MailDomain::getName) - .collect(Collectors.toSet())); + .map(MailDomain::getName) + .collect(Collectors.toSet())); organizationDto.setAllowedToBeDeleted(organizationService.isAllowedToBeDeleted(organization.getId())); return organizationDto; diff --git a/src/main/java/org/highmed/numportal/mapper/ProjectMapper.java b/src/main/java/org/highmed/numportal/mapper/ProjectMapper.java index 7aae32e8..8518ecb3 100644 --- a/src/main/java/org/highmed/numportal/mapper/ProjectMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/ProjectMapper.java @@ -5,6 +5,7 @@ import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.service.UserService; + import lombok.AllArgsConstructor; import org.modelmapper.ModelMapper; import org.modelmapper.PropertyMap; diff --git a/src/main/java/org/highmed/numportal/mapper/ProjectViewMapper.java b/src/main/java/org/highmed/numportal/mapper/ProjectViewMapper.java index abde79bb..05d60b26 100644 --- a/src/main/java/org/highmed/numportal/mapper/ProjectViewMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/ProjectViewMapper.java @@ -1,9 +1,10 @@ package org.highmed.numportal.mapper; -import org.highmed.numportal.domain.dto.ProjectViewTO; +import org.highmed.numportal.domain.dto.ProjectViewDto; import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.service.UserService; + import lombok.AllArgsConstructor; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Component; @@ -11,13 +12,14 @@ @Component @AllArgsConstructor public class ProjectViewMapper { - private final ModelMapper modelMapper; - private final UserService userService; - public ProjectViewTO convertToDto(Project project) { - ProjectViewTO projectViewTO = modelMapper.map(project, ProjectViewTO.class); - User coordinator = userService.getOwner(project.getCoordinator().getUserId()); - projectViewTO.setCoordinator(coordinator); - return projectViewTO; - } + private final ModelMapper modelMapper; + private final UserService userService; + + public ProjectViewDto convertToDto(Project project) { + ProjectViewDto projectViewDto = modelMapper.map(project, ProjectViewDto.class); + User coordinator = userService.getOwner(project.getCoordinator().getUserId()); + projectViewDto.setCoordinator(coordinator); + return projectViewDto; + } } diff --git a/src/main/java/org/highmed/numportal/mapper/TemplateMapper.java b/src/main/java/org/highmed/numportal/mapper/TemplateMapper.java index 02a4b86c..e122f407 100644 --- a/src/main/java/org/highmed/numportal/mapper/TemplateMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/TemplateMapper.java @@ -2,16 +2,17 @@ import org.highmed.numportal.domain.dto.TemplateInfoDto; import org.highmed.numportal.domain.dto.TemplateMetadataDto; + import lombok.AllArgsConstructor; import org.ehrbase.openehr.sdk.response.dto.ehrscape.TemplateMetaDataDto; import org.modelmapper.ModelMapper; import org.modelmapper.PropertyMap; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; @Component @AllArgsConstructor @@ -38,8 +39,8 @@ public TemplateMetadataDto convertToTemplateMetadataDto(TemplateMetaDataDto meta public List convertToTemplateInfoDtoList(Map templateInfoMap) { if (templateInfoMap != null) { return templateInfoMap.entrySet().stream() - .map(e -> TemplateInfoDto.builder().templateId(e.getKey()).name(e.getValue()).build()) - .collect(Collectors.toList()); + .map(e -> TemplateInfoDto.builder().templateId(e.getKey()).name(e.getValue()).build()) + .collect(Collectors.toList()); } else { return List.of(); } diff --git a/src/main/java/org/highmed/numportal/mapper/UserDetailsMapper.java b/src/main/java/org/highmed/numportal/mapper/UserDetailsMapper.java index f59aa86a..d313e8f9 100644 --- a/src/main/java/org/highmed/numportal/mapper/UserDetailsMapper.java +++ b/src/main/java/org/highmed/numportal/mapper/UserDetailsMapper.java @@ -2,6 +2,7 @@ import org.highmed.numportal.domain.dto.UserDetailsDto; import org.highmed.numportal.domain.model.admin.UserDetails; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; diff --git a/src/main/java/org/highmed/numportal/properties/ClamAVProperties.java b/src/main/java/org/highmed/numportal/properties/ClamAVProperties.java index 0baaef44..bd80cc4d 100644 --- a/src/main/java/org/highmed/numportal/properties/ClamAVProperties.java +++ b/src/main/java/org/highmed/numportal/properties/ClamAVProperties.java @@ -4,33 +4,34 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +/** + * NOTE # clamd.conf setup + * # Close the connection when the data size limit is exceeded. + * # The value should match your MTA's limit for a maximum attachment size. + * # Default: 100M + * #StreamMaxLength 25M + * + *

+ * TemporaryDirectory + */ @Data @Configuration @ConfigurationProperties(prefix = "clam-av") public class ClamAVProperties { - /** - * NOTE # clamd.conf setup - * # Close the connection when the data size limit is exceeded. - * # The value should match your MTA's limit for a maximum attachment size. - * # Default: 100M - * #StreamMaxLength 25M - * - * TemporaryDirectory - */ - - /** - * TCPAddr from clamd.conf - */ - private String host; - - /** - * TCPSocket from clamd.conf - */ - private int port; - - private int readTimeout; - - private int connectionTimeout; + + /** + * TCPAddr from clamd.conf + */ + private String host; + + /** + * TCPSocket from clamd.conf + */ + private int port; + + private int readTimeout; + + private int connectionTimeout; } diff --git a/src/main/java/org/highmed/numportal/properties/PseudonymsPsnWorkflowProperties.java b/src/main/java/org/highmed/numportal/properties/PseudonymsPsnWorkflowProperties.java index b901cfc5..b55509d8 100644 --- a/src/main/java/org/highmed/numportal/properties/PseudonymsPsnWorkflowProperties.java +++ b/src/main/java/org/highmed/numportal/properties/PseudonymsPsnWorkflowProperties.java @@ -9,13 +9,13 @@ @ConfigurationProperties(prefix = "requestpsnworkflow.params") public class PseudonymsPsnWorkflowProperties { - private String study; + private String study; - private String source; + private String source; - private String target; + private String target; - private String apiKey; + private String apiKey; - private String event; + private String event; } diff --git a/src/main/java/org/highmed/numportal/service/AqlService.java b/src/main/java/org/highmed/numportal/service/AqlService.java index e805708b..2b2f0512 100644 --- a/src/main/java/org/highmed/numportal/service/AqlService.java +++ b/src/main/java/org/highmed/numportal/service/AqlService.java @@ -1,14 +1,5 @@ package org.highmed.numportal.service; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.ehrbase.aqleditor.dto.aql.QueryValidationResponse; -import org.ehrbase.aqleditor.dto.aql.Result; -import org.ehrbase.aqleditor.service.AqlEditorAqlService; -import org.ehrbase.openehr.sdk.aql.parser.AqlParseException; import org.highmed.numportal.domain.dto.Language; import org.highmed.numportal.domain.dto.SearchCriteria; import org.highmed.numportal.domain.dto.SlimAqlDto; @@ -26,17 +17,48 @@ import org.highmed.numportal.service.exception.ForbiddenException; import org.highmed.numportal.service.exception.PrivacyException; import org.highmed.numportal.service.exception.ResourceNotFound; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ehrbase.aqleditor.dto.aql.QueryValidationResponse; +import org.ehrbase.aqleditor.dto.aql.Result; +import org.ehrbase.aqleditor.service.AqlEditorAqlService; +import org.ehrbase.openehr.sdk.aql.parser.AqlParseException; import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.JpaSort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.OffsetDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AQL_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ACCESS_THIS_AQL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_AQL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_FIND_AQL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CATEGORY_BY_ID_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CATEGORY_ID_CANT_BE_NULL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CATEGORY_WITH_ID_DOES_NOT_EXIST; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_AQL_ID; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.THE_CATEGORY_IS_NOT_EMPTY_CANT_DELETE_IT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS; @Slf4j @Service @@ -52,7 +74,8 @@ public class AqlService { private static final String AQL_CATEGORY = "category"; private static final List AQL_CATEGORY_SORT_FIELDS = Arrays.asList("name-de", "name-en"); - private static final List AQL_QUERY_SORT_FIELDS = Arrays.asList(AQL_NAME_GERMAN, AQL_NAME_ENGLISH, AUTHOR_NAME, ORGANIZATION_NAME, AQL_CREATE_DATE, AQL_CATEGORY); + private static final List AQL_QUERY_SORT_FIELDS = Arrays.asList( + AQL_NAME_GERMAN, AQL_NAME_ENGLISH, AUTHOR_NAME, ORGANIZATION_NAME, AQL_CREATE_DATE, AQL_CATEGORY); private final AqlRepository aqlRepository; private final AqlCategoryRepository aqlCategoryRepository; private final EhrBaseService ehrBaseService; @@ -64,7 +87,7 @@ public class AqlService { private final UserService userService; /** - * Counts the number of aql queries existing in the platform + * Counts the number of aql queries existing in the platform. * * @return The number of existing AQL queries in the platform */ @@ -77,7 +100,7 @@ public Aql getAqlById(Long id, String loggedInUserId) { var aql = aqlRepository.findById(id).orElseThrow( - () -> new ResourceNotFound(AqlService.class, AQL_NOT_FOUND, String.format(AQL_NOT_FOUND, id))); + () -> new ResourceNotFound(AqlService.class, AQL_NOT_FOUND, String.format(AQL_NOT_FOUND, id))); if (aql.isViewable(loggedInUserId)) { return aql; @@ -94,12 +117,12 @@ public List getVisibleAqls(String loggedInUserId) { public Page getVisibleAqls(String loggedInUserId, Pageable pageable, SearchCriteria searchCriteria) { UserDetails userDetails = userDetailsService.checkIsUserApproved(loggedInUserId); - Sort sort = validateAndGetSortForAQLQuery(searchCriteria); + Sort sort = validateAndGetSortForAqlQuery(searchCriteria); Pageable pageRequest; Page aqlPage; List aqlQueries; if (!searchCriteria.isSortByAuthor()) { - if(AQL_CATEGORY.equals(searchCriteria.getSortBy())) { + if (AQL_CATEGORY.equals(searchCriteria.getSortBy())) { // sort send on page request messes up the generated query for order by and ignores what is inside aql specification pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); } else { @@ -117,22 +140,23 @@ public Page getVisibleAqls(String loggedInUserId, Pageable pageable, Search } Language language = Objects.nonNull(searchCriteria.getLanguage()) ? searchCriteria.getLanguage() : Language.de; AqlSpecification aqlSpecification = AqlSpecification.builder() - .filter(searchCriteria.getFilter()) - .loggedInUserId(loggedInUserId) - .loggedInUserOrganizationId((userDetails.getOrganization() != null) ? userDetails.getOrganization().getId() : null) - .ownersUUID(usersUUID) - .language(language) - .sortOrder(sort.getOrderFor(searchCriteria.getSortBy())) - .build(); + .filter(searchCriteria.getFilter()) + .loggedInUserId(loggedInUserId) + .loggedInUserOrganizationId( + (userDetails.getOrganization() != null) ? userDetails.getOrganization().getId() : null) + .ownersUUID(usersUUID) + .language(language) + .sortOrder(sort.getOrderFor(searchCriteria.getSortBy())) + .build(); aqlPage = aqlRepository.findAll(aqlSpecification, pageRequest); aqlQueries = new ArrayList<>(aqlPage.getContent()); if (searchCriteria.isSortByAuthor()) { sortAqlQueries(aqlQueries, sort); aqlQueries = aqlQueries.stream() - .skip((long) pageable.getPageNumber() * pageable.getPageSize()) - .limit(pageable.getPageSize()) - .collect(Collectors.toList()); + .skip((long) pageable.getPageNumber() * pageable.getPageSize()) + .limit(pageable.getPageSize()) + .collect(Collectors.toList()); } return new PageImpl<>(aqlQueries, pageable, aqlPage.getTotalElements()); } @@ -154,13 +178,14 @@ private void sortAqlQueries(List aqlQueries, Sort sort) { } } } + public Aql createAql(Aql aql, String loggedInUserId, Long aqlCategoryId) { var userDetails = userDetailsService.checkIsUserApproved(loggedInUserId); if (Objects.nonNull(aqlCategoryId)) { AqlCategory aqlCategory = aqlCategoryRepository.findById(aqlCategoryId).orElseThrow(() -> - new ResourceNotFound(AqlService.class, CATEGORY_BY_ID_NOT_FOUND, - String.format(CATEGORY_BY_ID_NOT_FOUND, aqlCategoryId))); + new ResourceNotFound(AqlService.class, CATEGORY_BY_ID_NOT_FOUND, + String.format(CATEGORY_BY_ID_NOT_FOUND, aqlCategoryId))); aql.setCategory(aqlCategory); } @@ -180,13 +205,13 @@ public Aql updateAql(Aql aql, Long aqlId, String loggedInUserId, Long aqlCategor .orElseThrow(() -> new ResourceNotFound(AqlService.class, CANNOT_FIND_AQL, String.format(CANNOT_FIND_AQL, aqlId))); if (Objects.nonNull(aqlCategoryId)) { AqlCategory aqlCategory = aqlCategoryRepository.findById(aqlCategoryId).orElseThrow(() -> - new ResourceNotFound(AqlService.class, CATEGORY_BY_ID_NOT_FOUND, - String.format(CATEGORY_BY_ID_NOT_FOUND, aqlCategoryId))); + new ResourceNotFound(AqlService.class, CATEGORY_BY_ID_NOT_FOUND, + String.format(CATEGORY_BY_ID_NOT_FOUND, aqlCategoryId))); aqlToEdit.setCategory(aqlCategory); } if (aqlToEdit.hasEmptyOrDifferentOwner(loggedInUserId)) { - throw new ForbiddenException( AqlService.class, AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER, + throw new ForbiddenException(AqlService.class, AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER, String.format(AQL_EDIT_FOR_AQL_WITH_ID_IS_NOT_ALLOWED_AQL_HAS_DIFFERENT_OWNER, aqlId)); } @@ -281,6 +306,7 @@ public void deleteCategoryById(String loggedInUserId, Long id) { throw new BadRequestException(AqlService.class, THE_CATEGORY_IS_NOT_EMPTY_CANT_DELETE_IT); } } + public boolean existsById(Long aqlId) { return aqlRepository.existsById(aqlId); } @@ -300,7 +326,7 @@ private void validateQuery(String query) { try { log.error("AQL validation for query '{}' failed: {}", query, response.getMessage()); throw new BadRequestException(QueryValidationResponse.class, COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE, - String.format(COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE, mapper.writeValueAsString(response))); + String.format(COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE, mapper.writeValueAsString(response))); } catch (JsonProcessingException e) { log.error(COULD_NOT_SERIALIZE_AQL_VALIDATION_RESPONSE, e); } @@ -329,7 +355,7 @@ private Optional validateAndGetSort(SearchCriteria searchCriteria) { return Optional.of(JpaSort.unsafe(Sort.Direction.ASC, "name->>'de'")); } - private Sort validateAndGetSortForAQLQuery(SearchCriteria searchCriteria) { + private Sort validateAndGetSortForAqlQuery(SearchCriteria searchCriteria) { if (searchCriteria.isValid() && StringUtils.isNotEmpty(searchCriteria.getSortBy())) { if (!AQL_QUERY_SORT_FIELDS.contains(searchCriteria.getSortBy())) { throw new BadRequestException(AqlService.class, String.format("Invalid %s sortBy field for aql queries", searchCriteria.getSortBy())); diff --git a/src/main/java/org/highmed/numportal/service/CohortService.java b/src/main/java/org/highmed/numportal/service/CohortService.java index 0b3d5c4d..d2936f50 100644 --- a/src/main/java/org/highmed/numportal/service/CohortService.java +++ b/src/main/java/org/highmed/numportal/service/CohortService.java @@ -11,6 +11,17 @@ import org.highmed.numportal.domain.repository.CohortRepository; import org.highmed.numportal.domain.repository.ProjectRepository; import org.highmed.numportal.properties.PrivacyProperties; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.exception.BadRequestException; +import org.highmed.numportal.service.exception.ForbiddenException; +import org.highmed.numportal.service.exception.PrivacyException; +import org.highmed.numportal.service.exception.ResourceNotFound; +import org.highmed.numportal.service.executors.CohortExecutor; +import org.highmed.numportal.service.policy.EhrPolicy; +import org.highmed.numportal.service.policy.Policy; +import org.highmed.numportal.service.policy.ProjectPolicyService; +import org.highmed.numportal.service.policy.TemplatesPolicy; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; @@ -25,41 +36,36 @@ import org.ehrbase.openehr.sdk.aql.parser.AqlQueryParser; import org.ehrbase.openehr.sdk.aql.render.AqlRenderer; import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; -import org.highmed.numportal.service.ehrbase.EhrBaseService; -import org.highmed.numportal.service.exception.BadRequestException; -import org.highmed.numportal.service.exception.ForbiddenException; -import org.highmed.numportal.service.exception.PrivacyException; -import org.highmed.numportal.service.exception.ResourceNotFound; -import org.highmed.numportal.service.executors.CohortExecutor; -import org.highmed.numportal.service.policy.EhrPolicy; -import org.highmed.numportal.service.policy.Policy; -import org.highmed.numportal.service.policy.ProjectPolicyService; -import org.highmed.numportal.service.policy.TemplatesPolicy; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CHANGING_COHORT_ONLY_ALLOWED_BY_THE_OWNER_OF_THE_PROJECT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COHORT_CHANGE_ONLY_ALLOWED_ON_PROJECT_STATUS_DRAFT_OR_PENDING; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COHORT_GROUP_CANNOT_BE_EMPTY; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COHORT_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_AQL_ID; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_COHORT_GROUP_AQL_MISSING; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_COHORT_GROUP_AQL_MISSING_PARAMETERS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_COHORT_GROUP_CHILDREN_MISSING; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESULTS_WITHHELD_FOR_PRIVACY_REASONS; @Slf4j @Service @AllArgsConstructor public class CohortService { - private final CohortRepository cohortRepository; - private final CohortExecutor cohortExecutor; - private final UserDetailsService userDetailsService; - private final ModelMapper modelMapper; - private final AqlService aqlService; - private final ProjectRepository projectRepository; - private final PrivacyProperties privacyProperties; - private final ProjectPolicyService policyService; - private final EhrBaseService ehrBaseService; - private final ContentService contentService; - private final TemplateService templateService; - public static final String GET_PATIENTS_PER_CLINIC = "SELECT e/ehr_id/value as patient_id " + "FROM EHR e CONTAINS COMPOSITION c " @@ -74,11 +80,22 @@ public class CohortService { private static final String AGE_INTERVAL_LABEL = "%d-%d"; private static final int MAX_AGE = 122; private static final int AGE_INTERVAL = 10; + private final CohortRepository cohortRepository; + private final CohortExecutor cohortExecutor; + private final UserDetailsService userDetailsService; + private final ModelMapper modelMapper; + private final AqlService aqlService; + private final ProjectRepository projectRepository; + private final PrivacyProperties privacyProperties; + private final ProjectPolicyService policyService; + private final EhrBaseService ehrBaseService; + private final ContentService contentService; + private final TemplateService templateService; public Cohort getCohort(Long cohortId, String userId) { userDetailsService.checkIsUserApproved(userId); return cohortRepository.findById(cohortId).orElseThrow( - () -> new ResourceNotFound(CohortService.class, COHORT_NOT_FOUND, String.format(COHORT_NOT_FOUND, cohortId))); + () -> new ResourceNotFound(CohortService.class, COHORT_NOT_FOUND, String.format(COHORT_NOT_FOUND, cohortId))); } public Cohort createCohort(CohortDto cohortDto, String userId) { @@ -94,11 +111,11 @@ public Cohort createCohort(CohortDto cohortDto, String userId) { Cohort cohort = Cohort.builder() - .name(cohortDto.getName()) - .description(cohortDto.getDescription()) - .project(project) - .cohortGroup(convertToCohortGroupEntity(cohortDto.getCohortGroup())) - .build(); + .name(cohortDto.getName()) + .description(cohortDto.getDescription()) + .project(project) + .cohortGroup(convertToCohortGroupEntity(cohortDto.getCohortGroup())) + .build(); project.setCohort(cohort); log.info("Cohort created by user {}", userId); @@ -107,10 +124,10 @@ public Cohort createCohort(CohortDto cohortDto, String userId) { public Cohort toCohort(CohortDto cohortDto) { return Cohort.builder() - .name(cohortDto.getName()) - .description(cohortDto.getDescription()) - .cohortGroup(convertToCohortGroupEntity(cohortDto.getCohortGroup())) - .build(); + .name(cohortDto.getName()) + .description(cohortDto.getDescription()) + .cohortGroup(convertToCohortGroupEntity(cohortDto.getCohortGroup())) + .build(); } public Set executeCohort(long cohortId, Boolean allowUsageOutsideEu) { @@ -140,8 +157,8 @@ public Map getSizePerTemplates( Cohort cohort = Cohort.builder() - .cohortGroup(convertToCohortGroupEntity(requestDto.getCohortDto().getCohortGroup())) - .build(); + .cohortGroup(convertToCohortGroupEntity(requestDto.getCohortDto().getCohortGroup())) + .build(); Set ehrIds = cohortExecutor.execute(cohort, false); if (ehrIds.size() < privacyProperties.getMinHits()) { @@ -253,7 +270,7 @@ private void validateCohortParameters(CohortGroupDto cohortGroupDto) { } if (CollectionUtils.isNotEmpty(cohortGroupDto.getChildren())) { cohortGroupDto.getChildren() - .forEach(this::validateCohortParameters); + .forEach(this::validateCohortParameters); } } @@ -269,7 +286,7 @@ private CohortGroup convertToCohortGroupEntity(CohortGroupDto cohortGroupDto) { if (cohortGroupDto.getQuery() != null && cohortGroupDto.getQuery().getId() != null) { if (!aqlService.existsById(cohortGroupDto.getQuery().getId())) { throw new BadRequestException(CohortGroup.class, INVALID_AQL_ID, - String.format("%s: %s", INVALID_AQL_ID, cohortGroupDto.getQuery().getId())); + String.format("%s: %s", INVALID_AQL_ID, cohortGroupDto.getQuery().getId())); } } else { throw new BadRequestException(CohortGroup.class, INVALID_COHORT_GROUP_AQL_MISSING); @@ -279,14 +296,14 @@ private CohortGroup convertToCohortGroupEntity(CohortGroupDto cohortGroupDto) { if (cohortGroupDto.isGroup()) { if (CollectionUtils.isNotEmpty(cohortGroup.getChildren())) { cohortGroup.setChildren( - cohortGroupDto.getChildren().stream() - .map( - child -> { - CohortGroup cohortGroupChild = convertToCohortGroupEntity(child); - cohortGroupChild.setParent(cohortGroup); - return cohortGroupChild; - }) - .collect(Collectors.toList())); + cohortGroupDto.getChildren().stream() + .map( + child -> { + CohortGroup cohortGroupChild = convertToCohortGroupEntity(child); + cohortGroupChild.setParent(cohortGroup); + return cohortGroupChild; + }) + .collect(Collectors.toList())); } else { throw new BadRequestException(CohortService.class, INVALID_COHORT_GROUP_CHILDREN_MISSING); } @@ -347,7 +364,7 @@ private Map getSizesPerHospital(String loggedInUserId, String i for (String clinic : clinics) { if (Objects.nonNull(clinic)) { QueryResponseData queryResponseData = - ehrBaseService.executePlainQuery(String.format(GET_PATIENTS_PER_CLINIC, clinic, idsString)); + ehrBaseService.executePlainQuery(String.format(GET_PATIENTS_PER_CLINIC, clinic, idsString)); List> rows = queryResponseData.getRows(); if (rows == null) { sizes.put(clinic, 0); diff --git a/src/main/java/org/highmed/numportal/service/CommentService.java b/src/main/java/org/highmed/numportal/service/CommentService.java index 299ef761..6858647c 100644 --- a/src/main/java/org/highmed/numportal/service/CommentService.java +++ b/src/main/java/org/highmed/numportal/service/CommentService.java @@ -4,17 +4,23 @@ import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.admin.UserDetails; import org.highmed.numportal.domain.repository.CommentRepository; -import lombok.AllArgsConstructor; import org.highmed.numportal.service.exception.BadRequestException; import org.highmed.numportal.service.exception.ForbiddenException; import org.highmed.numportal.service.exception.ResourceNotFound; + +import lombok.AllArgsConstructor; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Service; import java.time.OffsetDateTime; import java.util.List; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_COMMENT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COMMENT_EDIT_FOR_COMMENT_WITH_ID_IS_NOT_ALLOWED_COMMENT_HAS_DIFFERENT_AUTHOR; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COMMENT_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_COMMENT_ID; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_DOES_NOT_EXIST; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_NOT_FOUND; @Service @AllArgsConstructor diff --git a/src/main/java/org/highmed/numportal/service/ContentService.java b/src/main/java/org/highmed/numportal/service/ContentService.java index d0658135..9969ef2c 100644 --- a/src/main/java/org/highmed/numportal/service/ContentService.java +++ b/src/main/java/org/highmed/numportal/service/ContentService.java @@ -1,6 +1,5 @@ package org.highmed.numportal.service; -import com.fasterxml.jackson.databind.ObjectMapper; import org.highmed.numportal.domain.dto.CardDto; import org.highmed.numportal.domain.dto.MetricsDto; import org.highmed.numportal.domain.dto.NavigationItemDto; @@ -8,10 +7,12 @@ import org.highmed.numportal.domain.model.Content; import org.highmed.numportal.domain.model.ContentType; import org.highmed.numportal.domain.repository.ContentItemRepository; -import lombok.extern.slf4j.Slf4j; -import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; import org.highmed.numportal.service.ehrbase.EhrBaseService; import org.highmed.numportal.service.exception.SystemException; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -22,46 +23,38 @@ import java.util.Map; import java.util.stream.Collectors; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COULDN_T_PARSE_CARD; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COULDN_T_PARSE_NAVIGATION_CONTENT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COULDN_T_SAVE_CARD; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COULDN_T_SAVE_NAVIGATION_CONTENT; @Slf4j @Service public class ContentService { - private final ContentItemRepository contentItemRepository; - - private final ObjectMapper mapper; - - private final ProjectService projectService; - - private final AqlService aqlService; - - private final OrganizationService organizationService; - - private final EhrBaseService ehrBaseService; - - private final UserDetailsService userDetailsService; - + public static final String LIST_CLINICS = + "SELECT distinct c/context/health_care_facility/name as health_care_facility FROM EHR e CONTAINS COMPOSITION c"; private static final int PROJECT_COUNT = 5; - private static final int SOFA_MIN = 0; private static final int SOFA_MAX = 24; private static final int SOFA_INTERVAL_LEN = 5; - - public static final String LIST_CLINICS = - "SELECT distinct c/context/health_care_facility/name as health_care_facility FROM EHR e CONTAINS COMPOSITION c"; - private static final String GET_CLINIC_SOFA_AVG = "SELECT avg(r/data[at0001]/events[at0002]/data[at0003]/items[at0041]/value/magnitude) as sofa_avg " + "FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION r[openEHR-EHR-OBSERVATION.sofa_score.v0] " + "WHERE c/context/health_care_facility/name = '%s'"; - private static final String GET_CLINIC_SOFA_COUNT_IN_INTERVAL = "SELECT count(r/data[at0001]/events[at0002]/data[at0003]/items[at0041]/value/magnitude) as sofa_score " + "FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION r[openEHR-EHR-OBSERVATION.sofa_score.v0] " + "WHERE r/data[at0001]/events[at0002]/data[at0003]/items[at0041]/value/magnitude >= %d " + "AND r/data[at0001]/events[at0002]/data[at0003]/items[at0041]/value/magnitude <= %d " + "AND c/context/health_care_facility/name = '%s'"; + private final ContentItemRepository contentItemRepository; + private final ObjectMapper mapper; + private final ProjectService projectService; + private final AqlService aqlService; + private final OrganizationService organizationService; + private final EhrBaseService ehrBaseService; + private final UserDetailsService userDetailsService; @Autowired public ContentService( @@ -97,91 +90,92 @@ public List getLatestProjects(List roles) { */ public MetricsDto getMetrics() { return MetricsDto.builder() - .aqls(aqlService.countAqls()) - .projects(projectService.countProjects()) - .organizations(organizationService.countOrganizations()) - .build(); + .aqls(aqlService.countAqls()) + .projects(projectService.countProjects()) + .organizations(organizationService.countOrganizations()) + .build(); } public String getNavigationItems() { try { - List contents = - contentItemRepository.findByType(ContentType.NAVIGATION).orElse(new ArrayList<>()); - if (contents.isEmpty()) { - return "[]"; - } else { - return contents.get(0).getContent(); - } - } catch (Exception e) { - log.error("Couldn't parse navigation content", e); - throw new SystemException(ContentService.class, COULDN_T_PARSE_NAVIGATION_CONTENT, String.format(COULDN_T_PARSE_NAVIGATION_CONTENT, e.getMessage())); + List contents = + contentItemRepository.findByType(ContentType.NAVIGATION).orElse(new ArrayList<>()); + if (contents.isEmpty()) { + return "[]"; + } else { + return contents.get(0).getContent(); } + } catch (Exception e) { + log.error("Couldn't parse navigation content", e); + throw new SystemException( + ContentService.class, COULDN_T_PARSE_NAVIGATION_CONTENT, String.format(COULDN_T_PARSE_NAVIGATION_CONTENT, e.getMessage())); + } } public void setNavigationItems(List navigationItemDtos) { - try { - List contents = - contentItemRepository.findByType(ContentType.NAVIGATION).orElse(new ArrayList<>()); - Content navigation; - if (contents.isEmpty()) { - navigation = Content.builder().type(ContentType.NAVIGATION).build(); - } else { - navigation = contents.get(0); - } - - navigation.setContent(mapper.writeValueAsString(navigationItemDtos)); - contentItemRepository.save(navigation); + try { + List contents = + contentItemRepository.findByType(ContentType.NAVIGATION).orElse(new ArrayList<>()); + Content navigation; + if (contents.isEmpty()) { + navigation = Content.builder().type(ContentType.NAVIGATION).build(); + } else { + navigation = contents.get(0); + } + + navigation.setContent(mapper.writeValueAsString(navigationItemDtos)); + contentItemRepository.save(navigation); } catch (Exception e) { log.error("Couldn't save navigation content", e); throw new SystemException(ContentService.class, COULDN_T_SAVE_NAVIGATION_CONTENT, - String.format(COULDN_T_SAVE_NAVIGATION_CONTENT, e.getMessage())); + String.format(COULDN_T_SAVE_NAVIGATION_CONTENT, e.getMessage())); } } public String getCards() { - try { - List contents = - contentItemRepository.findByType(ContentType.CARD).orElse(new ArrayList<>()); - if (contents.isEmpty()) { - return "[]"; - } else { - - return contents.get(0).getContent(); - } - } catch (Exception e) { - log.error("Couldn't parse card", e); - throw new SystemException(ContentService.class, COULDN_T_PARSE_CARD, - String.format(COULDN_T_PARSE_CARD, e.getMessage())); + try { + List contents = + contentItemRepository.findByType(ContentType.CARD).orElse(new ArrayList<>()); + if (contents.isEmpty()) { + return "[]"; + } else { + + return contents.get(0).getContent(); } + } catch (Exception e) { + log.error("Couldn't parse card", e); + throw new SystemException(ContentService.class, COULDN_T_PARSE_CARD, + String.format(COULDN_T_PARSE_CARD, e.getMessage())); + } } public void setCards(List cardDtos) { try { - List contents = - contentItemRepository.findByType(ContentType.CARD).orElse(new ArrayList<>()); - Content navigation; - if (contents.isEmpty()) { - navigation = Content.builder().type(ContentType.CARD).build(); - } else { - navigation = contents.get(0); - } - - navigation.setContent(mapper.writeValueAsString(cardDtos)); - contentItemRepository.save(navigation); + List contents = + contentItemRepository.findByType(ContentType.CARD).orElse(new ArrayList<>()); + Content navigation; + if (contents.isEmpty()) { + navigation = Content.builder().type(ContentType.CARD).build(); + } else { + navigation = contents.get(0); + } + + navigation.setContent(mapper.writeValueAsString(cardDtos)); + contentItemRepository.save(navigation); } catch (Exception e) { log.error("Couldn't save card", e); throw new SystemException(ContentService.class, COULDN_T_SAVE_CARD, - String.format(COULDN_T_SAVE_CARD, e.getMessage())); + String.format(COULDN_T_SAVE_CARD, e.getMessage())); } } - public List getClinics(String loggedInUserId) { - userDetailsService.checkIsUserApproved(loggedInUserId); - QueryResponseData responseData = ehrBaseService.executePlainQuery(LIST_CLINICS); - return responseData.getRows().stream() - .map(row -> (String) row.get(0)) - .collect(Collectors.toList()); - } + public List getClinics(String loggedInUserId) { + userDetailsService.checkIsUserApproved(loggedInUserId); + QueryResponseData responseData = ehrBaseService.executePlainQuery(LIST_CLINICS); + return responseData.getRows().stream() + .map(row -> (String) row.get(0)) + .collect(Collectors.toList()); + } public Map getClinicDistributions(String name) { Map distributions = new LinkedHashMap<>(); @@ -202,7 +196,7 @@ public Map getClinicDistributions(String name) { } public Map getClinicAverages(String loggedInUserId) { - userDetailsService.checkIsUserApproved(loggedInUserId); + userDetailsService.checkIsUserApproved(loggedInUserId); Map averages = new LinkedHashMap<>(); List clinics = getClinics(loggedInUserId); for (String clinic : clinics) { diff --git a/src/main/java/org/highmed/numportal/service/OrganizationService.java b/src/main/java/org/highmed/numportal/service/OrganizationService.java index 85f1c317..eec3ec58 100644 --- a/src/main/java/org/highmed/numportal/service/OrganizationService.java +++ b/src/main/java/org/highmed/numportal/service/OrganizationService.java @@ -10,44 +10,61 @@ import org.highmed.numportal.domain.repository.OrganizationRepository; import org.highmed.numportal.domain.specification.OrganizationSpecification; import org.highmed.numportal.events.DeactivateUserEvent; +import org.highmed.numportal.service.exception.BadRequestException; +import org.highmed.numportal.service.exception.ForbiddenException; +import org.highmed.numportal.service.exception.ResourceNotFound; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.highmed.numportal.service.exception.BadRequestException; -import org.highmed.numportal.service.exception.ForbiddenException; -import org.highmed.numportal.service.exception.ResourceNotFound; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; - -/** Service responsible for retrieving organization information from the terminology server */ +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ACCESS_THIS_RESOURCE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_UPDATE_ORGANIZATION; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_MAIL_DOMAIN; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_IS_NOT_EMPTY_CANT_DELETE_IT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_NAME_MUST_BE_UNIQUE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_NOT_FOUND; + +/** + * Service responsible for retrieving organization information from the terminology server + */ @Slf4j @Service @AllArgsConstructor public class OrganizationService { + private static final String DOMAIN_SEPARATOR = "@"; + private static final String DOMAIN_VALIDATION_REGEX = + "^(\\*\\.)?((?!-)[A-Za-z0-9-]{1,63}(? resolveOrganization(String email) { public List getMailDomainsByActiveOrganizations() { log.info("Load all mail domains from active organizations"); return mailDomainRepository.findAllByActiveOrganization().stream() - .map(MailDomain::getName) - .collect(Collectors.toList()); + .map(MailDomain::getName) + .collect(Collectors.toList()); } /** @@ -90,9 +107,9 @@ public long countOrganizations() { /** * Retrieves a list with available organizations * - * @return List with available organizations * @param roles * @param loggedInUserId + * @return List with available organizations */ public List getAllOrganizations(List roles, String loggedInUserId) { UserDetails user = userDetailsService.checkIsUserApproved(loggedInUserId); @@ -107,6 +124,7 @@ public List getAllOrganizations(List roles, String loggedI /** * Retrieves a list with available organizations + * * @param roles * @param loggedInUserId * @param searchCriteria @@ -118,13 +136,15 @@ public Page getAllOrganizations(List roles, String loggedI OrganizationSpecification organizationSpecification = new OrganizationSpecification(searchCriteria.getFilter()); Optional sortBy = validateAndGetSort(searchCriteria); if (roles.contains(Roles.SUPER_ADMIN)) { - PageRequest pageRequest = sortBy.map(sort -> PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort)).orElseGet(() -> PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); + PageRequest pageRequest = sortBy.map(sort -> PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort)) + .orElseGet(() -> PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); return organizationRepository.findAll(organizationSpecification, pageRequest); } else if (roles.contains(Roles.ORGANIZATION_ADMIN)) { return new PageImpl<>(List.of(user.getOrganization())); } return new PageImpl<>(Collections.emptyList()); } + /** * Retrieves an organization by external identifier * @@ -134,8 +154,8 @@ public Page getAllOrganizations(List roles, String loggedI public Organization getOrganizationById(Long id) { return organizationRepository - .findById(id) - .orElseThrow(() -> new ResourceNotFound(OrganizationService.class, ORGANIZATION_NOT_FOUND, String.format(ORGANIZATION_NOT_FOUND, id))); + .findById(id) + .orElseThrow(() -> new ResourceNotFound(OrganizationService.class, ORGANIZATION_NOT_FOUND, String.format(ORGANIZATION_NOT_FOUND, id))); } @Transactional @@ -145,68 +165,69 @@ public Organization create(String loggedInUserId, OrganizationDto organizationDt validateUniqueOrganizationName(organizationDto.getName(), null); validateMailDomains(organizationDto.getMailDomains()); organizationDto - .getMailDomains() - .forEach( - domain -> { - Optional mailDomain = - mailDomainRepository.findByName(domain.toLowerCase()); - if (mailDomain.isPresent()) { - throw new BadRequestException(Organization.class, ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, - String.format(ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, organizationDto.getName())); - } - }); + .getMailDomains() + .forEach( + domain -> { + Optional mailDomain = + mailDomainRepository.findByName(domain.toLowerCase()); + if (mailDomain.isPresent()) { + throw new BadRequestException(Organization.class, ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, + String.format(ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, organizationDto.getName())); + } + }); Organization organization = Organization.builder() - .name(organizationDto.getName()) - .active(Boolean.TRUE) - .build(); + .name(organizationDto.getName()) + .active(Boolean.TRUE) + .build(); organization.setDomains( - organizationDto.getMailDomains().stream() - .map( - domain -> - MailDomain.builder() - .name(domain.toLowerCase()) - .organization(organization) - .build()) - .collect(Collectors.toSet())); + organizationDto.getMailDomains().stream() + .map( + domain -> + MailDomain.builder() + .name(domain.toLowerCase()) + .organization(organization) + .build()) + .collect(Collectors.toSet())); return organizationRepository.save(organization); } @Transactional public Organization update( - Long organizationId, - OrganizationDto organizationDto, - List roles, - String loggedInUserId) { + Long organizationId, + OrganizationDto organizationDto, + List roles, + String loggedInUserId) { UserDetails user = userDetailsService.checkIsUserApproved(loggedInUserId); Organization organizationToEdit = - organizationRepository - .findById(organizationId) - .orElseThrow(() -> new ResourceNotFound(OrganizationService.class, ORGANIZATION_NOT_FOUND, String.format(ORGANIZATION_NOT_FOUND, organizationId))); + organizationRepository + .findById(organizationId) + .orElseThrow( + () -> new ResourceNotFound(OrganizationService.class, ORGANIZATION_NOT_FOUND, String.format(ORGANIZATION_NOT_FOUND, organizationId))); validateStatusChange(organizationId, organizationDto.getActive(), organizationToEdit.getActive(), user); validateUniqueOrganizationName(organizationDto.getName(), organizationId); validateMailDomains(organizationDto.getMailDomains()); organizationDto - .getMailDomains() - .forEach( - domain -> { - Optional mailDomain = - mailDomainRepository.findByName(domain.toLowerCase()); - if (mailDomain.isPresent() - && !mailDomain - .get() - .getOrganization() - .getId() - .equals(organizationToEdit.getId())) { - throw new BadRequestException(OrganizationService.class, ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, - String.format(ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, domain)); - } - }); + .getMailDomains() + .forEach( + domain -> { + Optional mailDomain = + mailDomainRepository.findByName(domain.toLowerCase()); + if (mailDomain.isPresent() + && !mailDomain + .get() + .getOrganization() + .getId() + .equals(organizationToEdit.getId())) { + throw new BadRequestException(OrganizationService.class, ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, + String.format(ORGANIZATION_MAIL_DOMAIN_ALREADY_EXISTS, domain)); + } + }); if (Roles.isSuperAdmin(roles)) { updateOrganization(organizationDto, organizationToEdit, loggedInUserId); @@ -214,7 +235,8 @@ public Organization update( if (user.getOrganization().getId().equals(organizationId)) { updateOrganization(organizationDto, organizationToEdit, loggedInUserId); } else { - throw new ForbiddenException(OrganizationService.class, CANNOT_UPDATE_ORGANIZATION, String.format(CANNOT_UPDATE_ORGANIZATION, organizationId)); + throw new ForbiddenException( + OrganizationService.class, CANNOT_UPDATE_ORGANIZATION, String.format(CANNOT_UPDATE_ORGANIZATION, organizationId)); } } else { throw new ForbiddenException(OrganizationService.class, CANNOT_ACCESS_THIS_RESOURCE); @@ -245,37 +267,38 @@ public boolean isAllowedToBeDeleted(Long organizationId) { } private void validateMailDomains(Set domains) { - if(Objects.nonNull(domains)) { + if (Objects.nonNull(domains)) { domains.forEach( - domain -> { - if (StringUtils.isEmpty(domain)) { - throw new BadRequestException(OrganizationService.class, ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY); - } - - if (!Pattern.matches(DOMAIN_VALIDATION_REGEX, domain.toLowerCase())) { - throw new BadRequestException(OrganizationService.class, INVALID_MAIL_DOMAIN, - String.format(INVALID_MAIL_DOMAIN, domain)); - } - }); + domain -> { + if (StringUtils.isEmpty(domain)) { + throw new BadRequestException(OrganizationService.class, ORGANIZATION_MAIL_DOMAIN_CANNOT_BE_NULL_OR_EMPTY); + } + + if (!Pattern.matches(DOMAIN_VALIDATION_REGEX, domain.toLowerCase())) { + throw new BadRequestException(OrganizationService.class, INVALID_MAIL_DOMAIN, + String.format(INVALID_MAIL_DOMAIN, domain)); + } + }); } } private void validateUniqueOrganizationName(String name, Long organizationId) { organizationRepository - .findByName(name) - .ifPresent( - d -> { - if (Objects.isNull(organizationId) || !d.getId().equals(organizationId)) { - throw new BadRequestException(OrganizationService.class, ORGANIZATION_NAME_MUST_BE_UNIQUE, - String.format(ORGANIZATION_NAME_MUST_BE_UNIQUE, name)); - } - }); + .findByName(name) + .ifPresent( + d -> { + if (Objects.isNull(organizationId) || !d.getId().equals(organizationId)) { + throw new BadRequestException(OrganizationService.class, ORGANIZATION_NAME_MUST_BE_UNIQUE, + String.format(ORGANIZATION_NAME_MUST_BE_UNIQUE, name)); + } + }); } private void validateStatusChange(Long organizationId, Boolean newStatus, Boolean oldStatus, UserDetails loggedInUser) { if (statusChanged(oldStatus, newStatus) && loggedInUser.getOrganization().getId().equals(organizationId)) { log.warn("User {} is not allowed to change status for own organization {}", loggedInUser.getUserId(), organizationId); - throw new ForbiddenException(OrganizationService.class, NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS, NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS); + throw new ForbiddenException( + OrganizationService.class, NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS, NOT_ALLOWED_TO_UPDATE_OWN_ORGANIZATION_STATUS); } } @@ -294,30 +317,32 @@ private void updateOrganization(OrganizationDto dto, Organization organization, if (Objects.nonNull(dto.getActive())) { organization.setActive(dto.getActive()); if (statusChanged(oldOrganizationStatus, dto.getActive()) && Boolean.FALSE.equals(dto.getActive())) { - log.info("Organization {} was deactivated by {}, so trigger event to deactivate all users assigned to this organization", organization.getId(), loggedInUserId); - DeactivateUserEvent deactivateUserEvent = new DeactivateUserEvent(this, organization.getId(), loggedInUserId); - applicationEventPublisher.publishEvent(deactivateUserEvent); + log.info( + "Organization {} was deactivated by {}, so trigger event to deactivate all users assigned to this organization", organization.getId(), + loggedInUserId); + DeactivateUserEvent deactivateUserEvent = new DeactivateUserEvent(this, organization.getId(), loggedInUserId); + applicationEventPublisher.publishEvent(deactivateUserEvent); } } Set newDomains = new HashSet<>(); dto.getMailDomains() - .forEach( - domainName -> { - Optional mailDomain = - mailDomainRepository.findByName(domainName.toLowerCase()); - if (mailDomain.isEmpty()) { - newDomains.add( - mailDomainRepository.save( - MailDomain.builder() - .name(domainName.toLowerCase()) - .organization(organization) - .build())); - } else { - newDomains.add(mailDomain.get()); - } - }); + .forEach( + domainName -> { + Optional mailDomain = + mailDomainRepository.findByName(domainName.toLowerCase()); + if (mailDomain.isEmpty()) { + newDomains.add( + mailDomainRepository.save( + MailDomain.builder() + .name(domainName.toLowerCase()) + .organization(organization) + .build())); + } else { + newDomains.add(mailDomain.get()); + } + }); if (organization.getDomains() != null) { organization.getDomains().clear(); @@ -342,8 +367,7 @@ private Optional matchOrganization(String domain) { } /** - * Converts an organization email domain list into a list containing correlation between domains - * and subdomains and sorts matches descending + * Converts an organization email domain list into a list containing correlation between domains and subdomains and sorts matches descending * * @param allDomains * @return @@ -352,13 +376,13 @@ private List toMailDomainDetails(List allDomains) List mailDomainDetails = new LinkedList<>(); allDomains.forEach( - mailDomain -> - mailDomainDetails.add( - MailDomainDetails.builder() - .domain(mailDomain.getName().toLowerCase()) - .regex(domainToRegex(mailDomain.getName().toLowerCase())) - .organization(mailDomain.getOrganization()) - .build())); + mailDomain -> + mailDomainDetails.add( + MailDomainDetails.builder() + .domain(mailDomain.getName().toLowerCase()) + .regex(domainToRegex(mailDomain.getName().toLowerCase())) + .organization(mailDomain.getOrganization()) + .build())); for (MailDomainDetails d1 : mailDomainDetails) { for (MailDomainDetails d2 : mailDomainDetails) { @@ -379,10 +403,12 @@ private String domainToRegex(String domain) { private Optional validateAndGetSort(SearchCriteria searchCriteria) { if (searchCriteria.isValid() && StringUtils.isNotEmpty(searchCriteria.getSortBy())) { if (!"name".equals(searchCriteria.getSortBy())) { - throw new BadRequestException(OrganizationService.class, String.format("Invalid %s sortBy field for organization", searchCriteria.getSortBy())); + throw new BadRequestException( + OrganizationService.class, String.format("Invalid %s sortBy field for organization", searchCriteria.getSortBy())); } - return Optional.of(Sort.by(Sort.Direction.valueOf(searchCriteria.getSort().toUpperCase()), - searchCriteria.getSortBy())); + return Optional.of(Sort.by( + Sort.Direction.valueOf(searchCriteria.getSort().toUpperCase()), + searchCriteria.getSortBy())); } return Optional.empty(); } @@ -390,6 +416,7 @@ private Optional validateAndGetSort(SearchCriteria searchCriteria) { @Data @Builder static class MailDomainDetails { + String domain; String regex; int matches; diff --git a/src/main/java/org/highmed/numportal/service/ProjectDocCreator.java b/src/main/java/org/highmed/numportal/service/ProjectDocCreator.java index cb9cd6f4..6b0f778c 100644 --- a/src/main/java/org/highmed/numportal/service/ProjectDocCreator.java +++ b/src/main/java/org/highmed/numportal/service/ProjectDocCreator.java @@ -9,10 +9,11 @@ import org.highmed.numportal.domain.model.Type; import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.domain.repository.CohortRepository; +import org.highmed.numportal.service.exception.SystemException; + import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; -import org.highmed.numportal.service.exception.SystemException; import org.springframework.context.MessageSource; import org.springframework.stereotype.Component; @@ -39,7 +40,8 @@ public class ProjectDocCreator { public byte[] getDocBytesOfProject(ProjectDto projectInfo, Locale locale) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, + OutputStreamWriter outputStreamWriter = new OutputStreamWriter( + outputStream, StandardCharsets.UTF_8); writeDocument(projectInfo, outputStreamWriter, locale); outputStreamWriter.close(); @@ -71,8 +73,8 @@ private void writeDocument(@NotNull ProjectDto project, OutputStreamWriter outpu String categoriesString = StringUtils.EMPTY; if (categories != null) { categoriesString = categories.stream().map(category -> messageSource.getMessage( - "category." + category.toString().toLowerCase(Locale.ROOT), null, locale)) - .collect(Collectors.joining(", ")); + "category." + category.toString().toLowerCase(Locale.ROOT), null, locale)) + .collect(Collectors.joining(", ")); } addSection("category", categoriesString, outputStreamWriter, locale); addSection("start_date", @@ -91,7 +93,7 @@ private void writeDocument(@NotNull ProjectDto project, OutputStreamWriter outpu List templates = project.getTemplates(); if (templates != null) { templatesString = templates.stream().map(TemplateInfoDto::getName) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\n")); } addSection("templates", templatesString, outputStreamWriter, locale); addSection("cohort", getCohort(project), outputStreamWriter, locale); @@ -103,8 +105,8 @@ private String getCohort(ProjectDto project) { return StringUtils.EMPTY; } Cohort cohort = cohortRepository.findById(cohortId) - .orElseThrow(() -> new SystemException(ProjectDocCreator.class, CAN_T_FIND_THE_COHORT_BY_ID, - String.format(CAN_T_FIND_THE_COHORT_BY_ID, cohortId))); + .orElseThrow(() -> new SystemException(ProjectDocCreator.class, CAN_T_FIND_THE_COHORT_BY_ID, + String.format(CAN_T_FIND_THE_COHORT_BY_ID, cohortId))); CohortGroup group = cohort.getCohortGroup(); if (group == null) { return StringUtils.EMPTY; @@ -139,7 +141,7 @@ private void printParameters(Map parameters, StringBuilder build } builder.append( parameters.entrySet().stream().map(entry -> entry.getKey() + ":" + entry.getValue()) - .collect(Collectors.joining(", "))); + .collect(Collectors.joining(", "))); } private String getYesNo(boolean yesNo, Locale locale) { diff --git a/src/main/java/org/highmed/numportal/service/ProjectService.java b/src/main/java/org/highmed/numportal/service/ProjectService.java index 21f0d7a8..7fb87368 100644 --- a/src/main/java/org/highmed/numportal/service/ProjectService.java +++ b/src/main/java/org/highmed/numportal/service/ProjectService.java @@ -1,11 +1,22 @@ package org.highmed.numportal.service; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.highmed.numportal.attachment.domain.dto.LightAttachmentDto; import org.highmed.numportal.attachment.service.AttachmentService; -import org.highmed.numportal.domain.dto.*; -import org.highmed.numportal.domain.model.*; +import org.highmed.numportal.domain.dto.CohortDto; +import org.highmed.numportal.domain.dto.Language; +import org.highmed.numportal.domain.dto.ProjectDto; +import org.highmed.numportal.domain.dto.ProjectInfoDto; +import org.highmed.numportal.domain.dto.SearchCriteria; +import org.highmed.numportal.domain.dto.TemplateInfoDto; +import org.highmed.numportal.domain.dto.UserDetailsDto; +import org.highmed.numportal.domain.dto.ZarsInfoDto; +import org.highmed.numportal.domain.model.Cohort; +import org.highmed.numportal.domain.model.ExportType; +import org.highmed.numportal.domain.model.Organization; +import org.highmed.numportal.domain.model.Project; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.model.ProjectTransition; +import org.highmed.numportal.domain.model.Roles; import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.domain.model.admin.UserDetails; import org.highmed.numportal.domain.repository.ProjectRepository; @@ -14,6 +25,32 @@ import org.highmed.numportal.mapper.ProjectMapper; import org.highmed.numportal.properties.ConsentProperties; import org.highmed.numportal.properties.PrivacyProperties; +import org.highmed.numportal.service.atna.AtnaService; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.ehrbase.ResponseFilter; +import org.highmed.numportal.service.email.ZarsService; +import org.highmed.numportal.service.exception.BadRequestException; +import org.highmed.numportal.service.exception.ForbiddenException; +import org.highmed.numportal.service.exception.PrivacyException; +import org.highmed.numportal.service.exception.ResourceNotFound; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.executors.CohortQueryLister; +import org.highmed.numportal.service.metric.ProjectsMetrics; +import org.highmed.numportal.service.notification.NotificationService; +import org.highmed.numportal.service.notification.dto.Notification; +import org.highmed.numportal.service.notification.dto.ProjectApprovalRequestNotification; +import org.highmed.numportal.service.notification.dto.ProjectCloseNotification; +import org.highmed.numportal.service.notification.dto.ProjectStartNotification; +import org.highmed.numportal.service.notification.dto.ProjectStatusChangeNotification; +import org.highmed.numportal.service.notification.dto.ProjectStatusChangeRequestNotification; +import org.highmed.numportal.service.policy.EhrPolicy; +import org.highmed.numportal.service.policy.EuropeanConsentPolicy; +import org.highmed.numportal.service.policy.Policy; +import org.highmed.numportal.service.policy.ProjectPolicyService; +import org.highmed.numportal.service.policy.TemplatesPolicy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,19 +62,12 @@ import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; import org.ehrbase.openehr.sdk.aql.parser.AqlQueryParser; import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; -import org.highmed.numportal.service.atna.AtnaService; -import org.highmed.numportal.service.ehrbase.EhrBaseService; -import org.highmed.numportal.service.ehrbase.ResponseFilter; -import org.highmed.numportal.service.email.ZarsService; -import org.highmed.numportal.service.exception.*; -import org.highmed.numportal.service.executors.CohortQueryLister; -import org.highmed.numportal.service.metric.ProjectsMetrics; -import org.highmed.numportal.service.notification.NotificationService; -import org.highmed.numportal.service.notification.dto.*; -import org.highmed.numportal.service.policy.*; -import org.highmed.numportal.service.exception.*; import org.modelmapper.ModelMapper; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; @@ -57,1211 +87,1245 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import static java.util.Objects.nonNull; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ACCESS_THIS_PROJECT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ARCHIVE_PROJECT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_PROJECT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_PROJECT_INVALID_STATUS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_CREATING_THE_PROJECT_PDF; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_WHILE_CREATING_THE_CSV_FILE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_WHILE_RETRIEVING_DATA; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_PROJECT_STATUS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_PROJECT_STATUS_PARAM; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NO_PERMISSIONS_TO_DELETE_ATTACHMENTS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NO_PERMISSIONS_TO_EDIT_THIS_PROJECT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_COHORT_CANNOT_BE_NULL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_TEMPLATES_CANNOT_BE_NULL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESEARCHER_NOT_APPROVED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESEARCHER_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESULTS_WITHHELD_FOR_PRIVACY_REASONS; @Service @Slf4j @AllArgsConstructor public class ProjectService { - private static final String ZIP_FILE_ENDING = ".zip"; - private static final String JSON_FILE_ENDING = ".json"; - private static final String ZIP_MEDIA_TYPE = "application/zip"; - private static final String CSV_FILE_PATTERN = "%s_%s.csv"; - - private final List availableSortFields = Arrays.asList(PROJECT_NAME, AUTHOR_NAME, ORGANIZATION_NAME, PROJECT_STATUS, PROJECT_CREATE_DATE); - - private static final String AUTHOR_NAME = "author"; - - private static final String ORGANIZATION_NAME = "organization"; + private static final String ZIP_FILE_ENDING = ".zip"; + private static final String JSON_FILE_ENDING = ".json"; + private static final String ZIP_MEDIA_TYPE = "application/zip"; + private static final String CSV_FILE_PATTERN = "%s_%s.csv"; + private static final String AUTHOR_NAME = "author"; + private static final String ORGANIZATION_NAME = "organization"; + private static final String PROJECT_NAME = "name"; + private static final String PROJECT_STATUS = "status"; + private static final String PROJECT_CREATE_DATE = "createDate"; + private final List availableSortFields = Arrays.asList(PROJECT_NAME, AUTHOR_NAME, ORGANIZATION_NAME, PROJECT_STATUS, PROJECT_CREATE_DATE); + private final ProjectRepository projectRepository; - private static final String PROJECT_NAME = "name"; + private final UserDetailsService userDetailsService; - private static final String PROJECT_STATUS = "status"; + private final EhrBaseService ehrBaseService; - private static final String PROJECT_CREATE_DATE = "createDate"; + private final ObjectMapper mapper; - private final ProjectRepository projectRepository; + private final AtnaService atnaService; - private final UserDetailsService userDetailsService; + private final UserService userService; - private final EhrBaseService ehrBaseService; + private final NotificationService notificationService; - private final ObjectMapper mapper; + private final ProjectTransitionRepository projectTransitionRepository; - private final AtnaService atnaService; + private final CohortQueryLister cohortQueryLister; - private final UserService userService; + private final ModelMapper modelMapper; - private final NotificationService notificationService; + @Nullable + private final ZarsService zarsService; - private final ProjectTransitionRepository projectTransitionRepository; + private final ProjectPolicyService projectPolicyService; - private final CohortQueryLister cohortQueryLister; + private final CohortService cohortService; - private final ModelMapper modelMapper; + private final ConsentProperties consentProperties; - @Nullable - private final ZarsService zarsService; + private final ResponseFilter responseFilter; - private final ProjectPolicyService projectPolicyService; + private final PrivacyProperties privacyProperties; - private final CohortService cohortService; + private final TemplateService templateService; - private final ConsentProperties consentProperties; + private final ProjectDocCreator projectDocCreator; - private final ResponseFilter responseFilter; + private final ProjectMapper projectMapper; - private final PrivacyProperties privacyProperties; + private final AttachmentService attachmentService; - private final TemplateService templateService; + private final ProjectsMetrics projectsMetrics; - private final ProjectDocCreator projectDocCreator; - private final ProjectMapper projectMapper; + @Transactional + public boolean deleteProject(Long projectId, String userId, List roles) throws ForbiddenException { + userDetailsService.checkIsUserApproved(userId); - private final AttachmentService attachmentService; + var project = + projectRepository + .findById(projectId) + .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); - private final ProjectsMetrics projectsMetrics; - - - @Transactional - public boolean deleteProject(Long projectId, String userId, List roles) throws ForbiddenException { - userDetailsService.checkIsUserApproved(userId); - - var project = - projectRepository - .findById(projectId) - .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); - - if (project.hasEmptyOrDifferentOwner(userId) && !roles.contains(Roles.SUPER_ADMIN)) { - throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_PROJECT, String.format(CANNOT_DELETE_PROJECT, projectId)); - } + if (project.hasEmptyOrDifferentOwner(userId) && !roles.contains(Roles.SUPER_ADMIN)) { + throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_PROJECT, String.format(CANNOT_DELETE_PROJECT, projectId)); + } - if (project.isDeletable()) { - attachmentService.deleteAllProjectAttachments(projectId, userId); - projectRepository.deleteById(projectId); - projectsMetrics.decreaseTotalNumber(); - log.info("Project {} was deleted by {}", projectId, userId); - } else { - throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_PROJECT_INVALID_STATUS, - String.format(CANNOT_DELETE_PROJECT_INVALID_STATUS, projectId, project.getStatus())); - } - return true; + if (project.isDeletable()) { + attachmentService.deleteAllProjectAttachments(projectId, userId); + projectRepository.deleteById(projectId); + projectsMetrics.decreaseTotalNumber(); + log.info("Project {} was deleted by {}", projectId, userId); + } else { + throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_PROJECT_INVALID_STATUS, + String.format(CANNOT_DELETE_PROJECT_INVALID_STATUS, projectId, project.getStatus())); } + return true; + } - @Transactional - public boolean archiveProject(Long projectId, String userId, List roles) { - var user = userDetailsService.checkIsUserApproved(userId); + @Transactional + public boolean archiveProject(Long projectId, String userId, List roles) { + var user = userDetailsService.checkIsUserApproved(userId); - var project = - projectRepository - .findById(projectId) - .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); + var project = + projectRepository + .findById(projectId) + .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); - if (project.hasEmptyOrDifferentOwner(userId) && !roles.contains(Roles.SUPER_ADMIN)) { - throw new ForbiddenException(ProjectService.class, CANNOT_ARCHIVE_PROJECT, String.format(CANNOT_ARCHIVE_PROJECT, projectId)); - } - ProjectStatus before = project.getStatus(); - validateStatus(project.getStatus(), ProjectStatus.ARCHIVED, roles); - persistTransition(project, project.getStatus(), ProjectStatus.ARCHIVED, user); - - project.setStatus(ProjectStatus.ARCHIVED); - projectRepository.save(project); - projectsMetrics.changeStatus(before, ProjectStatus.ARCHIVED); - return true; + if (project.hasEmptyOrDifferentOwner(userId) && !roles.contains(Roles.SUPER_ADMIN)) { + throw new ForbiddenException(ProjectService.class, CANNOT_ARCHIVE_PROJECT, String.format(CANNOT_ARCHIVE_PROJECT, projectId)); } - - /** - * Counts the number of projects existing in the platform - * - * @return The count of projects in the platform - */ - public long countProjects() { - return projectRepository.count(); + ProjectStatus before = project.getStatus(); + validateStatus(project.getStatus(), ProjectStatus.ARCHIVED, roles); + persistTransition(project, project.getStatus(), ProjectStatus.ARCHIVED, user); + + project.setStatus(ProjectStatus.ARCHIVED); + projectRepository.save(project); + projectsMetrics.changeStatus(before, ProjectStatus.ARCHIVED); + return true; + } + + /** + * Counts the number of projects existing in the platform + * + * @return The count of projects in the platform + */ + public long countProjects() { + return projectRepository.count(); + } + + /** + * Retrieves a list of latest projects information + * + * @param count number of projects to be retrieved + * @return The list of max requested count of the latest projects + */ + public List getLatestProjectsInfo(int count, List roles) { + + if (count < 1) { + return List.of(); } - /** - * Retrieves a list of latest projects information - * - * @param count number of projects to be retrieved - * @return The list of max requested count of the latest projects - */ - public List getLatestProjectsInfo(int count, List roles) { - - if (count < 1) { - return List.of(); - } - - List projects = projectRepository.findByStatusInOrderByCreateDateDesc(Arrays.asList(ProjectStatus.APPROVED, - ProjectStatus.PUBLISHED, ProjectStatus.CLOSED), PageRequest.of(0, count)); - return projects.stream() - .map(project -> toProjectInfo(project, roles)) - .collect(Collectors.toList()); + List projects = projectRepository.findByStatusInOrderByCreateDateDesc(Arrays.asList(ProjectStatus.APPROVED, + ProjectStatus.PUBLISHED, ProjectStatus.CLOSED), PageRequest.of(0, count)); + return projects.stream() + .map(project -> toProjectInfo(project, roles)) + .collect(Collectors.toList()); + } + + public String retrieveData(String query, Long projectId, String userId, Boolean defaultConfiguration) { + userDetailsService.checkIsUserApproved(userId); + Project project = validateAndRetrieveProject(projectId, userId); + log.info("Retrieve research data for project: {} by: user {}", projectId, userId); + List responseData; + if (BooleanUtils.isTrue(defaultConfiguration)) { + responseData = + executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); + } else { + responseData = executeCustomConfiguration(query, projectId, userId); } - public String retrieveData(String query, Long projectId, String userId, Boolean defaultConfiguration) { - userDetailsService.checkIsUserApproved(userId); - Project project = validateAndRetrieveProject(projectId, userId); - log.info("Retrieve research data for project: {} by: user {}", projectId, userId); - List responseData; - if (BooleanUtils.isTrue(defaultConfiguration)) { - responseData = - executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); - } else { - responseData = executeCustomConfiguration(query, projectId, userId); - } - - try { - return mapper.writeValueAsString(responseData); - } catch (JsonProcessingException e) { - throw new SystemException(ProjectService.class, AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL); - } + try { + return mapper.writeValueAsString(responseData); + } catch (JsonProcessingException e) { + throw new SystemException(ProjectService.class, AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL); } + } - private List executeDefaultConfiguration(Long projectId, Cohort cohort, Map templates) { + private List executeDefaultConfiguration(Long projectId, Cohort cohort, Map templates) { - if (templates == null || templates.isEmpty()) { - return List.of(); - } - - Set ehrIds = cohortService.executeCohort(cohort, false); - - if (ehrIds.size() < privacyProperties.getMinHits()) { - log.warn(RESULTS_WITHHELD_FOR_PRIVACY_REASONS); - throw new PrivacyException(ProjectService.class, RESULTS_WITHHELD_FOR_PRIVACY_REASONS); - } + if (templates == null || templates.isEmpty()) { + return List.of(); + } - List response = new LinkedList<>(); + Set ehrIds = cohortService.executeCohort(cohort, false); - templates.forEach( - (templateId, v) -> - response.addAll(retrieveTemplateData(ehrIds, templateId, projectId, false))); - return responseFilter.filterResponse(response); + if (ehrIds.size() < privacyProperties.getMinHits()) { + log.warn(RESULTS_WITHHELD_FOR_PRIVACY_REASONS); + throw new PrivacyException(ProjectService.class, RESULTS_WITHHELD_FOR_PRIVACY_REASONS); } - private List executeCustomConfiguration(String query, Long projectId, String userId) { - List response = executeAql(query, projectId, userId); - return responseFilter.filterResponse(response); + List response = new LinkedList<>(); + + templates.forEach( + (templateId, v) -> + response.addAll(retrieveTemplateData(ehrIds, templateId, projectId, false))); + return responseFilter.filterResponse(response); + } + + private List executeCustomConfiguration(String query, Long projectId, String userId) { + List response = executeAql(query, projectId, userId); + return responseFilter.filterResponse(response); + } + + private List retrieveTemplateData( + Set ehrIds, String templateId, Long projectId, Boolean usedOutsideEu) { + try { + AqlQuery aql = templateService.createSelectCompositionQuery(templateId); + + List policies = + collectProjectPolicies(ehrIds, Map.of(templateId, templateId), usedOutsideEu); + projectPolicyService.apply(aql, policies); + + List response = ehrBaseService.executeRawQuery(aql, projectId); + response.forEach(data -> data.setName(templateId)); + return response; + + } catch (ResourceNotFound e) { + log.error("Could not retrieve data for template {} and project {}. Failed with message {} ", templateId, projectId, e.getMessage(), e); + log.error(e.getMessage(), e); + } catch (Exception e) { + log.error(e.getMessage(), e); } + QueryResponseData response = new QueryResponseData(); + response.setName(templateId); + return List.of(response); + } - private List retrieveTemplateData( - Set ehrIds, String templateId, Long projectId, Boolean usedOutsideEu) { - try { - AqlQuery aql = templateService.createSelectCompositionQuery(templateId); + public List executeAql(String query, Long projectId, String userId) { + List queryResponseData; + Project project = null; + try { + userDetailsService.checkIsUserApproved(userId); - List policies = - collectProjectPolicies(ehrIds, Map.of(templateId, templateId), usedOutsideEu); - projectPolicyService.apply(aql, policies); + project = validateAndRetrieveProject(projectId, userId); - List response = ehrBaseService.executeRawQuery(aql, projectId); - response.forEach(data -> data.setName(templateId)); - return response; + Set ehrIds = + cohortService.executeCohort(project.getCohort().getId(), project.isUsedOutsideEu()); - } catch (ResourceNotFound e) { - log.error("Could not retrieve data for template {} and project {}. Failed with message {} ", templateId, projectId, e.getMessage(), e); - log.error(e.getMessage(), e); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - QueryResponseData response = new QueryResponseData(); - response.setName(templateId); - return List.of(response); - } + AqlQuery aql = AqlQueryParser.parse(query); - public List executeAql(String query, Long projectId, String userId) { - List queryResponseData; - Project project = null; - try { - userDetailsService.checkIsUserApproved(userId); + List policies = + collectProjectPolicies(ehrIds, project.getTemplates(), project.isUsedOutsideEu()); + projectPolicyService.apply(aql, policies); - project = validateAndRetrieveProject(projectId, userId); + queryResponseData = ehrBaseService.executeRawQuery(aql, projectId); - Set ehrIds = - cohortService.executeCohort(project.getCohort().getId(), project.isUsedOutsideEu()); + } catch (Exception e) { + atnaService.logDataExport(userId, projectId, project, false); + throw e; + } + atnaService.logDataExport(userId, projectId, project, true); + return queryResponseData; + } + + public String executeManagerProject(CohortDto cohortDto, List templates, String userId) { + var queryResponse = StringUtils.EMPTY; + var project = createManagerProject(); + try { + userDetailsService.checkIsUserApproved(userId); + var templateMap = CollectionUtils.isNotEmpty(templates) ? templates.stream().collect(Collectors.toMap(k -> k, v -> v)) : Collections.emptyMap(); + + List responseData = + executeDefaultConfiguration( + project.getId(), cohortService.toCohort(cohortDto), (Map) templateMap); + + queryResponse = mapper.writeValueAsString(responseData); + + } catch (Exception e) { + atnaService.logDataExport(userId, project.getId(), project, false); + throw new SystemException(ProjectService.class, ERROR_WHILE_RETRIEVING_DATA, + String.format(ERROR_WHILE_RETRIEVING_DATA, e.getLocalizedMessage())); + } + atnaService.logDataExport(userId, project.getId(), project, true); + return queryResponse; + } - AqlQuery aql = AqlQueryParser.parse(query); + public boolean streamResponseAsZip( + List queryResponseDataList, + String filenameStart, + OutputStream outputStream) { - List policies = - collectProjectPolicies(ehrIds, project.getTemplates(), project.isUsedOutsideEu()); - projectPolicyService.apply(aql, policies); + try (var zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)) { - queryResponseData = ehrBaseService.executeRawQuery(aql, projectId); + var index = 0; + for (QueryResponseData queryResponseData : queryResponseDataList) { - } catch (Exception e) { - atnaService.logDataExport(userId, projectId, project, false); - throw e; + String responseName = queryResponseData.getName(); + if (StringUtils.isEmpty(responseName)) { + responseName = String.valueOf(index); } - atnaService.logDataExport(userId, projectId, project, true); - return queryResponseData; + zipOutputStream.putNextEntry( + new ZipEntry(String.format(CSV_FILE_PATTERN, filenameStart, responseName))); + addResponseAsCsv(zipOutputStream, queryResponseData); + zipOutputStream.closeEntry(); + index++; + } + } catch (IOException e) { + log.error("Error creating a zip file for data export.", e); + throw new SystemException(ProjectService.class, ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, + String.format(ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, e.getLocalizedMessage())); } + return true; + } - public String executeManagerProject(CohortDto cohortDto, List templates, String userId) { - var queryResponse = StringUtils.EMPTY; - var project = createManagerProject(); - try { - userDetailsService.checkIsUserApproved(userId); - var templateMap = CollectionUtils.isNotEmpty(templates) ? templates.stream().collect(Collectors.toMap(k -> k, v -> v)) : Collections.emptyMap(); - - List responseData = - executeDefaultConfiguration( - project.getId(), cohortService.toCohort(cohortDto), (Map) templateMap); - - queryResponse = mapper.writeValueAsString(responseData); + private void addResponseAsCsv(ZipOutputStream zipOutputStream, QueryResponseData queryResponseData) { + List paths = new ArrayList<>(); - } catch (Exception e) { - atnaService.logDataExport(userId, project.getId(), project, false); - throw new SystemException(ProjectService.class, ERROR_WHILE_RETRIEVING_DATA, - String.format(ERROR_WHILE_RETRIEVING_DATA, e.getLocalizedMessage())); - } - atnaService.logDataExport(userId, project.getId(), project, true); - return queryResponse; + for (Map column : queryResponseData.getColumns()) { + paths.add(column.get("path")); } - - public boolean streamResponseAsZip( - List queryResponseDataList, - String filenameStart, - OutputStream outputStream) { - - try (var zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)) { - - var index = 0; - for (QueryResponseData queryResponseData : queryResponseDataList) { - - String responseName = queryResponseData.getName(); - if (StringUtils.isEmpty(responseName)) { - responseName = String.valueOf(index); - } - zipOutputStream.putNextEntry( - new ZipEntry(String.format(CSV_FILE_PATTERN, filenameStart, responseName))); - addResponseAsCsv(zipOutputStream, queryResponseData); - zipOutputStream.closeEntry(); - index++; - } - } catch (IOException e) { - log.error("Error creating a zip file for data export.", e); - throw new SystemException(ProjectService.class, ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, - String.format(ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, e.getLocalizedMessage())); - } - return true; + CSVPrinter printer; + try { + printer = + CSVFormat.EXCEL.builder() + .setHeader(paths.toArray(new String[]{})) + .build() + .print(new OutputStreamWriter(zipOutputStream, StandardCharsets.UTF_8)); + + for (List row : queryResponseData.getRows()) { + printer.printRecord(row); + } + printer.flush(); + } catch (IOException e) { + throw new SystemException(ProjectService.class, ERROR_WHILE_CREATING_THE_CSV_FILE, + String.format(ERROR_WHILE_CREATING_THE_CSV_FILE, e.getMessage())); } - - private void addResponseAsCsv(ZipOutputStream zipOutputStream, QueryResponseData queryResponseData) { - List paths = new ArrayList<>(); - - for (Map column : queryResponseData.getColumns()) { - paths.add(column.get("path")); - } - CSVPrinter printer; - try { - printer = - CSVFormat.EXCEL.builder() - .setHeader(paths.toArray(new String[]{})) - .build() - .print(new OutputStreamWriter(zipOutputStream, StandardCharsets.UTF_8)); - - for (List row : queryResponseData.getRows()) { - printer.printRecord(row); - } - printer.flush(); - } catch (IOException e) { - throw new SystemException(ProjectService.class, ERROR_WHILE_CREATING_THE_CSV_FILE, - String.format(ERROR_WHILE_CREATING_THE_CSV_FILE, e.getMessage())); - } + } + + public StreamingResponseBody getExportResponseBody( + String query, + Long projectId, + String userId, + ExportType format, + Boolean defaultConfiguration) { + + userDetailsService.checkIsUserApproved(userId); + Project project = validateAndRetrieveProject(projectId, userId); + List response; + + if (BooleanUtils.isTrue(defaultConfiguration)) { + response = + executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); + } else { + response = executeCustomConfiguration(query, projectId, userId); } - public StreamingResponseBody getExportResponseBody( - String query, - Long projectId, - String userId, - ExportType format, - Boolean defaultConfiguration) { - - userDetailsService.checkIsUserApproved(userId); - Project project = validateAndRetrieveProject(projectId, userId); - List response; - - if (BooleanUtils.isTrue(defaultConfiguration)) { - response = - executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); - } else { - response = executeCustomConfiguration(query, projectId, userId); - } - - if (format == ExportType.json) { - return exportJson(response); - } else { - return exportCsv(response, projectId); - } + if (format == ExportType.json) { + return exportJson(response); + } else { + return exportCsv(response, projectId); } + } - public StreamingResponseBody getManagerExportResponseBody(CohortDto cohortDto, List templates, String userId, ExportType format) { - userDetailsService.checkIsUserApproved(userId); - var project = createManagerProject(); + public StreamingResponseBody getManagerExportResponseBody(CohortDto cohortDto, List templates, String userId, ExportType format) { + userDetailsService.checkIsUserApproved(userId); + var project = createManagerProject(); - var templateMap = templates.stream().collect(Collectors.toMap(k -> k, v -> v)); + var templateMap = templates.stream().collect(Collectors.toMap(k -> k, v -> v)); - List response = - executeDefaultConfiguration( - project.getId(), cohortService.toCohort(cohortDto), templateMap); + List response = + executeDefaultConfiguration( + project.getId(), cohortService.toCohort(cohortDto), templateMap); - if (format == ExportType.json) { - return exportJson(response); - } else { - return exportCsv(response, project.getId()); - } + if (format == ExportType.json) { + return exportJson(response); + } else { + return exportCsv(response, project.getId()); } - - private StreamingResponseBody exportCsv(List response, Long projectId) { - return outputStream -> - streamResponseAsZip(response, getExportFilenameBody(projectId), outputStream); + } + + private StreamingResponseBody exportCsv(List response, Long projectId) { + return outputStream -> + streamResponseAsZip(response, getExportFilenameBody(projectId), outputStream); + } + + private StreamingResponseBody exportJson(List response) { + String json; + try { + json = mapper.writeValueAsString(response); + } catch (JsonProcessingException e) { + throw new SystemException(ProjectService.class, AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL); } - - private StreamingResponseBody exportJson(List response) { - String json; - try { - json = mapper.writeValueAsString(response); - } catch (JsonProcessingException e) { - throw new SystemException(ProjectService.class, AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL); - } - return outputStream -> { - outputStream.write(json.getBytes()); - outputStream.flush(); - outputStream.close(); - }; + return outputStream -> { + outputStream.write(json.getBytes()); + outputStream.flush(); + outputStream.close(); + }; + } + + public MultiValueMap getExportHeaders(ExportType format, Long projectId) { + MultiValueMap headers = new LinkedMultiValueMap<>(); + String fileEnding; + if (format == ExportType.json) { + fileEnding = JSON_FILE_ENDING; + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + } else { + fileEnding = ZIP_FILE_ENDING; + headers.add(HttpHeaders.CONTENT_TYPE, ZIP_MEDIA_TYPE); } - - public MultiValueMap getExportHeaders(ExportType format, Long projectId) { - MultiValueMap headers = new LinkedMultiValueMap<>(); - String fileEnding; - if (format == ExportType.json) { - fileEnding = JSON_FILE_ENDING; - headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - } else { - fileEnding = ZIP_FILE_ENDING; - headers.add(HttpHeaders.CONTENT_TYPE, ZIP_MEDIA_TYPE); - } - headers.add( - HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=" + getExportFilenameBody(projectId) + fileEnding); - return headers; + headers.add( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=" + getExportFilenameBody(projectId) + fileEnding); + return headers; + } + + public Optional getProjectById(String loggedInUserId, Long projectId) { + userDetailsService.checkIsUserApproved(loggedInUserId); + return projectRepository.findById(projectId); + } + + public boolean exists(Long id) { + return projectRepository.existsById(id); + } + + @Transactional + public Project createProject(ProjectDto projectDto, String userId, List roles) { + + var coordinator = userDetailsService.checkIsUserApproved(userId); + + var project = Project.builder().build(); + + validateStatus(null, projectDto.getStatus(), roles); + persistTransition(project, project.getStatus(), projectDto.getStatus(), coordinator); + + setTemplates(project, projectDto); + project.setResearchers(getResearchers(projectDto)); + + project.setStatus(projectDto.getStatus()); + project.setName(projectDto.getName()); + project.setDescription(projectDto.getDescription()); + project.setSimpleDescription(projectDto.getSimpleDescription()); + project.setUsedOutsideEu(projectDto.isUsedOutsideEu()); + project.setFirstHypotheses(projectDto.getFirstHypotheses()); + project.setSecondHypotheses(projectDto.getSecondHypotheses()); + project.setGoal(projectDto.getGoal()); + project.setCategories(projectDto.getCategories()); + project.setKeywords(projectDto.getKeywords()); + project.setCoordinator(coordinator); + project.setCreateDate(OffsetDateTime.now()); + project.setModifiedDate(OffsetDateTime.now()); + project.setStartDate(projectDto.getStartDate()); + project.setEndDate(projectDto.getEndDate()); + project.setFinanced(projectDto.isFinanced()); + + var savedProject = projectRepository.save(project); + projectsMetrics.increaseTotalNumber(); + if (savedProject.getStatus() == ProjectStatus.PENDING) { + registerToZars(project); } - public Optional getProjectById(String loggedInUserId, Long projectId) { - userDetailsService.checkIsUserApproved(loggedInUserId); - return projectRepository.findById(projectId); + List notifications = + collectNotifications( + savedProject, + savedProject.getStatus(), + null, + savedProject.getCoordinator().getUserId(), + savedProject.getResearchers(), + savedProject.getResearchers(), + userId); + + notificationService.send(notifications); + + return savedProject; + } + + @Transactional + public Project createMultipartProject(ProjectDto projectDto, String userId, List roles, MultipartFile[] files) { + Project savedProject = createProject(projectDto, userId, roles); + if (nonNull(files)) { + try { + LightAttachmentDto lightDto = LightAttachmentDto.builder() + .files(files) + .description(projectDto.getFilesDescription()) + .build(); + attachmentService.saveAttachments(savedProject.getId(), userId, lightDto, true); + } catch (IOException e) { + log.error("Exception in createMultipartProject saveAttachments" + e.getMessage()); + throw new RuntimeException(e); + } } - - public boolean exists(Long id) { - return projectRepository.existsById(id); + return savedProject; + } + + @Transactional + public Project updateProject(ProjectDto projectDto, Long id, String userId, List roles) { + var user = userDetailsService.checkIsUserApproved(userId); + + var projectToEdit = + projectRepository + .findById(id) + .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, id))); + + if (ProjectStatus.ARCHIVED.equals(projectToEdit.getStatus()) + || ProjectStatus.CLOSED.equals(projectToEdit.getStatus())) { + throw new ForbiddenException(ProjectService.class, CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS, + String.format(CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS, id, projectToEdit.getStatus())); } - @Transactional - public Project createProject(ProjectDto projectDto, String userId, List roles) { - - var coordinator = userDetailsService.checkIsUserApproved(userId); - - var project = Project.builder().build(); - - validateStatus(null, projectDto.getStatus(), roles); - persistTransition(project, project.getStatus(), projectDto.getStatus(), coordinator); - - setTemplates(project, projectDto); - project.setResearchers(getResearchers(projectDto)); - - project.setStatus(projectDto.getStatus()); - project.setName(projectDto.getName()); - project.setDescription(projectDto.getDescription()); - project.setSimpleDescription(projectDto.getSimpleDescription()); - project.setUsedOutsideEu(projectDto.isUsedOutsideEu()); - project.setFirstHypotheses(projectDto.getFirstHypotheses()); - project.setSecondHypotheses(projectDto.getSecondHypotheses()); - project.setGoal(projectDto.getGoal()); - project.setCategories(projectDto.getCategories()); - project.setKeywords(projectDto.getKeywords()); - project.setCoordinator(coordinator); - project.setCreateDate(OffsetDateTime.now()); - project.setModifiedDate(OffsetDateTime.now()); - project.setStartDate(projectDto.getStartDate()); - project.setEndDate(projectDto.getEndDate()); - project.setFinanced(projectDto.isFinanced()); - - var savedProject = projectRepository.save(project); - projectsMetrics.increaseTotalNumber(); - if (savedProject.getStatus() == ProjectStatus.PENDING) { - registerToZars(project); - } - - List notifications = - collectNotifications( - savedProject, - savedProject.getStatus(), - null, - savedProject.getCoordinator().getUserId(), - savedProject.getResearchers(), - savedProject.getResearchers(), - userId); - - notificationService.send(notifications); - - return savedProject; + if (CollectionUtils.isNotEmpty(roles) + && Roles.isProjectLead(roles) + && projectToEdit.isCoordinator(userId)) { + return updateProjectAllFields(projectDto, roles, user, projectToEdit); + } else if (CollectionUtils.isNotEmpty(roles) && Roles.isProjectApprover(roles)) { + Project savedProject = updateProjectStatus(projectDto, roles, user, projectToEdit); + deleteAttachments(projectDto.getAttachmentsToBeDeleted(), roles, user, savedProject); + if (ProjectStatus.REVIEWING.equals(projectToEdit.getStatus())) { + attachmentService.updateStatusChangeCounter(projectToEdit.getId()); + } + return savedProject; + } else { + throw new ForbiddenException(ProjectService.class, NO_PERMISSIONS_TO_EDIT_THIS_PROJECT); } - - @Transactional - public Project createMultipartProject(ProjectDto projectDto, String userId, List roles, MultipartFile[] files) { - Project savedProject = createProject(projectDto, userId, roles); - if(nonNull(files)){ - try { - LightAttachmentDto lightDto = LightAttachmentDto.builder() - .files(files) - .description(projectDto.getFilesDescription()) - .build(); - attachmentService.saveAttachments(savedProject.getId(), userId, lightDto, true); - } catch (IOException e) { - log.error("Exception in createMultipartProject saveAttachments" + e.getMessage()); - throw new RuntimeException(e); - } - } - return savedProject; + } + + @Transactional + public Project updateMultipartProject(ProjectDto projectDto, Long id, String userId, List roles, MultipartFile[] files) { + Project updatedProject = updateProject(projectDto, id, userId, roles); + if (nonNull(files)) { + try { + LightAttachmentDto lightDto = LightAttachmentDto.builder() + .files(files) + .description(projectDto.getFilesDescription()) + .build(); + attachmentService.saveAttachments(updatedProject.getId(), userId, lightDto, false); + } catch (IOException e) { + log.error("Exception in updateMultipartProject saveAttachments" + e.getMessage()); + throw new RuntimeException(e); + } } - - @Transactional - public Project updateProject(ProjectDto projectDto, Long id, String userId, List roles) { - var user = userDetailsService.checkIsUserApproved(userId); - - var projectToEdit = - projectRepository - .findById(id) - .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, id))); - - if (ProjectStatus.ARCHIVED.equals(projectToEdit.getStatus()) - || ProjectStatus.CLOSED.equals(projectToEdit.getStatus())) { - throw new ForbiddenException(ProjectService.class, CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS, - String.format(CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS, id, projectToEdit.getStatus())); - } - - if (CollectionUtils.isNotEmpty(roles) - && Roles.isProjectLead(roles) - && projectToEdit.isCoordinator(userId)) { - return updateProjectAllFields(projectDto, roles, user, projectToEdit); - } else if (CollectionUtils.isNotEmpty(roles) && Roles.isProjectApprover(roles)) { - Project savedProject = updateProjectStatus(projectDto, roles, user, projectToEdit); - deleteAttachments(projectDto.getAttachmentsToBeDeleted(), roles, user, savedProject); - if (ProjectStatus.REVIEWING.equals(projectToEdit.getStatus())) { - attachmentService.updateStatusChangeCounter(projectToEdit.getId()); - } - return savedProject; + return updatedProject; + } + + private Project updateProjectStatus(ProjectDto projectDto, List roles, UserDetails user, Project projectToEdit) { + + var oldProjectStatus = projectToEdit.getStatus(); + + validateStatus(projectToEdit.getStatus(), projectDto.getStatus(), roles); + persistTransition(projectToEdit, projectToEdit.getStatus(), projectDto.getStatus(), user); + projectToEdit.setStatus(projectDto.getStatus()); + var savedProject = projectRepository.save(projectToEdit); + projectsMetrics.changeStatus(oldProjectStatus, savedProject.getStatus()); + registerToZarsIfNecessary( + savedProject, + oldProjectStatus, + savedProject.getResearchers(), + savedProject.getResearchers()); + + List notifications = + collectNotifications( + savedProject, + savedProject.getStatus(), + oldProjectStatus, + savedProject.getCoordinator().getUserId(), + savedProject.getResearchers(), + savedProject.getResearchers(), + user.getUserId()); + + notificationService.send(notifications); + + return savedProject; + } + + private Project updateProjectAllFields( + ProjectDto projectDto, List roles, UserDetails user, Project projectToEdit) { + + ProjectStatus oldStatus = projectToEdit.getStatus(); + + validateCoordinatorIsOwner(projectToEdit, user.getUserId()); + validateStatus(projectToEdit.getStatus(), projectDto.getStatus(), roles); + + List newResearchers = getResearchers(projectDto); + List oldResearchers = projectToEdit.getResearchers(); + projectToEdit.setResearchers(newResearchers); + + persistTransition(projectToEdit, projectToEdit.getStatus(), projectDto.getStatus(), user); + + if (ProjectStatus.APPROVED.equals(projectToEdit.getStatus()) + || ProjectStatus.PUBLISHED.equals(projectToEdit.getStatus())) { + projectToEdit.setStatus(projectDto.getStatus()); + var savedProject = projectRepository.save(projectToEdit); + projectsMetrics.changeStatus(oldStatus, savedProject.getStatus()); + registerToZarsIfNecessary(savedProject, oldStatus, oldResearchers, newResearchers); + + List notifications = + collectNotifications( + savedProject, + savedProject.getStatus(), + oldStatus, + savedProject.getCoordinator().getUserId(), + newResearchers, + oldResearchers, + user.getUserId()); + + notificationService.send(notifications); + return savedProject; + } + setTemplates(projectToEdit, projectDto); + + deleteAttachments(projectDto.getAttachmentsToBeDeleted(), roles, user, projectToEdit); + + projectToEdit.setStatus(projectDto.getStatus()); + projectToEdit.setName(projectDto.getName()); + projectToEdit.setSimpleDescription(projectDto.getSimpleDescription()); + projectToEdit.setUsedOutsideEu(projectDto.isUsedOutsideEu()); + projectToEdit.setDescription(projectDto.getDescription()); + projectToEdit.setModifiedDate(OffsetDateTime.now()); + projectToEdit.setFirstHypotheses(projectDto.getFirstHypotheses()); + projectToEdit.setSecondHypotheses(projectDto.getSecondHypotheses()); + projectToEdit.setGoal(projectDto.getGoal()); + projectToEdit.setCategories(projectDto.getCategories()); + projectToEdit.setKeywords(projectDto.getKeywords()); + projectToEdit.setStartDate(projectDto.getStartDate()); + projectToEdit.setEndDate(projectDto.getEndDate()); + projectToEdit.setFinanced(projectDto.isFinanced()); + + var savedProject = projectRepository.save(projectToEdit); + projectsMetrics.changeStatus(oldStatus, savedProject.getStatus()); + if (ProjectStatus.REVIEWING.equals(savedProject.getStatus())) { + attachmentService.updateStatusChangeCounter(projectToEdit.getId()); + } + registerToZarsIfNecessary(savedProject, oldStatus, oldResearchers, newResearchers); + + List notifications = + collectNotifications( + savedProject, + savedProject.getStatus(), + oldStatus, + savedProject.getCoordinator().getUserId(), + newResearchers, + oldResearchers, + user.getUserId()); + + notificationService.send(notifications); + return savedProject; + } + + private void deleteAttachments(Set attachmentsToBeDeleted, List roles, UserDetails user, Project projectToEdit) { + if (CollectionUtils.isNotEmpty(attachmentsToBeDeleted)) { + if (Roles.isProjectLead(roles) && projectToEdit.isCoordinator(user.getUserId())) { + if (ProjectStatus.DRAFT.equals(projectToEdit.getStatus()) || ProjectStatus.CHANGE_REQUEST.equals(projectToEdit.getStatus())) { + attachmentService.deleteAttachments(attachmentsToBeDeleted, projectToEdit.getId(), user.getUserId(), false); + log.info("Project lead {} removed attachments {} from project {}", user.getUserId(), attachmentsToBeDeleted, projectToEdit.getId()); } else { - throw new ForbiddenException(ProjectService.class, NO_PERMISSIONS_TO_EDIT_THIS_PROJECT); + log.error("Not allowed to delete attachments for project {} because status is {} ", projectToEdit.getId(), projectToEdit.getStatus()); + throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, + String.format(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, projectToEdit.getStatus())); } - } - - @Transactional - public Project updateMultipartProject(ProjectDto projectDto, Long id, String userId, List roles, MultipartFile[] files) { - Project updatedProject = updateProject(projectDto, id, userId, roles); - if(nonNull(files)){ - try { - LightAttachmentDto lightDto = LightAttachmentDto.builder() - .files(files) - .description(projectDto.getFilesDescription()) - .build(); - attachmentService.saveAttachments(updatedProject.getId(), userId, lightDto, false); - } catch (IOException e) { - log.error("Exception in updateMultipartProject saveAttachments" + e.getMessage()); - throw new RuntimeException(e); - } + } else if (Roles.isProjectApprover(roles)) { + if (ProjectStatus.REVIEWING.equals(projectToEdit.getStatus())) { + attachmentService.deleteAttachments(attachmentsToBeDeleted, projectToEdit.getId(), user.getUserId(), true); + log.info("Project approver {} removed attachments {} from project {}", user.getUserId(), attachmentsToBeDeleted, projectToEdit.getId()); + } else { + log.error( + "Not allowed to delete attachments for project {} as project approver {} because status is {} ", projectToEdit.getId(), + user.getUserId(), projectToEdit.getStatus()); + throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, + String.format(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, projectToEdit.getStatus())); } - return updatedProject; + } else { + log.error("User {} is not allowed to delete attachments from project {}", user.getUserId(), projectToEdit.getId()); + throw new ForbiddenException(ProjectService.class, NO_PERMISSIONS_TO_DELETE_ATTACHMENTS); + } } - - private Project updateProjectStatus(ProjectDto projectDto, List roles, UserDetails user, Project projectToEdit) { - - var oldProjectStatus = projectToEdit.getStatus(); - - validateStatus(projectToEdit.getStatus(), projectDto.getStatus(), roles); - persistTransition(projectToEdit, projectToEdit.getStatus(), projectDto.getStatus(), user); - projectToEdit.setStatus(projectDto.getStatus()); - var savedProject = projectRepository.save(projectToEdit); - projectsMetrics.changeStatus(oldProjectStatus, savedProject.getStatus()); - registerToZarsIfNecessary( - savedProject, - oldProjectStatus, - savedProject.getResearchers(), - savedProject.getResearchers()); - - List notifications = - collectNotifications( - savedProject, - savedProject.getStatus(), - oldProjectStatus, - savedProject.getCoordinator().getUserId(), - savedProject.getResearchers(), - savedProject.getResearchers(), - user.getUserId()); - - notificationService.send(notifications); - - return savedProject; + } + + private List collectProjectPolicies( + Set ehrIds, Map templates, boolean usedOutsideEu) { + List policies = new LinkedList<>(); + policies.add(EhrPolicy.builder().cohortEhrIds(ehrIds).build()); + policies.add(TemplatesPolicy.builder().templatesMap(templates).build()); + + if (usedOutsideEu) { + policies.add( + EuropeanConsentPolicy.builder() + .oid(consentProperties.getAllowUsageOutsideEuOid()) + .build()); } - private Project updateProjectAllFields( - ProjectDto projectDto, List roles, UserDetails user, Project projectToEdit) { - - ProjectStatus oldStatus = projectToEdit.getStatus(); - - validateCoordinatorIsOwner(projectToEdit, user.getUserId()); - validateStatus(projectToEdit.getStatus(), projectDto.getStatus(), roles); - - List newResearchers = getResearchers(projectDto); - List oldResearchers = projectToEdit.getResearchers(); - projectToEdit.setResearchers(newResearchers); - - persistTransition(projectToEdit, projectToEdit.getStatus(), projectDto.getStatus(), user); - - if (ProjectStatus.APPROVED.equals(projectToEdit.getStatus()) - || ProjectStatus.PUBLISHED.equals(projectToEdit.getStatus())) { - projectToEdit.setStatus(projectDto.getStatus()); - var savedProject = projectRepository.save(projectToEdit); - projectsMetrics.changeStatus(oldStatus, savedProject.getStatus()); - registerToZarsIfNecessary(savedProject, oldStatus, oldResearchers, newResearchers); - - List notifications = - collectNotifications( - savedProject, - savedProject.getStatus(), - oldStatus, - savedProject.getCoordinator().getUserId(), - newResearchers, - oldResearchers, - user.getUserId()); - - notificationService.send(notifications); - return savedProject; - } - setTemplates(projectToEdit, projectDto); - - deleteAttachments(projectDto.getAttachmentsToBeDeleted(), roles, user, projectToEdit); - - projectToEdit.setStatus(projectDto.getStatus()); - projectToEdit.setName(projectDto.getName()); - projectToEdit.setSimpleDescription(projectDto.getSimpleDescription()); - projectToEdit.setUsedOutsideEu(projectDto.isUsedOutsideEu()); - projectToEdit.setDescription(projectDto.getDescription()); - projectToEdit.setModifiedDate(OffsetDateTime.now()); - projectToEdit.setFirstHypotheses(projectDto.getFirstHypotheses()); - projectToEdit.setSecondHypotheses(projectDto.getSecondHypotheses()); - projectToEdit.setGoal(projectDto.getGoal()); - projectToEdit.setCategories(projectDto.getCategories()); - projectToEdit.setKeywords(projectDto.getKeywords()); - projectToEdit.setStartDate(projectDto.getStartDate()); - projectToEdit.setEndDate(projectDto.getEndDate()); - projectToEdit.setFinanced(projectDto.isFinanced()); - - var savedProject = projectRepository.save(projectToEdit); - projectsMetrics.changeStatus(oldStatus, savedProject.getStatus()); - if (ProjectStatus.REVIEWING.equals(savedProject.getStatus())) { - attachmentService.updateStatusChangeCounter(projectToEdit.getId()); - } - registerToZarsIfNecessary(savedProject, oldStatus, oldResearchers, newResearchers); - - List notifications = - collectNotifications( - savedProject, - savedProject.getStatus(), - oldStatus, - savedProject.getCoordinator().getUserId(), - newResearchers, - oldResearchers, - user.getUserId()); - - notificationService.send(notifications); - return savedProject; + return policies; + } + + private List collectNotifications(Project project, ProjectStatus newStatus, + ProjectStatus oldStatus, + String coordinatorUserId, + List newResearchers, + List currentResearchers, + String approverUserId) { + + List notifications = new LinkedList<>(); + var coordinator = userService.getUserById(coordinatorUserId, false); + + if (isTransitionToPending(oldStatus, newStatus)) { + + Set approvers = userService.getByRole(Roles.STUDY_APPROVER); + + approvers.forEach( + approver -> { + ProjectApprovalRequestNotification notification = + ProjectApprovalRequestNotification.builder() + .coordinatorFirstName(coordinator.getFirstName()) + .coordinatorLastName(coordinator.getLastName()) + .projectTitle(project.getName()) + .recipientEmail(approver.getEmail()) + .recipientFirstName(approver.getFirstName()) + .recipientLastName(approver.getLastName()) + .projectId(project.getId()) + .coordinatorEmail(coordinator.getEmail()) + .build(); + notifications.add(notification); + }); } - private void deleteAttachments(Set attachmentsToBeDeleted, List roles, UserDetails user, Project projectToEdit) { - if (CollectionUtils.isNotEmpty(attachmentsToBeDeleted)) { - if (Roles.isProjectLead(roles) && projectToEdit.isCoordinator(user.getUserId())) { - if (ProjectStatus.DRAFT.equals(projectToEdit.getStatus()) || ProjectStatus.CHANGE_REQUEST.equals(projectToEdit.getStatus())) { - attachmentService.deleteAttachments(attachmentsToBeDeleted, projectToEdit.getId(), user.getUserId(), false); - log.info("Project lead {} removed attachments {} from project {}", user.getUserId(), attachmentsToBeDeleted, projectToEdit.getId()); - } else { - log.error("Not allowed to delete attachments for project {} because status is {} ", projectToEdit.getId(), projectToEdit.getStatus()); - throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, - String.format(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, projectToEdit.getStatus())); - } - } else if (Roles.isProjectApprover(roles)) { - if (ProjectStatus.REVIEWING.equals(projectToEdit.getStatus())) { - attachmentService.deleteAttachments(attachmentsToBeDeleted, projectToEdit.getId(), user.getUserId(), true); - log.info("Project approver {} removed attachments {} from project {}", user.getUserId(), attachmentsToBeDeleted, projectToEdit.getId()); - } else { - log.error("Not allowed to delete attachments for project {} as project approver {} because status is {} ", projectToEdit.getId(), user.getUserId(), projectToEdit.getStatus()); - throw new ForbiddenException(ProjectService.class, CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, - String.format(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, projectToEdit.getStatus())); - } - } else { - log.error("User {} is not allowed to delete attachments from project {}", user.getUserId(), projectToEdit.getId()); - throw new ForbiddenException(ProjectService.class, NO_PERMISSIONS_TO_DELETE_ATTACHMENTS); - } - } + if (isTransitionMadeByApprover(oldStatus, newStatus)) { + + var approver = userService.getUserById(approverUserId, false); + + if (isTransitionToChangeRequest(oldStatus, newStatus)) { + ProjectStatusChangeRequestNotification notification = new ProjectStatusChangeRequestNotification(coordinator.getEmail(), + coordinator.getFirstName(), + coordinator.getLastName(), approver.getFirstName(), approver.getLastName(), + project.getName(), newStatus, oldStatus, project.getId(), approver.getEmail()); + notifications.add(notification); + } else { + ProjectStatusChangeNotification notification = + ProjectStatusChangeNotification.builder() + .recipientFirstName(coordinator.getFirstName()) + .recipientLastName(coordinator.getLastName()) + .recipientEmail(coordinator.getEmail()) + .projectTitle(project.getName()) + .projectStatus(newStatus) + .approverFirstName(approver.getFirstName()) + .approverLastName(approver.getLastName()) + .projectId(project.getId()) + .oldProjectStatus(oldStatus) + .approverEmail(approver.getEmail()) + .build(); + notifications.add(notification); + } } - private List collectProjectPolicies( - Set ehrIds, Map templates, boolean usedOutsideEu) { - List policies = new LinkedList<>(); - policies.add(EhrPolicy.builder().cohortEhrIds(ehrIds).build()); - policies.add(TemplatesPolicy.builder().templatesMap(templates).build()); - - if (usedOutsideEu) { - policies.add( - EuropeanConsentPolicy.builder() - .oid(consentProperties.getAllowUsageOutsideEuOid()) - .build()); - } + if (isTransitionToPublished(oldStatus, newStatus) && newResearchers != null) { + List researcherIds = + newResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); - return policies; + createProjectStartedNotifications(project, notifications, coordinator, researcherIds); } - private List collectNotifications(Project project, ProjectStatus newStatus, - ProjectStatus oldStatus, - String coordinatorUserId, - List newResearchers, - List currentResearchers, - String approverUserId) { - - List notifications = new LinkedList<>(); - var coordinator = userService.getUserById(coordinatorUserId, false); - - if (isTransitionToPending(oldStatus, newStatus)) { - - Set approvers = userService.getByRole(Roles.STUDY_APPROVER); - - approvers.forEach( - approver -> { - ProjectApprovalRequestNotification notification = - ProjectApprovalRequestNotification.builder() - .coordinatorFirstName(coordinator.getFirstName()) - .coordinatorLastName(coordinator.getLastName()) - .projectTitle(project.getName()) - .recipientEmail(approver.getEmail()) - .recipientFirstName(approver.getFirstName()) - .recipientLastName(approver.getLastName()) - .projectId(project.getId()) - .coordinatorEmail(coordinator.getEmail()) - .build(); - notifications.add(notification); - }); - } + if (isTransitionToPublishedFromPublished(oldStatus, newStatus)) { + List newResearcherIds = new LinkedList<>(); + List currentResearchersIds = new LinkedList<>(); + if (newResearchers != null) { + newResearcherIds = + newResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); + } - if (isTransitionMadeByApprover(oldStatus, newStatus)) { - - var approver = userService.getUserById(approverUserId, false); - - if (isTransitionToChangeRequest(oldStatus, newStatus)) { - ProjectStatusChangeRequestNotification notification = new ProjectStatusChangeRequestNotification(coordinator.getEmail(), coordinator.getFirstName(), - coordinator.getLastName(), approver.getFirstName(), approver.getLastName(), - project.getName(), newStatus, oldStatus, project.getId(), approver.getEmail()); - notifications.add(notification); - } else { - ProjectStatusChangeNotification notification = - ProjectStatusChangeNotification.builder() - .recipientFirstName(coordinator.getFirstName()) - .recipientLastName(coordinator.getLastName()) - .recipientEmail(coordinator.getEmail()) - .projectTitle(project.getName()) - .projectStatus(newStatus) - .approverFirstName(approver.getFirstName()) - .approverLastName(approver.getLastName()) - .projectId(project.getId()) - .oldProjectStatus(oldStatus) - .approverEmail(approver.getEmail()) - .build(); - notifications.add(notification); - } - } + if (currentResearchers != null) { + currentResearchersIds = + currentResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); + } - if (isTransitionToPublished(oldStatus, newStatus) && newResearchers != null) { - List researcherIds = - newResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); + var addedResearchersIds = new ArrayList<>(newResearcherIds); + addedResearchersIds.removeAll(currentResearchersIds); - createProjectStartedNotifications(project, notifications, coordinator, researcherIds); - } + currentResearchersIds.removeAll(newResearcherIds); - if (isTransitionToPublishedFromPublished(oldStatus, newStatus)) { - List newResearcherIds = new LinkedList<>(); - List currentResearchersIds = new LinkedList<>(); - if (newResearchers != null) { - newResearcherIds = - newResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); - } - - if (currentResearchers != null) { - currentResearchersIds = - currentResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); - } - - var addedResearchersIds = new ArrayList<>(newResearcherIds); - addedResearchersIds.removeAll(currentResearchersIds); - - currentResearchersIds.removeAll(newResearcherIds); - - createProjectStartedNotifications(project, notifications, coordinator, addedResearchersIds); - createProjectClosedNotifications( - project.getName(), notifications, coordinator, currentResearchersIds); - } - - if (ProjectStatus.CLOSED.equals(newStatus)) { - List researcherIds = new LinkedList<>(); - if (currentResearchers != null) { - researcherIds = - currentResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); - } - createProjectClosedNotifications( - project.getName(), notifications, coordinator, researcherIds); - } - - return notifications; + createProjectStartedNotifications(project, notifications, coordinator, addedResearchersIds); + createProjectClosedNotifications( + project.getName(), notifications, coordinator, currentResearchersIds); } - private void createProjectClosedNotifications( - String projectName, - List notifications, - User coordinator, - List researcherIds) { - researcherIds.forEach( - r -> { - var researcher = userService.getUserById(r, false); - ProjectCloseNotification notification = - ProjectCloseNotification.builder() - .recipientEmail(researcher.getEmail()) - .recipientFirstName(researcher.getFirstName()) - .recipientLastName(researcher.getLastName()) - .coordinatorFirstName(coordinator.getFirstName()) - .coordinatorLastName(coordinator.getLastName()) - .projectTitle(projectName) - .build(); - notifications.add(notification); - }); + if (ProjectStatus.CLOSED.equals(newStatus)) { + List researcherIds = new LinkedList<>(); + if (currentResearchers != null) { + researcherIds = + currentResearchers.stream().map(UserDetails::getUserId).collect(Collectors.toList()); + } + createProjectClosedNotifications( + project.getName(), notifications, coordinator, researcherIds); } - private void createProjectStartedNotifications( - Project project, - List notifications, - User coordinator, - List researcherIds) { - researcherIds.forEach( - r -> { - var researcher = userService.getUserById(r, false); - ProjectStartNotification notification = - ProjectStartNotification.builder() - .recipientEmail(researcher.getEmail()) - .recipientFirstName(researcher.getFirstName()) - .recipientLastName(researcher.getLastName()) - .coordinatorFirstName(coordinator.getFirstName()) - .coordinatorLastName(coordinator.getLastName()) - .projectTitle(project.getName()) - .projectId(project.getId()) - .build(); - notifications.add(notification); - }); + return notifications; + } + + private void createProjectClosedNotifications( + String projectName, + List notifications, + User coordinator, + List researcherIds) { + researcherIds.forEach( + r -> { + var researcher = userService.getUserById(r, false); + ProjectCloseNotification notification = + ProjectCloseNotification.builder() + .recipientEmail(researcher.getEmail()) + .recipientFirstName(researcher.getFirstName()) + .recipientLastName(researcher.getLastName()) + .coordinatorFirstName(coordinator.getFirstName()) + .coordinatorLastName(coordinator.getLastName()) + .projectTitle(projectName) + .build(); + notifications.add(notification); + }); + } + + private void createProjectStartedNotifications( + Project project, + List notifications, + User coordinator, + List researcherIds) { + researcherIds.forEach( + r -> { + var researcher = userService.getUserById(r, false); + ProjectStartNotification notification = + ProjectStartNotification.builder() + .recipientEmail(researcher.getEmail()) + .recipientFirstName(researcher.getFirstName()) + .recipientLastName(researcher.getLastName()) + .coordinatorFirstName(coordinator.getFirstName()) + .coordinatorLastName(coordinator.getLastName()) + .projectTitle(project.getName()) + .projectId(project.getId()) + .build(); + notifications.add(notification); + }); + } + + private boolean isTransitionToPending(ProjectStatus oldStatus, ProjectStatus newStatus) { + return ProjectStatus.PENDING.equals(newStatus) && !newStatus.equals(oldStatus); + } + + private boolean isTransitionMadeByApprover(ProjectStatus oldStatus, ProjectStatus newStatus) { + return (ProjectStatus.APPROVED.equals(newStatus) + || ProjectStatus.DENIED.equals(newStatus) + || ProjectStatus.CHANGE_REQUEST.equals(newStatus) + || ProjectStatus.REVIEWING.equals(newStatus)) + && !newStatus.equals(oldStatus); + } + + private boolean isTransitionToPublished(ProjectStatus oldStatus, ProjectStatus newStatus) { + return ProjectStatus.PUBLISHED.equals(newStatus) && !newStatus.equals(oldStatus); + } + + private boolean isTransitionToPublishedFromPublished( + ProjectStatus oldStatus, ProjectStatus newStatus) { + return ProjectStatus.PUBLISHED.equals(oldStatus) && ProjectStatus.PUBLISHED.equals(newStatus); + } + + private boolean isTransitionToChangeRequest(ProjectStatus oldStatus, ProjectStatus newStatus) { + return ProjectStatus.CHANGE_REQUEST.equals(newStatus) && !newStatus.equals(oldStatus); + } + + private void registerToZarsIfNecessary( + Project project, + ProjectStatus oldStatus, + List oldResearchers, + List newResearchers) { + ProjectStatus newStatus = project.getStatus(); + if (((newStatus == ProjectStatus.PENDING + || newStatus == ProjectStatus.APPROVED + || newStatus == ProjectStatus.PUBLISHED + || newStatus == ProjectStatus.CLOSED) + && newStatus != oldStatus) + || (newStatus == ProjectStatus.PUBLISHED + && researchersAreDifferent(oldResearchers, newResearchers))) { + registerToZars(project); } - - private boolean isTransitionToPending(ProjectStatus oldStatus, ProjectStatus newStatus) { - return ProjectStatus.PENDING.equals(newStatus) && !newStatus.equals(oldStatus); + } + + private boolean researchersAreDifferent( + List oldResearchers, List newResearchers) { + return !(oldResearchers.containsAll(newResearchers) + && newResearchers.containsAll(oldResearchers)); + } + + public Page getProjects(String userId, List roles, SearchCriteria searchCriteria, Pageable pageable) { + + UserDetails loggedInUser = userDetailsService.checkIsUserApproved(userId); + + Sort sortBy = validateAndGetSort(searchCriteria); + List projects; + Page projectPage; + Pageable pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); + Set usersUUID = null; + if (searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_SEARCH_BY_KEY)) { + String searchValue = (String) searchCriteria.getFilter().get(SearchCriteria.FILTER_SEARCH_BY_KEY); + usersUUID = userService.findUsersUUID(searchValue); } - - private boolean isTransitionMadeByApprover(ProjectStatus oldStatus, ProjectStatus newStatus) { - return (ProjectStatus.APPROVED.equals(newStatus) - || ProjectStatus.DENIED.equals(newStatus) - || ProjectStatus.CHANGE_REQUEST.equals(newStatus) - || ProjectStatus.REVIEWING.equals(newStatus)) - && !newStatus.equals(oldStatus); + boolean sortByAuthor = searchCriteria.isSortByAuthor(); + if (sortByAuthor) { + long count = projectRepository.count(); + // load all projects because sort by author should be done in memory + pageRequest = PageRequest.of(0, count != 0 ? (int) count : 1); } - - private boolean isTransitionToPublished(ProjectStatus oldStatus, ProjectStatus newStatus) { - return ProjectStatus.PUBLISHED.equals(newStatus) && !newStatus.equals(oldStatus); + String sortByField = searchCriteria.isValid() && StringUtils.isNotEmpty(searchCriteria.getSortBy()) ? searchCriteria.getSortBy() : "modifiedDate"; + Language language = Objects.nonNull(searchCriteria.getLanguage()) ? searchCriteria.getLanguage() : Language.de; + ProjectSpecification projectSpecification = ProjectSpecification.builder() + .filter(searchCriteria.getFilter()) + .roles(roles) + .loggedInUserId(userId) + .loggedInUserOrganizationId( + loggedInUser.getOrganization() != null ? loggedInUser.getOrganization() + .getId() : null) + .ownersUUID(usersUUID) + .sortOrder(Objects.requireNonNull(sortBy.getOrderFor(sortByField)).ignoreCase()) + .language(language) + .build(); + projectPage = projectRepository.findProjects(projectSpecification, pageRequest); + projects = new ArrayList<>(projectPage.getContent()); + if (sortByAuthor) { + sortProjects(projects, sortBy); + projects = projects.stream() + .skip((long) pageable.getPageNumber() * pageable.getPageSize()) + .limit(pageable.getPageSize()) + .collect(Collectors.toList()); } - - private boolean isTransitionToPublishedFromPublished( - ProjectStatus oldStatus, ProjectStatus newStatus) { - return ProjectStatus.PUBLISHED.equals(oldStatus) && ProjectStatus.PUBLISHED.equals(newStatus); - } - - private boolean isTransitionToChangeRequest(ProjectStatus oldStatus, ProjectStatus newStatus) { - return ProjectStatus.CHANGE_REQUEST.equals(newStatus) && !newStatus.equals(oldStatus); + return new PageImpl<>(projects, pageable, projectPage.getTotalElements()); + } + + private Sort validateAndGetSort(SearchCriteria searchCriteria) { + if (searchCriteria.isValid() && StringUtils.isNotEmpty(searchCriteria.getSortBy())) { + if (!availableSortFields.contains(searchCriteria.getSortBy())) { + throw new BadRequestException(ProjectService.class, String.format("Invalid %s sortBy field for projects", searchCriteria.getSortBy())); + } + return Sort.by( + Sort.Direction.valueOf(searchCriteria.getSort().toUpperCase()), + searchCriteria.getSortBy()); } - - private void registerToZarsIfNecessary( - Project project, - ProjectStatus oldStatus, - List oldResearchers, - List newResearchers) { - ProjectStatus newStatus = project.getStatus(); - if (((newStatus == ProjectStatus.PENDING - || newStatus == ProjectStatus.APPROVED - || newStatus == ProjectStatus.PUBLISHED - || newStatus == ProjectStatus.CLOSED) - && newStatus != oldStatus) - || (newStatus == ProjectStatus.PUBLISHED - && researchersAreDifferent(oldResearchers, newResearchers))) { - registerToZars(project); + return Sort.by(Sort.Direction.DESC, "modifiedDate"); + } + + private void sortProjects(List projects, Sort sortBy) { + if (sortBy != null) { + Sort.Order authorOrder = sortBy.getOrderFor(AUTHOR_NAME); + if (authorOrder != null) { + Comparator byAuthorName = Comparator.comparing(project -> { + User coordinator = userService.getOwner(project.getCoordinator().getUserId()); + return Objects.requireNonNull(coordinator).getFullName().toUpperCase(); + }); + Sort.Direction sortOrder = authorOrder.getDirection(); + if (sortOrder.isAscending()) { + projects.sort(Comparator.nullsLast(byAuthorName)); + } else { + projects.sort(Comparator.nullsLast(byAuthorName.reversed())); } + } } - - private boolean researchersAreDifferent( - List oldResearchers, List newResearchers) { - return !(oldResearchers.containsAll(newResearchers) - && newResearchers.containsAll(oldResearchers)); + } + + public String getExportFilenameBody(Long projectId) { + return String.format( + "Project_%d_%s", + projectId, + LocalDateTime.now() + .truncatedTo(ChronoUnit.MINUTES) + .format(DateTimeFormatter.ISO_LOCAL_DATE)) + .replace('-', '_'); + } + + private void setTemplates(Project project, ProjectDto projectDto) { + if (projectDto.getTemplates() != null) { + Map map = + projectDto.getTemplates().stream() + .collect( + Collectors.toMap( + TemplateInfoDto::getTemplateId, TemplateInfoDto::getName, (t1, t2) -> t1)); + + project.setTemplates(map); } + } - public Page getProjects(String userId, List roles, SearchCriteria searchCriteria, Pageable pageable) { - - UserDetails loggedInUser = userDetailsService.checkIsUserApproved(userId); + private List getResearchers(ProjectDto projectDto) { + List newResearchersList = new LinkedList<>(); - Sort sortBy = validateAndGetSort(searchCriteria); - List projects; - Page projectPage; - Pageable pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); - Set usersUUID = null; - if (searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_SEARCH_BY_KEY)) { - String searchValue = (String) searchCriteria.getFilter().get(SearchCriteria.FILTER_SEARCH_BY_KEY); - usersUUID = userService.findUsersUUID(searchValue); - } - boolean sortByAuthor = searchCriteria.isSortByAuthor(); - if (sortByAuthor) { - long count = projectRepository.count(); - // load all projects because sort by author should be done in memory - pageRequest = PageRequest.of(0, count != 0 ? (int) count : 1); - } - String sortByField = searchCriteria.isValid() && StringUtils.isNotEmpty(searchCriteria.getSortBy()) ? searchCriteria.getSortBy() : "modifiedDate"; - Language language = Objects.nonNull(searchCriteria.getLanguage()) ? searchCriteria.getLanguage() : Language.de; - ProjectSpecification projectSpecification = ProjectSpecification.builder() - .filter(searchCriteria.getFilter()) - .roles(roles) - .loggedInUserId(userId) - .loggedInUserOrganizationId(loggedInUser.getOrganization() != null ? loggedInUser.getOrganization().getId() : null) - .ownersUUID(usersUUID) - .sortOrder(Objects.requireNonNull(sortBy.getOrderFor(sortByField)).ignoreCase()) - .language(language) - .build(); - projectPage = projectRepository.findProjects(projectSpecification, pageRequest); - projects = new ArrayList<>(projectPage.getContent()); - if (sortByAuthor) { - sortProjects(projects, sortBy); - projects = projects.stream() - .skip((long) pageable.getPageNumber() * pageable.getPageSize()) - .limit(pageable.getPageSize()) - .collect(Collectors.toList()); - } - return new PageImpl<>(projects, pageable, projectPage.getTotalElements()); - } + if (projectDto.getResearchers() != null) { + for (UserDetailsDto dto : projectDto.getResearchers()) { + Optional researcher = userDetailsService.getUserDetailsById(dto.getUserId()); - private Sort validateAndGetSort(SearchCriteria searchCriteria) { - if (searchCriteria.isValid() && StringUtils.isNotEmpty(searchCriteria.getSortBy())) { - if (!availableSortFields.contains(searchCriteria.getSortBy())) { - throw new BadRequestException(ProjectService.class, String.format("Invalid %s sortBy field for projects", searchCriteria.getSortBy())); - } - return Sort.by(Sort.Direction.valueOf(searchCriteria.getSort().toUpperCase()), - searchCriteria.getSortBy()); + if (researcher.isEmpty()) { + throw new BadRequestException(ProjectService.class, RESEARCHER_NOT_FOUND); } - return Sort.by(Sort.Direction.DESC, "modifiedDate"); - } - private void sortProjects(List projects, Sort sortBy) { - if (sortBy != null) { - Sort.Order authorOrder = sortBy.getOrderFor(AUTHOR_NAME); - if (authorOrder != null) { - Comparator byAuthorName = Comparator.comparing(project -> { - User coordinator = userService.getOwner(project.getCoordinator().getUserId()); - return Objects.requireNonNull(coordinator).getFullName().toUpperCase(); - }); - Sort.Direction sortOrder = authorOrder.getDirection(); - if (sortOrder.isAscending()) { - projects.sort(Comparator.nullsLast(byAuthorName)); - } else { - projects.sort(Comparator.nullsLast(byAuthorName.reversed())); - } - } + if (researcher.get().isNotApproved()) { + throw new BadRequestException(ProjectService.class, RESEARCHER_NOT_APPROVED); } - } - public String getExportFilenameBody(Long projectId) { - return String.format( - "Project_%d_%s", - projectId, - LocalDateTime.now() - .truncatedTo(ChronoUnit.MINUTES) - .format(DateTimeFormatter.ISO_LOCAL_DATE)) - .replace('-', '_'); + newResearchersList.add(researcher.get()); + } } + return newResearchersList; + } - private void setTemplates(Project project, ProjectDto projectDto) { - if (projectDto.getTemplates() != null) { - Map map = - projectDto.getTemplates().stream() - .collect( - Collectors.toMap( - TemplateInfoDto::getTemplateId, TemplateInfoDto::getName, (t1, t2) -> t1)); + private void validateStatus( + ProjectStatus initialStatus, ProjectStatus nextStatus, List roles) { - project.setTemplates(map); - } + if (nextStatus == null) { + throw new BadRequestException(ProjectService.class, INVALID_PROJECT_STATUS); } - private List getResearchers(ProjectDto projectDto) { - List newResearchersList = new LinkedList<>(); - - if (projectDto.getResearchers() != null) { - for (UserDetailsDto dto : projectDto.getResearchers()) { - Optional researcher = userDetailsService.getUserDetailsById(dto.getUserId()); - - if (researcher.isEmpty()) { - throw new BadRequestException(ProjectService.class, RESEARCHER_NOT_FOUND); - } - - if (researcher.get().isNotApproved()) { - throw new BadRequestException(ProjectService.class, RESEARCHER_NOT_APPROVED); - } - - newResearchersList.add(researcher.get()); - } - } - return newResearchersList; + if (initialStatus == null) { + if (!isValidInitialStatus(nextStatus)) { + throw new BadRequestException(ProjectService.class, INVALID_PROJECT_STATUS_PARAM, String.format(INVALID_PROJECT_STATUS_PARAM, nextStatus)); + } + } else if (initialStatus.nextStatusesAndRoles().containsKey(nextStatus)) { + List allowedRoles = initialStatus.nextStatusesAndRoles().get(nextStatus); + + Set intersectionSet = + roles.stream().distinct().filter(allowedRoles::contains).collect(Collectors.toSet()); + + if (intersectionSet.isEmpty()) { + throw new ForbiddenException(ProjectService.class, PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, + String.format(PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, initialStatus, nextStatus)); + } + } else { + throw new BadRequestException(ProjectService.class, PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, + String.format(PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, initialStatus, nextStatus)); } + } - private void validateStatus( - ProjectStatus initialStatus, ProjectStatus nextStatus, List roles) { + private boolean isValidInitialStatus(ProjectStatus status) { + return status.equals(ProjectStatus.DRAFT) || status.equals(ProjectStatus.PENDING); + } - if (nextStatus == null) { - throw new BadRequestException(ProjectService.class, INVALID_PROJECT_STATUS); - } + private void persistTransition( + Project project, ProjectStatus fromStatus, ProjectStatus toStatus, UserDetails user) { - if (initialStatus == null) { - if (!isValidInitialStatus(nextStatus)) { - throw new BadRequestException(ProjectService.class, INVALID_PROJECT_STATUS_PARAM, String.format(INVALID_PROJECT_STATUS_PARAM, nextStatus)); - } - } else if (initialStatus.nextStatusesAndRoles().containsKey(nextStatus)) { - List allowedRoles = initialStatus.nextStatusesAndRoles().get(nextStatus); + if (fromStatus != null && fromStatus.equals(toStatus)) { + return; + } - Set intersectionSet = - roles.stream().distinct().filter(allowedRoles::contains).collect(Collectors.toSet()); + var projectTransition = + ProjectTransition.builder() + .toStatus(toStatus) + .project(project) + .user(user) + .createDate(OffsetDateTime.now()) + .build(); - if (intersectionSet.isEmpty()) { - throw new ForbiddenException(ProjectService.class, PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, - String.format(PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, initialStatus, nextStatus)); - } - } else { - throw new BadRequestException(ProjectService.class, PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, - String.format(PROJECT_STATUS_TRANSITION_FROM_TO_IS_NOT_ALLOWED, initialStatus, nextStatus)); - } + if (fromStatus != null) { + projectTransition.setFromStatus(fromStatus); } - private boolean isValidInitialStatus(ProjectStatus status) { - return status.equals(ProjectStatus.DRAFT) || status.equals(ProjectStatus.PENDING); + if (project.getTransitions() != null) { + project.getTransitions().add(projectTransition); + } else { + project.setTransitions(Set.of(projectTransition)); } + } - private void persistTransition( - Project project, ProjectStatus fromStatus, ProjectStatus toStatus, UserDetails user) { - - if (fromStatus != null && fromStatus.equals(toStatus)) { - return; - } - - var projectTransition = - ProjectTransition.builder() - .toStatus(toStatus) - .project(project) - .user(user) - .createDate(OffsetDateTime.now()) - .build(); - - if (fromStatus != null) { - projectTransition.setFromStatus(fromStatus); - } - - if (project.getTransitions() != null) { - project.getTransitions().add(projectTransition); - } else { - project.setTransitions(Set.of(projectTransition)); - } + private void validateCoordinatorIsOwner(Project project, String loggedInUser) { + if (project.hasEmptyOrDifferentOwner(loggedInUser)) { + throw new ForbiddenException(ProjectService.class, CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER); } + } - private void validateCoordinatorIsOwner(Project project, String loggedInUser) { - if (project.hasEmptyOrDifferentOwner(loggedInUser)) { - throw new ForbiddenException(ProjectService.class, CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_OWNER); - } + private ProjectInfoDto toProjectInfo(Project project, List roles) { + if (project == null) { + return null; } - private ProjectInfoDto toProjectInfo(Project project, List roles) { - if (project == null) { - return null; - } - - var projectInfoDto = - ProjectInfoDto.builder() - .createDate(project.getCreateDate()) - .title(project.getName()) - .build(); - - if (project.getCoordinator() != null) { - if (roles.contains(Roles.RESEARCHER) - || roles.contains(Roles.STUDY_COORDINATOR) - || roles.contains(Roles.STUDY_APPROVER)) { - - var coordinator = userService.getUserById(project.getCoordinator().getUserId(), false); - projectInfoDto.setCoordinator( - String.format("%s %s", coordinator.getFirstName(), coordinator.getLastName())); - } else { - projectInfoDto.setCoordinator(StringUtils.EMPTY); - } - if (project.getCoordinator().getOrganization() != null) { - projectInfoDto.setOrganization(project.getCoordinator().getOrganization().getName()); - } - } - return projectInfoDto; + var projectInfoDto = + ProjectInfoDto.builder() + .createDate(project.getCreateDate()) + .title(project.getName()) + .build(); + + if (project.getCoordinator() != null) { + if (roles.contains(Roles.RESEARCHER) + || roles.contains(Roles.STUDY_COORDINATOR) + || roles.contains(Roles.STUDY_APPROVER)) { + + var coordinator = userService.getUserById(project.getCoordinator().getUserId(), false); + projectInfoDto.setCoordinator( + String.format("%s %s", coordinator.getFirstName(), coordinator.getLastName())); + } else { + projectInfoDto.setCoordinator(StringUtils.EMPTY); + } + if (project.getCoordinator().getOrganization() != null) { + projectInfoDto.setOrganization(project.getCoordinator().getOrganization().getName()); + } } - - private void registerToZars(Project project) { - if (zarsService != null) { - var zarsInfoDto = modelMapper.map(project, ZarsInfoDto.class); - zarsInfoDto.setCoordinator(getCoordinator(project)); - zarsInfoDto.setQueries(getQueries(project)); - zarsInfoDto.setApprovalDate(getApprovalDateIfExists(project)); - zarsInfoDto.setPartners(getPartners(project)); - zarsInfoDto.setClosedDate(getClosedDateIfExists(project)); - zarsService.registerToZars(zarsInfoDto); - } else { - log.error( - "Project change should be registered to ZARS, but necessary info is not configured. Not registered!"); - } + return projectInfoDto; + } + + private void registerToZars(Project project) { + if (zarsService != null) { + var zarsInfoDto = modelMapper.map(project, ZarsInfoDto.class); + zarsInfoDto.setCoordinator(getCoordinator(project)); + zarsInfoDto.setQueries(getQueries(project)); + zarsInfoDto.setApprovalDate(getApprovalDateIfExists(project)); + zarsInfoDto.setPartners(getPartners(project)); + zarsInfoDto.setClosedDate(getClosedDateIfExists(project)); + zarsService.registerToZars(zarsInfoDto); + } else { + log.error( + "Project change should be registered to ZARS, but necessary info is not configured. Not registered!"); } + } - @NotNull - private String getCoordinator(@NotNull Project project) { - return userService.getUserById(project.getCoordinator().getUserId(), false).getUsername(); - } + @NotNull + private String getCoordinator(@NotNull Project project) { + return userService.getUserById(project.getCoordinator().getUserId(), false).getUsername(); + } - @NotNull - private String getQueries(Project project) { - if (project.getCohort() == null) { - return StringUtils.EMPTY; - } - return String.join(", ", cohortQueryLister.list(project.getCohort())); + @NotNull + private String getQueries(Project project) { + if (project.getCohort() == null) { + return StringUtils.EMPTY; } - - @NotNull - private String getApprovalDateIfExists(Project project) { - List transitions = - projectTransitionRepository - .findAllByProjectIdAndFromStatusAndToStatus( - project.getId(), ProjectStatus.REVIEWING, ProjectStatus.APPROVED) - .orElse(Collections.emptyList()); - if (transitions.size() > 1) { - log.error( - "More than one transition from REVIEWING to APPROVED for project " + project.getId()); - return StringUtils.EMPTY; - } - if (transitions.size() == 1) { - return transitions.get(0).getCreateDate().format(DateTimeFormatter.ISO_LOCAL_DATE); - } - return StringUtils.EMPTY; + return String.join(", ", cohortQueryLister.list(project.getCohort())); + } + + @NotNull + private String getApprovalDateIfExists(Project project) { + List transitions = + projectTransitionRepository + .findAllByProjectIdAndFromStatusAndToStatus( + project.getId(), ProjectStatus.REVIEWING, ProjectStatus.APPROVED) + .orElse(Collections.emptyList()); + if (transitions.size() > 1) { + log.error( + "More than one transition from REVIEWING to APPROVED for project " + project.getId()); + return StringUtils.EMPTY; } + if (transitions.size() == 1) { + return transitions.get(0).getCreateDate().format(DateTimeFormatter.ISO_LOCAL_DATE); + } + return StringUtils.EMPTY; + } - @NotNull - private String getPartners(Project project) { - Set organizations = new HashSet<>(); + @NotNull + private String getPartners(Project project) { + Set organizations = new HashSet<>(); - if (project.getCoordinator().getOrganization() != null) { - organizations.add(project.getCoordinator().getOrganization()); - } - project - .getResearchers() - .forEach( - userDetails -> { - if (userDetails.getOrganization() != null) { - organizations.add(userDetails.getOrganization()); - } - }); - return organizations.stream().map(Organization::getName).collect(Collectors.joining(", ")); + if (project.getCoordinator().getOrganization() != null) { + organizations.add(project.getCoordinator().getOrganization()); } - - @NotNull - private String getClosedDateIfExists(Project project) { - List transitions = - projectTransitionRepository - .findAllByProjectIdAndFromStatusAndToStatus( - project.getId(), ProjectStatus.PUBLISHED, ProjectStatus.CLOSED) - .orElse(Collections.emptyList()); - if (transitions.size() > 1) { - throw new SystemException(ProjectService.class, MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT, - String.format(MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT, project.getId())); - } - if (transitions.size() == 1) { - return transitions.get(0).getCreateDate().format(DateTimeFormatter.ISO_LOCAL_DATE); - } - return StringUtils.EMPTY; + project + .getResearchers() + .forEach( + userDetails -> { + if (userDetails.getOrganization() != null) { + organizations.add(userDetails.getOrganization()); + } + }); + return organizations.stream().map(Organization::getName).collect(Collectors.joining(", ")); + } + + @NotNull + private String getClosedDateIfExists(Project project) { + List transitions = + projectTransitionRepository + .findAllByProjectIdAndFromStatusAndToStatus( + project.getId(), ProjectStatus.PUBLISHED, ProjectStatus.CLOSED) + .orElse(Collections.emptyList()); + if (transitions.size() > 1) { + throw new SystemException(ProjectService.class, MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT, + String.format(MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT, project.getId())); } + if (transitions.size() == 1) { + return transitions.get(0).getCreateDate().format(DateTimeFormatter.ISO_LOCAL_DATE); + } + return StringUtils.EMPTY; + } - private Project validateAndRetrieveProject(Long projectId, String userId) { - Project project = - projectRepository - .findById(projectId) - .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); - - if (project.getStatus() == null || !project.getStatus().equals(ProjectStatus.PUBLISHED)) { - throw new ForbiddenException(ProjectService.class, DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY); - } - - if (!project.isProjectResearcher(userId)) { - throw new ForbiddenException(ProjectService.class, CANNOT_ACCESS_THIS_PROJECT); - } + private Project validateAndRetrieveProject(Long projectId, String userId) { + Project project = + projectRepository + .findById(projectId) + .orElseThrow(() -> new ResourceNotFound(ProjectService.class, PROJECT_NOT_FOUND, String.format(PROJECT_NOT_FOUND, projectId))); - if (project.getCohort() == null) { - throw new BadRequestException(ProjectService.class, PROJECT_COHORT_CANNOT_BE_NULL, - String.format(PROJECT_COHORT_CANNOT_BE_NULL, project.getId())); - } + if (project.getStatus() == null || !project.getStatus().equals(ProjectStatus.PUBLISHED)) { + throw new ForbiddenException(ProjectService.class, DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY); + } - if (project.getTemplates() == null) { - throw new BadRequestException(ProjectService.class, PROJECT_TEMPLATES_CANNOT_BE_NULL, - String.format(PROJECT_TEMPLATES_CANNOT_BE_NULL, project.getId())); - } - return project; + if (!project.isProjectResearcher(userId)) { + throw new ForbiddenException(ProjectService.class, CANNOT_ACCESS_THIS_PROJECT); } - private Project createManagerProject() { - var undef = "undef"; - return Project.builder() - .id(0L) - .name("Manager data retrieval project") - .createDate(OffsetDateTime.now()) - .startDate(LocalDate.now()) - .description("Adhoc temp project for manager data retrieval") - .goal(undef) - .usedOutsideEu(false) - .firstHypotheses(undef) - .secondHypotheses(undef) - .description("Temporary project for manager data retrieval") - .coordinator(UserDetails.builder().userId(undef).organization(Organization.builder().id(0L).build()).build()) - .status(ProjectStatus.DENIED) - .build(); + if (project.getCohort() == null) { + throw new BadRequestException(ProjectService.class, PROJECT_COHORT_CANNOT_BE_NULL, + String.format(PROJECT_COHORT_CANNOT_BE_NULL, project.getId())); } - public byte[] getInfoDocBytes(Long id, String userId, Locale locale) { - userDetailsService.checkIsUserApproved(userId); - Project project = - projectRepository - .findById(id) - .orElseThrow(() -> new BadRequestException(ProjectService.class, PROJECT_NOT_FOUND)); - ProjectDto projectDto = projectMapper.convertToDto(project); - try { - return projectDocCreator.getDocBytesOfProject(projectDto, locale); - } catch (IOException e) { - throw new SystemException(ProjectService.class, ERROR_CREATING_THE_PROJECT_PDF, - String.format(ERROR_CREATING_THE_PROJECT_PDF, e.getMessage())); - } + if (project.getTemplates() == null) { + throw new BadRequestException(ProjectService.class, PROJECT_TEMPLATES_CANNOT_BE_NULL, + String.format(PROJECT_TEMPLATES_CANNOT_BE_NULL, project.getId())); + } + return project; + } + + private Project createManagerProject() { + var undef = "undef"; + return Project.builder() + .id(0L) + .name("Manager data retrieval project") + .createDate(OffsetDateTime.now()) + .startDate(LocalDate.now()) + .description("Adhoc temp project for manager data retrieval") + .goal(undef) + .usedOutsideEu(false) + .firstHypotheses(undef) + .secondHypotheses(undef) + .description("Temporary project for manager data retrieval") + .coordinator(UserDetails.builder().userId(undef).organization(Organization.builder().id(0L).build()).build()) + .status(ProjectStatus.DENIED) + .build(); + } + + public byte[] getInfoDocBytes(Long id, String userId, Locale locale) { + userDetailsService.checkIsUserApproved(userId); + Project project = + projectRepository + .findById(id) + .orElseThrow(() -> new BadRequestException(ProjectService.class, PROJECT_NOT_FOUND)); + ProjectDto projectDto = projectMapper.convertToDto(project); + try { + return projectDocCreator.getDocBytesOfProject(projectDto, locale); + } catch (IOException e) { + throw new SystemException(ProjectService.class, ERROR_CREATING_THE_PROJECT_PDF, + String.format(ERROR_CREATING_THE_PROJECT_PDF, e.getMessage())); } + } } diff --git a/src/main/java/org/highmed/numportal/service/TemplateService.java b/src/main/java/org/highmed/numportal/service/TemplateService.java index a3651c2b..c70ba3a8 100644 --- a/src/main/java/org/highmed/numportal/service/TemplateService.java +++ b/src/main/java/org/highmed/numportal/service/TemplateService.java @@ -2,6 +2,11 @@ import org.highmed.numportal.domain.dto.TemplateMetadataDto; import org.highmed.numportal.mapper.TemplateMapper; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.exception.BadRequestException; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.util.AqlQueryConstants; + import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.ehrbase.aqleditor.dto.containment.ContainmentDto; @@ -16,10 +21,6 @@ import org.ehrbase.openehr.sdk.aql.dto.select.SelectClause; import org.ehrbase.openehr.sdk.aql.dto.select.SelectExpression; import org.ehrbase.openehr.sdk.response.dto.ehrscape.TemplateMetaDataDto; -import org.highmed.numportal.service.ehrbase.EhrBaseService; -import org.highmed.numportal.service.exception.BadRequestException; -import org.highmed.numportal.service.exception.SystemException; -import org.highmed.numportal.service.util.AqlQueryConstants; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -51,8 +52,8 @@ public List getAllTemplatesMetadata(String userId) { List templateMetaDataDtos = ehrBaseService.getAllTemplatesMetadata(); return templateMetaDataDtos.stream() - .map(templateMapper::convertToTemplateMetadataDto) - .collect(Collectors.toList()); + .map(templateMapper::convertToTemplateMetadataDto) + .collect(Collectors.toList()); } public AqlQuery createSelectCompositionQuery(String templateId) { @@ -66,7 +67,7 @@ public AqlQuery createSelectCompositionQuery(String templateId) { } } catch (SystemException e) { throw new SystemException(TemplateService.class, CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID, - String.format(CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID, templateId)); + String.format(CANNOT_CREATE_QUERY_FOR_TEMPLATE_WITH_ID, templateId)); } } @@ -95,7 +96,7 @@ private AqlQuery createQuery(String archetypeId) { List fromPredList = new ArrayList<>(); ComparisonOperatorPredicate fromPred = new ComparisonOperatorPredicate(AqlObjectPathUtil.ARCHETYPE_NODE_ID, - ComparisonOperatorPredicate.PredicateComparisonOperator.EQ, new StringPrimitive(archetypeId)); + ComparisonOperatorPredicate.PredicateComparisonOperator.EQ, new StringPrimitive(archetypeId)); fromPredList.add(new AndOperatorPredicate(List.of(fromPred))); contains.setPredicates(fromPredList); from.setContains(contains); diff --git a/src/main/java/org/highmed/numportal/service/UserDetailsService.java b/src/main/java/org/highmed/numportal/service/UserDetailsService.java index b649a6a0..63b90777 100644 --- a/src/main/java/org/highmed/numportal/service/UserDetailsService.java +++ b/src/main/java/org/highmed/numportal/service/UserDetailsService.java @@ -7,8 +7,6 @@ import org.highmed.numportal.domain.repository.OrganizationRepository; import org.highmed.numportal.domain.repository.UserDetailsRepository; import org.highmed.numportal.domain.specification.UserDetailsSpecification; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.highmed.numportal.service.exception.ForbiddenException; import org.highmed.numportal.service.exception.ResourceNotFound; import org.highmed.numportal.service.exception.SystemException; @@ -20,6 +18,9 @@ import org.highmed.numportal.service.notification.dto.account.AccountApprovalNotification; import org.highmed.numportal.service.notification.dto.account.AccountStatusChangedNotification; import org.highmed.numportal.service.notification.dto.account.OrganizationUpdateNotification; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; @@ -29,15 +30,26 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.*; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; import static java.util.Objects.nonNull; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.USER_NOT_FOUND; @Slf4j @Service public class UserDetailsService { + private static final String USER_ATTRIBUTE_DEPARTMENT = "department"; + private static final String USER_ATTRIBUTE_REQUESTED_ROLE = "requested-role"; + private static final String USER_ATTRIBUTE_ADDITIONAl_NOTES = "notes"; + private static final String FULL_NAME_FORMAT = "%s %s"; private final UserDetailsRepository userDetailsRepository; private final OrganizationRepository organizationRepository; private final OrganizationService organizationService; @@ -45,20 +57,15 @@ public class UserDetailsService { private final UserService userService; private final UsersMetrics usersMetrics; - private static final String USER_ATTRIBUTE_DEPARTMENT = "department"; - private static final String USER_ATTRIBUTE_REQUESTED_ROLE = "requested-role"; - private static final String USER_ATTRIBUTE_ADDITIONAl_NOTES = "notes"; - private static final String FULL_NAME_FORMAT = "%s %s"; - @Autowired public UserDetailsService( - @Lazy UserService userService, - UserDetailsRepository userDetailsRepository, - OrganizationRepository organizationRepository, - @Lazy OrganizationService organizationService, - NotificationService notificationService, - UsersMetrics usersMetrics) { + @Lazy UserService userService, + UserDetailsRepository userDetailsRepository, + OrganizationRepository organizationRepository, + @Lazy OrganizationService organizationService, + NotificationService notificationService, + UsersMetrics usersMetrics) { this.userService = userService; this.notificationService = notificationService; this.organizationService = organizationService; @@ -81,7 +88,7 @@ public UserDetails createUserDetails(String userId, String emailAddress) { return userDetails.get(); } else { UserDetails newUserDetails = UserDetails.builder().userId(userId) - .createdDate(LocalDateTime.now(ZoneOffset.UTC)).build(); + .createdDate(LocalDateTime.now(ZoneOffset.UTC)).build(); organizationService .resolveOrganization(emailAddress) .ifPresent(newUserDetails::setOrganization); @@ -111,9 +118,10 @@ public UserDetails setOrganization(String loggedInUserId, String userId, Long or Organization organization = organizationRepository .findById(organizationId) - .orElseThrow(() -> new ResourceNotFound(UserDetailsService.class, ORGANIZATION_NOT_FOUND, String.format(ORGANIZATION_NOT_FOUND, organizationId))); + .orElseThrow( + () -> new ResourceNotFound(UserDetailsService.class, ORGANIZATION_NOT_FOUND, String.format(ORGANIZATION_NOT_FOUND, organizationId))); - if(nonNull(organization.getActive()) && (!organization.getActive())){ + if (nonNull(organization.getActive()) && (!organization.getActive())) { String logMessage = String.format(CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION, organization.getName()); log.warn(logMessage); throw new ForbiddenException(OrganizationService.class, CANNOT_ASSIGN_USER_TO_DEACTIVATED_ORGANIZATION, logMessage); @@ -158,7 +166,7 @@ public UserDetails approveUser(String loggedInUserId, String userId) { public UserDetails checkIsUserApproved(String userId) { UserDetails user = getUserDetailsById(userId).orElseThrow(() -> new SystemException(UserDetailsService.class, USER_NOT_FOUND, - String.format(USER_NOT_FOUND, userId))); + String.format(USER_NOT_FOUND, userId))); if (user.isNotApproved()) { log.warn("User {} is not approved", userId); @@ -187,7 +195,7 @@ public Long countUserDetailsByOrganization(Long organizationId) { @Transactional public void deactivateUsers(String loggedInUserId, Long organizationId) { List users = userDetailsRepository.findByOrganizationId(organizationId); - for(UserDetails userDetails : users) { + for (UserDetails userDetails : users) { log.info("Deactivate user {} ", userDetails.getUserId()); userService.updateUserActiveField(loggedInUserId, userDetails.getUserId(), Boolean.FALSE); } @@ -207,13 +215,14 @@ public void sendAccountStatusChangedNotification(String userId, String loggedInU if (user != null && admin != null) { AccountStatusChangedNotification statusChangedNotification = AccountStatusChangedNotification.builder() - .recipientEmail(user.getEmail()) - .recipientFirstName(user.getFirstName()) - .recipientLastName(user.getLastName()) - .adminEmail(admin.getEmail()) - .adminFullName(String.format(FULL_NAME_FORMAT, admin.getFirstName(), admin.getLastName())) - .userCurrentStatus(currentStatus) - .build(); + .recipientEmail(user.getEmail()) + .recipientFirstName(user.getFirstName()) + .recipientLastName(user.getLastName()) + .adminEmail(admin.getEmail()) + .adminFullName(String.format(FULL_NAME_FORMAT, + admin.getFirstName(), admin.getLastName())) + .userCurrentStatus(currentStatus) + .build(); notifications.add(statusChangedNotification); } else { log.warn("Could not create account status changed email notification."); @@ -230,12 +239,12 @@ private List collectAccountApprovalNotification( if (user != null && admin != null) { AccountApprovalNotification not = AccountApprovalNotification.builder() - .recipientEmail(user.getEmail()) - .recipientFirstName(user.getFirstName()) - .recipientLastName(user.getLastName()) - .adminEmail(admin.getEmail()) - .adminFullName(String.format(FULL_NAME_FORMAT, admin.getFirstName(), admin.getLastName())) - .build(); + .recipientEmail(user.getEmail()) + .recipientFirstName(user.getFirstName()) + .recipientLastName(user.getLastName()) + .adminEmail(admin.getEmail()) + .adminFullName(String.format(FULL_NAME_FORMAT, admin.getFirstName(), admin.getLastName())) + .build(); notifications.add(not); } else { @@ -255,14 +264,14 @@ private List collectOrganizationUpdateNotification( if (user != null && admin != null) { OrganizationUpdateNotification not = OrganizationUpdateNotification.builder() - .recipientEmail(user.getEmail()) - .recipientFirstName(user.getFirstName()) - .recipientLastName(user.getLastName()) - .adminEmail(admin.getEmail()) - .adminFullName(String.format(FULL_NAME_FORMAT, admin.getFirstName(), admin.getLastName())) - .organization(organization) - .formerOrganization(formerOrganization) - .build(); + .recipientEmail(user.getEmail()) + .recipientFirstName(user.getFirstName()) + .recipientLastName(user.getLastName()) + .adminEmail(admin.getEmail()) + .adminFullName(String.format(FULL_NAME_FORMAT, admin.getFirstName(), admin.getLastName())) + .organization(organization) + .formerOrganization(formerOrganization) + .build(); notifications.add(not); } else { @@ -291,16 +300,16 @@ private List collectOrganizationAdminNotifications(String userId) admin -> { NewUserNotification notification = NewUserNotification.builder() - .newUserEmail(user.getEmail()) - .newUserFirstName(user.getFirstName()) - .newUserLastName(user.getLastName()) - .recipientEmail(admin.getEmail()) - .recipientFirstName(admin.getFirstName()) - .recipientLastName(admin.getLastName()) - .department(userDepartment) - .requestedRoles(requestedRoles) - .notes(notes) - .build(); + .newUserEmail(user.getEmail()) + .newUserFirstName(user.getFirstName()) + .newUserLastName(user.getLastName()) + .recipientEmail(admin.getEmail()) + .recipientFirstName(admin.getFirstName()) + .recipientLastName(admin.getLastName()) + .department(userDepartment) + .requestedRoles(requestedRoles) + .notes(notes) + .build(); notifications.add(notification); }); @@ -318,19 +327,20 @@ private List collectAdminNotifications(String userId) { admin -> { NewUserWithoutOrganizationNotification notification = NewUserWithoutOrganizationNotification.builder() - .userEmail(user.getEmail()) - .userFirstName(user.getFirstName()) - .userLastName(user.getLastName()) - .recipientEmail(admin.getEmail()) - .recipientFirstName(admin.getFirstName()) - .recipientLastName(admin.getLastName()) - .build(); + .userEmail(user.getEmail()) + .userFirstName(user.getFirstName()) + .userLastName(user.getLastName()) + .recipientEmail(admin.getEmail()) + .recipientFirstName(admin.getFirstName()) + .recipientLastName(admin.getLastName()) + .build(); notifications.add(notification); }); return notifications; } private List getUserAttribute(User user, String attributeName) { - return user.getAttributes() != null ? (List) user.getAttributes().getOrDefault(attributeName, Collections.EMPTY_LIST) : Collections.EMPTY_LIST; + return user.getAttributes() != null ? (List) user.getAttributes().getOrDefault(attributeName, Collections.EMPTY_LIST) + : Collections.EMPTY_LIST; } } diff --git a/src/main/java/org/highmed/numportal/service/UserService.java b/src/main/java/org/highmed/numportal/service/UserService.java index 56d907aa..0caa6420 100644 --- a/src/main/java/org/highmed/numportal/service/UserService.java +++ b/src/main/java/org/highmed/numportal/service/UserService.java @@ -24,6 +24,7 @@ import org.highmed.numportal.service.notification.dto.account.RolesUpdateNotification; import org.highmed.numportal.service.notification.dto.account.UserNameUpdateNotification; import org.highmed.numportal.web.feign.KeycloakFeign; + import feign.FeignException; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -36,71 +37,82 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.concurrent.ConcurrentMapCache; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Nullable; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; +import javax.annotation.Nullable; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CACHE_IS_NOT_REACHABLE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_APPROVED_USER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_ENABLED_USER; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.FETCHING_USER_FROM_KEYCLOAK_FAILED; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NOT_ALLOWED_TO_REMOVE_THAT_ROLE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NOT_ALLOWED_TO_SET_THAT_ROLE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NOT_ALLOWED_TO_UPDATE_OWN_STATUS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NO_ROLES_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ORGANIZATION_ADMIN_CAN_ONLY_MANAGE_USERS_IN_THEIR_OWN_ORGANIZATION; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ROLE_OR_USER_NOT_FOUND; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.UNKNOWN_ROLE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.USER_NOT_FOUND; @Slf4j @Service @AllArgsConstructor public class UserService { - private final UserDetailsRepository userDetailsRepository; - - private final KeycloakFeign keycloakFeign; - - private final UserDetailsService userDetailsService; - - private final OrganizationMapper organizationMapper; - - private final NotificationService notificationService; - - private final CacheManager cacheManager; - - private final TranslationRepository translationRepository; - - private UsersMetrics usersMetrics; public static final String TRANSLATION_CACHE = "translation"; - public static final String USERS_CACHE = "users"; - private static final String KEYCLOACK_DEFAULT_ROLES_PREFIX = "default-roles-"; - - private final List availableSortFields = Arrays.asList(FIRST_NAME, LAST_NAME, ORGANIZATION_NAME, REGISTRATION_DATE, MAIL); - private static final String FIRST_NAME = "firstName"; - private static final String LAST_NAME = "lastName"; - private static final String ORGANIZATION_NAME = "organization"; - private static final String REGISTRATION_DATE = "registrationDate"; - private static final String MAIL = "email"; - private static final String ACTIVE = "enabled"; - private static final String LOG_KEYCLOAK_DELETE_USER = "Keycloak call to delete user {}"; - private static final String LOG_KEYCLOAK_ERROR_GET_USER = "Keycloak - could not retrieve user {}. Message: {}, status: {}"; - private static final String LOG_KEYCLOAK_ERROR_RESOURCE_NOT_FOUND = "Keycloak - resource not found. Message: {}, status: {}"; - private static final String LOG_KEYCLOAK_ERROR = "Keycloak - error occurred. Message: {}, status: {}"; + private final UserDetailsRepository userDetailsRepository; + private final KeycloakFeign keycloakFeign; + private final UserDetailsService userDetailsService; + private final OrganizationMapper organizationMapper; + private final NotificationService notificationService; + private final CacheManager cacheManager; + private final TranslationRepository translationRepository; + private final List availableSortFields = Arrays.asList(FIRST_NAME, LAST_NAME, ORGANIZATION_NAME, REGISTRATION_DATE, MAIL); + private UsersMetrics usersMetrics; @Transactional public void initializeUsersCache() { @@ -129,7 +141,7 @@ public void initializeTranslationCache() { } @Transactional - @CachePut(value = USERS_CACHE, key="#uuid") + @CachePut(value = USERS_CACHE, key = "#uuid") public User addUserToCache(String uuid) { return getUserById(uuid, true); } @@ -148,7 +160,7 @@ public void deleteUser(String userId, String loggedInUserId) { userDetailsService.deleteUserDetails(userId); } else { throw new BadRequestException(UserService.class, CANNOT_DELETE_APPROVED_USER, - String.format(CANNOT_DELETE_APPROVED_USER, userId)); + String.format(CANNOT_DELETE_APPROVED_USER, userId)); } } log.info("Logged in user {} deleted user with id {} ", loggedInUserId, userId); @@ -167,8 +179,24 @@ public User getUserById(String userId, boolean withRole, String loggedInUserId) } /** - * Retrieves user details without roles - used for determining the ownership on aql, projects and - * comments; caches results + * Retrieves user, portal user details and corresponding roles from identity provider + * + * @param userId External id of the user + * @return User + */ + @Transactional + public User getUserById(String userId, Boolean withRole) { + User user = getUser(userId); + + if (BooleanUtils.isTrue(withRole)) { + addRoles(user); + } + addUserDetails(user); + return user; + } + + /** + * Retrieves user details without roles - used for determining the ownership on aql, projects and comments; caches results * * @param userId the id of the user to fetch * @return the found user @@ -179,28 +207,11 @@ public User getUserById(String userId, boolean withRole, String loggedInUserId) public User getOwner(String userId) { try { return getUserById(userId, false); - } catch (ResourceNotFound e){ + } catch (ResourceNotFound e) { return null; } } - /** - * Retrieves user, portal user details and corresponding roles from identity provider - * - * @param userId External id of the user - * @return User - */ - @Transactional - public User getUserById(String userId, Boolean withRole) { - User user = getUser(userId); - - if (BooleanUtils.isTrue(withRole)) { - addRoles(user); - } - addUserDetails(user); - return user; - } - /** * Retrieves a set of user roles for a particular user from identity provider * @@ -212,6 +223,19 @@ public Set getUserRoles(String userId, String loggedInUserId) { return getUserRoles(userId); } + private Set getUserRoles(String userId) { + try { + return keycloakFeign.getRolesOfUser(userId); + } catch (FeignException.BadRequest | FeignException.InternalServerError e) { + log.error("Keycloak - failed to get user's {} roles. Message {}, status {}", userId, e.getMessage(), e.status()); + throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER, + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER, e.getMessage())); + } catch (FeignException.NotFound e) { + log.error(LOG_KEYCLOAK_ERROR_RESOURCE_NOT_FOUND, e.getMessage(), e.status()); + throw new ResourceNotFound(UserService.class, NO_ROLES_FOUND); + } + } + /** * Assigns roles to a particular user * @@ -230,30 +254,30 @@ public List setUserRoles(String userId, @NotNull List roleNames, removeRoles = existingRoles.stream() - .filter(role -> !roleNames.contains(role.getName())) - .toList() - .toArray(new Role[] {}); + .filter(role -> !roleNames.contains(role.getName())) + .toList() + .toArray(new Role[]{}); addRoles = roleNames.stream() - .filter( - role -> existingRoles.stream().noneMatch(role1 -> role1.getName().equals(role))) - .map(supportedRoles::get) - .peek( - role -> { - if (role == null) { - throw new BadRequestException(UserService.class, UNKNOWN_ROLE); - } - }) - .toList() - .toArray(new Role[] {}); + .filter( + role -> existingRoles.stream().noneMatch(role1 -> role1.getName().equals(role))) + .map(supportedRoles::get) + .peek( + role -> { + if (role == null) { + throw new BadRequestException(UserService.class, UNKNOWN_ROLE); + } + }) + .toList() + .toArray(new Role[]{}); if (Arrays.stream(removeRoles) - .anyMatch(role -> !Roles.isAllowedToSet(role.getName(), callerRoles))) { + .anyMatch(role -> !Roles.isAllowedToSet(role.getName(), callerRoles))) { throw new ForbiddenException(UserService.class, NOT_ALLOWED_TO_REMOVE_THAT_ROLE); } if (Arrays.stream(addRoles) - .anyMatch(role -> !Roles.isAllowedToSet(role.getName(), callerRoles))) { + .anyMatch(role -> !Roles.isAllowedToSet(role.getName(), callerRoles))) { throw new ForbiddenException(UserService.class, NOT_ALLOWED_TO_SET_THAT_ROLE); } @@ -270,7 +294,7 @@ public List setUserRoles(String userId, @NotNull List roleNames, } catch (FeignException.BadRequest | FeignException.InternalServerError e) { log.error(LOG_KEYCLOAK_ERROR, e.getMessage(), e.status()); throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, e.getMessage())); } catch (FeignException.NotFound e) { log.error(LOG_KEYCLOAK_ERROR_RESOURCE_NOT_FOUND, e.getMessage(), e.status()); throw new ResourceNotFound(UserService.class, ROLE_OR_USER_NOT_FOUND); @@ -283,21 +307,8 @@ public List setUserRoles(String userId, @NotNull List roleNames, return roleNames; } - private Set getUserRoles(String userId) { - try { - return keycloakFeign.getRolesOfUser(userId); - } catch (FeignException.BadRequest | FeignException.InternalServerError e) { - log.error("Keycloak - failed to get user's {} roles. Message {}, status {}", userId, e.getMessage(), e.status()); - throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USER_ROLES_PLEASE_TRY_AGAIN_LATER, e.getMessage())); - } catch (FeignException.NotFound e) { - log.error(LOG_KEYCLOAK_ERROR_RESOURCE_NOT_FOUND, e.getMessage(), e.status()); - throw new ResourceNotFound(UserService.class, NO_ROLES_FOUND); - } - } - private Set getUserRoleNames(String userId) { - return getUserRoles(userId).stream().map(Role::getName).collect(Collectors.toSet()); + return getUserRoles(userId).stream().map(Role::getName).collect(Collectors.toSet()); } private void addUserDetails(User user) { @@ -324,21 +335,20 @@ private void addRoles(User user) { /** * Retrieved a list of users that match the search criteria * - * @param searchCriteria filter[approved] Indicates that the user has been approved by the admin, - * filter[search] A string contained in username, first or last name, or email - * filter[withRoles] flag whether to add roles to the user structure, if present, or not + * @param searchCriteria filter[approved] Indicates that the user has been approved by the admin, filter[search] A string contained in username, + * first or last name, or email filter[withRoles] flag whether to add roles to the user structure, if present, or not * @return the users that match the search parameters and with optional roles if indicated */ @Transactional public Page searchUsers(String loggedInUserId, List callerRoles, SearchCriteria searchCriteria, - Pageable pageable) { + Pageable pageable) { UserDetails loggedInUser = userDetailsService.checkIsUserApproved(loggedInUserId); validateSort(searchCriteria); Set usersUUID = new HashSet<>(); List requestedRoles = Collections.emptyList(); - boolean searchCriteriaProvided = searchCriteria.getFilter() != null && - searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_SEARCH_BY_KEY); + boolean searchCriteriaProvided = + searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_SEARCH_BY_KEY); boolean filterByRoles = searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_BY_ROLES); if (filterByRoles) { requestedRoles = getRequestedRoles(retrieveSearchField(searchCriteria, SearchCriteria.FILTER_BY_ROLES)); @@ -366,9 +376,8 @@ public Page searchUsers(String loggedInUserId, List callerRoles, S Set filteredUsersUUID = userDetailsList.stream().map(UserDetails::getUserId).collect(Collectors.toSet()); List filteredUsers = new ArrayList<>(); - Boolean withRoles = searchCriteria.getFilter() != null && - searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_USER_WITH_ROLES_KEY) ? - Boolean.valueOf((String) searchCriteria.getFilter().get(SearchCriteria.FILTER_USER_WITH_ROLES_KEY)) : null; + Boolean withRoles = searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(SearchCriteria.FILTER_USER_WITH_ROLES_KEY) + ? Boolean.valueOf((String) searchCriteria.getFilter().get(SearchCriteria.FILTER_USER_WITH_ROLES_KEY)) : null; boolean loadUserRoles = (withRoles != null && withRoles) || Roles.isProjectLead(callerRoles); for (String uuid : filteredUsersUUID) { try { @@ -381,21 +390,22 @@ public Page searchUsers(String loggedInUserId, List callerRoles, S if (isSortActive(searchCriteria)) { sortUsers(filteredUsers, searchCriteria); filteredUsers = filteredUsers.stream() - .skip((long) pageable.getPageNumber() * pageable.getPageSize()) - .limit(pageable.getPageSize()) - .collect(Collectors.toList()); + .skip((long) pageable.getPageNumber() * pageable.getPageSize()) + .limit(pageable.getPageSize()) + .collect(Collectors.toList()); } return new PageImpl<>(new ArrayList<>(filteredUsers), pageable, userDetailsPage.getTotalElements()); } private T retrieveSearchField(SearchCriteria searchCriteria, String fieldKey) { - if(searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(fieldKey)) { + if (searchCriteria.getFilter() != null && searchCriteria.getFilter().containsKey(fieldKey)) { return (T) searchCriteria.getFilter().get(fieldKey); } return null; } - private UserDetailsSpecification buildUserSpecification(UserDetails loggedInUser, List callerRoles, SearchCriteria searchCriteria, Set usersUUID) { + private UserDetailsSpecification buildUserSpecification(UserDetails loggedInUser, List callerRoles, SearchCriteria searchCriteria, + Set usersUUID) { Boolean approved = null; Long organizationId = null; @@ -417,16 +427,16 @@ private UserDetailsSpecification buildUserSpecification(UserDetails loggedInUser } } return UserDetailsSpecification.builder() - .approved(approved) - .loggedInUserOrganizationId(organizationId) - .usersUUID(usersUUID) - .build(); + .approved(approved) + .loggedInUserOrganizationId(organizationId) + .usersUUID(usersUUID) + .build(); } private void sortUsers(List users, SearchCriteria searchCriteria) { String field = searchCriteria.getSortBy() != null ? searchCriteria.getSortBy() : REGISTRATION_DATE; - Sort.Direction sortOrder = searchCriteria.getSort() != null ? - Sort.Direction.valueOf(searchCriteria.getSort().toUpperCase()) : Sort.Direction.DESC; + Sort.Direction sortOrder = + searchCriteria.getSort() != null ? Sort.Direction.valueOf(searchCriteria.getSort().toUpperCase()) : Sort.Direction.DESC; Comparator userComparator = getComparator(field); if (sortOrder.isAscending()) { users.sort(Comparator.nullsLast(userComparator)); @@ -440,7 +450,7 @@ private Comparator getComparator(String field) { case FIRST_NAME -> Comparator.comparing(u -> u.getFirstName().toUpperCase()); case LAST_NAME -> Comparator.comparing(u -> u.getLastName().toUpperCase()); case ORGANIZATION_NAME -> - Comparator.comparing(user -> user.getOrganization() != null ? user.getOrganization().getName().toUpperCase() : StringUtils.EMPTY); + Comparator.comparing(user -> user.getOrganization() != null ? user.getOrganization().getName().toUpperCase() : StringUtils.EMPTY); case MAIL -> Comparator.comparing(u -> u.getEmail().toUpperCase()); default -> Comparator.comparing(User::getCreatedTimestamp); }; @@ -456,7 +466,7 @@ public Set getByRole(String role) { } catch (FeignException fe) { log.error(LOG_KEYCLOAK_ERROR, fe.getMessage(), fe.status()); throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, fe.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, fe.getMessage())); } } @@ -469,12 +479,12 @@ private List collectUserNameUpdateNotification( if (user != null && admin != null) { UserNameUpdateNotification not = UserNameUpdateNotification.builder() - .recipientEmail(user.getEmail()) - .recipientFirstName(user.getFirstName()) - .recipientLastName(user.getLastName()) - .adminEmail(admin.getEmail()) - .adminFullName(String.format("%s %s", admin.getFirstName(), admin.getLastName())) - .build(); + .recipientEmail(user.getEmail()) + .recipientFirstName(user.getFirstName()) + .recipientLastName(user.getLastName()) + .adminEmail(admin.getEmail()) + .adminFullName(String.format("%s %s", admin.getFirstName(), admin.getLastName())) + .build(); notifications.add(not); } else { @@ -499,16 +509,16 @@ private List collectRolesUpdateNotification( if (user != null && admin != null && (rolesAdded.length > 0 || rolesRemoved.length > 0)) { RolesUpdateNotification notification = RolesUpdateNotification.builder() - .recipientEmail(user.getEmail()) - .recipientFirstName(user.getFirstName()) - .recipientLastName(user.getLastName()) - .adminEmail(admin.getEmail()) - .adminFullName(String.format("%s %s", admin.getFirstName(), admin.getLastName())) - .rolesRemoved( - Arrays.stream(rolesRemoved).map(Role::getName).collect(Collectors.toList())) - .rolesAdded(Arrays.stream(rolesAdded).map(Role::getName).collect(Collectors.toList())) - .allRoles(allRoles.stream().map(Role::getName).collect(Collectors.toList())) - .build(); + .recipientEmail(user.getEmail()) + .recipientFirstName(user.getFirstName()) + .recipientLastName(user.getLastName()) + .adminEmail(admin.getEmail()) + .adminFullName(String.format("%s %s", admin.getFirstName(), admin.getLastName())) + .rolesRemoved( + Arrays.stream(rolesRemoved).map(Role::getName).collect(Collectors.toList())) + .rolesAdded(Arrays.stream(rolesAdded).map(Role::getName).collect(Collectors.toList())) + .allRoles(allRoles.stream().map(Role::getName).collect(Collectors.toList())) + .build(); notifications.add(notification); } @@ -525,7 +535,7 @@ private void deleteNotVerifiedUser(String userId) { } catch (FeignException e) { log.error(LOG_KEYCLOAK_ERROR, e.getMessage(), e.status()); throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_WHILE_DELETING_USER_PLEASE_TRY_AGAIN_LATER, e.getMessage())); } } else { throw new BadRequestException(UserService.class, CANNOT_DELETE_ENABLED_USER); @@ -534,7 +544,6 @@ private void deleteNotVerifiedUser(String userId) { /** * Refresh users cache every 8 hours - * */ @Scheduled(fixedRate = 28800000) @Transactional @@ -548,36 +557,36 @@ public void refreshUsersCache() { } private void deleteNotApprovedUser(UserDetails userDetails) { - String userId = userDetails.getUserId(); + String userId = userDetails.getUserId(); try { - if (userId != null){ - - Timestamp createdAt; - if (isNull(userDetails.getCreatedDate())) { - User userForDeletion = keycloakFeign.getUser(userDetails.getUserId()); - if (isNull(userForDeletion.getCreatedTimestamp())){ - return; - } else { - createdAt = new Timestamp(userForDeletion.getCreatedTimestamp()); - } + if (userId != null) { + + Timestamp createdAt; + if (isNull(userDetails.getCreatedDate())) { + User userForDeletion = keycloakFeign.getUser(userDetails.getUserId()); + if (isNull(userForDeletion.getCreatedTimestamp())) { + return; } else { - createdAt = new Timestamp(userDetails.getCreatedDate().toInstant(ZoneOffset.UTC).toEpochMilli()); + createdAt = new Timestamp(userForDeletion.getCreatedTimestamp()); } + } else { + createdAt = new Timestamp(userDetails.getCreatedDate().toInstant(ZoneOffset.UTC).toEpochMilli()); + } - Date createdAtDate=new Date(createdAt.getTime()); - boolean shouldDelete = LocalDateTime.from(createdAtDate.toInstant().atZone(ZoneId.of("UTC"))). - plusDays(30).isBefore(LocalDateTime.now()); - if (shouldDelete) { - log.debug(LOG_KEYCLOAK_DELETE_USER, userId); - keycloakFeign.deleteUser(userId); - userDetailsService.deleteUserDetails(userId); - log.info("- deleteUnapprovedUsersAfter30Days - userID: {} isApproved: {} deletedUser: {}", userId, userDetails.isApproved(), userDetails); - } + Date createdAtDate = new Date(createdAt.getTime()); + boolean shouldDelete = LocalDateTime.from(createdAtDate.toInstant().atZone(ZoneId.of("UTC"))) + .plusDays(30).isBefore(LocalDateTime.now()); + if (shouldDelete) { + log.debug(LOG_KEYCLOAK_DELETE_USER, userId); + keycloakFeign.deleteUser(userId); + userDetailsService.deleteUserDetails(userId); + log.info("- deleteUnapprovedUsersAfter30Days - userID: {} isApproved: {} deletedUser: {}", userId, userDetails.isApproved(), userDetails); } + } } catch (FeignException.BadRequest | FeignException.InternalServerError e) { log.error(LOG_KEYCLOAK_ERROR, e.getMessage(), e.status()); throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, e.getMessage())); } catch (FeignException.NotFound e) { log.error(LOG_KEYCLOAK_ERROR_GET_USER, userId, e.getMessage(), e.status()); throw new ResourceNotFound(UserService.class, USER_NOT_FOUND, String.format(USER_NOT_FOUND, userId)); @@ -609,8 +618,9 @@ && belongToSameOrganization(loggedInUser, userToChange))) { updateName(userIdToChange, userName); log.info("User's {} name was changed by {}", userIdToChange, loggedInUserId); } else { - throw new ForbiddenException(UserService.class, - CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES); + throw new ForbiddenException( + UserService.class, + CAN_ONLY_CHANGE_OWN_NAME_ORG_ADMIN_NAMES_OF_THE_PEOPLE_IN_THE_ORGANIZATION_AND_SUPERUSER_ALL_NAMES); } notificationService.send(collectUserNameUpdateNotification(userIdToChange, loggedInUserId)); @@ -618,6 +628,7 @@ && belongToSameOrganization(loggedInUser, userToChange))) { } /** + * update active Field of user * * @param loggedInUserId * @param userId @@ -652,18 +663,19 @@ public User updateUserActiveField(@NotNull String loggedInUserId, @NotNull Strin private void validateUserRolesAndOrganization(String loggedInUserId, String userId, List callerRoles) { UserDetails loggedInUser = userDetailsService.checkIsUserApproved(loggedInUserId); - UserDetails userToUpdate = userDetailsService.getUserDetailsById(userId).orElseThrow(()-> - new SystemException(UserService.class, USER_NOT_FOUND, - String.format(USER_NOT_FOUND, userId))); + UserDetails userToUpdate = userDetailsService.getUserDetailsById(userId).orElseThrow(() -> + new SystemException(UserService.class, USER_NOT_FOUND, + String.format(USER_NOT_FOUND, userId))); if (Roles.isOrganizationAdmin(callerRoles) - && !Roles.isSuperAdmin(callerRoles) - && !belongToSameOrganization(loggedInUser, userToUpdate)) { + && !Roles.isSuperAdmin(callerRoles) + && !belongToSameOrganization(loggedInUser, userToUpdate)) { throw new ForbiddenException(UserService.class, ORGANIZATION_ADMIN_CAN_ONLY_MANAGE_USERS_IN_THEIR_OWN_ORGANIZATION); } } /** * Retrieved a list of users UUID that match the search criteria + * * @param search A string contained in username, first or last name, or email * @return a Set of filteredUsers */ @@ -672,56 +684,60 @@ public Set findUsersUUID(String search) { } private Set filterKeycloakUsers(String search, List roles, Optional enabledFlag, Language language) { - Set userUUIDs = new HashSet<>(); + Set userUuids = new HashSet<>(); ConcurrentMapCache usersCache = (ConcurrentMapCache) cacheManager.getCache(USERS_CACHE); if ((StringUtils.isNotEmpty(search) || CollectionUtils.isNotEmpty(roles) || enabledFlag.isPresent()) - && usersCache != null && usersCache.getNativeCache().size() != 0) { + && usersCache != null && usersCache.getNativeCache().size() != 0) { ConcurrentMap users = usersCache.getNativeCache(); for (Map.Entry entry : users.entrySet()) { - filterUsers(search, roles, enabledFlag, userUUIDs, entry, language); + filterUsers(search, roles, enabledFlag, userUuids, entry, language); } - return userUUIDs; + return userUuids; } return Collections.emptySet(); } private void filterUsers(String search, List roles, Optional enabledFlag, - Set userUUIDs, Map.Entry entry, Language language) { + Set userUuids, Map.Entry entry, Language language) { boolean filterByNameEnabled = StringUtils.isNotEmpty(search); boolean filterByRoleEnabled = CollectionUtils.isNotEmpty(roles); Set translations = getTranslated(EntityGroup.ROLE_NAME, language); - List rolesTranslated = search !=null ? - translations.stream().filter(t->t.getValue().toUpperCase().contains(search.toUpperCase())) - .map(Translation::getProperty).toList(): new ArrayList<>(); + List rolesTranslated = + search != null + ? translations.stream().filter(t -> t.getValue().toUpperCase().contains(search.toUpperCase())).map(Translation::getProperty).toList() + : new ArrayList<>(); if (entry.getValue() instanceof User user) { boolean enabledFilter = enabledFlag.isEmpty() || enabledFlag.get().equals(user.getEnabled()); if (filterByNameEnabled && filterByRoleEnabled) { if ((StringUtils.containsIgnoreCase(user.getFullName(), search) || StringUtils.containsIgnoreCase(user.getEmail(), search) - || CollectionUtils.containsAny(user.getRoles(), rolesTranslated)) && - nonNull(user.getRoles()) && CollectionUtils.containsAny(user.getRoles(), roles) && enabledFilter) - userUUIDs.add((String) entry.getKey()); - } else if (filterByNameEnabled && (StringUtils.containsIgnoreCase(user.getFullName(), search) || - StringUtils.containsIgnoreCase(user.getEmail(), search) || nonNull(user.getRoles()) && CollectionUtils.containsAny(user.getRoles(), rolesTranslated)) - && enabledFilter) { - userUUIDs.add((String) entry.getKey()); + || CollectionUtils.containsAny(user.getRoles(), rolesTranslated)) + && nonNull(user.getRoles()) && CollectionUtils.containsAny(user.getRoles(), roles) && enabledFilter) { + userUuids.add((String) entry.getKey()); + } + } else if (filterByNameEnabled && (StringUtils.containsIgnoreCase(user.getFullName(), search) + || StringUtils.containsIgnoreCase(user.getEmail(), search) || nonNull(user.getRoles()) && CollectionUtils.containsAny( + user.getRoles(), rolesTranslated)) + && enabledFilter) { + userUuids.add((String) entry.getKey()); } else if (filterByRoleEnabled && CollectionUtils.containsAny(user.getRoles(), roles) && enabledFilter) { - userUUIDs.add((String) entry.getKey()); + userUuids.add((String) entry.getKey()); } else if (!filterByNameEnabled && !filterByRoleEnabled && enabledFlag.isPresent() && enabledFilter) { - userUUIDs.add((String) entry.getKey()); + userUuids.add((String) entry.getKey()); } } } private Set getTranslated(EntityGroup entityGroup, Language language) { - ConcurrentMap cm = cacheManager.getCache(TRANSLATION_CACHE) != null ? - (ConcurrentMap) Objects.requireNonNull(cacheManager.getCache(TRANSLATION_CACHE)).getNativeCache() : null; - if (isNull(cm)){ + ConcurrentMap cm = cacheManager.getCache(TRANSLATION_CACHE) != null + ? (ConcurrentMap) Objects.requireNonNull(cacheManager.getCache(TRANSLATION_CACHE)).getNativeCache() + : null; + if (isNull(cm)) { throw new ResourceNotFound(UserService.class, CACHE_IS_NOT_REACHABLE); } Set translationList = new HashSet<>(); cm.forEach((aLong, translation) -> { - if(nonNull(language) && translation.getLanguage().compareTo(language) == 0 && translation.getEntityGroup() == entityGroup){ + if (nonNull(language) && translation.getLanguage().compareTo(language) == 0 && translation.getEntityGroup() == entityGroup) { translationList.add(translation); } }); @@ -738,7 +754,7 @@ private void updateName(String userId, UserNameDto userNameDto) { } catch (FeignException fe) { log.error(LOG_KEYCLOAK_ERROR, fe.getMessage(), fe.status()); throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, fe.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_PLEASE_TRY_AGAIN_LATER, fe.getMessage())); } } @@ -775,17 +791,30 @@ private User getUser(String uuid, Boolean withRole) { return user; } } - return getUserById(uuid, withRole); + return getUserById(uuid, withRole); + } + + private User getUser(String userId) { + try { + return keycloakFeign.getUser(userId); + } catch (FeignException.BadRequest | FeignException.InternalServerError e) { + log.error(LOG_KEYCLOAK_ERROR, e.getMessage(), e.status()); + throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, e.getMessage())); + } catch (FeignException.NotFound e) { + log.error(LOG_KEYCLOAK_ERROR_GET_USER, userId, e.getMessage(), e.status()); + throw new ResourceNotFound(UserService.class, USER_NOT_FOUND, String.format(USER_NOT_FOUND, userId)); + } } private List getRequestedRoles(String roles) { return Arrays.stream(roles.split(",")) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } private Map getUserRaw(@NotNull String userId) { try { - Map userRaw = keycloakFeign.getUserRaw(userId); + Map userRaw = keycloakFeign.getUserRaw(userId); if (Objects.isNull(userRaw)) { throw new SystemException(UserService.class, FETCHING_USER_FROM_KEYCLOAK_FAILED); } @@ -795,17 +824,4 @@ private Map getUserRaw(@NotNull String userId) { throw new SystemException(UserService.class, FETCHING_USER_FROM_KEYCLOAK_FAILED); } } - - private User getUser(String userId) { - try { - return keycloakFeign.getUser(userId); - } catch (FeignException.BadRequest | FeignException.InternalServerError e) { - log.error(LOG_KEYCLOAK_ERROR, e.getMessage(), e.status()); - throw new SystemException(UserService.class, AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_RETRIEVE_USERS_PLEASE_TRY_AGAIN_LATER, e.getMessage())); - } catch (FeignException.NotFound e) { - log.error(LOG_KEYCLOAK_ERROR_GET_USER, userId, e.getMessage(), e.status()); - throw new ResourceNotFound(UserService.class, USER_NOT_FOUND, String.format(USER_NOT_FOUND, userId)); - } - } } diff --git a/src/main/java/org/highmed/numportal/service/atna/AtnaService.java b/src/main/java/org/highmed/numportal/service/atna/AtnaService.java index 0d30a744..31060510 100644 --- a/src/main/java/org/highmed/numportal/service/atna/AtnaService.java +++ b/src/main/java/org/highmed/numportal/service/atna/AtnaService.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.atna; +import org.highmed.numportal.domain.model.Project; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.highmed.numportal.domain.model.Project; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.openehealth.ipf.commons.audit.AuditException; @@ -16,9 +17,9 @@ import org.openehealth.ipf.commons.audit.types.EventType; import org.springframework.stereotype.Service; +import java.util.List; import javax.annotation.Nullable; import javax.annotation.PostConstruct; -import java.util.List; @Service @Slf4j @@ -26,9 +27,9 @@ public class AtnaService { private static final String EVENT_CODE_DATA_EXPORT = "110106"; private static final String SYSTEM_NAME = "Num portal"; - private DefaultAuditContext auditContext; private final AtnaProperties properties; private final ObjectMapper mapper; + private DefaultAuditContext auditContext; public AtnaService(AtnaProperties properties, ObjectMapper mapper) { this.properties = properties; @@ -47,9 +48,9 @@ public void logDataExport( String userId, Long projectId, @Nullable Project project, boolean successful) { AuditMessage auditMessage = new DataExportBuilder( - successful ? EventOutcomeIndicator.Success : EventOutcomeIndicator.MajorFailure, - EventType.of(EVENT_CODE_DATA_EXPORT, SYSTEM_NAME, "Export"), - XspaPoUCode.Research) + successful ? EventOutcomeIndicator.Success : EventOutcomeIndicator.MajorFailure, + EventType.of(EVENT_CODE_DATA_EXPORT, SYSTEM_NAME, "Export"), + XspaPoUCode.Research) .addActiveParticipant(new ActiveParticipantType(userId, true)) .addStudyParticipantObject(String.valueOf(projectId), getProjectDetails(project)) .setAuditSource(auditContext) diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/CompositionFlattener.java b/src/main/java/org/highmed/numportal/service/ehrbase/CompositionFlattener.java index f4158b10..42019fa0 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/CompositionFlattener.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/CompositionFlattener.java @@ -1,5 +1,7 @@ package org.highmed.numportal.service.ehrbase; +import org.highmed.numportal.service.exception.SystemException; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nedap.archie.rm.composition.Composition; @@ -11,10 +13,11 @@ import org.ehrbase.openehr.sdk.util.exception.SdkException; import org.ehrbase.openehr.sdk.webtemplate.model.WebTemplate; import org.ehrbase.openehr.sdk.webtemplate.templateprovider.CachedTemplateProvider; -import org.highmed.numportal.service.exception.SystemException; import org.openehr.schemas.v1.OPERATIONALTEMPLATE; import org.springframework.stereotype.Component; +import java.util.Map; +import java.util.Optional; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.cache.Cache; @@ -24,8 +27,6 @@ import javax.cache.expiry.CreatedExpiryPolicy; import javax.cache.expiry.Duration; import javax.cache.spi.CachingProvider; -import java.util.Map; -import java.util.Optional; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_PARSE_RESULTS; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_PARSE_RESULTS_COMPOSITION_MISSING_TEMPLATE_ID; @@ -34,16 +35,13 @@ @RequiredArgsConstructor public class CompositionFlattener { - private final ObjectMapper objectMapper = new ObjectMapper(); - - private CachedTemplateProvider cachedTemplateProvider; - private final ClientTemplateProvider clientTemplateProvider; - - private Cache flatJsonCache; - private static final String FLAT_JSON_CACHE = "flatJsonCache"; private static final String OPERATIONAL_TEMPLATE_CACHE = "operationalTemplateCache"; private static final String WEB_TEMPLATE_CACHE = "webTemplateCache"; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final ClientTemplateProvider clientTemplateProvider; + private CachedTemplateProvider cachedTemplateProvider; + private Cache flatJsonCache; public Map flatten(Composition composition) { @@ -54,7 +52,7 @@ public Map flatten(Composition composition) { return objectMapper.readValue(getFlatJson(templateId).marshal(composition), Map.class); } catch (JsonProcessingException e) { throw new SystemException(CompositionFlattener.class, CANNOT_PARSE_RESULTS, - String.format(CANNOT_PARSE_RESULTS, e.getMessage())); + String.format(CANNOT_PARSE_RESULTS, e.getMessage())); } catch (SdkException e) { throw new SystemException(SdkException.class, e.getMessage()); } @@ -65,8 +63,8 @@ private FlatJson getFlatJson(String templateId) { if (cachedFlatJson.isEmpty()) { FlatJson flatJson = - (FlatJson) new FlatJasonProvider(cachedTemplateProvider) - .buildFlatJson(FlatFormat.SIM_SDT, templateId); + (FlatJson) new FlatJasonProvider(cachedTemplateProvider) + .buildFlatJson(FlatFormat.SIM_SDT, templateId); flatJsonCache.put(templateId, flatJson); return flatJson; diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/CompositionResponseDataBuilder.java b/src/main/java/org/highmed/numportal/service/ehrbase/CompositionResponseDataBuilder.java index ecd87aa4..99712a1d 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/CompositionResponseDataBuilder.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/CompositionResponseDataBuilder.java @@ -9,17 +9,22 @@ import org.ehrbase.openehr.sdk.serialisation.jsonencoding.CanonicalJson; import org.springframework.stereotype.Component; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; @Component @AllArgsConstructor @Slf4j public class CompositionResponseDataBuilder { - private final ObjectMapper mapper; - private final CompositionFlattener compositionFlattener; private static final String COLUMN_NAME = "name"; private static final String COLUMN_PATH = "path"; + private final ObjectMapper mapper; + private final CompositionFlattener compositionFlattener; public QueryResponseData build(List> compositions) { List> flatCompositionsList = createCompositionsMap(compositions); diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java b/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java index e68a3622..8e8f79c2 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java @@ -1,5 +1,11 @@ package org.highmed.numportal.service.ehrbase; +import org.highmed.numportal.domain.model.Aql; +import org.highmed.numportal.properties.EhrBaseProperties; +import org.highmed.numportal.service.exception.BadRequestException; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.util.AqlQueryConstants; + import com.nedap.archie.rm.support.identification.UUID; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -20,19 +26,24 @@ import org.ehrbase.openehr.sdk.response.dto.ehrscape.TemplateMetaDataDto; import org.ehrbase.openehr.sdk.util.exception.ClientException; import org.ehrbase.openehr.sdk.util.exception.WrongStatusCodeException; -import org.highmed.numportal.domain.model.Aql; -import org.highmed.numportal.properties.EhrBaseProperties; -import org.highmed.numportal.service.exception.BadRequestException; -import org.highmed.numportal.service.exception.SystemException; -import org.highmed.numportal.service.util.AqlQueryConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_MESSAGE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_AQL_QUERY; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NO_DATA_COLUMNS_IN_THE_QUERY_RESULT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.QUERY_RESULT_DOESN_T_CONTAIN_EHR_STATUS_COLUMN; /** * Service using the EhrBaseSDK to talk to the EhrBaseAPI @@ -42,7 +53,7 @@ public class EhrBaseService { private static final Aql ALL_PATIENTS_IDS = - Aql.builder().query("SELECT e/ehr_id/value FROM EHR e").build(); + Aql.builder().query("SELECT e/ehr_id/value FROM EHR e").build(); private static final String COMPOSITION_KEY = "_type"; private static final String NAME = "name"; @@ -56,10 +67,10 @@ public class EhrBaseService { @Autowired public EhrBaseService( - DefaultRestClient restClient, - CompositionResponseDataBuilder compositionResponseDataBuilder, - @Lazy Pseudonymity pseudonymity, - EhrBaseProperties ehrBaseProperties) { + DefaultRestClient restClient, + CompositionResponseDataBuilder compositionResponseDataBuilder, + @Lazy Pseudonymity pseudonymity, + EhrBaseProperties ehrBaseProperties) { this.restClient = restClient; this.compositionResponseDataBuilder = compositionResponseDataBuilder; this.pseudonymity = pseudonymity; @@ -103,12 +114,13 @@ public Set retrieveEligiblePatientIds(String query) { } catch (ClientException e) { log.error(ERROR_MESSAGE, e.getMessage(), e); throw new SystemException(EhrBaseService.class, AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); } } /** * Executes a raw aql query + * * @param aqlDto The aql query * @return QueryResponseData */ @@ -120,8 +132,8 @@ public List executeRawQuery(AqlQuery aqlDto, Long projectId) try { try { log.info( - String.format( - "[AQL QUERY] EHR request query: %s ", query)); + String.format( + "[AQL QUERY] EHR request query: %s ", query)); } catch (Exception e) { log.error("Error parsing query while logging", e); } @@ -136,7 +148,7 @@ public List executeRawQuery(AqlQuery aqlDto, Long projectId) } catch (ClientException e) { log.error(ERROR_MESSAGE, e.getMessage(), e); throw new SystemException(EhrBaseService.class, AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); } } @@ -153,7 +165,7 @@ public QueryResponseData executePlainQuery(String queryString) { } catch (ClientException e) { log.error(ERROR_MESSAGE, e.getMessage(), e); throw new SystemException(EhrBaseService.class, AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); } } @@ -186,7 +198,7 @@ public List getAllTemplatesMetadata() { } catch (ClientException e) { log.error(ERROR_MESSAGE, e.getMessage(), e); throw new SystemException(EhrBaseService.class, AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES, - String.format(AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES, e.getMessage())); + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_GET_TEMPLATES, e.getMessage())); } } @@ -267,14 +279,14 @@ private List getAndRemoveEhrStatusColumn(QueryResponseData compositions) } columns.remove(0); return compositions.getRows().stream() - .map(row -> row.remove(0)) - .map(String.class::cast) - .collect(Collectors.toList()); + .map(row -> row.remove(0)) + .map(String.class::cast) + .collect(Collectors.toList()); } private boolean isComposition(Object object) { return object instanceof Map - && ((Map) object).containsKey(COMPOSITION_KEY) - && ((Map) object).get(COMPOSITION_KEY).equals(AqlQueryConstants.COMPOSITION_TYPE); + && ((Map) object).containsKey(COMPOSITION_KEY) + && ((Map) object).get(COMPOSITION_KEY).equals(AqlQueryConstants.COMPOSITION_TYPE); } } diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java b/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java index f67f9cea..939a473e 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java @@ -1,5 +1,9 @@ package org.highmed.numportal.service.ehrbase; +import org.highmed.numportal.domain.dto.ParameterOptionsDto; +import org.highmed.numportal.service.UserDetailsService; +import org.highmed.numportal.service.util.AqlQueryConstants; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.ObjectMapper; @@ -30,16 +34,17 @@ import org.ehrbase.openehr.sdk.client.openehrclient.defaultrestclient.TemporalAccessorDeSerializer; import org.ehrbase.openehr.sdk.client.openehrclient.defaultrestclient.VersionUidDeSerializer; import org.ehrbase.openehr.sdk.serialisation.jsonencoding.ArchieObjectMapperProvider; -import org.highmed.numportal.domain.dto.ParameterOptionsDto; -import org.highmed.numportal.service.UserDetailsService; -import org.highmed.numportal.service.util.AqlQueryConstants; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachePut; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.temporal.TemporalAccessor; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; @Slf4j @Service @@ -106,12 +111,12 @@ public void evictParametersCache() { private ParameterOptionsDto getParameters(String aqlPath, String archetypeId, String postfix) { String query; - if(aqlPath.startsWith("/")) { + if (aqlPath.startsWith("/")) { query = - createQueryString(aqlPath.substring(1, aqlPath.length() - postfix.length()), archetypeId); + createQueryString(aqlPath.substring(1, aqlPath.length() - postfix.length()), archetypeId); } else { query = - createQueryString(aqlPath.substring(0, aqlPath.length() - postfix.length()), archetypeId); + createQueryString(aqlPath.substring(0, aqlPath.length() - postfix.length()), archetypeId); } try { log.info( @@ -133,8 +138,8 @@ private ParameterOptionsDto getParameters(String aqlPath, String archetypeId, St var rowString = buildAqlObjectMapper().writeValueAsString(row.get(0)); log.debug("[AQL parameter] query response data row {} ", rowString); var element = - (SingleValuedDataValue) - buildAqlObjectMapper().readValue(rowString, RMObject.class); + (SingleValuedDataValue) + buildAqlObjectMapper().readValue(rowString, RMObject.class); if (Objects.nonNull(element.getValue())) { if (element.getValue().getClass().isAssignableFrom(DvCodedText.class)) { convertDvCodedText((DvCodedText) element.getValue(), parameterOptions, postfix); @@ -172,13 +177,15 @@ private ParameterOptionsDto getParameters(String aqlPath, String archetypeId, St Map options = parameterOptions.getOptions(); parameterOptions.setOptions(new LinkedHashMap<>()); options.keySet().stream() - .sorted(String.CASE_INSENSITIVE_ORDER) - .forEach(e -> parameterOptions.getOptions().put(e, options.get(e))); + .sorted(String.CASE_INSENSITIVE_ORDER) + .forEach(e -> parameterOptions.getOptions().put(e, options.get(e))); return parameterOptions; } - /** Create the aql query for retrieving all distinct existing values of a certain aql path */ + /** + * Create the aql query for retrieving all distinct existing values of a certain aql path + */ private String createQueryString(String aqlPath, String archetypeId) { var aql = new AqlQuery(); @@ -204,7 +211,7 @@ private String createQueryString(String aqlPath, String archetypeId) { // generate contains expression ContainmentClassExpression contains = new ContainmentClassExpression(); contains.setType(StringUtils.substringBetween(archetypeId, "openEHR-EHR-", ".")); - contains.setIdentifier("c0"+"[" + archetypeId + "]"); + contains.setIdentifier("c0" + "[" + archetypeId + "]"); from.setContains(contains); diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/Pseudonymity.java b/src/main/java/org/highmed/numportal/service/ehrbase/Pseudonymity.java index 3d6f355a..074a6c7b 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/Pseudonymity.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/Pseudonymity.java @@ -1,9 +1,11 @@ package org.highmed.numportal.service.ehrbase; -import ca.uhn.fhir.context.FhirContext; import org.highmed.numportal.properties.FttpProperties; import org.highmed.numportal.properties.PrivacyProperties; import org.highmed.numportal.properties.PseudonymsPsnWorkflowProperties; +import org.highmed.numportal.service.exception.ResourceNotFound; + +import ca.uhn.fhir.context.FhirContext; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; @@ -17,14 +19,17 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -import org.highmed.numportal.service.exception.ResourceNotFound; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Parameters; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -39,7 +44,7 @@ public class Pseudonymity { private static final String PSEUDONYMS_COULD_NOT_BE_RETRIEVED_MESSAGE = "Pseudonyms could not be retrieved"; private static final String REQUEST_FAILED_RETRIEVED_MESSAGE = - "Request to retrieve pseudonyms failed"; + "Request to retrieve pseudonyms failed"; // is just a guessing...because there are also codes that start with codex_ but we do not receive the pseudonym back (example codex_A12CB2) private static final String EXTERNAL_REF_ID_REGEX_GREIFSWALD_COMPLIANT = "codex_[A-Z0-9-]{6}"; @@ -89,8 +94,8 @@ private List getPseudonymsData(List secondLevelPseudonyms, Long } Map paramsData = groupPseudonyms(params); return secondLevelPseudonyms.stream() - .map(original -> findPseudonymForOriginal(paramsData, original, projectId).get()) - .collect(Collectors.toList()); + .map(original -> findPseudonymForOriginal(paramsData, original, projectId).get()) + .collect(Collectors.toList()); } else { if (Boolean.TRUE.equals(fake3rdPartyPseudonymEnabled)) { // something did not work on Greisfwald side, so generate fake 3rd party pseudonyms @@ -118,7 +123,7 @@ private Optional retrievePseudonyms(Parameters parameters) { return Optional.of(fhirContext.newXmlParser().parseResource(Parameters.class, resp)); } else { log.error("Could not retrieve pseudonyms. Expected status code 200, received {} with response body: {} ", - response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity())); + response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity())); throw new ResourceNotFound(Pseudonymity.class, REQUEST_FAILED_RETRIEVED_MESSAGE); } } catch (Exception e) { @@ -139,6 +144,7 @@ private Optional retrievePseudonyms(Parameters parameters) { /** * https://simplifier.net/guide/ttp-fhir-gateway-ig/markdown-WorkflowBasierteVerwaltung-Operations-requestPsnWorkflow?version=current + * * @param projectId * @return */ @@ -148,11 +154,12 @@ private Parameters initParameters(Long projectId) { parameters.addParameter("source", pseudonymsPsnWorkflowProperties.getSource()); parameters.addParameter("target", pseudonymsPsnWorkflowProperties.getTarget() + projectId); parameters.addParameter("apikey", pseudonymsPsnWorkflowProperties.getApiKey()); - parameters.addParameter("event", pseudonymsPsnWorkflowProperties.getEvent()); + parameters.addParameter("event", pseudonymsPsnWorkflowProperties.getEvent()); return parameters; } - private Optional findPseudonymForOriginal(Map parameters, String original, Long projectId) { + private Optional findPseudonymForOriginal(Map parameters, String original, + Long projectId) { if (Pattern.matches(EXTERNAL_REF_ID_REGEX_GREIFSWALD_COMPLIANT, original) && parameters.containsKey(original)) { var param = parameters.get(original); String pseudonym = getPartValue(PSEUDONYM, param); @@ -176,14 +183,14 @@ private Optional findPseudonymForOriginal(Map generateNumThirdLevelPseudonym(List secondLevelPseudonyms, Long projectId) { return secondLevelPseudonyms.stream() - .map(original -> generateNumThirdLevelPseudonym(original, projectId)) - .collect(Collectors.toList()); + .map(original -> generateNumThirdLevelPseudonym(original, projectId)) + .collect(Collectors.toList()); } private String generateNumThirdLevelPseudonym(String original, Long projectId) { log.debug("For id {} was generated fake 3rd level pseudonym", original); return new DigestUtils(DIGEST_ALGORITHM) - .digestAsHex(original + projectId + privacyProperties.getPseudonymitySecret()); + .digestAsHex(original + projectId + privacyProperties.getPseudonymitySecret()); } private String getPartValue(String value, Parameters.ParametersParameterComponent param) { diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/ResponseFilter.java b/src/main/java/org/highmed/numportal/service/ehrbase/ResponseFilter.java index 4d616aa6..300c8c3c 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/ResponseFilter.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/ResponseFilter.java @@ -6,7 +6,6 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -17,11 +16,13 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; @Component @NoArgsConstructor @Slf4j public class ResponseFilter { + private HashSet pathFilters; private List regexpFilters; @@ -29,19 +30,19 @@ public class ResponseFilter { public void initialize() { try { BufferedReader pathResource = new BufferedReader(new InputStreamReader( - new ClassPathResource("resultfilters/pathfilters.txt").getInputStream(), StandardCharsets.UTF_8)); + new ClassPathResource("resultfilters/pathfilters.txt").getInputStream(), StandardCharsets.UTF_8)); pathFilters = new HashSet<>(pathResource.lines().toList()); BufferedReader regexpResource = new BufferedReader(new InputStreamReader( - new ClassPathResource("resultfilters/regexpfilters.txt").getInputStream(), StandardCharsets.UTF_8)); + new ClassPathResource("resultfilters/regexpfilters.txt").getInputStream(), StandardCharsets.UTF_8)); regexpFilters = regexpResource.lines().map(Pattern::compile).collect( - Collectors.toList()); + Collectors.toList()); } catch (IOException e) { log.error("Failed to read project data filters, can't filter results."); } } public List filterResponse(List queryResponseDataList) { - if(pathFilters == null){ + if (pathFilters == null) { return queryResponseDataList; } List resultList = new ArrayList<>(); @@ -49,7 +50,7 @@ public List filterResponse(List queryRespo List> filteredColumns = new ArrayList<>(); List> filteredRows = new ArrayList<>(); QueryResponseData filteredResponse = new QueryResponseData(); - if(isValidQueryResponseData(queryResponseData)) { + if (isValidQueryResponseData(queryResponseData)) { for (int i = 0; i < queryResponseData.getRows().size(); i++) { filteredRows.add(new ArrayList<>()); } @@ -77,7 +78,7 @@ private boolean isValidQueryResponseData(QueryResponseData queryResponseData) { private boolean keepColumn(Map column) { String path = column.get("path"); - if(pathFilters.contains(path)){ + if (pathFilters.contains(path)) { return false; } return regexpFilters.stream().filter(regexp -> regexp.matcher(path).matches()).findFirst().isEmpty(); diff --git a/src/main/java/org/highmed/numportal/service/email/ZarsService.java b/src/main/java/org/highmed/numportal/service/email/ZarsService.java index b21fdb2d..5c18a783 100644 --- a/src/main/java/org/highmed/numportal/service/email/ZarsService.java +++ b/src/main/java/org/highmed/numportal/service/email/ZarsService.java @@ -1,5 +1,7 @@ package org.highmed.numportal.service.email; +import org.highmed.numportal.domain.dto.ZarsInfoDto; + import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -7,20 +9,19 @@ import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.apache.commons.lang3.StringUtils; -import org.highmed.numportal.domain.dto.ZarsInfoDto; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.MessageSource; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; @Service @Slf4j @@ -77,9 +78,9 @@ private String generateCSV(@NotNull ZarsInfoDto zarsInfoDto) { } var writer = new StringWriter(); try (CSVPrinter printer = CSVFormat.EXCEL.builder() - .setHeader(zarsHeaders) - .build() - .print(writer)) { + .setHeader(zarsHeaders) + .build() + .print(writer)) { printer.printRecord(generateProjectRow(zarsInfoDto)); printer.flush(); @@ -104,8 +105,8 @@ private List generateProjectRow(@NotNull ZarsInfoDto zarsInfoDto) { values.add(String.join(", ", zarsInfoDto.getKeywords())); // Keywords values.add( zarsInfoDto.getCategories().stream() - .map(category -> translate("category", category.toString())) - .collect(Collectors.joining(", "))); // Categories + .map(category -> translate("category", category.toString())) + .collect(Collectors.joining(", "))); // Categories values.add(zarsInfoDto.getQueries()); // One or more queries values.add(zarsInfoDto.getApprovalDate()); // Approval date values.add("NA"); // Contract end date diff --git a/src/main/java/org/highmed/numportal/service/exception/BadCredentialsException.java b/src/main/java/org/highmed/numportal/service/exception/BadCredentialsException.java index b882829e..63733f2c 100644 --- a/src/main/java/org/highmed/numportal/service/exception/BadCredentialsException.java +++ b/src/main/java/org/highmed/numportal/service/exception/BadCredentialsException.java @@ -5,9 +5,9 @@ @AllArgsConstructor @Getter -public class BadCredentialsException extends RuntimeException { +public class BadCredentialsException extends RuntimeException { - private final Class entity; + private final Class entity; - private final String parameter; + private final String parameter; } diff --git a/src/main/java/org/highmed/numportal/service/exception/BadRequestException.java b/src/main/java/org/highmed/numportal/service/exception/BadRequestException.java index 32fe15da..edbb142f 100644 --- a/src/main/java/org/highmed/numportal/service/exception/BadRequestException.java +++ b/src/main/java/org/highmed/numportal/service/exception/BadRequestException.java @@ -5,24 +5,24 @@ @Getter public class BadRequestException extends RuntimeException { - private final Class entity; + private final Class entity; - private final String paramValue; + private final String paramValue; - private final String message; + private final String message; - public BadRequestException(Class entity, String paramValue, String message) { - super(message); - this.entity = entity; - this.paramValue = paramValue; - this.message = message; - } + public BadRequestException(Class entity, String paramValue, String message) { + super(message); + this.entity = entity; + this.paramValue = paramValue; + this.message = message; + } - public BadRequestException(Class entity, String message) { - super(message); - this.entity = entity; - this.paramValue = message; - this.message = message; - } + public BadRequestException(Class entity, String message) { + super(message); + this.entity = entity; + this.paramValue = message; + this.message = message; + } } diff --git a/src/main/java/org/highmed/numportal/service/exception/CustomException.java b/src/main/java/org/highmed/numportal/service/exception/CustomException.java index 9825ea37..b9d05d17 100644 --- a/src/main/java/org/highmed/numportal/service/exception/CustomException.java +++ b/src/main/java/org/highmed/numportal/service/exception/CustomException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class CustomException extends RuntimeException { +public class CustomException extends RuntimeException { private final String message; } diff --git a/src/main/java/org/highmed/numportal/service/exception/CustomizedExceptionHandler.java b/src/main/java/org/highmed/numportal/service/exception/CustomizedExceptionHandler.java index 107d0c4b..0f141128 100644 --- a/src/main/java/org/highmed/numportal/service/exception/CustomizedExceptionHandler.java +++ b/src/main/java/org/highmed/numportal/service/exception/CustomizedExceptionHandler.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.exception; +import org.highmed.numportal.service.exception.dto.ErrorDetails; + import lombok.extern.slf4j.Slf4j; import org.ehrbase.openehr.sdk.util.exception.WrongStatusCodeException; -import org.highmed.numportal.service.exception.dto.ErrorDetails; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -15,327 +16,353 @@ import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static java.util.Objects.nonNull; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.*; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PASS_NOT_MATCHING; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RECORD_ALREADY_EXISTS; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RECORD_NOT_FOUND_MSG; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.TOKEN_IS_NOT_VALID_MSG; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.USER_UNAUTHORISED_EXCEPTION; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.errorMap; @Slf4j public class CustomizedExceptionHandler extends ResponseEntityExceptionHandler { - @ExceptionHandler(TokenIsNotValidException.class) - public ResponseEntity handleTokenErrors( - TokenIsNotValidException exception) { - - var errors = Map.of( exception.getEntity().getSimpleName(), - exception.getEntityId() ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( errorMap.get( TOKEN_IS_NOT_VALID_MSG ).getId() ) - .argumentsList( errorMap.get( TOKEN_IS_NOT_VALID_MSG ).getArgumentsList() ) - .message( TOKEN_IS_NOT_VALID_MSG ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.badRequest().body( errorDetails ); - } - - @ExceptionHandler(UserUnauthorizedException.class) - public ResponseEntity handleUserErrors( - UserUnauthorizedException exception) { - - var errors = Map.of( exception.getClass().getSimpleName(), - exception.getMessage() ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( errorMap.get( USER_UNAUTHORISED_EXCEPTION ).getId() ) - .argumentsList( List.of( exception.getUserId() ) ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.NOT_ACCEPTABLE ).body( errorDetails ); - } - - @ExceptionHandler(EntityNotFoundException.class) - public ResponseEntity handleEntityErrors( - EntityNotFoundException exception) { - - var errors = Map.of( exception.getEntity().getSimpleName(), - exception.getEntityId() ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( errorMap.get(RECORD_NOT_FOUND_MSG ).getId() ) - .argumentsList( errorMap.get(RECORD_NOT_FOUND_MSG ).getArgumentsList() ) - .message(RECORD_NOT_FOUND_MSG ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.badRequest().body( errorDetails ); - } - - @ExceptionHandler(SameEntityExistsException.class) - public ResponseEntity handleSameEntityExistsErrors( - SameEntityExistsException exception) { - - var errors = Map.of( exception.getEntity().getSimpleName(), - exception.getParameter() ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( errorMap.get( RECORD_ALREADY_EXISTS ).getId() ) - .argumentsList( errorMap.get( RECORD_ALREADY_EXISTS ).getArgumentsList() ) - .message( RECORD_ALREADY_EXISTS ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.CONFLICT ).body( errorDetails ); - } - - @ExceptionHandler(BadCredentialsException.class) - public ResponseEntity handlePasswordMatchingErrors( - BadCredentialsException exception) { - - var errors = Map.of( exception.getEntity().getSimpleName(), - exception.getParameter() ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( errorMap.get( PASS_NOT_MATCHING ).getId() ) - .argumentsList( errorMap.get( PASS_NOT_MATCHING ).getArgumentsList() ) - .message( PASS_NOT_MATCHING ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.badRequest().body( errorDetails ); - } - - @ExceptionHandler(UsernameNotFoundOrNoLongerActiveException.class) - public ResponseEntity handleUsernameNotFoundOrNoLongerActiveErrors( - UsernameNotFoundOrNoLongerActiveException exception) { - - var errors = Map.of( exception.getEntity().getSimpleName(), - exception.getParameter() ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( errorMap.get( USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE ).getId() ) - .argumentsList( errorMap.get( USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE ).getArgumentsList() ) - .message( USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.NOT_ACCEPTABLE ).body( errorDetails ); - } - - @ExceptionHandler(CustomException.class) - public ResponseEntity handleCustomErrors( - CustomException exception) { - - var errors = Map.of( "Error Message", exception.getMessage() ); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getMessage())) ? errorMap.get( exception.getMessage() ).getId() : -1) - .argumentsList( new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.BAD_REQUEST ).body( errorDetails ); - } - - @ExceptionHandler(BadRequestException.class) - public ResponseEntity handleBadRequestErrors( - BadRequestException exception) { - - var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; - var description = exception.getMessage(); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getParamValue())) ? errorMap.get( exception.getParamValue() ).getId() : -1) - .argumentsList( nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.BAD_REQUEST ).body( errorDetails ); - } - - @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException(AccessDeniedException exception) { - - var description = exception.getMessage(); - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId(-1) - .argumentsList(new ArrayList<>()) - .message(exception.getMessage()) - .details(errors) - .build(); - log.debug(exception.getMessage(), exception); - return new ResponseEntity<>(errorDetails, new HttpHeaders(), HttpStatus.FORBIDDEN); - } - - @ExceptionHandler(ForbiddenException.class) - public ResponseEntity handleForbiddenErrors(ForbiddenException exception) { - - var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; - var description = exception.getMessage(); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getParamValue())) ? errorMap.get( exception.getParamValue() ).getId() : -1) - .argumentsList( nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return new ResponseEntity<>(errorDetails, new HttpHeaders(), HttpStatus.FORBIDDEN); - } - - @ExceptionHandler(PrivacyException.class) - public ResponseEntity handlePrivacyErrors( - PrivacyException exception) { - - var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; - var description = exception.getMessage(); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getParamValue())) ? errorMap.get( exception.getParamValue() ).getId() : -1) - .argumentsList( nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS ).body( errorDetails ); - } - - @ExceptionHandler(ResourceNotFound.class) - public ResponseEntity handleResourceNotFoundErrors( - ResourceNotFound exception) { - - var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; - var description = exception.getMessage(); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getParamValue())) ? errorMap.get( exception.getParamValue() ).getId() : -1) - .argumentsList( nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.NOT_FOUND ).body( errorDetails ); - } - - @ExceptionHandler(SystemException.class) - public ResponseEntity handleSystemException( - SystemException exception) { - - var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; - var description = exception.getMessage(); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getParamValue())) ? errorMap.get( exception.getParamValue() ).getId() : -1) - .argumentsList( nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.error(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.BAD_REQUEST ).body( errorDetails ); - } - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgumentException( - IllegalArgumentException exception) { - - var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; - var description = exception.getMessage(); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : description); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getParamValue())) ? errorMap.get( exception.getParamValue() ).getId() : -1) - .argumentsList( nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>() ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - log.debug(exception.getMessage(), exception); - return ResponseEntity.status( HttpStatus.BAD_REQUEST ).body( errorDetails ); - } - - @ExceptionHandler({WrongStatusCodeException.class}) - public ResponseEntity handleWrongStatus(WrongStatusCodeException exception) { - String actualStatusCode = String.valueOf(exception.getActualStatusCode()); - String expectedStatusCode = String.valueOf(exception.getExpectedStatusCode()); - - var errors = Map.of( "Error Message", - nonNull(exception.getMessage()) ? exception.getMessage() : "WrongStatusCodeException.class"); - ErrorDetails errorDetails = ErrorDetails - .builder() - .messageId( nonNull(errorMap.get( exception.getMessage())) ? errorMap.get( exception.getMessage() ).getId() : -1) - .argumentsList( Arrays.asList(actualStatusCode, expectedStatusCode) ) - .message( exception.getMessage() ) - .details( errors ) - .build(); - - log.error(exception.getMessage(), exception); - - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); - } - @Override - protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { - Map errors = new HashMap<>(); - ex.getFieldErrors() - .forEach( error -> errors.put( error.getField(), error.getDefaultMessage() ) ); - - ErrorDetails errorDetails = ErrorDetails - .builder() - .message( "Error" ) - .details( errors ) - .build(); - return ResponseEntity.status( HttpStatus.BAD_REQUEST ).body( errorDetails ); - } - - @Override - protected ResponseEntity handleHandlerMethodValidationException(HandlerMethodValidationException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { - Map errors = new HashMap<>(); - for (final var validation : ex.getAllValidationResults()) { - if (validation instanceof ParameterErrors) { - ParameterErrors parameterErrors = (ParameterErrors) validation; - parameterErrors.getFieldErrors().forEach(fieldError -> errors.put(fieldError.getField(), fieldError.getDefaultMessage())); - } else { - final String parameterName = validation.getMethodParameter().getParameterName(); - validation - .getResolvableErrors() - .forEach( - error -> errors.put(parameterName, error.getDefaultMessage())); - } - } - ErrorDetails errorDetails = ErrorDetails - .builder() - .message("Validation failed") - .details(errors) - .build(); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + @ExceptionHandler(TokenIsNotValidException.class) + public ResponseEntity handleTokenErrors( + TokenIsNotValidException exception) { + + var errors = Map.of( + exception.getEntity().getSimpleName(), + exception.getEntityId()); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(errorMap.get(TOKEN_IS_NOT_VALID_MSG).getId()) + .argumentsList(errorMap.get(TOKEN_IS_NOT_VALID_MSG).getArgumentsList()) + .message(TOKEN_IS_NOT_VALID_MSG) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.badRequest().body(errorDetails); + } + + @ExceptionHandler(UserUnauthorizedException.class) + public ResponseEntity handleUserErrors( + UserUnauthorizedException exception) { + + var errors = Map.of( + exception.getClass().getSimpleName(), + exception.getMessage()); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(errorMap.get(USER_UNAUTHORISED_EXCEPTION).getId()) + .argumentsList(List.of(exception.getUserId())) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(errorDetails); + } + + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity handleEntityErrors( + EntityNotFoundException exception) { + + var errors = Map.of( + exception.getEntity().getSimpleName(), + exception.getEntityId()); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(errorMap.get(RECORD_NOT_FOUND_MSG).getId()) + .argumentsList(errorMap.get(RECORD_NOT_FOUND_MSG).getArgumentsList()) + .message(RECORD_NOT_FOUND_MSG) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.badRequest().body(errorDetails); + } + + @ExceptionHandler(SameEntityExistsException.class) + public ResponseEntity handleSameEntityExistsErrors( + SameEntityExistsException exception) { + + var errors = Map.of( + exception.getEntity().getSimpleName(), + exception.getParameter()); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(errorMap.get(RECORD_ALREADY_EXISTS).getId()) + .argumentsList(errorMap.get(RECORD_ALREADY_EXISTS).getArgumentsList()) + .message(RECORD_ALREADY_EXISTS) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorDetails); + } + + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity handlePasswordMatchingErrors( + BadCredentialsException exception) { + + var errors = Map.of( + exception.getEntity().getSimpleName(), + exception.getParameter()); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(errorMap.get(PASS_NOT_MATCHING).getId()) + .argumentsList(errorMap.get(PASS_NOT_MATCHING).getArgumentsList()) + .message(PASS_NOT_MATCHING) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.badRequest().body(errorDetails); + } + + @ExceptionHandler(UsernameNotFoundOrNoLongerActiveException.class) + public ResponseEntity handleUsernameNotFoundOrNoLongerActiveErrors( + UsernameNotFoundOrNoLongerActiveException exception) { + + var errors = Map.of( + exception.getEntity().getSimpleName(), + exception.getParameter()); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(errorMap.get(USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE).getId()) + .argumentsList(errorMap.get(USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE).getArgumentsList()) + .message(USERNAME_NOT_FOUND_OR_NO_LONGER_ACTIVE) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(errorDetails); + } + + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomErrors( + CustomException exception) { + + var errors = Map.of("Error Message", exception.getMessage()); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getMessage())) ? errorMap.get(exception.getMessage()).getId() : -1) + .argumentsList(new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequestErrors( + BadRequestException exception) { + + var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; + var description = exception.getMessage(); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getParamValue())) ? errorMap.get(exception.getParamValue()).getId() : -1) + .argumentsList(nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity handleAccessDeniedException(AccessDeniedException exception) { + + var description = exception.getMessage(); + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(-1) + .argumentsList(new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return new ResponseEntity<>(errorDetails, new HttpHeaders(), HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbiddenErrors(ForbiddenException exception) { + + var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; + var description = exception.getMessage(); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getParamValue())) ? errorMap.get(exception.getParamValue()).getId() : -1) + .argumentsList(nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return new ResponseEntity<>(errorDetails, new HttpHeaders(), HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(PrivacyException.class) + public ResponseEntity handlePrivacyErrors( + PrivacyException exception) { + + var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; + var description = exception.getMessage(); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getParamValue())) ? errorMap.get(exception.getParamValue()).getId() : -1) + .argumentsList(nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS).body(errorDetails); + } + + @ExceptionHandler(ResourceNotFound.class) + public ResponseEntity handleResourceNotFoundErrors( + ResourceNotFound exception) { + + var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; + var description = exception.getMessage(); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getParamValue())) ? errorMap.get(exception.getParamValue()).getId() : -1) + .argumentsList(nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDetails); + } + + @ExceptionHandler(SystemException.class) + public ResponseEntity handleSystemException( + SystemException exception) { + + var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; + var description = exception.getMessage(); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getParamValue())) ? errorMap.get(exception.getParamValue()).getId() : -1) + .argumentsList(nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.error(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException( + IllegalArgumentException exception) { + + var className = nonNull(exception.getEntity()) ? exception.getEntity().getSimpleName() : null; + var description = exception.getMessage(); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : description); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getParamValue())) ? errorMap.get(exception.getParamValue()).getId() : -1) + .argumentsList(nonNull(exception.getEntity()) ? Arrays.asList(className, description) : new ArrayList<>()) + .message(exception.getMessage()) + .details(errors) + .build(); + log.debug(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } + + @ExceptionHandler({WrongStatusCodeException.class}) + public ResponseEntity handleWrongStatus(WrongStatusCodeException exception) { + String actualStatusCode = String.valueOf(exception.getActualStatusCode()); + String expectedStatusCode = String.valueOf(exception.getExpectedStatusCode()); + + var errors = Map.of( + "Error Message", + nonNull(exception.getMessage()) ? exception.getMessage() : "WrongStatusCodeException.class"); + ErrorDetails errorDetails = ErrorDetails + .builder() + .messageId(nonNull(errorMap.get(exception.getMessage())) ? errorMap.get(exception.getMessage()).getId() : -1) + .argumentsList(Arrays.asList(actualStatusCode, expectedStatusCode)) + .message(exception.getMessage()) + .details(errors) + .build(); + + log.error(exception.getMessage(), exception); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } + + @Override + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, + WebRequest request) { + Map errors = new HashMap<>(); + ex.getFieldErrors() + .forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); + + ErrorDetails errorDetails = ErrorDetails + .builder() + .message("Error") + .details(errors) + .build(); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } + + @Override + protected ResponseEntity handleHandlerMethodValidationException(HandlerMethodValidationException ex, HttpHeaders headers, + HttpStatusCode status, WebRequest request) { + Map errors = new HashMap<>(); + for (final var validation : ex.getAllValidationResults()) { + if (validation instanceof ParameterErrors parameterErrors) { + parameterErrors.getFieldErrors().forEach(fieldError -> errors.put(fieldError.getField(), fieldError.getDefaultMessage())); + } else { + final String parameterName = validation.getMethodParameter().getParameterName(); + validation + .getResolvableErrors() + .forEach( + error -> errors.put(parameterName, error.getDefaultMessage())); + } } + ErrorDetails errorDetails = ErrorDetails + .builder() + .message("Validation failed") + .details(errors) + .build(); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDetails); + } } diff --git a/src/main/java/org/highmed/numportal/service/exception/EntityNotFoundException.java b/src/main/java/org/highmed/numportal/service/exception/EntityNotFoundException.java index bf7fb75d..fa88837f 100644 --- a/src/main/java/org/highmed/numportal/service/exception/EntityNotFoundException.java +++ b/src/main/java/org/highmed/numportal/service/exception/EntityNotFoundException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class EntityNotFoundException extends RuntimeException { +public class EntityNotFoundException extends RuntimeException { private final Class entity; private final String entityId; diff --git a/src/main/java/org/highmed/numportal/service/exception/ExceptionDto.java b/src/main/java/org/highmed/numportal/service/exception/ExceptionDto.java index 7ecd7ed4..f7855e36 100644 --- a/src/main/java/org/highmed/numportal/service/exception/ExceptionDto.java +++ b/src/main/java/org/highmed/numportal/service/exception/ExceptionDto.java @@ -8,6 +8,7 @@ @AllArgsConstructor @Getter public class ExceptionDto { - int id; - List argumentsList; + + int id; + List argumentsList; } diff --git a/src/main/java/org/highmed/numportal/service/exception/ForbiddenException.java b/src/main/java/org/highmed/numportal/service/exception/ForbiddenException.java index e2ab583b..d8c44917 100644 --- a/src/main/java/org/highmed/numportal/service/exception/ForbiddenException.java +++ b/src/main/java/org/highmed/numportal/service/exception/ForbiddenException.java @@ -24,10 +24,10 @@ public ForbiddenException() { entity = null; } - public ForbiddenException(Class entity, String message) { - super(message); - this.entity = entity; - this.paramValue = message; - this.message = message; - } + public ForbiddenException(Class entity, String message) { + super(message); + this.entity = entity; + this.paramValue = message; + this.message = message; + } } diff --git a/src/main/java/org/highmed/numportal/service/exception/IllegalArgumentException.java b/src/main/java/org/highmed/numportal/service/exception/IllegalArgumentException.java index baeb43ad..e56b9ab5 100644 --- a/src/main/java/org/highmed/numportal/service/exception/IllegalArgumentException.java +++ b/src/main/java/org/highmed/numportal/service/exception/IllegalArgumentException.java @@ -5,17 +5,17 @@ @Getter public class IllegalArgumentException extends RuntimeException { - private final Class entity; + private final Class entity; - private final String paramValue; + private final String paramValue; - private final String message; + private final String message; - public IllegalArgumentException(Class entity, String message) { - super(message); - this.entity = entity; - this.paramValue = message; - this.message = message; - } + public IllegalArgumentException(Class entity, String message) { + super(message); + this.entity = entity; + this.paramValue = message; + this.message = message; + } } \ No newline at end of file diff --git a/src/main/java/org/highmed/numportal/service/exception/SameEntityExistsException.java b/src/main/java/org/highmed/numportal/service/exception/SameEntityExistsException.java index 04bbbd5e..28e76c31 100644 --- a/src/main/java/org/highmed/numportal/service/exception/SameEntityExistsException.java +++ b/src/main/java/org/highmed/numportal/service/exception/SameEntityExistsException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class SameEntityExistsException extends RuntimeException { +public class SameEntityExistsException extends RuntimeException { private final Class entity; private final String parameter; diff --git a/src/main/java/org/highmed/numportal/service/exception/TokenIsNotValidException.java b/src/main/java/org/highmed/numportal/service/exception/TokenIsNotValidException.java index ee226a3c..bb5d8af1 100644 --- a/src/main/java/org/highmed/numportal/service/exception/TokenIsNotValidException.java +++ b/src/main/java/org/highmed/numportal/service/exception/TokenIsNotValidException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class TokenIsNotValidException extends RuntimeException { +public class TokenIsNotValidException extends RuntimeException { private final Class entity; private final String entityId; diff --git a/src/main/java/org/highmed/numportal/service/exception/UserUnauthorizedException.java b/src/main/java/org/highmed/numportal/service/exception/UserUnauthorizedException.java index 340159a5..523d3351 100644 --- a/src/main/java/org/highmed/numportal/service/exception/UserUnauthorizedException.java +++ b/src/main/java/org/highmed/numportal/service/exception/UserUnauthorizedException.java @@ -1,6 +1,7 @@ package org.highmed.numportal.service.exception; import org.highmed.numportal.domain.model.admin.UserDetails; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.log4j.Log4j2; @@ -10,21 +11,21 @@ @Getter public class UserUnauthorizedException extends RuntimeException { - UserDetails userDetails; - String userId; - String message; + UserDetails userDetails; + String userId; + String message; - public UserUnauthorizedException(UserDetails userDetails) { - super("User " + userDetails.getUserId() + " is not authorized!"); - this.userDetails = userDetails; - this.userId = userDetails.getUserId(); - log.info("User: {} is not authorized!", userDetails.getUserId()); - } + public UserUnauthorizedException(UserDetails userDetails) { + super("User " + userDetails.getUserId() + " is not authorized!"); + this.userDetails = userDetails; + this.userId = userDetails.getUserId(); + log.info("User: {} is not authorized!", userDetails.getUserId()); + } - public UserUnauthorizedException(String message) { - super(message); - this.message = message; - this.userId = userDetails.getUserId(); - } + public UserUnauthorizedException(String message) { + super(message); + this.message = message; + this.userId = userDetails.getUserId(); + } } diff --git a/src/main/java/org/highmed/numportal/service/exception/UsernameNotFoundOrNoLongerActiveException.java b/src/main/java/org/highmed/numportal/service/exception/UsernameNotFoundOrNoLongerActiveException.java index d0ff27ca..efa4abb3 100644 --- a/src/main/java/org/highmed/numportal/service/exception/UsernameNotFoundOrNoLongerActiveException.java +++ b/src/main/java/org/highmed/numportal/service/exception/UsernameNotFoundOrNoLongerActiveException.java @@ -5,8 +5,8 @@ @AllArgsConstructor @Getter -public class UsernameNotFoundOrNoLongerActiveException extends RuntimeException { +public class UsernameNotFoundOrNoLongerActiveException extends RuntimeException { - private final Class entity; - private final String parameter; + private final Class entity; + private final String parameter; } diff --git a/src/main/java/org/highmed/numportal/service/exception/dto/ExceptionDto.java b/src/main/java/org/highmed/numportal/service/exception/dto/ExceptionDto.java index 5521b136..0020f0c0 100644 --- a/src/main/java/org/highmed/numportal/service/exception/dto/ExceptionDto.java +++ b/src/main/java/org/highmed/numportal/service/exception/dto/ExceptionDto.java @@ -8,6 +8,7 @@ @AllArgsConstructor @Getter public class ExceptionDto { - int id; - List argumentsList; + + int id; + List argumentsList; } diff --git a/src/main/java/org/highmed/numportal/service/exception/implementation/BadCredentialsException.java b/src/main/java/org/highmed/numportal/service/exception/implementation/BadCredentialsException.java index b01e54a1..da5dbff4 100644 --- a/src/main/java/org/highmed/numportal/service/exception/implementation/BadCredentialsException.java +++ b/src/main/java/org/highmed/numportal/service/exception/implementation/BadCredentialsException.java @@ -5,9 +5,9 @@ @AllArgsConstructor @Getter -public class BadCredentialsException extends RuntimeException { +public class BadCredentialsException extends RuntimeException { - private final Class entity; + private final Class entity; - private final String parameter; + private final String parameter; } diff --git a/src/main/java/org/highmed/numportal/service/exception/implementation/CustomException.java b/src/main/java/org/highmed/numportal/service/exception/implementation/CustomException.java index b5f731aa..a08a6f30 100644 --- a/src/main/java/org/highmed/numportal/service/exception/implementation/CustomException.java +++ b/src/main/java/org/highmed/numportal/service/exception/implementation/CustomException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class CustomException extends RuntimeException { +public class CustomException extends RuntimeException { private final String message; } diff --git a/src/main/java/org/highmed/numportal/service/exception/implementation/EntityNotFoundException.java b/src/main/java/org/highmed/numportal/service/exception/implementation/EntityNotFoundException.java index d2f066e1..e2d16052 100644 --- a/src/main/java/org/highmed/numportal/service/exception/implementation/EntityNotFoundException.java +++ b/src/main/java/org/highmed/numportal/service/exception/implementation/EntityNotFoundException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class EntityNotFoundException extends RuntimeException { +public class EntityNotFoundException extends RuntimeException { private final Class entity; private final String entityId; diff --git a/src/main/java/org/highmed/numportal/service/exception/implementation/SameEntityExistsException.java b/src/main/java/org/highmed/numportal/service/exception/implementation/SameEntityExistsException.java index 028ed908..f1a32730 100644 --- a/src/main/java/org/highmed/numportal/service/exception/implementation/SameEntityExistsException.java +++ b/src/main/java/org/highmed/numportal/service/exception/implementation/SameEntityExistsException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class SameEntityExistsException extends RuntimeException { +public class SameEntityExistsException extends RuntimeException { private final Class entity; private final String parameter; diff --git a/src/main/java/org/highmed/numportal/service/exception/implementation/TokenIsNotValidException.java b/src/main/java/org/highmed/numportal/service/exception/implementation/TokenIsNotValidException.java index 9377293d..c98be47d 100644 --- a/src/main/java/org/highmed/numportal/service/exception/implementation/TokenIsNotValidException.java +++ b/src/main/java/org/highmed/numportal/service/exception/implementation/TokenIsNotValidException.java @@ -5,7 +5,7 @@ @AllArgsConstructor @Getter -public class TokenIsNotValidException extends RuntimeException { +public class TokenIsNotValidException extends RuntimeException { private final Class entity; private final String entityId; diff --git a/src/main/java/org/highmed/numportal/service/exception/implementation/UsernameNotFoundOrNoLongerActiveException.java b/src/main/java/org/highmed/numportal/service/exception/implementation/UsernameNotFoundOrNoLongerActiveException.java index f08ed263..f1a5d3f8 100644 --- a/src/main/java/org/highmed/numportal/service/exception/implementation/UsernameNotFoundOrNoLongerActiveException.java +++ b/src/main/java/org/highmed/numportal/service/exception/implementation/UsernameNotFoundOrNoLongerActiveException.java @@ -5,8 +5,8 @@ @AllArgsConstructor @Getter -public class UsernameNotFoundOrNoLongerActiveException extends RuntimeException { +public class UsernameNotFoundOrNoLongerActiveException extends RuntimeException { - private final Class entity; - private final String parameter; + private final Class entity; + private final String parameter; } diff --git a/src/main/java/org/highmed/numportal/service/executors/AqlExecutor.java b/src/main/java/org/highmed/numportal/service/executors/AqlExecutor.java index 1d325c83..c47624b8 100644 --- a/src/main/java/org/highmed/numportal/service/executors/AqlExecutor.java +++ b/src/main/java/org/highmed/numportal/service/executors/AqlExecutor.java @@ -2,6 +2,10 @@ import org.highmed.numportal.domain.model.CohortAql; import org.highmed.numportal.properties.ConsentProperties; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.policy.EuropeanConsentPolicy; +import org.highmed.numportal.service.policy.ProjectPolicyService; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.MapUtils; @@ -13,12 +17,13 @@ import org.ehrbase.openehr.sdk.aql.render.AqlRenderer; import org.ehrbase.openehr.sdk.aql.util.AqlUtil; import org.ehrbase.openehr.sdk.generator.commons.aql.parameter.ParameterValue; -import org.highmed.numportal.service.ehrbase.EhrBaseService; -import org.highmed.numportal.service.policy.EuropeanConsentPolicy; -import org.highmed.numportal.service.policy.ProjectPolicyService; import org.springframework.stereotype.Service; -import java.util.*; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; @Slf4j @Service @@ -54,8 +59,8 @@ private void applyPolicy(CohortAql cohortAql) { aql, List.of( EuropeanConsentPolicy.builder() - .oid(consentProperties.getAllowUsageOutsideEuOid()) - .build())); + .oid(consentProperties.getAllowUsageOutsideEuOid()) + .build())); cohortAql.setQuery(AqlRenderer.render(aql)); } diff --git a/src/main/java/org/highmed/numportal/service/executors/CohortExecutor.java b/src/main/java/org/highmed/numportal/service/executors/CohortExecutor.java index 462e0fd7..4c453881 100644 --- a/src/main/java/org/highmed/numportal/service/executors/CohortExecutor.java +++ b/src/main/java/org/highmed/numportal/service/executors/CohortExecutor.java @@ -3,11 +3,12 @@ import org.highmed.numportal.domain.model.Cohort; import org.highmed.numportal.domain.model.CohortGroup; import org.highmed.numportal.domain.model.Type; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.exception.IllegalArgumentException; + import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.SetUtils; -import org.highmed.numportal.service.ehrbase.EhrBaseService; -import org.highmed.numportal.service.exception.IllegalArgumentException; import org.springframework.stereotype.Service; import java.util.List; @@ -41,8 +42,8 @@ public Set executeGroup(CohortGroup cohortGroup, Boolean allowUsageOutsi List> sets = cohortGroup.getChildren().stream() - .map(e -> executeGroup(e, allowUsageOutsideEu)) - .collect(Collectors.toList()); + .map(e -> executeGroup(e, allowUsageOutsideEu)) + .collect(Collectors.toList()); return setOperations.apply( cohortGroup.getOperator(), sets, ehrBaseService.getAllPatientIds()); diff --git a/src/main/java/org/highmed/numportal/service/executors/CohortQueryLister.java b/src/main/java/org/highmed/numportal/service/executors/CohortQueryLister.java index 4687e18c..a6a0f341 100644 --- a/src/main/java/org/highmed/numportal/service/executors/CohortQueryLister.java +++ b/src/main/java/org/highmed/numportal/service/executors/CohortQueryLister.java @@ -4,6 +4,7 @@ import org.highmed.numportal.domain.model.CohortAql; import org.highmed.numportal.domain.model.CohortGroup; import org.highmed.numportal.domain.model.Type; + import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/org/highmed/numportal/service/executors/SetOperationsService.java b/src/main/java/org/highmed/numportal/service/executors/SetOperationsService.java index db11a9d6..3fd5d6f3 100644 --- a/src/main/java/org/highmed/numportal/service/executors/SetOperationsService.java +++ b/src/main/java/org/highmed/numportal/service/executors/SetOperationsService.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.executors; import org.highmed.numportal.domain.model.Operator; -import org.apache.commons.collections4.CollectionUtils; import org.highmed.numportal.service.exception.IllegalArgumentException; + +import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Service; import java.util.HashSet; diff --git a/src/main/java/org/highmed/numportal/service/logger/ContextLog.java b/src/main/java/org/highmed/numportal/service/logger/ContextLog.java index 9be0827f..c690f800 100644 --- a/src/main/java/org/highmed/numportal/service/logger/ContextLog.java +++ b/src/main/java/org/highmed/numportal/service/logger/ContextLog.java @@ -9,8 +9,10 @@ @Retention(RetentionPolicy.RUNTIME) public @interface ContextLog { - String type() default ""; - String description() default ""; - boolean dtoPrint() default true; + String type() default ""; + + String description() default ""; + + boolean dtoPrint() default true; } \ No newline at end of file diff --git a/src/main/java/org/highmed/numportal/service/logger/ContextLogger.java b/src/main/java/org/highmed/numportal/service/logger/ContextLogger.java index edc84630..51a8ad65 100644 --- a/src/main/java/org/highmed/numportal/service/logger/ContextLogger.java +++ b/src/main/java/org/highmed/numportal/service/logger/ContextLogger.java @@ -5,11 +5,11 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.util.Optional; @@ -18,64 +18,64 @@ @Component public class ContextLogger { - private static final Logger logger = LoggerFactory.getLogger(ContextLogger.class); + private static final Logger logger = LoggerFactory.getLogger(ContextLogger.class); - @Before("@annotation(contextLog)") - public boolean logBefore(JoinPoint joinPoint, ContextLog contextLog) { + private static String getPathVariableValue(JoinPoint joinPoint) { + MethodSignature ms = (MethodSignature) joinPoint.getSignature(); + Annotation[][] array = ms.getMethod().getParameterAnnotations(); + for (int i = 0; i < array.length; i++) { + for (int j = 0; j < array[i].length; j++) { + if (array[i][j].annotationType().equals(org.springframework.web.bind.annotation.PathVariable.class)) { + return joinPoint.getArgs()[i].toString(); - if (SecurityContextHolder.getContext().getAuthentication() == null) { - return false; } - try { - String type = contextLog.type(); - Jwt principal = (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - String userId = principal.getSubject(); - String id = getPathVariableValue(joinPoint); - boolean printDto = contextLog.dtoPrint(); - Optional dto = Optional.empty(); - if (printDto) { - dto = getDto(joinPoint); - printDto = dto.isPresent(); - } - logger.info( - "Operation: '{}', ID: '{}', DTO: '{}'", contextLog.description(), - id, - printDto?dto.get():"", - StructuredArguments.keyValue("type", type), - StructuredArguments.keyValue("loggedInUserId", userId) - ); - } catch (Exception e) { - logger.error("Cannot log context log {}", e); - } - return true; + } } + return ""; + } - private static String getPathVariableValue(JoinPoint joinPoint) { - MethodSignature ms = (MethodSignature) joinPoint.getSignature(); - Annotation[][] array = ms.getMethod().getParameterAnnotations(); - for(int i = 0; i< array.length; i++){ - for(int j = 0; j< array[i].length; j++){ - if(array[i][j].annotationType().equals(org.springframework.web.bind.annotation.PathVariable.class)){ - return joinPoint.getArgs()[i].toString(); + private static Optional getDto(JoinPoint joinPoint) { + MethodSignature ms = (MethodSignature) joinPoint.getSignature(); + Annotation[][] array = ms.getMethod().getParameterAnnotations(); + for (int i = 0; i < array.length; i++) { + for (int j = 0; j < array[i].length; j++) { + if (array[i][j].annotationType().equals(org.springframework.web.bind.annotation.RequestBody.class)) { + return Optional.of(joinPoint.getArgs()[i]); - } - } } - return ""; + } } + return Optional.empty(); + } - private static Optional getDto(JoinPoint joinPoint) { - MethodSignature ms = (MethodSignature) joinPoint.getSignature(); - Annotation[][] array = ms.getMethod().getParameterAnnotations(); - for(int i = 0; i< array.length; i++){ - for(int j = 0; j< array[i].length; j++){ - if(array[i][j].annotationType().equals(org.springframework.web.bind.annotation.RequestBody.class)){ - return Optional.of(joinPoint.getArgs()[i]); + @Before("@annotation(contextLog)") + public boolean logBefore(JoinPoint joinPoint, ContextLog contextLog) { - } - } - } - return Optional.empty(); + if (SecurityContextHolder.getContext().getAuthentication() == null) { + return false; + } + try { + String type = contextLog.type(); + Jwt principal = (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + String userId = principal.getSubject(); + String id = getPathVariableValue(joinPoint); + boolean printDto = contextLog.dtoPrint(); + Optional dto = Optional.empty(); + if (printDto) { + dto = getDto(joinPoint); + printDto = dto.isPresent(); + } + logger.info( + "Operation: '{}', ID: '{}', DTO: '{}'", contextLog.description(), + id, + printDto ? dto.get() : "", + StructuredArguments.keyValue("type", type), + StructuredArguments.keyValue("loggedInUserId", userId) + ); + } catch (Exception e) { + logger.error("Cannot log context log {}", e); } + return true; + } } diff --git a/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java b/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java index 1b5c7c9b..7e878880 100644 --- a/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java +++ b/src/main/java/org/highmed/numportal/service/metric/ProjectsMetrics.java @@ -1,11 +1,12 @@ package org.highmed.numportal.service.metric; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.repository.ProjectRepository; + import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.highmed.numportal.domain.model.ProjectStatus; -import org.highmed.numportal.domain.repository.ProjectRepository; import org.springframework.stereotype.Component; /** @@ -15,65 +16,72 @@ @Component @Slf4j public class ProjectsMetrics { - private double totalNumberOfProjects; - private double approvedProjects; - private double publishedProjects; - private double closedProjects; - private double archivedProjects; - public ProjectsMetrics(MeterRegistry registry, ProjectRepository projectRepository) { - Gauge.builder("custom.metric.project.totalNumber.counter", this::getTotalNumberOfProjects) - .description("Total number of projects") - .register(registry); - Gauge.builder("custom.metric.project.approved.counter", this::getApprovedProjects) - .description("Approved projects") - .register(registry); - Gauge.builder("custom.metric.project.published.counter", this::getPublishedProjects) - .description("Published projects") - .register(registry); - Gauge.builder("custom.metric.project.closed.counter", this::getClosedProjects) - .description("Closed projects") - .register(registry); - Gauge.builder("custom.metric.project.archived.counter", this::getArchivedProjects) - .description("Archived projects") - .register(registry); + private double totalNumberOfProjects; + private double approvedProjects; + private double publishedProjects; + private double closedProjects; + private double archivedProjects; - totalNumberOfProjects = projectRepository.count(); - approvedProjects = projectRepository.countByStatus(ProjectStatus.APPROVED); - publishedProjects = projectRepository.countByStatus(ProjectStatus.PUBLISHED); - closedProjects = projectRepository.countByStatus(ProjectStatus.CLOSED); - archivedProjects = projectRepository.countByStatus(ProjectStatus.ARCHIVED); - } + public ProjectsMetrics(MeterRegistry registry, ProjectRepository projectRepository) { + Gauge.builder("custom.metric.project.totalNumber.counter", this::getTotalNumberOfProjects) + .description("Total number of projects") + .register(registry); + Gauge.builder("custom.metric.project.approved.counter", this::getApprovedProjects) + .description("Approved projects") + .register(registry); + Gauge.builder("custom.metric.project.published.counter", this::getPublishedProjects) + .description("Published projects") + .register(registry); + Gauge.builder("custom.metric.project.closed.counter", this::getClosedProjects) + .description("Closed projects") + .register(registry); + Gauge.builder("custom.metric.project.archived.counter", this::getArchivedProjects) + .description("Archived projects") + .register(registry); - public void increaseTotalNumber(){ - totalNumberOfProjects++; - } + totalNumberOfProjects = projectRepository.count(); + approvedProjects = projectRepository.countByStatus(ProjectStatus.APPROVED); + publishedProjects = projectRepository.countByStatus(ProjectStatus.PUBLISHED); + closedProjects = projectRepository.countByStatus(ProjectStatus.CLOSED); + archivedProjects = projectRepository.countByStatus(ProjectStatus.ARCHIVED); + } - public void decreaseTotalNumber(){ - totalNumberOfProjects--; - } + public void increaseTotalNumber() { + totalNumberOfProjects++; + } + + public void decreaseTotalNumber() { + totalNumberOfProjects--; + } - public void changeStatus(ProjectStatus before, ProjectStatus after){ - if(before == null){ - log.error("The project status cannot be null!"); - return; - } - if(before.equals(after)){ - return; - } - switch (before){ - case APPROVED -> approvedProjects --; - case PUBLISHED -> publishedProjects --; - case CLOSED -> closedProjects --; - case ARCHIVED -> archivedProjects --; - } + public void changeStatus(ProjectStatus before, ProjectStatus after) { + if (before == null) { + log.error("The project status cannot be null!"); + return; + } + if (before.equals(after)) { + return; + } + switch (before) { + case APPROVED -> approvedProjects--; + case PUBLISHED -> publishedProjects--; + case CLOSED -> closedProjects--; + case ARCHIVED -> archivedProjects--; + default -> { + // Nothing to do + } + } - switch (after){ - case APPROVED -> approvedProjects ++; - case PUBLISHED -> publishedProjects ++; - case CLOSED -> closedProjects ++; - case ARCHIVED -> archivedProjects ++; - } + switch (after) { + case APPROVED -> approvedProjects++; + case PUBLISHED -> publishedProjects++; + case CLOSED -> closedProjects++; + case ARCHIVED -> archivedProjects++; + default -> { + // Nothing to do + } } + } } diff --git a/src/main/java/org/highmed/numportal/service/metric/UsersMetrics.java b/src/main/java/org/highmed/numportal/service/metric/UsersMetrics.java index dddd82c8..62f6d406 100644 --- a/src/main/java/org/highmed/numportal/service/metric/UsersMetrics.java +++ b/src/main/java/org/highmed/numportal/service/metric/UsersMetrics.java @@ -1,12 +1,13 @@ package org.highmed.numportal.service.metric; +import org.highmed.numportal.domain.model.admin.UserDetails; +import org.highmed.numportal.domain.repository.UserDetailsRepository; +import org.highmed.numportal.web.feign.KeycloakFeign; + import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import jakarta.validation.constraints.NotNull; import lombok.Getter; -import org.highmed.numportal.domain.model.admin.UserDetails; -import org.highmed.numportal.domain.repository.UserDetailsRepository; -import org.highmed.numportal.web.feign.KeycloakFeign; import org.springframework.stereotype.Component; import java.util.List; @@ -19,52 +20,52 @@ @Component public class UsersMetrics { - private double unapprovedUsers; - private double activeUsers; - private double inactiveUsers; + private double unapprovedUsers; + private double activeUsers; + private double inactiveUsers; - public UsersMetrics(MeterRegistry registry, UserDetailsRepository userDetailsRepository, KeycloakFeign keycloakFeign) { - Gauge.builder("custom.metric.user.unapproved.counter", this::getUnapprovedUsers) - .description("Unapproved users") - .register(registry); - Gauge.builder("custom.metric.user.active.counter", this::getActiveUsers) - .description("Active users") - .register(registry); - Gauge.builder("custom.metric.user.inactive.counter", this::getInactiveUsers) - .description("Inactive users") - .register(registry); + public UsersMetrics(MeterRegistry registry, UserDetailsRepository userDetailsRepository, KeycloakFeign keycloakFeign) { + Gauge.builder("custom.metric.user.unapproved.counter", this::getUnapprovedUsers) + .description("Unapproved users") + .register(registry); + Gauge.builder("custom.metric.user.active.counter", this::getActiveUsers) + .description("Active users") + .register(registry); + Gauge.builder("custom.metric.user.inactive.counter", this::getInactiveUsers) + .description("Inactive users") + .register(registry); - Optional> unapproved = userDetailsRepository.findAllByApproved(false); - unapproved.ifPresent(userDetails -> this.unapprovedUsers = userDetails.size()); - this.inactiveUsers = keycloakFeign.countUsers(false); - // decrease because of service account - this.activeUsers = keycloakFeign.countUsers(true) - 1; + Optional> unapproved = userDetailsRepository.findAllByApproved(false); + unapproved.ifPresent(userDetails -> this.unapprovedUsers = userDetails.size()); + this.inactiveUsers = keycloakFeign.countUsers(false); + // decrease because of service account + this.activeUsers = keycloakFeign.countUsers(true) - 1; - } + } - public void addNewUserAsUnapproved() { - this.unapprovedUsers++; - } + public void addNewUserAsUnapproved() { + this.unapprovedUsers++; + } - public void approveUser() { - this.unapprovedUsers--; - } + public void approveUser() { + this.unapprovedUsers--; + } - public void updateCountStatus(@NotNull boolean active) { - if(active) { - this.incrementActiveUsers(); - } else { - this.incrementInactiveUsers(); - } + public void updateCountStatus(@NotNull boolean active) { + if (active) { + this.incrementActiveUsers(); + } else { + this.incrementInactiveUsers(); } + } - private void incrementActiveUsers() { - this.activeUsers++; - this.inactiveUsers--; - } + private void incrementActiveUsers() { + this.activeUsers++; + this.inactiveUsers--; + } - private void incrementInactiveUsers() { - this.inactiveUsers++; - this.activeUsers--; - } + private void incrementInactiveUsers() { + this.inactiveUsers++; + this.activeUsers--; + } } diff --git a/src/main/java/org/highmed/numportal/service/notification/NotificationService.java b/src/main/java/org/highmed/numportal/service/notification/NotificationService.java index 964f0429..5eb8f9d9 100644 --- a/src/main/java/org/highmed/numportal/service/notification/NotificationService.java +++ b/src/main/java/org/highmed/numportal/service/notification/NotificationService.java @@ -1,12 +1,13 @@ package org.highmed.numportal.service.notification; import org.highmed.numportal.properties.NumProperties; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.highmed.numportal.service.email.EmailService; import org.highmed.numportal.service.email.MessageSourceWrapper; import org.highmed.numportal.service.notification.dto.Notification; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/NewUserNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/NewUserNotification.java index 7a6ef52b..e268f915 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/NewUserNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/NewUserNotification.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.notification.dto; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; import java.util.Iterator; import java.util.List; @@ -24,13 +25,13 @@ public class NewUserNotification extends Notification { @Builder public NewUserNotification( - String newUserEmail, - String newUserFirstName, - String newUserLastName, - List requestedRoles, String department, - String notes, String recipientEmail, - String recipientFirstName, - String recipientLastName) { + String newUserEmail, + String newUserFirstName, + String newUserLastName, + List requestedRoles, String department, + String notes, String recipientEmail, + String recipientFirstName, + String recipientLastName) { this.newUserEmail = newUserEmail; this.newUserFirstName = newUserFirstName; diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/NewUserWithoutOrganizationNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/NewUserWithoutOrganizationNotification.java index e9028558..5bf9cb1b 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/NewUserWithoutOrganizationNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/NewUserWithoutOrganizationNotification.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.notification.dto; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; public class NewUserWithoutOrganizationNotification extends Notification { @@ -21,7 +22,7 @@ public NewUserWithoutOrganizationNotification( String userLastName, String recipientEmail, String recipientFirstName, - String recipientLastName){ + String recipientLastName) { this.userEmail = userEmail; this.userFirstName = userFirstName; this.userLastName = userLastName; diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/Notification.java b/src/main/java/org/highmed/numportal/service/notification/dto/Notification.java index 0e720a88..95d1bbca 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/Notification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/Notification.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.notification.dto; +import org.highmed.numportal.service.email.MessageSourceWrapper; + import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.highmed.numportal.service.email.MessageSourceWrapper; import java.net.MalformedURLException; import java.net.URL; diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectApprovalRequestNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectApprovalRequestNotification.java index 949b4323..9dfa3294 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectApprovalRequestNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectApprovalRequestNotification.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.notification.dto; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; public class ProjectApprovalRequestNotification extends Notification { diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectCloseNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectCloseNotification.java index a8821f14..608cf059 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectCloseNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectCloseNotification.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.notification.dto; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; public class ProjectCloseNotification extends Notification { diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStartNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStartNotification.java index 436655d4..572d59fc 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStartNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStartNotification.java @@ -1,8 +1,9 @@ package org.highmed.numportal.service.notification.dto; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; public class ProjectStartNotification extends Notification { diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeNotification.java index b9e4e3f3..5ba8110e 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeNotification.java @@ -1,17 +1,17 @@ package org.highmed.numportal.service.notification.dto; import org.highmed.numportal.domain.model.ProjectStatus; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; public class ProjectStatusChangeNotification extends Notification { + protected static final String PROJECT_STATUS_CHANGE_BODY_KEY = "mail.project-status-change.body"; private static final String PROJECT_STATUS_CHANGE_SUBJECT_KEY = "mail.project-status-change.subject"; - protected static final String PROJECT_STATUS_CHANGE_BODY_KEY = "mail.project-status-change.body"; - protected final String approverFirstName; protected final String approverLastName; protected final String approverEmail; diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeRequestNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeRequestNotification.java index fa5e4633..35dbafae 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeRequestNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/ProjectStatusChangeRequestNotification.java @@ -1,37 +1,40 @@ package org.highmed.numportal.service.notification.dto; import org.highmed.numportal.domain.model.ProjectStatus; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; +import lombok.Builder; + import java.time.Year; public class ProjectStatusChangeRequestNotification extends ProjectStatusChangeNotification { - @Builder(builderMethodName = "changeRequestBuilder") - public ProjectStatusChangeRequestNotification(String recipientEmail, String recipientFirstName, - String recipientLastName, String approverFirstName, - String approverLastName, String projectTitle, - ProjectStatus projectStatus, ProjectStatus oldProjectStatus, - long projectId, String approverEmail) { - super(recipientEmail, recipientFirstName, recipientLastName, approverFirstName, approverLastName, projectTitle, projectStatus, oldProjectStatus, projectId, approverEmail); - } + @Builder(builderMethodName = "changeRequestBuilder") + public ProjectStatusChangeRequestNotification(String recipientEmail, String recipientFirstName, + String recipientLastName, String approverFirstName, + String approverLastName, String projectTitle, + ProjectStatus projectStatus, ProjectStatus oldProjectStatus, + long projectId, String approverEmail) { + super( + recipientEmail, recipientFirstName, recipientLastName, approverFirstName, approverLastName, projectTitle, projectStatus, oldProjectStatus, + projectId, approverEmail); + } - @Override - public String getNotificationBody(MessageSourceWrapper messageSource, String url) { - String copyright = messageSource.getMessage(COPYRIGHT_KEY, Year.now()); - return messageSource.getMessage( - PROJECT_STATUS_CHANGE_BODY_KEY, - recipientFirstName, - recipientLastName, - projectTitle, - projectStatus, - approverFirstName, - approverLastName, - copyright, - url, - getProjectEditUrl(url, projectId), - oldProjectStatus, - approverEmail); - } + @Override + public String getNotificationBody(MessageSourceWrapper messageSource, String url) { + String copyright = messageSource.getMessage(COPYRIGHT_KEY, Year.now()); + return messageSource.getMessage( + PROJECT_STATUS_CHANGE_BODY_KEY, + recipientFirstName, + recipientLastName, + projectTitle, + projectStatus, + approverFirstName, + approverLastName, + copyright, + url, + getProjectEditUrl(url, projectId), + oldProjectStatus, + approverEmail); + } } diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountApprovalNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountApprovalNotification.java index 3f87e9f6..b508ed53 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountApprovalNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountApprovalNotification.java @@ -1,9 +1,11 @@ package org.highmed.numportal.service.notification.dto.account; -import lombok.Builder; + import org.highmed.numportal.service.email.MessageSourceWrapper; import org.highmed.numportal.service.notification.dto.Notification; +import lombok.Builder; + import java.time.Year; public class AccountApprovalNotification extends Notification { diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountStatusChangedNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountStatusChangedNotification.java index e23b9b10..458d7ae4 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountStatusChangedNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/account/AccountStatusChangedNotification.java @@ -1,59 +1,60 @@ package org.highmed.numportal.service.notification.dto.account; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; import org.highmed.numportal.service.notification.dto.Notification; +import lombok.Builder; + import java.time.Year; public class AccountStatusChangedNotification extends Notification { - private static final String ACCOUNT_ACTIVE_SUBJECT = "mail.user-account-active.subject"; - private static final String ACCOUNT_ACTIVE_BODY = "mail.user-account-active-body"; - private static final String ACCOUNT_INACTIVE_SUBJECT = "mail.user-account-inactive.subject"; - private static final String ACCOUNT_INACTIVE_BODY = "mail.user-account-inactive.body"; - private Boolean userCurrentStatus; - - @Builder - public AccountStatusChangedNotification( - String recipientEmail, - String recipientFirstName, - String recipientLastName, - String adminEmail, - String adminFullName, - Boolean userCurrentStatus) { - this.recipientEmail = recipientEmail; - this.recipientFirstName = recipientFirstName; - this.recipientLastName = recipientLastName; - this.adminFullName = adminFullName; - this.adminEmail = adminEmail; - this.userCurrentStatus = userCurrentStatus; - } + private static final String ACCOUNT_ACTIVE_SUBJECT = "mail.user-account-active.subject"; + private static final String ACCOUNT_ACTIVE_BODY = "mail.user-account-active-body"; + private static final String ACCOUNT_INACTIVE_SUBJECT = "mail.user-account-inactive.subject"; + private static final String ACCOUNT_INACTIVE_BODY = "mail.user-account-inactive.body"; + private final Boolean userCurrentStatus; + + @Builder + public AccountStatusChangedNotification( + String recipientEmail, + String recipientFirstName, + String recipientLastName, + String adminEmail, + String adminFullName, + Boolean userCurrentStatus) { + this.recipientEmail = recipientEmail; + this.recipientFirstName = recipientFirstName; + this.recipientLastName = recipientLastName; + this.adminFullName = adminFullName; + this.adminEmail = adminEmail; + this.userCurrentStatus = userCurrentStatus; + } - @Override - public String getNotificationBody(MessageSourceWrapper messageSource, String url) { - String copyright = messageSource.getMessage(COPYRIGHT_KEY, Year.now()); - String messageKey; - if (Boolean.TRUE.equals(userCurrentStatus)) { - messageKey = ACCOUNT_ACTIVE_BODY; - } else { - messageKey = ACCOUNT_INACTIVE_BODY; - } - return messageSource.getMessage( - messageKey, - recipientFirstName, - recipientLastName, - url, - adminEmail, - adminFullName, - copyright); + @Override + public String getNotificationBody(MessageSourceWrapper messageSource, String url) { + String copyright = messageSource.getMessage(COPYRIGHT_KEY, Year.now()); + String messageKey; + if (Boolean.TRUE.equals(userCurrentStatus)) { + messageKey = ACCOUNT_ACTIVE_BODY; + } else { + messageKey = ACCOUNT_INACTIVE_BODY; } + return messageSource.getMessage( + messageKey, + recipientFirstName, + recipientLastName, + url, + adminEmail, + adminFullName, + copyright); + } - @Override - public String getNotificationSubject(MessageSourceWrapper messageSource) { - if (Boolean.TRUE.equals(userCurrentStatus)) { - return messageSource.getMessage(ACCOUNT_ACTIVE_SUBJECT); - } - return messageSource.getMessage(ACCOUNT_INACTIVE_SUBJECT); + @Override + public String getNotificationSubject(MessageSourceWrapper messageSource) { + if (Boolean.TRUE.equals(userCurrentStatus)) { + return messageSource.getMessage(ACCOUNT_ACTIVE_SUBJECT); } + return messageSource.getMessage(ACCOUNT_INACTIVE_SUBJECT); + } } diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/account/OrganizationUpdateNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/account/OrganizationUpdateNotification.java index 7300fc26..1bd73576 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/account/OrganizationUpdateNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/account/OrganizationUpdateNotification.java @@ -1,9 +1,10 @@ package org.highmed.numportal.service.notification.dto.account; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; import org.highmed.numportal.service.notification.dto.Notification; +import lombok.Builder; + import java.time.Year; public class OrganizationUpdateNotification extends Notification { diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/account/RolesUpdateNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/account/RolesUpdateNotification.java index d0b7038a..9b0444d6 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/account/RolesUpdateNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/account/RolesUpdateNotification.java @@ -1,11 +1,12 @@ package org.highmed.numportal.service.notification.dto.account; import org.highmed.numportal.domain.model.Roles; +import org.highmed.numportal.service.email.MessageSourceWrapper; +import org.highmed.numportal.service.notification.dto.Notification; + import lombok.Builder; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.highmed.numportal.service.email.MessageSourceWrapper; -import org.highmed.numportal.service.notification.dto.Notification; import java.time.Year; import java.util.HashMap; diff --git a/src/main/java/org/highmed/numportal/service/notification/dto/account/UserNameUpdateNotification.java b/src/main/java/org/highmed/numportal/service/notification/dto/account/UserNameUpdateNotification.java index 4752e1f1..484f9083 100644 --- a/src/main/java/org/highmed/numportal/service/notification/dto/account/UserNameUpdateNotification.java +++ b/src/main/java/org/highmed/numportal/service/notification/dto/account/UserNameUpdateNotification.java @@ -1,9 +1,10 @@ package org.highmed.numportal.service.notification.dto.account; -import lombok.Builder; import org.highmed.numportal.service.email.MessageSourceWrapper; import org.highmed.numportal.service.notification.dto.Notification; +import lombok.Builder; + import java.time.Year; public class UserNameUpdateNotification extends Notification { diff --git a/src/main/java/org/highmed/numportal/service/policy/EhrPolicy.java b/src/main/java/org/highmed/numportal/service/policy/EhrPolicy.java index e5f75540..65e866a2 100644 --- a/src/main/java/org/highmed/numportal/service/policy/EhrPolicy.java +++ b/src/main/java/org/highmed/numportal/service/policy/EhrPolicy.java @@ -1,5 +1,8 @@ package org.highmed.numportal.service.policy; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.util.AqlQueryConstants; + import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; @@ -8,8 +11,6 @@ import org.ehrbase.openehr.sdk.aql.dto.operand.IdentifiedPath; import org.ehrbase.openehr.sdk.aql.dto.path.AqlObjectPath; import org.ehrbase.openehr.sdk.aql.dto.select.SelectExpression; -import org.highmed.numportal.service.exception.SystemException; -import org.highmed.numportal.service.util.AqlQueryConstants; import java.util.ArrayList; import java.util.List; @@ -18,12 +19,14 @@ import static org.highmed.numportal.domain.templates.ExceptionsTemplate.COHORT_SIZE_CANNOT_BE_0; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_AQL; -/** Restricts the aql to a set of ehr ids defined by the project cohort */ +/** + * Restricts the aql to a set of ehr ids defined by the project cohort + */ @Slf4j public class EhrPolicy extends Policy { - private Set cohortEhrIds; + private final Set cohortEhrIds; @Builder public EhrPolicy(Set cohortEhrIds) { diff --git a/src/main/java/org/highmed/numportal/service/policy/EuropeanConsentPolicy.java b/src/main/java/org/highmed/numportal/service/policy/EuropeanConsentPolicy.java index 0bf5b608..32d1af4b 100644 --- a/src/main/java/org/highmed/numportal/service/policy/EuropeanConsentPolicy.java +++ b/src/main/java/org/highmed/numportal/service/policy/EuropeanConsentPolicy.java @@ -1,10 +1,11 @@ package org.highmed.numportal.service.policy; +import org.highmed.numportal.service.exception.SystemException; + import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; import org.ehrbase.openehr.sdk.aql.dto.operand.Primitive; -import org.highmed.numportal.service.exception.SystemException; import java.util.List; @@ -12,8 +13,7 @@ import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_AQL; /** - * Restricts the aql to a particular oid which defines the user consent for project data being used - * outside the European Union + * Restricts the aql to a particular oid which defines the user consent for project data being used outside the European Union */ @Slf4j public class EuropeanConsentPolicy extends Policy { @@ -21,7 +21,7 @@ public class EuropeanConsentPolicy extends Policy { private static final String FEEDER_AUDIT_PATH = "feeder_audit/feeder_system_audit/other_details[openEHR-EHR-ITEM_TREE.generic.v1]/items[at0001]/value/id"; - private String oid; + private final String oid; @Builder public EuropeanConsentPolicy(String oid) { @@ -38,11 +38,11 @@ public boolean apply(AqlQuery aql) { throw new SystemException(EuropeanConsentPolicy.class, INVALID_AQL); } - logAqlQuery(log, aql,"[AQL QUERY] Aql before executing EuropeanConsentPolicy: %s "); + logAqlQuery(log, aql, "[AQL QUERY] Aql before executing EuropeanConsentPolicy: %s "); List oidValues = toSimpleValueList(List.of(oid)); restrictAqlWithCompositionAttribute(aql, FEEDER_AUDIT_PATH, oidValues); - logAqlQuery(log, aql,"[AQL QUERY] Aql after executing EuropeanConsentPolicy: %s "); + logAqlQuery(log, aql, "[AQL QUERY] Aql after executing EuropeanConsentPolicy: %s "); return true; } } diff --git a/src/main/java/org/highmed/numportal/service/policy/Policy.java b/src/main/java/org/highmed/numportal/service/policy/Policy.java index 7d30d4c2..73df3450 100644 --- a/src/main/java/org/highmed/numportal/service/policy/Policy.java +++ b/src/main/java/org/highmed/numportal/service/policy/Policy.java @@ -1,5 +1,7 @@ package org.highmed.numportal.service.policy; +import org.highmed.numportal.service.util.AqlQueryConstants; + import org.apache.commons.collections.CollectionUtils; import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; import org.ehrbase.openehr.sdk.aql.dto.condition.LogicalOperatorCondition; @@ -16,13 +18,19 @@ import org.ehrbase.openehr.sdk.aql.dto.path.AqlObjectPath; import org.ehrbase.openehr.sdk.aql.dto.select.SelectExpression; import org.ehrbase.openehr.sdk.aql.render.AqlRenderer; -import org.highmed.numportal.service.util.AqlQueryConstants; import org.slf4j.Logger; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; import java.util.stream.Collectors; -/** Defines a certain project policy to be applied to an aql query */ +/** + * Defines a certain project policy to be applied to an aql query + */ public abstract class Policy { public abstract boolean apply(AqlQuery aql); @@ -85,15 +93,15 @@ protected void extendWhereClause(AqlQuery aql, List selectExpr List whereConditions = new ArrayList<>(); selectExpressions.forEach( - selectFieldDto -> { - MatchesCondition matches = new MatchesCondition(); - matches.setStatement((IdentifiedPath) selectFieldDto.getColumnExpression()); - List operands = values.stream() - .map(v -> new StringPrimitive(v.getValue().toString())) - .collect(Collectors.toList()); - matches.setValues(operands); - whereConditions.add(matches); - }); + selectFieldDto -> { + MatchesCondition matches = new MatchesCondition(); + matches.setStatement((IdentifiedPath) selectFieldDto.getColumnExpression()); + List operands = values.stream() + .map(v -> new StringPrimitive(v.getValue().toString())) + .collect(Collectors.toList()); + matches.setValues(operands); + whereConditions.add(matches); + }); LogicalOperatorCondition newWhere = new LogicalOperatorCondition(); newWhere.setValues(new ArrayList<>()); @@ -110,7 +118,8 @@ protected void extendWhereClause(AqlQuery aql, List selectExpr aql.setWhere(newWhere); } - protected void extendContainsClause(AqlQuery aql, List whereClauseSelectFields, Containment contains, int nextContainmentId, String attrPath) { + protected void extendContainsClause(AqlQuery aql, List whereClauseSelectFields, Containment contains, int nextContainmentId, + String attrPath) { IdentifiedPath path = new IdentifiedPath(); path.setPath(AqlObjectPath.parse(attrPath)); @@ -121,7 +130,6 @@ protected void extendContainsClause(AqlQuery aql, List whereCl ContainmentSetOperator newContains = new ContainmentSetOperator(); newContains.setValues(new ArrayList<>()); - ContainmentClassExpression containmentClassExpression = new ContainmentClassExpression(); containmentClassExpression.setType(AqlQueryConstants.COMPOSITION_TYPE); containmentClassExpression.setIdentifier("c" + nextContainmentId); @@ -136,8 +144,8 @@ protected void extendContainsClause(AqlQuery aql, List whereCl protected List toSimpleValueList(Collection list) { return list.stream() - .map(StringPrimitive::new) - .collect(Collectors.toList()); + .map(StringPrimitive::new) + .collect(Collectors.toList()); } protected List findCompositionsIdentifier(Containment dto) { @@ -207,8 +215,9 @@ protected Integer findNextContainmentId(Containment dto) { protected void logAqlQuery(Logger log, AqlQuery aql, String logMessage) { try { log.debug( - String.format(logMessage, - AqlRenderer.render(aql))); + String.format( + logMessage, + AqlRenderer.render(aql))); } catch (Exception e) { log.error("Cannot parse aql query while logging", e); } diff --git a/src/main/java/org/highmed/numportal/service/policy/ProjectPolicyService.java b/src/main/java/org/highmed/numportal/service/policy/ProjectPolicyService.java index f46c8bbc..eeaf7691 100644 --- a/src/main/java/org/highmed/numportal/service/policy/ProjectPolicyService.java +++ b/src/main/java/org/highmed/numportal/service/policy/ProjectPolicyService.java @@ -11,28 +11,28 @@ @Service public class ProjectPolicyService { - private static final String ERROR_MESSAGE = "Cannot parse aql query while logging"; - - public void apply(AqlQuery aql, List policies) { - - try { - log.info( - String.format( - "[AQL QUERY] Aql before executing project policies: %s ", - AqlRenderer.render(aql))); - } catch (Exception e) { - log.error(ERROR_MESSAGE, e); - } - - policies.forEach(policy -> policy.apply(aql)); - - try { - log.info( - String.format( - "[AQL QUERY] Aql after executing project policies: %s ", - AqlRenderer.render(aql))); - } catch (Exception e) { - log.error(ERROR_MESSAGE, e); - } + private static final String ERROR_MESSAGE = "Cannot parse aql query while logging"; + + public void apply(AqlQuery aql, List policies) { + + try { + log.info( + String.format( + "[AQL QUERY] Aql before executing project policies: %s ", + AqlRenderer.render(aql))); + } catch (Exception e) { + log.error(ERROR_MESSAGE, e); + } + + policies.forEach(policy -> policy.apply(aql)); + + try { + log.info( + String.format( + "[AQL QUERY] Aql after executing project policies: %s ", + AqlRenderer.render(aql))); + } catch (Exception e) { + log.error(ERROR_MESSAGE, e); } + } } diff --git a/src/main/java/org/highmed/numportal/service/policy/TemplatesPolicy.java b/src/main/java/org/highmed/numportal/service/policy/TemplatesPolicy.java index f59c432b..f7519fa6 100644 --- a/src/main/java/org/highmed/numportal/service/policy/TemplatesPolicy.java +++ b/src/main/java/org/highmed/numportal/service/policy/TemplatesPolicy.java @@ -1,11 +1,12 @@ package org.highmed.numportal.service.policy; +import org.highmed.numportal.service.exception.SystemException; + import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.MapUtils; import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; import org.ehrbase.openehr.sdk.aql.dto.operand.Primitive; -import org.highmed.numportal.service.exception.SystemException; import java.util.List; import java.util.Map; @@ -13,13 +14,15 @@ import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_AQL; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.NO_TEMPLATES_ATTACHED_TO_THE_PROJECT; -/** Restricts the aql to a set of templates defined by the project */ +/** + * Restricts the aql to a set of templates defined by the project + */ @Slf4j public class TemplatesPolicy extends Policy { private static final String TEMPLATE_ID_PATH = "archetype_details/template_id/value"; - private Map templatesMap; + private final Map templatesMap; @Builder public TemplatesPolicy(Map templatesMap) { @@ -37,12 +40,12 @@ public boolean apply(AqlQuery aql) { throw new SystemException(TemplatesPolicy.class, INVALID_AQL); } - logAqlQuery(log, aql,"[AQL QUERY] Aql before executing TemplatesPolicy: %s "); + logAqlQuery(log, aql, "[AQL QUERY] Aql before executing TemplatesPolicy: %s "); List templateValues = toSimpleValueList(templatesMap.keySet()); restrictAqlWithCompositionAttribute(aql, TEMPLATE_ID_PATH, templateValues); - logAqlQuery(log, aql,"[AQL QUERY] Aql after executing TemplatesPolicy: %s "); + logAqlQuery(log, aql, "[AQL QUERY] Aql after executing TemplatesPolicy: %s "); return true; } } diff --git a/src/main/java/org/highmed/numportal/service/util/AqlQueryConstants.java b/src/main/java/org/highmed/numportal/service/util/AqlQueryConstants.java index a86e6ced..00270e10 100644 --- a/src/main/java/org/highmed/numportal/service/util/AqlQueryConstants.java +++ b/src/main/java/org/highmed/numportal/service/util/AqlQueryConstants.java @@ -2,12 +2,12 @@ public class AqlQueryConstants { - public static final String EHR_CONTAINMENT_IDENTIFIER = "e"; - public static final String COMPOSITION_TYPE = "COMPOSITION"; - public static final String EHR_TYPE = "EHR"; - public static final String COMPOSITION_CONTAINMENT_IDENTIFIER = "c0"; - public static final String EHR_ID_PATH = "ehr_id/value"; + public static final String EHR_CONTAINMENT_IDENTIFIER = "e"; + public static final String COMPOSITION_TYPE = "COMPOSITION"; + public static final String EHR_TYPE = "EHR"; + public static final String COMPOSITION_CONTAINMENT_IDENTIFIER = "c0"; + public static final String EHR_ID_PATH = "ehr_id/value"; - private AqlQueryConstants() { - } + private AqlQueryConstants() { + } } diff --git a/src/main/java/org/highmed/numportal/web/config/ApplicationSecurity.java b/src/main/java/org/highmed/numportal/web/config/ApplicationSecurity.java index 2c151ac1..46bf7a1c 100644 --- a/src/main/java/org/highmed/numportal/web/config/ApplicationSecurity.java +++ b/src/main/java/org/highmed/numportal/web/config/ApplicationSecurity.java @@ -18,31 +18,32 @@ public class ApplicationSecurity { private static final String[] AUTH_WHITELIST = {"/swagger-*/**", "/v2/**", "/v3/**", "/admin/health", - "/admin/log-level", "/admin/log-level/*", "/admin/manuel-url", "/admin/services-status"}; + "/admin/log-level", "/admin/log-level/*", "/admin/manuel-url", "/admin/services-status"}; @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity - .httpBasic(AbstractHttpConfigurer::disable) - .formLogin(AbstractHttpConfigurer::disable); + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable); httpSecurity - .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) - .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> - jwt.jwtAuthenticationConverter(new AuthorizationConverter()))) - .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .cors(Customizer.withDefaults()); + .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> + jwt.jwtAuthenticationConverter(new AuthorizationConverter()))) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .cors(Customizer.withDefaults()); return httpSecurity.build(); } - @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> - web.ignoring() - .requestMatchers(AUTH_WHITELIST) - .requestMatchers(HttpMethod.GET, "/content/navigation") - .requestMatchers(HttpMethod.GET, "/content/cards") - .requestMatchers(HttpMethod.GET, "/content/metrics") - .requestMatchers(HttpMethod.GET, "/actuator/health") - .requestMatchers(HttpMethod.GET, "/actuator/info") - .requestMatchers(HttpMethod.GET, "/actuator/prometheus"); - } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> + web.ignoring() + .requestMatchers(AUTH_WHITELIST) + .requestMatchers(HttpMethod.GET, "/content/navigation") + .requestMatchers(HttpMethod.GET, "/content/cards") + .requestMatchers(HttpMethod.GET, "/content/metrics") + .requestMatchers(HttpMethod.GET, "/actuator/health") + .requestMatchers(HttpMethod.GET, "/actuator/info") + .requestMatchers(HttpMethod.GET, "/actuator/prometheus"); + } } diff --git a/src/main/java/org/highmed/numportal/web/config/AuthorizationConverter.java b/src/main/java/org/highmed/numportal/web/config/AuthorizationConverter.java index 26d4a14f..9e68fb25 100644 --- a/src/main/java/org/highmed/numportal/web/config/AuthorizationConverter.java +++ b/src/main/java/org/highmed/numportal/web/config/AuthorizationConverter.java @@ -1,9 +1,5 @@ package org.highmed.numportal.web.config; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.SetUtils; import org.springframework.core.convert.converter.Converter; @@ -14,6 +10,11 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.stereotype.Component; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Component public class AuthorizationConverter implements Converter { @@ -30,12 +31,12 @@ public AbstractAuthenticationToken convert(Jwt jwt) { private Collection extractAuthorities(Jwt jwt) { Map realmAccess = jwt.getClaim(REALM_ACCESS); if (realmAccess != null) { - final List roles = (List)realmAccess.get(ROLES_CLAIM); + final List roles = (List) realmAccess.get(ROLES_CLAIM); if (CollectionUtils.isNotEmpty(roles)) { return roles.stream() - .map(role -> new SimpleGrantedAuthority(ROLE_PREFIX + role)) - .collect(Collectors.toSet()); + .map(role -> new SimpleGrantedAuthority(ROLE_PREFIX + role)) + .collect(Collectors.toSet()); } } return SetUtils.emptySet(); diff --git a/src/main/java/org/highmed/numportal/web/config/CorsConfig.java b/src/main/java/org/highmed/numportal/web/config/CorsConfig.java index 8d2a3f6f..4d6b3471 100644 --- a/src/main/java/org/highmed/numportal/web/config/CorsConfig.java +++ b/src/main/java/org/highmed/numportal/web/config/CorsConfig.java @@ -1,7 +1,7 @@ package org.highmed.numportal.web.config; import org.highmed.numportal.properties.CorsProperties; -import java.util.Collections; + import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,6 +10,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; +import java.util.Collections; + @Configuration @RequiredArgsConstructor public class CorsConfig { diff --git a/src/main/java/org/highmed/numportal/web/config/FeignClientConfig.java b/src/main/java/org/highmed/numportal/web/config/FeignClientConfig.java index d2f53021..45b64977 100644 --- a/src/main/java/org/highmed/numportal/web/config/FeignClientConfig.java +++ b/src/main/java/org/highmed/numportal/web/config/FeignClientConfig.java @@ -1,6 +1,7 @@ package org.highmed.numportal.web.config; import org.highmed.numportal.web.feign.KeycloakFeign; + import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/highmed/numportal/web/config/SwaggerConfig.java b/src/main/java/org/highmed/numportal/web/config/SwaggerConfig.java index 1ab7f793..39f6ad04 100644 --- a/src/main/java/org/highmed/numportal/web/config/SwaggerConfig.java +++ b/src/main/java/org/highmed/numportal/web/config/SwaggerConfig.java @@ -1,6 +1,7 @@ package org.highmed.numportal.web.config; import org.highmed.numportal.properties.SwaggerProperties; + import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.security.OAuthFlow; @@ -15,6 +16,7 @@ @Configuration @AllArgsConstructor public class SwaggerConfig { + private static final String SEC_CONFIG_NAME = "oauth_setting"; private static final String NUM_PACKAGES_TO_SCAN = "org.highmed.numportal.web.controller"; @@ -75,24 +77,25 @@ public GroupedOpenApi attachmentApi() { @Bean public OpenAPI customOpenAPI() { OAuthFlow oAuthFlow = new OAuthFlow() - .tokenUrl(swaggerProperties.getTokenUri()) - .authorizationUrl(swaggerProperties.getAuthUri()); + .tokenUrl(swaggerProperties.getTokenUri()) + .authorizationUrl(swaggerProperties.getAuthUri()); return new OpenAPI() - .components(new Components() - .addSecuritySchemes("security_auth", new SecurityScheme() - .name(SEC_CONFIG_NAME) - .flows(new OAuthFlows().authorizationCode(oAuthFlow)) - .type(SecurityScheme.Type.OAUTH2) - .scheme("oauth2"))) - .addSecurityItem(new SecurityRequirement().addList("security_auth")); + .components(new Components() + .addSecuritySchemes("security_auth", new SecurityScheme() + .name(SEC_CONFIG_NAME) + .flows(new OAuthFlows().authorizationCode(oAuthFlow)) + .type(SecurityScheme.Type.OAUTH2) + .scheme("oauth2"))) + .addSecurityItem(new SecurityRequirement().addList("security_auth")); } + private GroupedOpenApi getDocket(String groupName, String pathRegexp, String... packagesToScan) { return GroupedOpenApi.builder() - .group(groupName) - .packagesToScan(packagesToScan) - .pathsToMatch(pathRegexp) - .build(); + .group(groupName) + .packagesToScan(packagesToScan) + .pathsToMatch(pathRegexp) + .build(); } } diff --git a/src/main/java/org/highmed/numportal/web/controller/AdminController.java b/src/main/java/org/highmed/numportal/web/controller/AdminController.java index b2c83844..79acf81a 100644 --- a/src/main/java/org/highmed/numportal/web/controller/AdminController.java +++ b/src/main/java/org/highmed/numportal/web/controller/AdminController.java @@ -1,25 +1,28 @@ package org.highmed.numportal.web.controller; -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; import org.highmed.numportal.NumPortalApplication; -import org.highmed.numportal.domain.model.Roles; -import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.domain.dto.OrganizationDto; import org.highmed.numportal.domain.dto.SearchCriteria; import org.highmed.numportal.domain.dto.UserNameDto; +import org.highmed.numportal.domain.model.Roles; +import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.properties.NumProperties; import org.highmed.numportal.service.UserDetailsService; import org.highmed.numportal.service.UserService; import org.highmed.numportal.service.ehrbase.Pseudonymity; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; +import org.highmed.numportal.web.feign.KeycloakFeign; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; -import org.highmed.numportal.web.feign.KeycloakFeign; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -33,17 +36,30 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; @RestController @RequestMapping(value = "/admin/", produces = "application/json") @@ -67,7 +83,7 @@ public class AdminController extends CustomizedExceptionHandler { @GetMapping(value = "manuel-url", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(description = "Returns value for user manual URL") - public ResponseEntity> getExternalUrls(){ + public ResponseEntity> getExternalUrls() { Map map = new HashMap<>(); map.put("userManualUrl", numProperties.getUserManualUrl()); return ResponseEntity.ok(map); @@ -85,7 +101,7 @@ public ResponseEntity getLogLevel() { public ResponseEntity setLogLevel(@NotNull @PathVariable String logLevel) { Logger numLogger = (Logger) LoggerFactory.getLogger(NumPortalApplication.class.getPackageName()); Level level = Level.valueOf(logLevel); - numLogger.setLevel(level);//Default log level is DEBUG. If {logLevel} == Wrong Status + numLogger.setLevel(level); //Default log level is DEBUG. If {logLevel} == Wrong Status if (Level.DEBUG.equals(level) || Level.INFO.equals(level)) { // when DEBUG, logging is enabled for keycloak client Logger keycloakClient = (Logger) LoggerFactory.getLogger(KeycloakFeign.class.getName()); @@ -104,16 +120,15 @@ public void deleteUser(@AuthenticationPrincipal @NotNull Jwt principal, @PathVar @GetMapping("user/{userId}") @Operation(description = "Retrieves the information about the given user") - public ResponseEntity getUser( - @AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { + public ResponseEntity getUser(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { return ResponseEntity.ok(userService.getUserById(userId, true, principal.getSubject())); } @GetMapping("user/{userId}/role") @Operation(description = "Retrieves the roles of the given user") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN) - public ResponseEntity> getRolesOfUser( - @AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { + public ResponseEntity> getRolesOfUser(@AuthenticationPrincipal @NotNull Jwt principal, + @NotNull @PathVariable String userId) { return ResponseEntity.ok(userService.getUserRoles(userId, principal.getSubject())); } @@ -121,8 +136,8 @@ public ResponseEntity> getRol @PostMapping("user/{userId}/role") @Operation(description = "Updates the users roles to the given set.") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN) - public ResponseEntity> updateRoles(@AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @PathVariable String userId, @NotNull @RequestBody List roles) { + public ResponseEntity> updateRoles(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId, + @NotNull @RequestBody List roles) { List updatedRoles = userService.setUserRoles(userId, roles, principal.getSubject(), Roles.extractRoles(principal)); userService.addUserToCache(userId); @@ -133,9 +148,7 @@ public ResponseEntity> updateRoles(@AuthenticationPrincipal @NotNul @PostMapping("user/{userId}/organization") @Operation(description = "Sets the user organization") @PreAuthorize(Role.SUPER_ADMIN) - public ResponseEntity setOrganization( - @AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @PathVariable String userId, + public ResponseEntity setOrganization(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId, @NotNull @RequestBody OrganizationDto organization) { userDetailsService.setOrganization(principal.getSubject(), userId, organization.getId()); @@ -145,8 +158,7 @@ public ResponseEntity setOrganization( @ContextLog(type = "UserManagement", description = "Creates user details") @PostMapping("user/{userId}") @Operation(description = "Creates user details") - public ResponseEntity createUserOnFirstLogin( - @AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { + public ResponseEntity createUserOnFirstLogin(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { userDetailsService.createUserDetails(userId, principal.getClaimAsString(EMAIL_CLAIM)); return ResponseEntity.ok(SUCCESS_REPLY); @@ -155,8 +167,7 @@ public ResponseEntity createUserOnFirstLogin( @ContextLog(type = "UserManagement", description = "Update user name", dtoPrint = false) @PostMapping("user/{userId}/name") @Operation(description = "Changes user name") - public ResponseEntity changeUserName( - @AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId, + public ResponseEntity changeUserName(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId, @NotNull @Valid @RequestBody UserNameDto userName) { userService.changeUserName(userId, userName, principal.getSubject(), Roles.extractRoles(principal)); return ResponseEntity.ok(SUCCESS_REPLY); @@ -166,23 +177,21 @@ public ResponseEntity changeUserName( @PostMapping("user/{userId}/approve") @Operation(description = "Adds the given organization to the user") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN) - public ResponseEntity approveUser( - @AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { + public ResponseEntity approveUser(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId) { userDetailsService.approveUser(principal.getSubject(), userId); return ResponseEntity.ok(SUCCESS_REPLY); } - + @GetMapping("user/all") @Operation(description = "Retrieves a set of users that match the search string") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN_OR_STUDY_COORDINATOR) - public ResponseEntity> searchUsersWithPagination(@AuthenticationPrincipal @NotNull Jwt principal, @PageableDefault(size = 100) Pageable pageable, - SearchCriteria criteria) { + public ResponseEntity> searchUsersWithPagination(@AuthenticationPrincipal @NotNull Jwt principal, + @PageableDefault(size = 100) Pageable pageable, SearchCriteria criteria) { // filter[approved] true, false (optional -> omitting it returns both) // filter[search] search input (optional) // filter[withRoles] true or false (optional) // filter[enabled] true or false (optional) - return ResponseEntity.ok( - userService.searchUsers(principal.getSubject(), Roles.extractRoles(principal), criteria, pageable)); + return ResponseEntity.ok(userService.searchUsers(principal.getSubject(), Roles.extractRoles(principal), criteria, pageable)); } @@ -190,17 +199,17 @@ public ResponseEntity> searchUsersWithPagination(@AuthenticationPrinc @PostMapping("user/{userId}/status") @Operation(description = "Updates user status for active flag (enabled field in keycloak representation).") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN) - public ResponseEntity updateUserDetails(@AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @PathVariable String userId, @NotNull @RequestBody Boolean active) { + public ResponseEntity updateUserDetails(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PathVariable String userId, + @NotNull @RequestBody Boolean active) { userService.updateUserActiveField(principal.getSubject(), userId, active, Roles.extractRoles(principal)); return ResponseEntity.ok(SUCCESS_REPLY); } - + @PostMapping(path = "pseudo/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(description = "Endpoint used for testing 3rd party pseudonyms") @PreAuthorize(Role.SUPER_ADMIN) public ResponseEntity testThirdPartyPseudonyms(@AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @RequestParam("file") MultipartFile csvFile) throws IOException { + @NotNull @RequestParam("file") MultipartFile csvFile) throws IOException { List secondLevelPseudonyms = new ArrayList<>(); String header = "original,pseudonym"; try (BufferedReader reader = new BufferedReader(new InputStreamReader(csvFile.getInputStream()))) { @@ -211,10 +220,8 @@ public ResponseEntity testThirdPartyPseudonyms(@Authentic } MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE); - headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + - String.format("codex_result_%s_%s", - LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).format(DateTimeFormatter.ISO_LOCAL_DATE), - csvFile.getOriginalFilename())); + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + String.format("codex_result_%s_%s", + LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).format(DateTimeFormatter.ISO_LOCAL_DATE), csvFile.getOriginalFilename())); StreamingResponseBody streamingResponseBody = outputStream -> { BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write(header); diff --git a/src/main/java/org/highmed/numportal/web/controller/AqlController.java b/src/main/java/org/highmed/numportal/web/controller/AqlController.java index 98304e77..38bd6263 100644 --- a/src/main/java/org/highmed/numportal/web/controller/AqlController.java +++ b/src/main/java/org/highmed/numportal/web/controller/AqlController.java @@ -1,6 +1,10 @@ package org.highmed.numportal.web.controller; -import org.highmed.numportal.domain.dto.*; +import org.highmed.numportal.domain.dto.AqlCategoryDto; +import org.highmed.numportal.domain.dto.AqlDto; +import org.highmed.numportal.domain.dto.ParameterOptionsDto; +import org.highmed.numportal.domain.dto.SearchCriteria; +import org.highmed.numportal.domain.dto.SlimAqlDto; import org.highmed.numportal.domain.model.Aql; import org.highmed.numportal.domain.model.AqlCategory; import org.highmed.numportal.domain.model.Roles; @@ -8,11 +12,15 @@ import org.highmed.numportal.service.AqlService; import org.highmed.numportal.service.ehrbase.ParameterService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; import org.modelmapper.ModelMapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -22,11 +30,16 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.stream.Collectors; @@ -87,19 +100,19 @@ public void deleteAql(@AuthenticationPrincipal @NotNull Jwt principal, @NotNull @PreAuthorize(Role.MANAGER_OR_STUDY_COORDINATOR_OR_RESEARCHER_OR_CRITERIA_EDITOR) public ResponseEntity> getAqls(@AuthenticationPrincipal @NotNull Jwt principal) { return ResponseEntity.ok(aqlService.getVisibleAqls(principal.getSubject()).stream() - .map(mapper::convertToDto) - .collect(Collectors.toList())); + .map(mapper::convertToDto) + .collect(Collectors.toList())); } @GetMapping("/all") @Operation(description = "Retrieves a list of visible aqls, all owned by logged in user and all public") @PreAuthorize(Role.MANAGER_OR_STUDY_COORDINATOR_OR_RESEARCHER_OR_CRITERIA_EDITOR) public ResponseEntity> getAqls(@AuthenticationPrincipal @NotNull Jwt principal, - @PageableDefault(size = 50) Pageable pageable, SearchCriteria searchCriteria) { + @PageableDefault(size = 50) Pageable pageable, SearchCriteria searchCriteria) { Page searchResult = aqlService.getVisibleAqls(principal.getSubject(), pageable, searchCriteria); List content = searchResult.getContent().stream() - .map(mapper::convertToDto) - .collect(Collectors.toList()); + .map(mapper::convertToDto) + .collect(Collectors.toList()); return ResponseEntity.ok(new PageImpl<>(content, pageable, searchResult.getTotalElements())); } @@ -115,7 +128,7 @@ public ResponseEntity getAqlSize(@AuthenticationPrincipal @NotNull Jwt pri @Operation(description = "Creates a category. If there is an id in the DTO, it is ignored.") @PreAuthorize(Role.CRITERIA_EDITOR) public ResponseEntity createCategory(@AuthenticationPrincipal @NotNull Jwt principal, - @Valid @NotNull @RequestBody AqlCategoryDto aqlCategoryDto) { + @Valid @NotNull @RequestBody AqlCategoryDto aqlCategoryDto) { var aqlCategory = aqlService.createAqlCategory(principal.getSubject(), AqlCategory.builder().name(aqlCategoryDto.getName()).build()); return ResponseEntity.ok(modelMapper.map(aqlCategory, AqlCategoryDto.class)); @@ -126,8 +139,8 @@ public ResponseEntity createCategory(@AuthenticationPrincipal @N @Operation(description = "Updates a category. If present, the id in the DTO is ignored.") @PreAuthorize(Role.CRITERIA_EDITOR) public ResponseEntity updateCategory(@AuthenticationPrincipal @NotNull Jwt principal, - @PathVariable("id") Long categoryId, - @Valid @NotNull @RequestBody AqlCategoryDto aqlCategoryDto) { + @PathVariable("id") Long categoryId, + @Valid @NotNull @RequestBody AqlCategoryDto aqlCategoryDto) { var aqlCategory = aqlService.updateAqlCategory(principal.getSubject(), @@ -148,9 +161,9 @@ public void deleteAqlCategory(@AuthenticationPrincipal @NotNull Jwt principal, @ @Operation(description = "Retrieves the list of categories.") public ResponseEntity> getAqlCategories() { return ResponseEntity.ok( - aqlService.getAqlCategories().stream() - .map(category -> modelMapper.map(category, AqlCategoryDto.class)) - .collect(Collectors.toList())); + aqlService.getAqlCategories().stream() + .map(category -> modelMapper.map(category, AqlCategoryDto.class)) + .collect(Collectors.toList())); } @GetMapping(value = "/category/all") @@ -158,12 +171,12 @@ public ResponseEntity> getAqlCategories() { public ResponseEntity> getAqlCategories(@PageableDefault(size = 50) Pageable pageable, SearchCriteria searchCriteria) { Page searchResult = aqlService.getAqlCategories(pageable, searchCriteria); List content = searchResult.getContent().stream() - .map(category -> { - AqlCategoryDto categoryDto = modelMapper.map(category, AqlCategoryDto.class); - categoryDto.setAllowedToBeDeleted(aqlService.aqlCategoryIsAllowedToBeDeleted(category.getId())); - return categoryDto; - }) - .collect(Collectors.toList()); + .map(category -> { + AqlCategoryDto categoryDto = modelMapper.map(category, AqlCategoryDto.class); + categoryDto.setAllowedToBeDeleted(aqlService.aqlCategoryIsAllowedToBeDeleted(category.getId())); + return categoryDto; + }) + .collect(Collectors.toList()); return ResponseEntity.ok(new PageImpl<>(content, pageable, searchResult.getTotalElements())); } diff --git a/src/main/java/org/highmed/numportal/web/controller/CohortController.java b/src/main/java/org/highmed/numportal/web/controller/CohortController.java index 946d626d..8f2d1035 100644 --- a/src/main/java/org/highmed/numportal/web/controller/CohortController.java +++ b/src/main/java/org/highmed/numportal/web/controller/CohortController.java @@ -10,14 +10,15 @@ import org.highmed.numportal.service.CohortService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; import org.highmed.numportal.service.exception.dto.ErrorDetails; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; @@ -25,7 +26,14 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.*; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.WebRequest; import java.util.Map; @@ -106,9 +114,9 @@ public ResponseEntity> getSizePerTemplates( if (!Roles.extractRoles(principal).contains(Roles.MANAGER)) { sizePerTemplate = sizePerTemplate.entrySet().stream() - .collect( - Collectors.toMap( - Entry::getKey, entry -> cohortService.getRoundedSize(entry.getValue()))); + .collect( + Collectors.toMap( + Entry::getKey, entry -> cohortService.getRoundedSize(entry.getValue()))); } return ResponseEntity.ok(sizePerTemplate); @@ -129,14 +137,15 @@ public ResponseEntity getCohortGroupSizeWithDistribution( } @Override - protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, + WebRequest request) { log.warn("Request for {} failed with error message {} ", request.getDescription(false), ex.getMessage()); - Map errors = Map.of("Error", "error.missing_request_body"); + Map errors = Map.of("Error", "error.missing_request_body"); ErrorDetails errorDetails = ErrorDetails - .builder() - .message( "Request body is required" ) - .details( errors ) - .build(); - return ResponseEntity.status(status).body( errorDetails ); + .builder() + .message("Request body is required") + .details(errors) + .build(); + return ResponseEntity.status(status).body(errorDetails); } } diff --git a/src/main/java/org/highmed/numportal/web/controller/ContentController.java b/src/main/java/org/highmed/numportal/web/controller/ContentController.java index e32bacc8..57063326 100644 --- a/src/main/java/org/highmed/numportal/web/controller/ContentController.java +++ b/src/main/java/org/highmed/numportal/web/controller/ContentController.java @@ -7,6 +7,9 @@ import org.highmed.numportal.domain.model.Roles; import org.highmed.numportal.service.ContentService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -17,8 +20,6 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -27,7 +28,11 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +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 java.util.ArrayList; import java.util.List; @@ -46,8 +51,8 @@ public class ContentController extends CustomizedExceptionHandler { @GetMapping("/navigation") @Operation(description = "Retrieves a list of navigation items") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "500", description = "Internal server error") + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "500", description = "Internal server error") }) public ResponseEntity getNavigationItems() { return ResponseEntity.ok(contentService.getNavigationItems()); @@ -96,16 +101,16 @@ public ResponseEntity> getLatestProjects( @Operation(description = "Retrieves a list of navigation items") @PreAuthorize(Role.CONTENT_ADMIN) @ApiResponses({ - @ApiResponse(responseCode= "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "400", description = "Bad request"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden"), - @ApiResponse(responseCode = "500", description = "Internal server error") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "Bad request"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "403", description = "Forbidden"), + @ApiResponse(responseCode = "500", description = "Internal server error") }) public ResponseEntity setNavigationItems( @Valid @NotNull @RequestBody @Size(max = 5) List navigationItemDtos) { contentService.setNavigationItems(navigationItemDtos); - for(NavigationItemDto navigationItemDto : navigationItemDtos){ + for (NavigationItemDto navigationItemDto : navigationItemDtos) { logger.info("Set navigation item: {}", navigationItemDto); } return ResponseEntity.ok("Success"); @@ -114,8 +119,8 @@ public ResponseEntity setNavigationItems( @GetMapping("/cards") @Operation(description = "Retrieves a list of cards") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "500", description = "Internal server error") + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "500", description = "Internal server error") }) public ResponseEntity getCards() { return ResponseEntity.ok(contentService.getCards()); @@ -126,16 +131,16 @@ public ResponseEntity getCards() { @Operation(description = "Retrieves a list of cards") @PreAuthorize(Role.CONTENT_ADMIN) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "400", description = "Bad request"), - @ApiResponse(responseCode = "401", description = "Unauthorized"), - @ApiResponse(responseCode = "403", description = "Forbidden"), - @ApiResponse(responseCode = "500", description = "Internal server error") + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Bad request"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "403", description = "Forbidden"), + @ApiResponse(responseCode = "500", description = "Internal server error") }) public ResponseEntity setCards( @Valid @NotNull @RequestBody @Size(max = 8) List cardDtos) { contentService.setCards(cardDtos); - for(CardDto cardDto : cardDtos){ + for (CardDto cardDto : cardDtos) { logger.info("Set card item: {}", cardDto); } return ResponseEntity.ok("Success"); diff --git a/src/main/java/org/highmed/numportal/web/controller/NumAttachmentController.java b/src/main/java/org/highmed/numportal/web/controller/NumAttachmentController.java index 9e719add..e3afe641 100644 --- a/src/main/java/org/highmed/numportal/web/controller/NumAttachmentController.java +++ b/src/main/java/org/highmed/numportal/web/controller/NumAttachmentController.java @@ -5,23 +5,36 @@ import org.highmed.numportal.attachment.domain.model.Attachment; import org.highmed.numportal.attachment.service.AttachmentService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; + import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; import org.modelmapper.ModelMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.*; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; @@ -33,68 +46,68 @@ @ConditionalOnProperty(prefix = "num", name = "enableAttachmentDatabase", havingValue = "true") public class NumAttachmentController extends CustomizedExceptionHandler { - private final ModelMapper modelMapper; - private final AttachmentService attachmentService; + private final ModelMapper modelMapper; + private final AttachmentService attachmentService; - @ContextLog(type = "AttachmentManagement", description = "Create a new attachment") - @Operation(description = "Create a new attachment") - @PreAuthorize(Role.SUPER_ADMIN) - @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createAttachment(@AuthenticationPrincipal @NotNull Jwt principal, @RequestParam Long projectId, - @RequestParam(required = false) String description, - @NotNull @RequestPart("file") MultipartFile file) throws IOException { - attachmentService.saveAttachment(file, description, principal.getSubject(), projectId); - return ResponseEntity.ok("ok"); - } + @ContextLog(type = "AttachmentManagement", description = "Create a new attachment") + @Operation(description = "Create a new attachment") + @PreAuthorize(Role.SUPER_ADMIN) + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createAttachment(@AuthenticationPrincipal @NotNull Jwt principal, @RequestParam Long projectId, + @RequestParam(required = false) String description, + @NotNull @RequestPart("file") MultipartFile file) throws IOException { + attachmentService.saveAttachment(file, description, principal.getSubject(), projectId); + return ResponseEntity.ok("ok"); + } - @ContextLog(type = "AttachmentManagement", description = "Create multiple attachments for a project with given ID") - @Operation(description = "Create multiple attachments for a project with given ID") - @PreAuthorize(Role.STUDY_COORDINATOR) - @PostMapping(path = "/{projectId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createMultipleAttachments(@AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @PathVariable Long projectId, - @ModelAttribute @Valid LightAttachmentDto lightDto) throws IOException { - attachmentService.saveAttachments(projectId, principal.getSubject(), lightDto, false); - return ResponseEntity.ok("ok"); - } + @ContextLog(type = "AttachmentManagement", description = "Create multiple attachments for a project with given ID") + @Operation(description = "Create multiple attachments for a project with given ID") + @PreAuthorize(Role.STUDY_COORDINATOR) + @PostMapping(path = "/{projectId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createMultipleAttachments(@AuthenticationPrincipal @NotNull Jwt principal, + @NotNull @PathVariable Long projectId, + @ModelAttribute @Valid LightAttachmentDto lightDto) throws IOException { + attachmentService.saveAttachments(projectId, principal.getSubject(), lightDto, false); + return ResponseEntity.ok("ok"); + } - @Operation(description = "Get a list of all attachments for one project (by projectId)") - @GetMapping("/project/{projectId}") - public ResponseEntity> getAllAttachments(@NotNull @PathVariable Long projectId) { - return ResponseEntity.ok(attachmentService.getAttachmentsBy(projectId).stream() - .map(attachment -> modelMapper.map(attachment, AttachmentDto.class)) - .collect(Collectors.toList())); - } + @Operation(description = "Get a list of all attachments for one project (by projectId)") + @GetMapping("/project/{projectId}") + public ResponseEntity> getAllAttachments(@NotNull @PathVariable Long projectId) { + return ResponseEntity.ok(attachmentService.getAttachmentsBy(projectId).stream() + .map(attachment -> modelMapper.map(attachment, AttachmentDto.class)) + .collect(Collectors.toList())); + } - @Operation(description = "Retrieves a list of existing attachments") - @GetMapping("/all") - @PreAuthorize(Role.SUPER_ADMIN) - public ResponseEntity> getAttachments() { - return ResponseEntity.ok(attachmentService.listAttachments().stream() - .map(attachment -> modelMapper.map(attachment, AttachmentDto.class)) - .collect(Collectors.toList())); - } + @Operation(description = "Retrieves a list of existing attachments") + @GetMapping("/all") + @PreAuthorize(Role.SUPER_ADMIN) + public ResponseEntity> getAttachments() { + return ResponseEntity.ok(attachmentService.listAttachments().stream() + .map(attachment -> modelMapper.map(attachment, AttachmentDto.class)) + .collect(Collectors.toList())); + } - @ContextLog(type = "AttachmentManagement", description = "Delete attachment") - @DeleteMapping("/{attachmentId}") - @Operation(description = "Delete attachment") - @PreAuthorize(Role.SUPER_ADMIN) - public void deleteAql(@AuthenticationPrincipal @NotNull Jwt principal, @PathVariable Long attachmentId) { - attachmentService.deleteById(attachmentId, principal.getSubject()); - } + @ContextLog(type = "AttachmentManagement", description = "Delete attachment") + @DeleteMapping("/{attachmentId}") + @Operation(description = "Delete attachment") + @PreAuthorize(Role.SUPER_ADMIN) + public void deleteAql(@AuthenticationPrincipal @NotNull Jwt principal, @PathVariable Long attachmentId) { + attachmentService.deleteById(attachmentId, principal.getSubject()); + } - @ContextLog(type = "AttachmentManagement", description = "Download attachment") - @Operation(description = "Download attachment with given id") - @GetMapping("/{attachmentId}") - public ResponseEntity downloadAttachment(@NotNull @PathVariable Long attachmentId) { - Attachment attachment = attachmentService.getAttachmentById(attachmentId); - HttpHeaders header = new HttpHeaders(); - header.setContentDisposition(ContentDisposition.builder("attachment").filename(attachment.getName()).build()); - StreamingResponseBody responseBody = outputStream -> { - outputStream.write(attachment.getContent()); - outputStream.flush(); - outputStream.close(); - }; - return new ResponseEntity<>(responseBody, header, HttpStatus.OK); - } + @ContextLog(type = "AttachmentManagement", description = "Download attachment") + @Operation(description = "Download attachment with given id") + @GetMapping("/{attachmentId}") + public ResponseEntity downloadAttachment(@NotNull @PathVariable Long attachmentId) { + Attachment attachment = attachmentService.getAttachmentById(attachmentId); + HttpHeaders header = new HttpHeaders(); + header.setContentDisposition(ContentDisposition.builder("attachment").filename(attachment.getName()).build()); + StreamingResponseBody responseBody = outputStream -> { + outputStream.write(attachment.getContent()); + outputStream.flush(); + outputStream.close(); + }; + return new ResponseEntity<>(responseBody, header, HttpStatus.OK); + } } diff --git a/src/main/java/org/highmed/numportal/web/controller/OrganizationController.java b/src/main/java/org/highmed/numportal/web/controller/OrganizationController.java index d76b62a8..94b52ab2 100644 --- a/src/main/java/org/highmed/numportal/web/controller/OrganizationController.java +++ b/src/main/java/org/highmed/numportal/web/controller/OrganizationController.java @@ -1,17 +1,20 @@ package org.highmed.numportal.web.controller; -import org.highmed.numportal.domain.model.Organization; -import org.highmed.numportal.domain.model.Roles; import org.highmed.numportal.domain.dto.OrganizationDto; import org.highmed.numportal.domain.dto.SearchCriteria; +import org.highmed.numportal.domain.model.Organization; +import org.highmed.numportal.domain.model.Roles; import org.highmed.numportal.mapper.OrganizationMapper; import org.highmed.numportal.service.OrganizationService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -20,10 +23,15 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.stream.Collectors; @@ -52,27 +60,27 @@ public ResponseEntity getOrganizationById(@NotNull @PathVariabl @Operation(description = "Retrieves a list of available organizations") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN) public ResponseEntity> getAllOrganizations( - @AuthenticationPrincipal @NotNull Jwt principal) { + @AuthenticationPrincipal @NotNull Jwt principal) { return ResponseEntity.ok(organizationService - .getAllOrganizations(Roles.extractRoles(principal), principal.getSubject()) - .stream() - .map(mapper::convertToDto) - .collect(Collectors.toList())); + .getAllOrganizations(Roles.extractRoles(principal), principal.getSubject()) + .stream() + .map(mapper::convertToDto) + .collect(Collectors.toList())); } @GetMapping("/all") @Operation(description = "Retrieves a list of available organizations") @PreAuthorize(Role.SUPER_ADMIN_OR_ORGANIZATION_ADMIN) public ResponseEntity> getOrganizations(@AuthenticationPrincipal @NotNull Jwt principal, - @PageableDefault(size = 20) Pageable pageable, - SearchCriteria criteria) { + @PageableDefault(size = 20) Pageable pageable, + SearchCriteria criteria) { Page organizationPage = organizationService - .getAllOrganizations(Roles.extractRoles(principal), principal.getSubject(), criteria, pageable); + .getAllOrganizations(Roles.extractRoles(principal), principal.getSubject(), criteria, pageable); List content = organizationPage.getContent() - .stream() - .map(mapper::convertToDto) - .collect(Collectors.toList()); + .stream() + .map(mapper::convertToDto) + .collect(Collectors.toList()); return ResponseEntity.ok(new PageImpl<>(content, pageable, organizationPage.getTotalElements())); } @@ -96,11 +104,11 @@ public ResponseEntity updateOrganization(@AuthenticationPrincip @Valid @NotNull @RequestBody OrganizationDto organizationDto) { OrganizationDto updatedOrganizationDto = mapper.convertToDto( - organizationService.update( - organizationId, - organizationDto, - Roles.extractRoles(principal), - principal.getSubject())); + organizationService.update( + organizationId, + organizationDto, + Roles.extractRoles(principal), + principal.getSubject())); return ResponseEntity.ok(updatedOrganizationDto); } @@ -110,7 +118,7 @@ public ResponseEntity updateOrganization(@AuthenticationPrincip @DeleteMapping(value = "/{id}") @PreAuthorize(Role.SUPER_ADMIN) public void deleteOrganization(@AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @PathVariable("id") Long organizationId) { + @NotNull @PathVariable("id") Long organizationId) { organizationService.deleteOrganization(organizationId, principal.getSubject()); } } diff --git a/src/main/java/org/highmed/numportal/web/controller/ProfileController.java b/src/main/java/org/highmed/numportal/web/controller/ProfileController.java index 5f0b268f..58848c7b 100644 --- a/src/main/java/org/highmed/numportal/web/controller/ProfileController.java +++ b/src/main/java/org/highmed/numportal/web/controller/ProfileController.java @@ -3,7 +3,9 @@ import org.highmed.numportal.domain.model.admin.User; import org.highmed.numportal.service.UserService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; + import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -12,8 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import jakarta.validation.constraints.NotNull; - @RestController @RequestMapping(value = "/profile", produces = "application/json") @AllArgsConstructor diff --git a/src/main/java/org/highmed/numportal/web/controller/ProjectController.java b/src/main/java/org/highmed/numportal/web/controller/ProjectController.java index 0a2e30e7..6b9afeef 100644 --- a/src/main/java/org/highmed/numportal/web/controller/ProjectController.java +++ b/src/main/java/org/highmed/numportal/web/controller/ProjectController.java @@ -1,6 +1,11 @@ package org.highmed.numportal.web.controller; -import org.highmed.numportal.domain.dto.*; +import org.highmed.numportal.domain.dto.CommentDto; +import org.highmed.numportal.domain.dto.ManagerProjectDto; +import org.highmed.numportal.domain.dto.ProjectDto; +import org.highmed.numportal.domain.dto.ProjectViewDto; +import org.highmed.numportal.domain.dto.RawQueryDto; +import org.highmed.numportal.domain.dto.SearchCriteria; import org.highmed.numportal.domain.model.Comment; import org.highmed.numportal.domain.model.ExportType; import org.highmed.numportal.domain.model.Project; @@ -12,14 +17,15 @@ import org.highmed.numportal.service.ProjectService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; import org.highmed.numportal.service.exception.ResourceNotFound; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.web.config.Role; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import org.highmed.numportal.service.logger.ContextLog; -import org.highmed.numportal.web.config.Role; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -33,7 +39,16 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @@ -60,12 +75,13 @@ public class ProjectController extends CustomizedExceptionHandler { @GetMapping("/all") @Operation(description = "Retrieves a list of projects the user is allowed to see") @PreAuthorize(Role.STUDY_COORDINATOR_OR_RESEARCHER_OR_APPROVER) - public ResponseEntity> getProjects(@AuthenticationPrincipal @NotNull Jwt principal, @PageableDefault(size = 100) Pageable pageable, SearchCriteria criteria) { + public ResponseEntity> getProjects(@AuthenticationPrincipal @NotNull Jwt principal, + @PageableDefault(size = 100) Pageable pageable, SearchCriteria criteria) { Page projectPage = projectService.getProjects(principal.getSubject(), Roles.extractRoles(principal), criteria, pageable); - List content = projectPage.getContent() - .stream() - .map(projectViewMapper::convertToDto) - .collect(Collectors.toList()); + List content = projectPage.getContent() + .stream() + .map(projectViewMapper::convertToDto) + .collect(Collectors.toList()); return ResponseEntity.ok(new PageImpl<>(content, pageable, projectPage.getTotalElements())); } @@ -73,7 +89,7 @@ public ResponseEntity> getProjects(@AuthenticationPrincipal @Operation(description = "Retrieves a project by id") @PreAuthorize(Role.STUDY_COORDINATOR_OR_RESEARCHER_OR_APPROVER) public ResponseEntity getProjectById(@AuthenticationPrincipal @NotNull Jwt principal, - @NotNull @PathVariable Long id) { + @NotNull @PathVariable Long id) { Optional project = projectService.getProjectById(principal.getSubject(), id); if (project.isEmpty()) { @@ -102,16 +118,16 @@ public ResponseEntity createProject( @ContextLog(type = "ProjektManagement", description = "Create multipart project") @PostMapping(path = "/new", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) @Operation( - description = "Creates a project; the logged in user is assigned as coordinator of the project") + description = "Creates a project; the logged in user is assigned as coordinator of the project") @PreAuthorize(Role.STUDY_COORDINATOR) public ResponseEntity createMultipartProject( - @AuthenticationPrincipal @NotNull Jwt principal, - @Valid @NotNull @RequestPart ProjectDto projectDto, - @RequestPart(value = "files", required = false) @Valid MultipartFile[] files) { + @AuthenticationPrincipal @NotNull Jwt principal, + @Valid @NotNull @RequestPart ProjectDto projectDto, + @RequestPart(value = "files", required = false) @Valid MultipartFile[] files) { Project project = - projectService.createMultipartProject( - projectDto, principal.getSubject(), Roles.extractRoles(principal), files); + projectService.createMultipartProject( + projectDto, principal.getSubject(), Roles.extractRoles(principal), files); return ResponseEntity.ok(projectMapper.convertToDto(project)); } @@ -119,7 +135,7 @@ public ResponseEntity createMultipartProject( @ContextLog(type = "ProjektManagement", description = "Update project") @PutMapping(value = "/{id}") @Operation(description = - "Updates a project; the logged in user is assigned as coordinator of the project at creation time") + "Updates a project; the logged in user is assigned as coordinator of the project at creation time") @PreAuthorize(Role.STUDY_COORDINATOR_OR_APPROVER) public ResponseEntity updateProject( @AuthenticationPrincipal @NotNull Jwt principal, @@ -136,17 +152,17 @@ public ResponseEntity updateProject( @ContextLog(type = "ProjektManagement", description = "Update multipart project") @PutMapping(value = "/new/{id}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) @Operation(description = - "Updates a project; the logged in user is assigned as coordinator of the project at creation time") + "Updates a project; the logged in user is assigned as coordinator of the project at creation time") @PreAuthorize(Role.STUDY_COORDINATOR_OR_APPROVER) public ResponseEntity updateMultipartProject( - @AuthenticationPrincipal @NotNull Jwt principal, - @PathVariable("id") Long projectId, - @Valid @NotNull @RequestPart ProjectDto projectDto, - @RequestPart(value = "files", required = false) @Valid MultipartFile[] files) { + @AuthenticationPrincipal @NotNull Jwt principal, + @PathVariable("id") Long projectId, + @Valid @NotNull @RequestPart ProjectDto projectDto, + @RequestPart(value = "files", required = false) @Valid MultipartFile[] files) { Project project = - projectService.updateMultipartProject( - projectDto, projectId, principal.getSubject(), Roles.extractRoles(principal), files); + projectService.updateMultipartProject( + projectDto, projectId, principal.getSubject(), Roles.extractRoles(principal), files); return ResponseEntity.ok(projectMapper.convertToDto(project)); } @@ -187,8 +203,8 @@ public ResponseEntity exportResults( @NotNull @PathVariable Long projectId, @RequestParam(required = false) Boolean defaultConfiguration, @RequestParam(required = false) - @Parameter( description = "A string defining the output format. Valid values are 'csv' and 'json'. Default is csv.") - ExportType format) { + @Parameter(description = "A string defining the output format. Valid values are 'csv' and 'json'. Default is csv.") + ExportType format) { StreamingResponseBody streamingResponseBody = projectService.getExportResponseBody( query.getQuery(), projectId, principal.getSubject(), format, defaultConfiguration); @@ -205,7 +221,7 @@ public ResponseEntity exportManagerResults( @RequestBody @Valid ManagerProjectDto managerProjectDto, @RequestParam(required = false) @Parameter(description = "A string defining the output format. Valid values are 'csv' and 'json'. Default is csv.") - ExportType format) { + ExportType format) { StreamingResponseBody streamingResponseBody = projectService.getManagerExportResponseBody( managerProjectDto.getCohort(), managerProjectDto.getTemplates(), principal.getSubject(), @@ -223,8 +239,8 @@ public ResponseEntity> getComments( @NotNull @PathVariable Long projectId) { return ResponseEntity.ok( commentService.getComments(projectId, principal.getSubject()).stream() - .map(commentMapper::convertToDto) - .collect(Collectors.toList())); + .map(commentMapper::convertToDto) + .collect(Collectors.toList())); } @ContextLog(type = "ProjektManagement", description = "Add comment") @@ -300,12 +316,13 @@ public ResponseEntity getProjectInfoPDF( @NotNull @PathVariable Long id, @RequestParam @Parameter(description = "The language the document should be returned in (en/de)") - String locale) { + String locale) { byte[] docBytes = projectService.getInfoDocBytes(id, principal.getSubject(), new Locale(locale)); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); - headers.add(HttpHeaders.CONTENT_DISPOSITION, + headers.add( + HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=Project_" + id + ".txt"); return new ResponseEntity<>(docBytes, headers, HttpStatus.OK); } diff --git a/src/main/java/org/highmed/numportal/web/controller/TemplateController.java b/src/main/java/org/highmed/numportal/web/controller/TemplateController.java index 2c7f574a..36520682 100644 --- a/src/main/java/org/highmed/numportal/web/controller/TemplateController.java +++ b/src/main/java/org/highmed/numportal/web/controller/TemplateController.java @@ -3,7 +3,9 @@ import org.highmed.numportal.domain.dto.TemplateMetadataDto; import org.highmed.numportal.service.TemplateService; import org.highmed.numportal.service.exception.CustomizedExceptionHandler; + import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -12,7 +14,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import jakarta.validation.constraints.NotNull; import java.util.List; @Controller diff --git a/src/main/java/org/highmed/numportal/web/feign/KeycloakFeign.java b/src/main/java/org/highmed/numportal/web/feign/KeycloakFeign.java index 8bb99e12..576b24a7 100644 --- a/src/main/java/org/highmed/numportal/web/feign/KeycloakFeign.java +++ b/src/main/java/org/highmed/numportal/web/feign/KeycloakFeign.java @@ -3,12 +3,18 @@ import org.highmed.numportal.domain.model.admin.Role; import org.highmed.numportal.domain.model.admin.User; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + import java.util.Map; import java.util.Set; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.*; - @FeignClient(name = "keycloak", url = "${userstore.url}") public interface KeycloakFeign { @@ -22,7 +28,7 @@ public interface KeycloakFeign { Set getRolesOfUser(@PathVariable String userId); @GetMapping("/users/count") - Long countUsers( @RequestParam("enabled") boolean enabled); + Long countUsers(@RequestParam("enabled") boolean enabled); @PostMapping("/users/{userId}/role-mappings/realm") void addRoles(@PathVariable String userId, @RequestBody Role[] role); diff --git a/src/main/java/org/highmed/numportal/web/feign/config/ClientTokenProvider.java b/src/main/java/org/highmed/numportal/web/feign/config/ClientTokenProvider.java index 87bfa232..891cd012 100644 --- a/src/main/java/org/highmed/numportal/web/feign/config/ClientTokenProvider.java +++ b/src/main/java/org/highmed/numportal/web/feign/config/ClientTokenProvider.java @@ -1,7 +1,5 @@ package org.highmed.numportal.web.feign.config; -import java.util.Collection; - import lombok.AllArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -11,6 +9,8 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.stereotype.Component; +import java.util.Collection; + @Component @AllArgsConstructor public class ClientTokenProvider implements TokenProvider { diff --git a/src/main/java/org/highmed/numportal/web/feign/config/OAuth2Configuration.java b/src/main/java/org/highmed/numportal/web/feign/config/OAuth2Configuration.java index 5e37f65f..128ad22e 100644 --- a/src/main/java/org/highmed/numportal/web/feign/config/OAuth2Configuration.java +++ b/src/main/java/org/highmed/numportal/web/feign/config/OAuth2Configuration.java @@ -1,7 +1,5 @@ package org.highmed.numportal.web.feign.config; -import java.util.ArrayList; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper; @@ -16,6 +14,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import java.util.ArrayList; +import java.util.List; + @Configuration @EnableConfigurationProperties(OAuth2ClientProperties.class) @Slf4j diff --git a/src/test/java/org/highmed/numportal/attachment/service/FileScanServiceTest.java b/src/test/java/org/highmed/numportal/attachment/service/FileScanServiceTest.java index ad854844..64a36d24 100644 --- a/src/test/java/org/highmed/numportal/attachment/service/FileScanServiceTest.java +++ b/src/test/java/org/highmed/numportal/attachment/service/FileScanServiceTest.java @@ -1,7 +1,5 @@ package org.highmed.numportal.attachment.service; -import org.highmed.numportal.attachment.service.ClamAVService; -import org.highmed.numportal.attachment.service.FileScanService; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; diff --git a/src/test/java/org/highmed/numportal/domain/specification/AqlSpecificationTest.java b/src/test/java/org/highmed/numportal/domain/specification/AqlSpecificationTest.java index a43fee32..a8b99d27 100644 --- a/src/test/java/org/highmed/numportal/domain/specification/AqlSpecificationTest.java +++ b/src/test/java/org/highmed/numportal/domain/specification/AqlSpecificationTest.java @@ -1,7 +1,6 @@ package org.highmed.numportal.domain.specification; import jakarta.persistence.criteria.*; -import org.highmed.numportal.domain.specification.AqlSpecification; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; diff --git a/src/test/java/org/highmed/numportal/domain/specification/ProjectSpecificationTest.java b/src/test/java/org/highmed/numportal/domain/specification/ProjectSpecificationTest.java index 19bce4dd..9fa87536 100644 --- a/src/test/java/org/highmed/numportal/domain/specification/ProjectSpecificationTest.java +++ b/src/test/java/org/highmed/numportal/domain/specification/ProjectSpecificationTest.java @@ -1,7 +1,6 @@ package org.highmed.numportal.domain.specification; import jakarta.persistence.criteria.*; -import org.highmed.numportal.domain.specification.ProjectSpecification; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java index 6963186c..6371bad3 100644 --- a/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java @@ -17,7 +17,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.highmed.numportal.domain.dto.ProjectDto; -import org.highmed.numportal.domain.dto.ProjectViewTO; +import org.highmed.numportal.domain.dto.ProjectViewDto; import org.highmed.numportal.domain.dto.TemplateInfoDto; import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.domain.model.Project; @@ -243,7 +243,7 @@ public void studyCoordinatorShouldGetAllHisProjects() { MvcResult result = mockMvc.perform(get(PROJECT_PATH + "/all").with(csrf())).andExpect(status().isOk()).andReturn(); - Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); + Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); assertEquals(8, projectsPage.getContent().size()); } @@ -262,9 +262,9 @@ public void studyCoordinatorShouldGetAllHisProjectsPaginated() { .with(csrf())) .andExpect(status().isOk()) .andReturn(); - Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); + Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); assertEquals(8, projectsPage.getContent().size()); - List projects = projectsPage.getContent(); + List projects = projectsPage.getContent(); assertEquals("approved", projects.get(0).getName()); } @@ -281,11 +281,11 @@ public void researcherInAStudyShouldGetPublishedProjects() { .param("page","0") .param("size", "10") .with(csrf())).andExpect(status().isOk()).andReturn(); - Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); + Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); assertEquals(1, projectsPage.getContent().size()); assertNotNull( projectsPage.getContent().stream() - .filter(projectViewTO -> projectViewTO.getName().equals("published")) + .filter(projectViewDto -> projectViewDto.getName().equals("published")) .findFirst() .orElse(null)); } @@ -299,7 +299,7 @@ public void researcherNotInAStudyShouldNotGetProjects() { MvcResult result = mockMvc.perform(get(PROJECT_PATH + "/all").with(csrf())).andExpect(status().isOk()).andReturn(); - Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); + Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); assertEquals(0, projectsPage.getContent().size()); } @@ -312,7 +312,7 @@ public void studyApproverShouldGetAllExceptDraftAndArchivedProjects() { MvcResult result = mockMvc.perform(get(PROJECT_PATH + "/all").with(csrf())).andExpect(status().isOk()).andReturn(); - Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); + Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); assertEquals(7, projectsPage.getContent().size()); assertNotNull(projectsPage.getContent().stream() .filter(projectDto -> projectDto.getName().equals("pending")) @@ -373,7 +373,7 @@ public void studyCoordinatorShouldNotGetOtherCoordinatorsProjectsExceptProperSta MvcResult result = mockMvc.perform(get(PROJECT_PATH + "/all").with(csrf())).andExpect(status().isOk()).andReturn(); - Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); + Page projectsPage = mapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() {}); assertEquals(3, projectsPage.getContent().size()); } diff --git a/src/test/java/org/highmed/numportal/mapper/ProjectViewMapperTest.java b/src/test/java/org/highmed/numportal/mapper/ProjectViewMapperTest.java index a935eacb..44b2ffea 100644 --- a/src/test/java/org/highmed/numportal/mapper/ProjectViewMapperTest.java +++ b/src/test/java/org/highmed/numportal/mapper/ProjectViewMapperTest.java @@ -1,6 +1,5 @@ package org.highmed.numportal.mapper; -import org.highmed.numportal.mapper.ProjectViewMapper; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -10,7 +9,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import org.modelmapper.ModelMapper; -import org.highmed.numportal.domain.dto.ProjectViewTO; +import org.highmed.numportal.domain.dto.ProjectViewDto; import org.highmed.numportal.domain.model.Project; import org.highmed.numportal.domain.model.ProjectStatus; import org.highmed.numportal.domain.model.admin.UserDetails; @@ -43,14 +42,14 @@ public void shouldCorrectlyConvertProjectToProjectViewTO() { .endDate(LocalDate.now()) .coordinator(UserDetails.builder().userId("userId").build()) .build(); - ProjectViewTO projectViewTO = projectViewMapper.convertToDto(project); - - Assert.assertNotNull(projectViewTO); - assertThat(projectViewTO.getId(), is(project.getId())); - assertThat(projectViewTO.getName(), is(project.getName())); - assertThat(projectViewTO.getStatus(), is(project.getStatus())); - assertThat(projectViewTO.getStartDate(), is(project.getStartDate())); - assertThat(projectViewTO.getEndDate(), is(project.getEndDate())); + ProjectViewDto projectViewDto = projectViewMapper.convertToDto(project); + + Assert.assertNotNull(projectViewDto); + assertThat(projectViewDto.getId(), is(project.getId())); + assertThat(projectViewDto.getName(), is(project.getName())); + assertThat(projectViewDto.getStatus(), is(project.getStatus())); + assertThat(projectViewDto.getStartDate(), is(project.getStartDate())); + assertThat(projectViewDto.getEndDate(), is(project.getEndDate())); Mockito.verify(userService, Mockito.times(1)).getOwner("userId"); } } From ec48f2d7bb0f830d6c4909fccb535ebcfeaff52e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:45:16 +0200 Subject: [PATCH 19/45] Bump ch.qos.logback:logback-classic from 1.5.9 to 1.5.10 (#628) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.9 to 1.5.10. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.9...v_1.5.10) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 25f6bd29..bbaf871a 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.0.5 8.0 - 1.5.9 + 1.5.10 3.2.1 2.6.0 17 From bc1e61bbb14c0d1f00657f5c16860974a91e8971 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:50:52 +0200 Subject: [PATCH 20/45] Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.5.0 to 3.5.1 (#624) Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.5.0 to 3.5.1. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.0...surefire-3.5.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bbaf871a..38ded08e 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 17 1.12.0 0.8.12 - 3.5.0 + 3.5.1 3.5.1 3.10.8 4.9.10 From ac584125c6303413f053c39dc4293c6e07367126 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:28:37 +0200 Subject: [PATCH 21/45] Bump ch.qos.logback:logback-classic from 1.5.10 to 1.5.11 (#629) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.10 to 1.5.11. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.10...v_1.5.11) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 38ded08e..bbdc1b1b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.0.5 8.0 - 1.5.10 + 1.5.11 3.2.1 2.6.0 17 From 57e999f6b441712fc5c4d4e8bf3b863e2618ef8e Mon Sep 17 00:00:00 2001 From: rabeaM Date: Mon, 28 Oct 2024 09:50:04 +0100 Subject: [PATCH 22/45] Ignore Test --- .../tests/CohortControllerIT.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java index f379583d..6201ebb8 100644 --- a/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java @@ -7,7 +7,14 @@ import org.highmed.numportal.domain.dto.CohortAqlDto; import org.highmed.numportal.domain.dto.CohortDto; import org.highmed.numportal.domain.dto.CohortGroupDto; -import org.highmed.numportal.domain.model.*; +import org.highmed.numportal.domain.model.Aql; +import org.highmed.numportal.domain.model.Cohort; +import org.highmed.numportal.domain.model.Operator; +import org.highmed.numportal.domain.model.Project; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.model.CohortAql; +import org.highmed.numportal.domain.model.CohortGroup; +import org.highmed.numportal.domain.model.Type; import org.highmed.numportal.domain.model.admin.UserDetails; import org.highmed.numportal.domain.repository.AqlRepository; import org.highmed.numportal.domain.repository.CohortRepository; @@ -103,11 +110,11 @@ public void shouldAccessCohortApiWithRightRole() { .parameters(Map.of("Geburtsdatum", "1982-06-08")) .build(); Cohort cohort = Cohort.builder() - .name("Geburtsdatum cohort") - .project(projectRepository.findById(saved.getId()).get()) - .cohortGroup(cohortGroup) - .description("just testing") - .build(); + .name("Geburtsdatum cohort") + .project(projectRepository.findById(saved.getId()).get()) + .cohortGroup(cohortGroup) + .description("just testing") + .build(); cohort = cohortRepository.save(cohort); Long id = cohort.getId(); mockMvc.perform(get(String.format("%s/%s", COHORT_PATH, id))).andExpect(status().isOk()); @@ -142,6 +149,7 @@ public void shouldHandleNotApprovedUserWhenSavingCohort() { } @Test + @Ignore @SneakyThrows @WithMockNumUser(roles = {STUDY_COORDINATOR}) public void shouldHandleInvalidCohort() { From da5dbddd2e0b0660cbd988583863da2c21c05a84 Mon Sep 17 00:00:00 2001 From: rabeaM Date: Mon, 28 Oct 2024 10:16:33 +0100 Subject: [PATCH 23/45] Ignore Test --- .../numportal/integrationtesting/tests/CohortControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java index 6201ebb8..65f7deea 100644 --- a/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/CohortControllerIT.java @@ -122,6 +122,7 @@ public void shouldAccessCohortApiWithRightRole() { @Test @SneakyThrows + @Ignore @WithMockNumUser( userId = UNAUTHORIZED_USER_ID, roles = {STUDY_COORDINATOR}) @@ -149,7 +150,6 @@ public void shouldHandleNotApprovedUserWhenSavingCohort() { } @Test - @Ignore @SneakyThrows @WithMockNumUser(roles = {STUDY_COORDINATOR}) public void shouldHandleInvalidCohort() { From 608679fc021c7f1a249e197ca523bc4e787d8d5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:37:47 +0100 Subject: [PATCH 24/45] Bump org.apache.maven.plugins:maven-checkstyle-plugin (#635) Bumps [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) from 3.5.0 to 3.6.0. - [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.5.0...maven-checkstyle-plugin-3.6.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bbdc1b1b..12c64bf6 100644 --- a/pom.xml +++ b/pom.xml @@ -469,7 +469,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.5.0 + 3.6.0 com.puppycrawl.tools From 079a1452784e5acc3091ad49728e8bf20cfac7d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:50:59 +0100 Subject: [PATCH 25/45] Bump ch.qos.logback:logback-classic from 1.5.11 to 1.5.12 (#637) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.11 to 1.5.12. - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.11...v_1.5.12) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 12c64bf6..c4c496ae 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.0.5 8.0 - 1.5.11 + 1.5.12 3.2.1 2.6.0 17 From 81edac71b0fdb713e7b3e777dcbbb8e815b9ad50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:51:16 +0100 Subject: [PATCH 26/45] Bump com.puppycrawl.tools:checkstyle from 10.18.2 to 10.19.0 (#636) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 10.18.2 to 10.19.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.18.2...checkstyle-10.19.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c4c496ae..68f74509 100644 --- a/pom.xml +++ b/pom.xml @@ -474,7 +474,7 @@ com.puppycrawl.tools checkstyle - 10.18.2 + 10.19.0 From e55a82b48e82a77f18fcf34e14a7c56ff3c39d44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:51:35 +0100 Subject: [PATCH 27/45] Bump org.apache.maven.plugins:maven-dependency-plugin (#634) Bumps [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin) from 3.8.0 to 3.8.1. - [Release notes](https://github.com/apache/maven-dependency-plugin/releases) - [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.8.0...maven-dependency-plugin-3.8.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-dependency-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 68f74509..be9cd007 100644 --- a/pom.xml +++ b/pom.xml @@ -278,7 +278,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.8.0 + 3.8.1 copy-dependencies From 74f287c36e38f82325064d069acca4e20558783f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:51:54 +0100 Subject: [PATCH 28/45] Bump org.springframework.boot:spring-boot-starter-parent (#633) Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.3.4 to 3.3.5. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.3.4...v3.3.5) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index be9cd007..aecba4d5 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.4 + 3.3.5 From 923ba9be45302fb4d9a2c573f5e3b4dfe7fd9066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:52:12 +0100 Subject: [PATCH 29/45] Bump testcontainers.version from 1.20.2 to 1.20.3 (#632) Bumps `testcontainers.version` from 1.20.2 to 1.20.3. Updates `org.testcontainers:postgresql` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.20.2...1.20.3) Updates `org.testcontainers:mockserver` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.20.2...1.20.3) --- updated-dependencies: - dependency-name: org.testcontainers:postgresql dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.testcontainers:mockserver dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aecba4d5..618390b1 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ false 42.7.4 6.8.3 - 1.20.2 + 1.20.3 5.15.0 false 2023.0.3 From 8e44b9c87dc596acf9559c1c0f2f26b21f422b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:44:22 +0100 Subject: [PATCH 30/45] Bump ehr-sdk.version from 2.18.0 to 2.19.0 (#638) Bumps `ehr-sdk.version` from 2.18.0 to 2.19.0. Updates `org.ehrbase.openehr.sdk:client` from 2.18.0 to 2.19.0 Updates `org.ehrbase.openehr.sdk:aql` from 2.18.0 to 2.19.0 --- updated-dependencies: - dependency-name: org.ehrbase.openehr.sdk:client dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.ehrbase.openehr.sdk:aql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 618390b1..7806df25 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 4.4 2.17.0 - 2.18.0 + 2.19.0 1.0.5 8.0 From 186a67472245f175bb350d22b0ded20052da59d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:33:06 +0100 Subject: [PATCH 31/45] Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.5.1 to 3.5.2 (#641) Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.5.1 to 3.5.2. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.1...surefire-3.5.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7806df25..1abdf6aa 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 17 1.12.0 0.8.12 - 3.5.1 + 3.5.2 3.5.1 3.10.8 4.9.10 From 9b086ea5c840dd73b3ed1a1b7f71066ad8b9b8fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:34:43 +0100 Subject: [PATCH 32/45] Bump com.puppycrawl.tools:checkstyle from 10.19.0 to 10.20.0 (#639) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 10.19.0 to 10.20.0. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.19.0...checkstyle-10.20.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1abdf6aa..7c669dc8 100644 --- a/pom.xml +++ b/pom.xml @@ -474,7 +474,7 @@ com.puppycrawl.tools checkstyle - 10.19.0 + 10.20.0 From a9406f25978228e2b1f2b349ec1783300d1273da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:49:20 +0100 Subject: [PATCH 33/45] Bump surefire.version from 3.5.1 to 3.5.2 (#640) Bumps `surefire.version` from 3.5.1 to 3.5.2. Updates `org.apache.maven.surefire:surefire-junit47` from 3.5.1 to 3.5.2 Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.1 to 3.5.2 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.1...surefire-3.5.2) --- updated-dependencies: - dependency-name: org.apache.maven.surefire:surefire-junit47 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7c669dc8..82810e70 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 1.12.0 0.8.12 3.5.2 - 3.5.1 + 3.5.2 3.10.8 4.9.10 false From bbceb2cd6edcb6855dabbf1e4497a28c4af841f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:42:45 +0100 Subject: [PATCH 34/45] Bump com.puppycrawl.tools:checkstyle from 10.20.0 to 10.20.1 (#643) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 10.20.0 to 10.20.1. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.20.0...checkstyle-10.20.1) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 82810e70..f8e7c715 100644 --- a/pom.xml +++ b/pom.xml @@ -474,7 +474,7 @@ com.puppycrawl.tools checkstyle - 10.20.0 + 10.20.1 From 5b69251b0b472068b0deb3d1defabe977fb272d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:43:22 +0100 Subject: [PATCH 35/45] Bump ehr-sdk.version from 2.19.0 to 2.20.0 (#642) Bumps `ehr-sdk.version` from 2.19.0 to 2.20.0. Updates `org.ehrbase.openehr.sdk:client` from 2.19.0 to 2.20.0 Updates `org.ehrbase.openehr.sdk:aql` from 2.19.0 to 2.20.0 --- updated-dependencies: - dependency-name: org.ehrbase.openehr.sdk:client dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.ehrbase.openehr.sdk:aql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f8e7c715..d3615b58 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 4.4 2.17.0 - 2.19.0 + 2.20.0 1.0.5 8.0 From 2833e64f0c8e42b7c95ccc99bce7919689587c1a Mon Sep 17 00:00:00 2001 From: Andreas Date: Tue, 19 Nov 2024 13:34:21 +0100 Subject: [PATCH 36/45] Use SELECT COUNT to get number of patients (#645) instead of returning all IDs and using size(). --- .../highmed/numportal/service/AqlService.java | 10 ++-- .../service/ehrbase/EhrBaseService.java | 46 +++++++++++++++++++ .../numportal/service/AqlServiceTest.java | 4 +- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/highmed/numportal/service/AqlService.java b/src/main/java/org/highmed/numportal/service/AqlService.java index 2b2f0512..fdbca8ac 100644 --- a/src/main/java/org/highmed/numportal/service/AqlService.java +++ b/src/main/java/org/highmed/numportal/service/AqlService.java @@ -250,19 +250,19 @@ public long getAqlSize(SlimAqlDto aql, String userId) { validateQuery(aql.getQuery()); - Set ehrIds; + int numberOfPatients; try { - ehrIds = - ehrBaseService.retrieveEligiblePatientIds(Aql.builder().query(aql.getQuery()).build()); + numberOfPatients = + ehrBaseService.retrieveNumberOfPatients(Aql.builder().query(aql.getQuery()).build()); } catch (AqlParseException e) { throw new BadRequestException(AqlParseException.class, e.getLocalizedMessage(), e.getMessage()); } - if (ehrIds.size() < privacyProperties.getMinHits()) { + if (numberOfPatients < privacyProperties.getMinHits()) { log.warn(TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS); throw new PrivacyException(AqlService.class, TOO_FEW_MATCHES_RESULTS_WITHHELD_FOR_PRIVACY_REASONS); } - return ehrIds.size(); + return numberOfPatients; } public List getAqlCategories() { diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java b/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java index 8e8f79c2..1db0a363 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java @@ -11,6 +11,7 @@ import org.apache.commons.lang3.StringUtils; import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; import org.ehrbase.openehr.sdk.aql.dto.containment.ContainmentClassExpression; +import org.ehrbase.openehr.sdk.aql.dto.operand.CountDistinctAggregateFunction; import org.ehrbase.openehr.sdk.aql.dto.operand.IdentifiedPath; import org.ehrbase.openehr.sdk.aql.dto.path.AqlObjectPath; import org.ehrbase.openehr.sdk.aql.dto.select.SelectExpression; @@ -118,6 +119,51 @@ public Set retrieveEligiblePatientIds(String query) { } } + /** + * Retrieves the number of patients for the given aql + * + * @param aql The aql to retrieve patient ids for + * @return number of patients + * @throws WrongStatusCodeException in case if a malformed aql + */ + public int retrieveNumberOfPatients(Aql aql) { + return retrieveNumberOfPatients(aql.getQuery()); + } + + public int retrieveNumberOfPatients(String query) { + log.debug("EhrBase retrieve number of patients for query: {} ", query); + AqlQuery dto = AqlQueryParser.parse(query); + SelectExpression selectExpression = new SelectExpression(); + + var count = new CountDistinctAggregateFunction(); + selectExpression.setColumnExpression(count); + + IdentifiedPath ehrIdPath = new IdentifiedPath(); + ehrIdPath.setPath(AqlObjectPath.parse(AqlQueryConstants.EHR_ID_PATH)); + + ContainmentClassExpression containmentClassExpression = new ContainmentClassExpression(); + containmentClassExpression.setType(AqlQueryConstants.EHR_TYPE); + containmentClassExpression.setIdentifier(AqlQueryConstants.EHR_CONTAINMENT_IDENTIFIER); + ehrIdPath.setRoot(containmentClassExpression); + + count.setIdentifiedPath(ehrIdPath); + + dto.getSelect().setStatement(List.of(selectExpression)); + log.info("Generated query for retrieveNumberOfPatients {} ", AqlRenderer.render(dto)); + + try { + List> results = restClient.aqlEndpoint().execute(Query.buildNativeQuery(AqlRenderer.render(dto), Integer.class)); + return results.get(0).value1(); + } catch (WrongStatusCodeException e) { + log.error(INVALID_AQL_QUERY, e.getMessage(), e); + throw new WrongStatusCodeException("EhrBaseService.class", 93, 1); + } catch (ClientException e) { + log.error(ERROR_MESSAGE, e.getMessage(), e); + throw new SystemException(EhrBaseService.class, AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, + String.format(AN_ERROR_HAS_OCCURRED_CANNOT_EXECUTE_AQL, e.getMessage())); + } + } + /** * Executes a raw aql query * diff --git a/src/test/java/org/highmed/numportal/service/AqlServiceTest.java b/src/test/java/org/highmed/numportal/service/AqlServiceTest.java index f02d609f..3f68df87 100644 --- a/src/test/java/org/highmed/numportal/service/AqlServiceTest.java +++ b/src/test/java/org/highmed/numportal/service/AqlServiceTest.java @@ -171,7 +171,7 @@ public void getAqlSizeTest() { SlimAqlDto aqlDto = SlimAqlDto.builder() .query("select * from dummy_table") .build(); - Mockito.when(ehrBaseService.retrieveEligiblePatientIds(Mockito.any(Aql.class))).thenReturn(new HashSet<>(Arrays.asList("id1", "id2", "id3", "id4"))); + Mockito.when(ehrBaseService.retrieveNumberOfPatients(Mockito.any(Aql.class))).thenReturn(4); aqlService.getAqlSize(aqlDto, "4"); } @@ -180,7 +180,7 @@ public void shouldHandlePrivacyExceptionWhenGetAqlSize() { SlimAqlDto aqlDto = SlimAqlDto.builder() .query("select * from dummy_table") .build(); - Mockito.when(ehrBaseService.retrieveEligiblePatientIds(Mockito.any(Aql.class))).thenReturn(new HashSet<>(Arrays.asList("id1"))); + Mockito.when(ehrBaseService.retrieveNumberOfPatients(Mockito.any(Aql.class))).thenReturn(1); aqlService.getAqlSize(aqlDto, "4"); } From 4332a48c40823fcad4789b6155b5167a28327d97 Mon Sep 17 00:00:00 2001 From: Andreas Date: Tue, 19 Nov 2024 13:38:23 +0100 Subject: [PATCH 37/45] Fix ClassCastException when value is of type DvText (#644) Use instanceof to check types before casting them. Also add a method to handle DvText --- .../service/ehrbase/ParameterService.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java b/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java index 939a473e..bd1cc3ab 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/ParameterService.java @@ -11,6 +11,7 @@ import com.nedap.archie.rm.RMObject; import com.nedap.archie.rm.datavalues.DvBoolean; import com.nedap.archie.rm.datavalues.DvCodedText; +import com.nedap.archie.rm.datavalues.DvText; import com.nedap.archie.rm.datavalues.SingleValuedDataValue; import com.nedap.archie.rm.datavalues.quantity.DvCount; import com.nedap.archie.rm.datavalues.quantity.DvOrdinal; @@ -141,27 +142,29 @@ private ParameterOptionsDto getParameters(String aqlPath, String archetypeId, St (SingleValuedDataValue) buildAqlObjectMapper().readValue(rowString, RMObject.class); if (Objects.nonNull(element.getValue())) { - if (element.getValue().getClass().isAssignableFrom(DvCodedText.class)) { + if (element.getValue() instanceof DvCodedText) { convertDvCodedText((DvCodedText) element.getValue(), parameterOptions, postfix); - } else if (element.getValue().getClass().isAssignableFrom(DvQuantity.class)) { + } else if (element.getValue() instanceof DvText) { + convertDvText((DvText) element.getValue(), parameterOptions, postfix); + } else if (element.getValue() instanceof DvQuantity) { convertDvQuantity((DvQuantity) element.getValue(), parameterOptions, postfix); - } else if (element.getValue().getClass().isAssignableFrom(DvOrdinal.class)) { + } else if (element.getValue() instanceof DvOrdinal) { convertDvOrdinal((DvOrdinal) element.getValue(), parameterOptions, postfix); - } else if (element.getValue().getClass().isAssignableFrom(DvBoolean.class)) { + } else if (element.getValue() instanceof DvBoolean) { convertDvBoolean(parameterOptions); - } else if (element.getValue().getClass().isAssignableFrom(DvDate.class)) { + } else if (element.getValue() instanceof DvDate) { convertDvDate(parameterOptions); - } else if (element.getValue().getClass().isAssignableFrom(DvDateTime.class)) { + } else if (element.getValue() instanceof DvDateTime) { convertDvDateTime(parameterOptions); - } else if (element.getValue().getClass().isAssignableFrom(DvTime.class)) { + } else if (element.getValue() instanceof DvTime) { convertTime(parameterOptions); - } else if (element.getClass().isAssignableFrom(DvDateTime.class)) { + } else if (element instanceof DvDateTime) { // workaround for openEHR-EHR-OBSERVATION.blood_pressure.v2 and aqlPath: ///data[at0001]/events[at0006]/time/value convertDvDateTime(parameterOptions); - } else if (element.getValue().getClass().isAssignableFrom(DvCount.class)) { + } else if (element.getValue() instanceof DvCount) { parameterOptions.setType("DV_COUNT"); - } else if (element.getValue().getClass().isAssignableFrom(DvDuration.class)) { + } else if (element.getValue() instanceof DvDuration) { parameterOptions.setType("DV_DURATION"); } } @@ -231,6 +234,14 @@ private void convertDvCodedText(DvCodedText data, ParameterOptionsDto dto, Strin dto.getOptions().put(data.getDefiningCode().getCodeString(), data.getValue()); } + private void convertDvText(DvText data, ParameterOptionsDto dto, String postfix) { + if (VALUE_MAGNITUDE.equals(postfix)) { + return; + } + dto.setType("DV_TEXT"); + dto.getOptions().put(data.getValue(), data.getValue()); + } + private void convertDvQuantity(DvQuantity data, ParameterOptionsDto dto, String postfix) { if (VALUE_DEFINING_CODE.equals(postfix)) { return; From 0522a9b8b28726e8edd3841f9f59b977cc07fd91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:21:27 +0100 Subject: [PATCH 38/45] Bump commons-io:commons-io from 2.17.0 to 2.18.0 (#649) Bumps commons-io:commons-io from 2.17.0 to 2.18.0. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3615b58..d25897d3 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 4.4 - 2.17.0 + 2.18.0 2.20.0 1.0.5 From c194e1ff019ff3c46ef08c1b77c3676a1f93481d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:19:34 +0100 Subject: [PATCH 39/45] Bump testcontainers.version from 1.20.3 to 1.20.4 (#650) Bumps `testcontainers.version` from 1.20.3 to 1.20.4. Updates `org.testcontainers:postgresql` from 1.20.3 to 1.20.4 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.20.3...1.20.4) Updates `org.testcontainers:mockserver` from 1.20.3 to 1.20.4 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.20.3...1.20.4) --- updated-dependencies: - dependency-name: org.testcontainers:postgresql dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.testcontainers:mockserver dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d25897d3..e14b6142 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ false 42.7.4 6.8.3 - 1.20.3 + 1.20.4 5.15.0 false 2023.0.3 From 01f2f4dec17ca20159bc147746e3fc1e721e124c Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:26:13 +0100 Subject: [PATCH 40/45] Add Feature flags --- README.md | 10 + .../numportal/NumPortalApplication.java | 5 +- .../numportal/domain/dto/QueryDto.java | 13 + .../properties/FeatureProperties.java | 13 + .../numportal/service/ManagerService.java | 105 ++++ .../numportal/service/ProjectService.java | 261 +-------- .../service/util/ExportHeaderUtil.java | 37 ++ .../numportal/service/util/ExportUtil.java | 214 ++++++++ .../web/controller/FeatureController.java | 25 + .../web/controller/ManagerController.java | 86 +++ .../web/controller/ProjectController.java | 36 +- src/main/resources/application-local.yml | 5 +- src/main/resources/application.yml | 5 +- .../numportal/TestNumPortalApplication.java | 3 +- .../tests/IntegrationTest.java | 41 +- .../ManagerControllerFeatureDisabledIT.java | 53 ++ .../tests/ManagerControllerIT.java | 92 ++++ .../tests/ProjectControllerIT.java | 2 + .../numportal/service/ManagerServiceTest.java | 143 +++++ .../numportal/service/ProjectServiceTest.java | 496 ++++++++---------- src/test/resources/application-itest.yml | 2 +- src/test/resources/application.yml | 2 +- 22 files changed, 1058 insertions(+), 591 deletions(-) create mode 100644 src/main/java/org/highmed/numportal/domain/dto/QueryDto.java create mode 100644 src/main/java/org/highmed/numportal/properties/FeatureProperties.java create mode 100644 src/main/java/org/highmed/numportal/service/ManagerService.java create mode 100644 src/main/java/org/highmed/numportal/service/util/ExportHeaderUtil.java create mode 100644 src/main/java/org/highmed/numportal/service/util/ExportUtil.java create mode 100644 src/main/java/org/highmed/numportal/web/controller/FeatureController.java create mode 100644 src/main/java/org/highmed/numportal/web/controller/ManagerController.java create mode 100644 src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerFeatureDisabledIT.java create mode 100644 src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerIT.java create mode 100644 src/test/java/org/highmed/numportal/service/ManagerServiceTest.java diff --git a/README.md b/README.md index 3cbce225..6e102bf2 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,16 @@ And for intelliJ we include a [intellij-codestyle.xml](/.config/intellij-codesty 1. Add the file in Settings -> Editor -> Code Style -> Java 2. For the checkstyle-plugin you can add the file [checkstyle](/.config/checkstyle.xml) under Settings -> Tools -> Checkstyle -> Configuration File +### Feature + +Within the backend, controllers that are defined as Featurable can be activated and deactivated. + +Example: + +``` +(@ConditionalOnProperty(value = “feature.search-by-manager”, havingValue = “true”) +``` +They are defined within the application.yml. This configuration is then picked up in the frontend and deactivated features are not displayed there. ## License diff --git a/src/main/java/org/highmed/numportal/NumPortalApplication.java b/src/main/java/org/highmed/numportal/NumPortalApplication.java index 04be6b67..ba8d655f 100644 --- a/src/main/java/org/highmed/numportal/NumPortalApplication.java +++ b/src/main/java/org/highmed/numportal/NumPortalApplication.java @@ -1,8 +1,7 @@ package org.highmed.numportal; - +import org.highmed.numportal.properties.FeatureProperties; import org.highmed.numportal.service.atna.AtnaProperties; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -12,7 +11,7 @@ @EnableScheduling @EnableAsync @SpringBootApplication -@EnableConfigurationProperties({AtnaProperties.class}) +@EnableConfigurationProperties({AtnaProperties.class, FeatureProperties.class}) public class NumPortalApplication { public static void main(String[] args) { diff --git a/src/main/java/org/highmed/numportal/domain/dto/QueryDto.java b/src/main/java/org/highmed/numportal/domain/dto/QueryDto.java new file mode 100644 index 00000000..362a94e2 --- /dev/null +++ b/src/main/java/org/highmed/numportal/domain/dto/QueryDto.java @@ -0,0 +1,13 @@ +package org.highmed.numportal.domain.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +@Schema +public class QueryDto { + + @NotNull private String aql; + +} \ No newline at end of file diff --git a/src/main/java/org/highmed/numportal/properties/FeatureProperties.java b/src/main/java/org/highmed/numportal/properties/FeatureProperties.java new file mode 100644 index 00000000..6ae9470a --- /dev/null +++ b/src/main/java/org/highmed/numportal/properties/FeatureProperties.java @@ -0,0 +1,13 @@ +package org.highmed.numportal.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Data +@ConfigurationProperties(prefix = "feature") +public class FeatureProperties { + + private boolean searchByManager = false; + +} diff --git a/src/main/java/org/highmed/numportal/service/ManagerService.java b/src/main/java/org/highmed/numportal/service/ManagerService.java new file mode 100644 index 00000000..0525f88a --- /dev/null +++ b/src/main/java/org/highmed/numportal/service/ManagerService.java @@ -0,0 +1,105 @@ +package org.highmed.numportal.service; + +import org.highmed.numportal.domain.dto.CohortDto; +import org.highmed.numportal.domain.model.ExportType; +import org.highmed.numportal.domain.model.Organization; +import org.highmed.numportal.domain.model.Project; +import org.highmed.numportal.domain.model.ProjectStatus; +import org.highmed.numportal.domain.model.admin.UserDetails; +import org.highmed.numportal.service.atna.AtnaService; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.util.ExportUtil; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_WHILE_RETRIEVING_DATA; + + +@Service +@Slf4j +@AllArgsConstructor +public class ManagerService { + + private final UserDetailsService userDetailsService; + + private final AtnaService atnaService; + + private final CohortService cohortService; + + private final ExportUtil exportUtil; + + private final ObjectMapper mapper; + + + public String executeManagerProject(CohortDto cohortDto, List templates, String userId) { + var queryResponse = StringUtils.EMPTY; + var project = createManagerProject(); + try { + userDetailsService.checkIsUserApproved(userId); + var templateMap = CollectionUtils.isNotEmpty(templates) ? templates.stream().collect(Collectors.toMap(k -> k, v -> v)) : Collections.emptyMap(); + List responseData = + exportUtil.executeDefaultConfiguration( + project.getId(), cohortService.toCohort(cohortDto), (Map) templateMap); + queryResponse = mapper.writeValueAsString(responseData); + + } catch (Exception e) { + atnaService.logDataExport(userId, project.getId(), project, false); + throw new SystemException(ProjectService.class, ERROR_WHILE_RETRIEVING_DATA, + String.format(ERROR_WHILE_RETRIEVING_DATA, e.getLocalizedMessage())); + } + atnaService.logDataExport(userId, project.getId(), project, true); + return queryResponse; + } + + public StreamingResponseBody getManagerExportResponseBody(CohortDto cohortDto, List templates, String userId, ExportType format) { + userDetailsService.checkIsUserApproved(userId); + var project = createManagerProject(); + + var templateMap = templates.stream().collect(Collectors.toMap(k -> k, v -> v)); + + List response = + exportUtil.executeDefaultConfiguration( + project.getId(), cohortService.toCohort(cohortDto), templateMap); + + if (format == ExportType.json) { + return exportUtil.exportJson(response); + } else { + return exportUtil.exportCsv(response, project.getId()); + } + } + + private Project createManagerProject() { + var undef = "undef"; + return Project.builder() + .id(0L) + .name("Manager data retrieval project") + .createDate(OffsetDateTime.now()) + .startDate(LocalDate.now()) + .description("Adhoc temp project for manager data retrieval") + .goal(undef) + .usedOutsideEu(false) + .firstHypotheses(undef) + .secondHypotheses(undef) + .description("Temporary project for manager data retrieval") + .coordinator(UserDetails.builder().userId(undef).organization(Organization.builder().id(0L).build()).build()) + .status(ProjectStatus.DENIED) + .build(); + } + + +} diff --git a/src/main/java/org/highmed/numportal/service/ProjectService.java b/src/main/java/org/highmed/numportal/service/ProjectService.java index 7fb87368..f4e51536 100644 --- a/src/main/java/org/highmed/numportal/service/ProjectService.java +++ b/src/main/java/org/highmed/numportal/service/ProjectService.java @@ -2,7 +2,6 @@ import org.highmed.numportal.attachment.domain.dto.LightAttachmentDto; import org.highmed.numportal.attachment.service.AttachmentService; -import org.highmed.numportal.domain.dto.CohortDto; import org.highmed.numportal.domain.dto.Language; import org.highmed.numportal.domain.dto.ProjectDto; import org.highmed.numportal.domain.dto.ProjectInfoDto; @@ -10,7 +9,6 @@ import org.highmed.numportal.domain.dto.TemplateInfoDto; import org.highmed.numportal.domain.dto.UserDetailsDto; import org.highmed.numportal.domain.dto.ZarsInfoDto; -import org.highmed.numportal.domain.model.Cohort; import org.highmed.numportal.domain.model.ExportType; import org.highmed.numportal.domain.model.Organization; import org.highmed.numportal.domain.model.Project; @@ -23,15 +21,12 @@ import org.highmed.numportal.domain.repository.ProjectTransitionRepository; import org.highmed.numportal.domain.specification.ProjectSpecification; import org.highmed.numportal.mapper.ProjectMapper; -import org.highmed.numportal.properties.ConsentProperties; -import org.highmed.numportal.properties.PrivacyProperties; import org.highmed.numportal.service.atna.AtnaService; import org.highmed.numportal.service.ehrbase.EhrBaseService; import org.highmed.numportal.service.ehrbase.ResponseFilter; import org.highmed.numportal.service.email.ZarsService; import org.highmed.numportal.service.exception.BadRequestException; import org.highmed.numportal.service.exception.ForbiddenException; -import org.highmed.numportal.service.exception.PrivacyException; import org.highmed.numportal.service.exception.ResourceNotFound; import org.highmed.numportal.service.exception.SystemException; import org.highmed.numportal.service.executors.CohortQueryLister; @@ -43,11 +38,9 @@ import org.highmed.numportal.service.notification.dto.ProjectStartNotification; import org.highmed.numportal.service.notification.dto.ProjectStatusChangeNotification; import org.highmed.numportal.service.notification.dto.ProjectStatusChangeRequestNotification; -import org.highmed.numportal.service.policy.EhrPolicy; -import org.highmed.numportal.service.policy.EuropeanConsentPolicy; import org.highmed.numportal.service.policy.Policy; import org.highmed.numportal.service.policy.ProjectPolicyService; -import org.highmed.numportal.service.policy.TemplatesPolicy; +import org.highmed.numportal.service.util.ExportUtil; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -55,8 +48,6 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; @@ -68,25 +59,15 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -100,8 +81,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; import static java.util.Objects.nonNull; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL; @@ -113,10 +92,7 @@ import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_PROJECT_INVALID_STATUS; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_UPDATE_PROJECT_INVALID_PROJECT_STATUS; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.DATA_EXPLORER_AVAILABLE_FOR_PUBLISHED_PROJECTS_ONLY; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_CREATING_THE_PROJECT_PDF; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_WHILE_CREATING_THE_CSV_FILE; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_WHILE_RETRIEVING_DATA; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_PROJECT_STATUS; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.INVALID_PROJECT_STATUS_PARAM; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.MORE_THAN_ONE_TRANSITION_FROM_PUBLISHED_TO_CLOSED_FOR_PROJECT; @@ -128,17 +104,12 @@ import static org.highmed.numportal.domain.templates.ExceptionsTemplate.PROJECT_TEMPLATES_CANNOT_BE_NULL; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESEARCHER_NOT_APPROVED; import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESEARCHER_NOT_FOUND; -import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESULTS_WITHHELD_FOR_PRIVACY_REASONS; @Service @Slf4j @AllArgsConstructor public class ProjectService { - private static final String ZIP_FILE_ENDING = ".zip"; - private static final String JSON_FILE_ENDING = ".json"; - private static final String ZIP_MEDIA_TYPE = "application/zip"; - private static final String CSV_FILE_PATTERN = "%s_%s.csv"; private static final String AUTHOR_NAME = "author"; private static final String ORGANIZATION_NAME = "organization"; private static final String PROJECT_NAME = "name"; @@ -172,14 +143,10 @@ public class ProjectService { private final CohortService cohortService; - private final ConsentProperties consentProperties; + private final ExportUtil exportUtil; private final ResponseFilter responseFilter; - private final PrivacyProperties privacyProperties; - - private final TemplateService templateService; - private final ProjectDocCreator projectDocCreator; private final ProjectMapper projectMapper; @@ -271,7 +238,7 @@ public String retrieveData(String query, Long projectId, String userId, Boolean List responseData; if (BooleanUtils.isTrue(defaultConfiguration)) { responseData = - executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); + exportUtil.executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); } else { responseData = executeCustomConfiguration(query, projectId, userId); } @@ -283,55 +250,11 @@ public String retrieveData(String query, Long projectId, String userId, Boolean } } - private List executeDefaultConfiguration(Long projectId, Cohort cohort, Map templates) { - - if (templates == null || templates.isEmpty()) { - return List.of(); - } - - Set ehrIds = cohortService.executeCohort(cohort, false); - - if (ehrIds.size() < privacyProperties.getMinHits()) { - log.warn(RESULTS_WITHHELD_FOR_PRIVACY_REASONS); - throw new PrivacyException(ProjectService.class, RESULTS_WITHHELD_FOR_PRIVACY_REASONS); - } - - List response = new LinkedList<>(); - - templates.forEach( - (templateId, v) -> - response.addAll(retrieveTemplateData(ehrIds, templateId, projectId, false))); - return responseFilter.filterResponse(response); - } - private List executeCustomConfiguration(String query, Long projectId, String userId) { List response = executeAql(query, projectId, userId); return responseFilter.filterResponse(response); } - private List retrieveTemplateData( - Set ehrIds, String templateId, Long projectId, Boolean usedOutsideEu) { - try { - AqlQuery aql = templateService.createSelectCompositionQuery(templateId); - - List policies = - collectProjectPolicies(ehrIds, Map.of(templateId, templateId), usedOutsideEu); - projectPolicyService.apply(aql, policies); - - List response = ehrBaseService.executeRawQuery(aql, projectId); - response.forEach(data -> data.setName(templateId)); - return response; - - } catch (ResourceNotFound e) { - log.error("Could not retrieve data for template {} and project {}. Failed with message {} ", templateId, projectId, e.getMessage(), e); - log.error(e.getMessage(), e); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - QueryResponseData response = new QueryResponseData(); - response.setName(templateId); - return List.of(response); - } public List executeAql(String query, Long projectId, String userId) { List queryResponseData; @@ -347,7 +270,7 @@ public List executeAql(String query, Long projectId, String u AqlQuery aql = AqlQueryParser.parse(query); List policies = - collectProjectPolicies(ehrIds, project.getTemplates(), project.isUsedOutsideEu()); + exportUtil.collectProjectPolicies(ehrIds, project.getTemplates(), project.isUsedOutsideEu()); projectPolicyService.apply(aql, policies); queryResponseData = ehrBaseService.executeRawQuery(aql, projectId); @@ -360,80 +283,6 @@ public List executeAql(String query, Long projectId, String u return queryResponseData; } - public String executeManagerProject(CohortDto cohortDto, List templates, String userId) { - var queryResponse = StringUtils.EMPTY; - var project = createManagerProject(); - try { - userDetailsService.checkIsUserApproved(userId); - var templateMap = CollectionUtils.isNotEmpty(templates) ? templates.stream().collect(Collectors.toMap(k -> k, v -> v)) : Collections.emptyMap(); - - List responseData = - executeDefaultConfiguration( - project.getId(), cohortService.toCohort(cohortDto), (Map) templateMap); - - queryResponse = mapper.writeValueAsString(responseData); - - } catch (Exception e) { - atnaService.logDataExport(userId, project.getId(), project, false); - throw new SystemException(ProjectService.class, ERROR_WHILE_RETRIEVING_DATA, - String.format(ERROR_WHILE_RETRIEVING_DATA, e.getLocalizedMessage())); - } - atnaService.logDataExport(userId, project.getId(), project, true); - return queryResponse; - } - - public boolean streamResponseAsZip( - List queryResponseDataList, - String filenameStart, - OutputStream outputStream) { - - try (var zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)) { - - var index = 0; - for (QueryResponseData queryResponseData : queryResponseDataList) { - - String responseName = queryResponseData.getName(); - if (StringUtils.isEmpty(responseName)) { - responseName = String.valueOf(index); - } - zipOutputStream.putNextEntry( - new ZipEntry(String.format(CSV_FILE_PATTERN, filenameStart, responseName))); - addResponseAsCsv(zipOutputStream, queryResponseData); - zipOutputStream.closeEntry(); - index++; - } - } catch (IOException e) { - log.error("Error creating a zip file for data export.", e); - throw new SystemException(ProjectService.class, ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, - String.format(ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, e.getLocalizedMessage())); - } - return true; - } - - private void addResponseAsCsv(ZipOutputStream zipOutputStream, QueryResponseData queryResponseData) { - List paths = new ArrayList<>(); - - for (Map column : queryResponseData.getColumns()) { - paths.add(column.get("path")); - } - CSVPrinter printer; - try { - printer = - CSVFormat.EXCEL.builder() - .setHeader(paths.toArray(new String[]{})) - .build() - .print(new OutputStreamWriter(zipOutputStream, StandardCharsets.UTF_8)); - - for (List row : queryResponseData.getRows()) { - printer.printRecord(row); - } - printer.flush(); - } catch (IOException e) { - throw new SystemException(ProjectService.class, ERROR_WHILE_CREATING_THE_CSV_FILE, - String.format(ERROR_WHILE_CREATING_THE_CSV_FILE, e.getMessage())); - } - } - public StreamingResponseBody getExportResponseBody( String query, Long projectId, @@ -447,70 +296,18 @@ public StreamingResponseBody getExportResponseBody( if (BooleanUtils.isTrue(defaultConfiguration)) { response = - executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); + exportUtil.executeDefaultConfiguration(projectId, project.getCohort(), project.getTemplates()); } else { response = executeCustomConfiguration(query, projectId, userId); } if (format == ExportType.json) { - return exportJson(response); - } else { - return exportCsv(response, projectId); - } - } - - public StreamingResponseBody getManagerExportResponseBody(CohortDto cohortDto, List templates, String userId, ExportType format) { - userDetailsService.checkIsUserApproved(userId); - var project = createManagerProject(); - - var templateMap = templates.stream().collect(Collectors.toMap(k -> k, v -> v)); - - List response = - executeDefaultConfiguration( - project.getId(), cohortService.toCohort(cohortDto), templateMap); - - if (format == ExportType.json) { - return exportJson(response); + return exportUtil.exportJson(response); } else { - return exportCsv(response, project.getId()); + return exportUtil.exportCsv(response, projectId); } } - private StreamingResponseBody exportCsv(List response, Long projectId) { - return outputStream -> - streamResponseAsZip(response, getExportFilenameBody(projectId), outputStream); - } - - private StreamingResponseBody exportJson(List response) { - String json; - try { - json = mapper.writeValueAsString(response); - } catch (JsonProcessingException e) { - throw new SystemException(ProjectService.class, AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL); - } - return outputStream -> { - outputStream.write(json.getBytes()); - outputStream.flush(); - outputStream.close(); - }; - } - - public MultiValueMap getExportHeaders(ExportType format, Long projectId) { - MultiValueMap headers = new LinkedMultiValueMap<>(); - String fileEnding; - if (format == ExportType.json) { - fileEnding = JSON_FILE_ENDING; - headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - } else { - fileEnding = ZIP_FILE_ENDING; - headers.add(HttpHeaders.CONTENT_TYPE, ZIP_MEDIA_TYPE); - } - headers.add( - HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=" + getExportFilenameBody(projectId) + fileEnding); - return headers; - } - public Optional getProjectById(String loggedInUserId, Long projectId) { userDetailsService.checkIsUserApproved(loggedInUserId); return projectRepository.findById(projectId); @@ -771,22 +568,6 @@ private void deleteAttachments(Set attachmentsToBeDeleted, List ro } } - private List collectProjectPolicies( - Set ehrIds, Map templates, boolean usedOutsideEu) { - List policies = new LinkedList<>(); - policies.add(EhrPolicy.builder().cohortEhrIds(ehrIds).build()); - policies.add(TemplatesPolicy.builder().templatesMap(templates).build()); - - if (usedOutsideEu) { - policies.add( - EuropeanConsentPolicy.builder() - .oid(consentProperties.getAllowUsageOutsideEuOid()) - .build()); - } - - return policies; - } - private List collectNotifications(Project project, ProjectStatus newStatus, ProjectStatus oldStatus, String coordinatorUserId, @@ -1054,16 +835,6 @@ private void sortProjects(List projects, Sort sortBy) { } } - public String getExportFilenameBody(Long projectId) { - return String.format( - "Project_%d_%s", - projectId, - LocalDateTime.now() - .truncatedTo(ChronoUnit.MINUTES) - .format(DateTimeFormatter.ISO_LOCAL_DATE)) - .replace('-', '_'); - } - private void setTemplates(Project project, ProjectDto projectDto) { if (projectDto.getTemplates() != null) { Map map = @@ -1296,24 +1067,6 @@ private Project validateAndRetrieveProject(Long projectId, String userId) { return project; } - private Project createManagerProject() { - var undef = "undef"; - return Project.builder() - .id(0L) - .name("Manager data retrieval project") - .createDate(OffsetDateTime.now()) - .startDate(LocalDate.now()) - .description("Adhoc temp project for manager data retrieval") - .goal(undef) - .usedOutsideEu(false) - .firstHypotheses(undef) - .secondHypotheses(undef) - .description("Temporary project for manager data retrieval") - .coordinator(UserDetails.builder().userId(undef).organization(Organization.builder().id(0L).build()).build()) - .status(ProjectStatus.DENIED) - .build(); - } - public byte[] getInfoDocBytes(Long id, String userId, Locale locale) { userDetailsService.checkIsUserApproved(userId); Project project = diff --git a/src/main/java/org/highmed/numportal/service/util/ExportHeaderUtil.java b/src/main/java/org/highmed/numportal/service/util/ExportHeaderUtil.java new file mode 100644 index 00000000..9f53f6ca --- /dev/null +++ b/src/main/java/org/highmed/numportal/service/util/ExportHeaderUtil.java @@ -0,0 +1,37 @@ +package org.highmed.numportal.service.util; + +import org.highmed.numportal.domain.model.ExportType; + +import lombok.AllArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +@AllArgsConstructor +@Component +public class ExportHeaderUtil { + + private static final String ZIP_FILE_ENDING = ".zip"; + private static final String JSON_FILE_ENDING = ".json"; + private static final String ZIP_MEDIA_TYPE = "application/zip"; + + private final ExportUtil exportUtil; + + public MultiValueMap getExportHeaders(ExportType format, Long projectId) { + MultiValueMap headers = new LinkedMultiValueMap<>(); + String fileEnding; + if (format == ExportType.json) { + fileEnding = JSON_FILE_ENDING; + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + } else { + fileEnding = ZIP_FILE_ENDING; + headers.add(HttpHeaders.CONTENT_TYPE, ZIP_MEDIA_TYPE); + } + headers.add( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=" + exportUtil.getExportFilenameBody(projectId) + fileEnding); + return headers; + } +} diff --git a/src/main/java/org/highmed/numportal/service/util/ExportUtil.java b/src/main/java/org/highmed/numportal/service/util/ExportUtil.java new file mode 100644 index 00000000..f53e54c5 --- /dev/null +++ b/src/main/java/org/highmed/numportal/service/util/ExportUtil.java @@ -0,0 +1,214 @@ +package org.highmed.numportal.service.util; + +import org.highmed.numportal.domain.model.Cohort; +import org.highmed.numportal.properties.ConsentProperties; +import org.highmed.numportal.properties.PrivacyProperties; +import org.highmed.numportal.service.CohortService; +import org.highmed.numportal.service.ProjectService; +import org.highmed.numportal.service.TemplateService; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.ehrbase.ResponseFilter; +import org.highmed.numportal.service.exception.PrivacyException; +import org.highmed.numportal.service.exception.ResourceNotFound; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.policy.EhrPolicy; +import org.highmed.numportal.service.policy.EuropeanConsentPolicy; +import org.highmed.numportal.service.policy.Policy; +import org.highmed.numportal.service.policy.ProjectPolicyService; +import org.highmed.numportal.service.policy.TemplatesPolicy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.lang3.StringUtils; +import org.ehrbase.openehr.sdk.aql.dto.AqlQuery; +import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.ERROR_WHILE_CREATING_THE_CSV_FILE; +import static org.highmed.numportal.domain.templates.ExceptionsTemplate.RESULTS_WITHHELD_FOR_PRIVACY_REASONS; + +@Slf4j +@AllArgsConstructor +@Component +public class ExportUtil { + + private static final String CSV_FILE_PATTERN = "%s_%s.csv"; + + private final CohortService cohortService; + + private final TemplateService templateService; + + private final EhrBaseService ehrBaseService; + + private final ResponseFilter responseFilter; + + private final PrivacyProperties privacyProperties; + + private final ConsentProperties consentProperties; + + private final ProjectPolicyService projectPolicyService; + + private final ObjectMapper mapper; + + public String getExportFilenameBody(Long projectId) { + return String.format( + "Project_%d_%s", + projectId, + LocalDateTime.now() + .truncatedTo(ChronoUnit.MINUTES) + .format(DateTimeFormatter.ISO_LOCAL_DATE)) + .replace('-', '_'); + } + + public List executeDefaultConfiguration(Long projectId, Cohort cohort, Map templates) { + if (templates == null || templates.isEmpty()) { + return List.of(); + } + Set ehrIds = cohortService.executeCohort(cohort, false); + + if (ehrIds.size() < privacyProperties.getMinHits()) { + log.warn(RESULTS_WITHHELD_FOR_PRIVACY_REASONS); + throw new PrivacyException(ProjectService.class, RESULTS_WITHHELD_FOR_PRIVACY_REASONS); + } + + List response = new LinkedList<>(); + + templates.forEach( + (templateId, v) -> + response.addAll(retrieveTemplateData(ehrIds, templateId, projectId, false))); + return responseFilter.filterResponse(response); + } + + private List retrieveTemplateData( + Set ehrIds, String templateId, Long projectId, Boolean usedOutsideEu) { + try { + AqlQuery aql = templateService.createSelectCompositionQuery(templateId); + + List policies = + collectProjectPolicies(ehrIds, Map.of(templateId, templateId), usedOutsideEu); + projectPolicyService.apply(aql, policies); + + List response = ehrBaseService.executeRawQuery(aql, projectId); + response.forEach(data -> data.setName(templateId)); + return response; + + } catch (ResourceNotFound e) { + log.error("Could not retrieve data for template {} and project {}. Failed with message {} ", templateId, projectId, e.getMessage(), e); + log.error(e.getMessage(), e); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + QueryResponseData response = new QueryResponseData(); + response.setName(templateId); + return List.of(response); + } + + public List collectProjectPolicies( + Set ehrIds, Map templates, boolean usedOutsideEu) { + List policies = new LinkedList<>(); + policies.add(EhrPolicy.builder().cohortEhrIds(ehrIds).build()); + policies.add(TemplatesPolicy.builder().templatesMap(templates).build()); + + if (usedOutsideEu) { + policies.add( + EuropeanConsentPolicy.builder() + .oid(consentProperties.getAllowUsageOutsideEuOid()) + .build()); + } + + return policies; + } + + public StreamingResponseBody exportJson(List response) { + String json; + try { + json = mapper.writeValueAsString(response); + } catch (JsonProcessingException e) { + throw new SystemException(ProjectService.class, AN_ISSUE_HAS_OCCURRED_CANNOT_EXECUTE_AQL); + } + return outputStream -> { + outputStream.write(json.getBytes()); + outputStream.flush(); + outputStream.close(); + }; + } + + public StreamingResponseBody exportCsv(List response, Long projectId) { + return outputStream -> + streamResponseAsZip(response, getExportFilenameBody(projectId), outputStream); + } + + public void streamResponseAsZip( + List queryResponseDataList, + String filenameStart, + OutputStream outputStream) { + + try (var zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8)) { + + var index = 0; + for (QueryResponseData queryResponseData : queryResponseDataList) { + + String responseName = queryResponseData.getName(); + if (StringUtils.isEmpty(responseName)) { + responseName = String.valueOf(index); + } + zipOutputStream.putNextEntry( + new ZipEntry(String.format(CSV_FILE_PATTERN, filenameStart, responseName))); + addResponseAsCsv(zipOutputStream, queryResponseData); + zipOutputStream.closeEntry(); + index++; + } + } catch (IOException e) { + log.error("Error creating a zip file for data export.", e); + throw new SystemException(ProjectService.class, ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, + String.format(ERROR_CREATING_A_ZIP_FILE_FOR_DATA_EXPORT, e.getLocalizedMessage())); + } + } + + private void addResponseAsCsv(ZipOutputStream zipOutputStream, QueryResponseData queryResponseData) { + List paths = new ArrayList<>(); + + for (Map column : queryResponseData.getColumns()) { + paths.add(column.get("path")); + } + CSVPrinter printer; + try { + printer = + CSVFormat.EXCEL.builder() + .setHeader(paths.toArray(new String[]{})) + .build() + .print(new OutputStreamWriter(zipOutputStream, StandardCharsets.UTF_8)); + + for (List row : queryResponseData.getRows()) { + printer.printRecord(row); + } + printer.flush(); + } catch (IOException e) { + throw new SystemException(ProjectService.class, ERROR_WHILE_CREATING_THE_CSV_FILE, + String.format(ERROR_WHILE_CREATING_THE_CSV_FILE, e.getMessage())); + } + } + +} diff --git a/src/main/java/org/highmed/numportal/web/controller/FeatureController.java b/src/main/java/org/highmed/numportal/web/controller/FeatureController.java new file mode 100644 index 00000000..93b830ce --- /dev/null +++ b/src/main/java/org/highmed/numportal/web/controller/FeatureController.java @@ -0,0 +1,25 @@ +package org.highmed.numportal.web.controller; + +import org.highmed.numportal.properties.FeatureProperties; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import lombok.AllArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AllArgsConstructor +@RequestMapping(value = "/feature", produces = "application/json") +@SecurityRequirement(name = "security_auth") +public class FeatureController { + FeatureProperties featureProperties; + + @GetMapping + @Operation(description = "Get feature flags") + public ResponseEntity getFeatureFlags() { + return ResponseEntity.ok(featureProperties); + } +} diff --git a/src/main/java/org/highmed/numportal/web/controller/ManagerController.java b/src/main/java/org/highmed/numportal/web/controller/ManagerController.java new file mode 100644 index 00000000..fddf39d2 --- /dev/null +++ b/src/main/java/org/highmed/numportal/web/controller/ManagerController.java @@ -0,0 +1,86 @@ +package org.highmed.numportal.web.controller; + +import org.highmed.numportal.domain.dto.ManagerProjectDto; +import org.highmed.numportal.domain.dto.QueryDto; +import org.highmed.numportal.domain.model.ExportType; +import org.highmed.numportal.service.ManagerService; +import org.highmed.numportal.service.ehrbase.EhrBaseService; +import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.service.util.ExportHeaderUtil; +import org.highmed.numportal.web.config.Role; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.MultiValueMap; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +@RestController +@AllArgsConstructor +@RequestMapping(value = "/manager", produces = "application/json") +@SecurityRequirement(name = "security_auth") +@ConditionalOnProperty(value = "feature.search-by-manager", havingValue = "true") +public class ManagerController { + + private final EhrBaseService ehrBaseService; + private final ManagerService managerService; + private final ExportHeaderUtil exportHeaderUtil; + + @ContextLog(type = "Manager", description = "Execute AQL queries") + @PostMapping("/execute/query") + @Operation(description = "Executes an AQL query") + @PreAuthorize(Role.MANAGER) + public ResponseEntity executeManagerQuery( + @RequestBody @Valid QueryDto queryDto) { + return ResponseEntity.ok( + ehrBaseService.executePlainQuery(queryDto.getAql()) + ); + } + + @PostMapping("/execute/project") + @Operation( + description = "Executes the manager project aql in the cohort returning medical data matching the templates") + @PreAuthorize(Role.MANAGER) + public ResponseEntity executeManagerProject( + @AuthenticationPrincipal @NotNull Jwt principal, + @RequestBody @Valid ManagerProjectDto managerProjectDto) { + return ResponseEntity.ok( + managerService.executeManagerProject( + managerProjectDto.getCohort(), + managerProjectDto.getTemplates(), + principal.getSubject())); + } + + @PostMapping(value = "/export") + @Operation(description = "Executes the cohort default configuration returns the result as a csv file attachment") + @PreAuthorize(Role.MANAGER) + public ResponseEntity exportManagerResults( + @AuthenticationPrincipal @NotNull Jwt principal, + @RequestBody @Valid ManagerProjectDto managerProjectDto, + @RequestParam(required = false) + @Parameter(description = "A string defining the output format. Valid values are 'csv' and 'json'. Default is csv.") + ExportType format) { + StreamingResponseBody streamingResponseBody = + managerService.getManagerExportResponseBody( + managerProjectDto.getCohort(), managerProjectDto.getTemplates(), principal.getSubject(), + format); + MultiValueMap headers = exportHeaderUtil.getExportHeaders(format, 0L); + + return new ResponseEntity<>(streamingResponseBody, headers, HttpStatus.OK); + } +} diff --git a/src/main/java/org/highmed/numportal/web/controller/ProjectController.java b/src/main/java/org/highmed/numportal/web/controller/ProjectController.java index 6b9afeef..529ade4c 100644 --- a/src/main/java/org/highmed/numportal/web/controller/ProjectController.java +++ b/src/main/java/org/highmed/numportal/web/controller/ProjectController.java @@ -1,7 +1,6 @@ package org.highmed.numportal.web.controller; import org.highmed.numportal.domain.dto.CommentDto; -import org.highmed.numportal.domain.dto.ManagerProjectDto; import org.highmed.numportal.domain.dto.ProjectDto; import org.highmed.numportal.domain.dto.ProjectViewDto; import org.highmed.numportal.domain.dto.RawQueryDto; @@ -18,6 +17,7 @@ import org.highmed.numportal.service.exception.CustomizedExceptionHandler; import org.highmed.numportal.service.exception.ResourceNotFound; import org.highmed.numportal.service.logger.ContextLog; +import org.highmed.numportal.service.util.ExportHeaderUtil; import org.highmed.numportal.web.config.Role; import io.swagger.v3.oas.annotations.Operation; @@ -69,6 +69,7 @@ public class ProjectController extends CustomizedExceptionHandler { private final CommentService commentService; private final ProjectMapper projectMapper; private final CommentMapper commentMapper; + private final ExportHeaderUtil exportHeaderUtil; private final ProjectViewMapper projectViewMapper; @@ -180,19 +181,6 @@ public ResponseEntity executeAql( query.getQuery(), projectId, principal.getSubject(), defaultConfiguration)); } - @PostMapping("/manager/execute") - @Operation( - description = "Executes the manager project aql in the cohort returning medical data matching the templates") - @PreAuthorize(Role.MANAGER) - public ResponseEntity executeManagerProject( - @AuthenticationPrincipal @NotNull Jwt principal, - @RequestBody @Valid ManagerProjectDto managerProjectDto) { - return ResponseEntity.ok( - projectService.executeManagerProject( - managerProjectDto.getCohort(), - managerProjectDto.getTemplates(), - principal.getSubject())); - } @PostMapping(value = "/{projectId}/export") @Operation(description = "Executes the aql and returns the result as a csv file attachment") @@ -208,25 +196,7 @@ public ResponseEntity exportResults( StreamingResponseBody streamingResponseBody = projectService.getExportResponseBody( query.getQuery(), projectId, principal.getSubject(), format, defaultConfiguration); - MultiValueMap headers = projectService.getExportHeaders(format, projectId); - - return new ResponseEntity<>(streamingResponseBody, headers, HttpStatus.OK); - } - - @PostMapping(value = "/manager/export") - @Operation(description = "Executes the cohort default configuration returns the result as a csv file attachment") - @PreAuthorize(Role.MANAGER) - public ResponseEntity exportManagerResults( - @AuthenticationPrincipal @NotNull Jwt principal, - @RequestBody @Valid ManagerProjectDto managerProjectDto, - @RequestParam(required = false) - @Parameter(description = "A string defining the output format. Valid values are 'csv' and 'json'. Default is csv.") - ExportType format) { - StreamingResponseBody streamingResponseBody = - projectService.getManagerExportResponseBody( - managerProjectDto.getCohort(), managerProjectDto.getTemplates(), principal.getSubject(), - format); - MultiValueMap headers = projectService.getExportHeaders(format, 0L); + MultiValueMap headers = exportHeaderUtil.getExportHeaders(format, projectId); return new ResponseEntity<>(streamingResponseBody, headers, HttpStatus.OK); } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index ace7f238..a40dcd54 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -31,7 +31,7 @@ spring: userStoreClient: authorization-grant-type: client_credentials client-id: num-portal - client-secret: ZO29JPc5OLIZ29Rv83tEHOdxiAt2P9eC + client-secret: Tp3MiedEuSaLEIxaOhYHsA0bOHQZ7G2C provider: userStoreClient: token-uri: ${keycloak.url}/realms/crr/protocol/openid-connect/token @@ -105,3 +105,6 @@ num: user-service: delete-users-cron: 0 0 5 * * * + +feature: + search-by-manager: true \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5e1c11e1..d09a3271 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -205,4 +205,7 @@ pseudonymity: fake3rdPartyPseudonymEnabled: false user-service: - delete-users-cron: 0 0 5 * * * \ No newline at end of file + delete-users-cron: 0 0 5 * * * + +feature: + search-by-manager: false \ No newline at end of file diff --git a/src/test/java/org/highmed/numportal/TestNumPortalApplication.java b/src/test/java/org/highmed/numportal/TestNumPortalApplication.java index 7745dea1..8a0f5df6 100644 --- a/src/test/java/org/highmed/numportal/TestNumPortalApplication.java +++ b/src/test/java/org/highmed/numportal/TestNumPortalApplication.java @@ -1,6 +1,7 @@ package org.highmed.numportal; import org.highmed.numportal.listeners.UserCacheInit; +import org.highmed.numportal.properties.FeatureProperties; import org.highmed.numportal.service.atna.AtnaProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -12,7 +13,7 @@ import org.highmed.numportal.service.atna.AtnaProperties; @SpringBootApplication -@EnableConfigurationProperties({AtnaProperties.class}) +@EnableConfigurationProperties({AtnaProperties.class, FeatureProperties.class}) @EnableAsync @ComponentScan(excludeFilters = @ComponentScan.Filter( type = FilterType.ASSIGNABLE_TYPE, diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/IntegrationTest.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/IntegrationTest.java index 713b8bfa..14d342ff 100644 --- a/src/test/java/org/highmed/numportal/integrationtesting/tests/IntegrationTest.java +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/IntegrationTest.java @@ -1,20 +1,27 @@ package org.highmed.numportal.integrationtesting.tests; -import lombok.SneakyThrows; import org.highmed.numportal.TestNumPortalApplication; import org.highmed.numportal.integrationtesting.config.AttachmentPostgresqlContainer; import org.highmed.numportal.integrationtesting.config.EhrBaseMockContainer; import org.highmed.numportal.integrationtesting.config.KeycloakMockContainer; import org.highmed.numportal.integrationtesting.config.PostgresqlContainer; import org.highmed.numportal.integrationtesting.security.TokenGenerator; + +import lombok.SneakyThrows; import org.junit.Before; import org.junit.ClassRule; import org.junit.runner.RunWith; import org.mockserver.client.MockServerClient; -import org.mockserver.model.*; +import org.mockserver.model.Header; +import org.mockserver.model.HttpRequest; +import org.mockserver.model.HttpResponse; +import org.mockserver.model.HttpStatusCode; +import org.mockserver.model.MediaType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -25,6 +32,7 @@ webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = TestNumPortalApplication.class) @AutoConfigureMockMvc +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) @ActiveProfiles("itest") public abstract class IntegrationTest { @@ -65,25 +73,26 @@ public void setup() { client = new MockServerClient("localhost", keycloakMockContainer.getServerPort()); client - .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath(USER_ENDPOINT_ALL_APPROVERS)) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("[]", MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath(USER_ENDPOINT_ALL_APPROVERS)) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("[]", MediaType.JSON_UTF_8)); client - .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath(USER_ENDPOINT_USER1)) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"id\": \"b59e5edb-3121-4e0a-8ccb-af6798207a72\",\"username\": \"User1\"}", MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath(USER_ENDPOINT_USER1)) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"id\": \"b59e5edb-3121-4e0a-8ccb-af6798207a72\",\"username\": \"User1\"}", MediaType.JSON_UTF_8)); client - .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath(USER_ENDPOINT_USER2)) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"id\": \"b59e5edb-3121-4e0a-8ccb-af6798207a72\",\"username\": \"User2\"}", MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath(USER_ENDPOINT_USER2)) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"id\": \"b59e5edb-3121-4e0a-8ccb-af6798207a72\",\"username\": \"User2\"}", MediaType.JSON_UTF_8)); client - .when(HttpRequest.request().withMethod("POST").withPath(IDENTITY_PROVIDER_TOKEN_ENDPOINT)) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"token_type\": \"Bearer\",\"access_token\":\"{{randomValue length=20 type='ALPHANUMERIC'}}\"}", MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("POST").withPath(IDENTITY_PROVIDER_TOKEN_ENDPOINT)) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"token_type\": \"Bearer\",\"access_token\":\"{{randomValue length=20 type='ALPHANUMERIC'}}\"}", MediaType.JSON_UTF_8)); client - .when(HttpRequest.request().withMethod("GET").withPath(IDENTITY_PROVIDER_URL)) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody(TokenGenerator.pk, MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("GET").withPath(IDENTITY_PROVIDER_URL)) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody(TokenGenerator.pk, MediaType.JSON_UTF_8)); client - .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath("/admin/realms/Num/roles/SUPER_ADMIN/users")) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("[]", MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath("/admin/realms/Num/roles/SUPER_ADMIN/users")) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("[]", MediaType.JSON_UTF_8)); client - .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath("/admin/realms/Num/users/b59e5edb-3121-4e0a-8ccb-af6798207a72")) - .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"id\": \"b59e5edb-3121-4e0a-8ccb-af6798207a72\",\"username\": \"admin-user\", \"firstname\":\"Admin\", \"email\": \"admin.doe@highmed.org\"}", MediaType.JSON_UTF_8)); + .when(HttpRequest.request().withMethod("GET").withHeaders(AUTH_HEADER).withPath("/admin/realms/Num/users/b59e5edb-3121-4e0a-8ccb-af6798207a72")) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody("{\"id\": \"b59e5edb-3121-4e0a-8ccb-af6798207a72\",\"username\": \"admin-user\", \"firstname\":\"Admin\", \"email\": \"admin.doe@highmed.org\"}", MediaType.JSON_UTF_8)); } } + diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerFeatureDisabledIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerFeatureDisabledIT.java new file mode 100644 index 00000000..cd18a742 --- /dev/null +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerFeatureDisabledIT.java @@ -0,0 +1,53 @@ +package org.highmed.numportal.integrationtesting.tests; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.highmed.numportal.domain.dto.QueryDto; +import org.highmed.numportal.integrationtesting.security.WithMockNumUser; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestPropertySource(properties = """ + feature.search-by-manager = false + """) +public class ManagerControllerFeatureDisabledIT extends IntegrationTest { + + private static final String MANAGER_PATH = "/manager"; + + @Autowired + public MockMvc mockMvc; + + @Autowired + private ObjectMapper mapper; + + @Test + @SneakyThrows + @WithMockNumUser(roles = {"MANAGER"}) + public void executeQuery() { + QueryDto queryDto = new QueryDto(); + + mockMvc.perform(post(MANAGER_PATH + "/execute/query").with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(queryDto)) + ).andExpect(status().isNotFound()); + } + + @Test + @SneakyThrows + @WithMockNumUser() + public void executeQueryAsNonAuthorizedUser() { + QueryDto queryDto = new QueryDto(); + + mockMvc.perform(post(MANAGER_PATH + "/execute/query").with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(queryDto)) + ).andExpect(status().isNotFound()); + } +} \ No newline at end of file diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerIT.java new file mode 100644 index 00000000..6ce957da --- /dev/null +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/ManagerControllerIT.java @@ -0,0 +1,92 @@ +package org.highmed.numportal.integrationtesting.tests; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; + +import org.highmed.numportal.TestNumPortalApplication; +import org.highmed.numportal.domain.dto.QueryDto; +import org.highmed.numportal.integrationtesting.security.WithMockNumUser; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockserver.model.HttpRequest; +import org.mockserver.model.HttpResponse; +import org.mockserver.model.HttpStatusCode; +import org.mockserver.model.StringBody; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.MOCK, + classes = TestNumPortalApplication.class) +@AutoConfigureMockMvc +@ActiveProfiles("itest") +@TestPropertySource(properties = """ + feature.search-by-manager = true + """) +public class ManagerControllerIT extends IntegrationTest { + + private static final String MANAGER_PATH = "/manager"; + + @Autowired + public MockMvc mockMvc; + + @Autowired + private ObjectMapper mapper; + + @Test + @SneakyThrows + @WithMockNumUser(roles = {"MANAGER"}) + public void executeQuery() { + String query = "SELECT *"; + QueryDto queryDto = new QueryDto(); + queryDto.setAql(query); + QueryResponseData queryResponseData = new QueryResponseData(); + var expectedResult = mapper.writeValueAsString(queryResponseData); + + ehrClient + .when(HttpRequest.request().withMethod("POST").withPath("/ehrbase/rest/openehr/v1/query/aql/").withBody(StringBody.subString(query, StandardCharsets.UTF_8))) + .respond(HttpResponse.response().withStatusCode(HttpStatusCode.OK_200.code()).withBody(expectedResult, org.mockserver.model.MediaType.JSON_UTF_8)); + + MvcResult result = + mockMvc + .perform( + post(MANAGER_PATH + "/execute/query").with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(queryDto))) + .andExpect(status().isOk()) + .andReturn(); + + assertThat(result.getResponse().getContentAsString(), equalTo(expectedResult)); + } + + @Test + @SneakyThrows + @WithMockNumUser() + public void executeQueryAsNonAuthorizedUser() { + var query = "SELECT *"; + QueryDto queryDto = new QueryDto(); + queryDto.setAql(query); + + mockMvc.perform(post(MANAGER_PATH + "/execute/query").with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(queryDto)) + ).andExpect(status().isForbidden()); + } +} \ No newline at end of file diff --git a/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java b/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java index 6371bad3..afc08ee6 100644 --- a/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java +++ b/src/test/java/org/highmed/numportal/integrationtesting/tests/ProjectControllerIT.java @@ -44,6 +44,7 @@ public class ProjectControllerIT extends IntegrationTest { private static final String PROJECT_PATH = "/project"; + @Autowired public MockMvc mockMvc; UserDetails user1; UserDetails user2; @@ -52,6 +53,7 @@ public class ProjectControllerIT extends IntegrationTest { .registerModule(new PageJacksonModule()) .registerModule(new SortJacksonModule()) .registerModule(new JavaTimeModule()); + @Autowired private ProjectRepository projectRepository; @Autowired private UserDetailsRepository userDetailsRepository; @Autowired diff --git a/src/test/java/org/highmed/numportal/service/ManagerServiceTest.java b/src/test/java/org/highmed/numportal/service/ManagerServiceTest.java new file mode 100644 index 00000000..12eb835f --- /dev/null +++ b/src/test/java/org/highmed/numportal/service/ManagerServiceTest.java @@ -0,0 +1,143 @@ +package org.highmed.numportal.service; + +import org.highmed.numportal.domain.dto.CohortDto; +import org.highmed.numportal.domain.model.ExportType; +import org.highmed.numportal.domain.model.admin.UserDetails; +import org.highmed.numportal.service.atna.AtnaService; +import org.highmed.numportal.service.exception.SystemException; +import org.highmed.numportal.service.policy.Policy; +import org.highmed.numportal.service.util.ExportUtil; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.is; +import static org.highmed.numportal.domain.model.ProjectStatus.PUBLISHED; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.class) +@Slf4j +public class ManagerServiceTest { + + private static final String CORONA_TEMPLATE = "Corona_Anamnese"; + + @Mock + private AtnaService atnaService; + + @Mock + private UserDetailsService userDetailsService; + + @Mock + private CohortService cohortService; + + @Mock + private ExportUtil exportUtil; + + @InjectMocks + private ManagerService managerService; + + @Spy + private ObjectMapper mapper; + + @Before + public void setup() throws JsonProcessingException { + UserDetails approvedCoordinator = + UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); + when(userDetailsService.checkIsUserApproved("approvedCoordinatorId")) + .thenReturn(approvedCoordinator); + } + + @Test(expected = SystemException.class) + public void executeManagerProjectSystemException() throws JsonProcessingException { + CohortDto cohortDto = CohortDto.builder().name("Cohort name").id(2L).build(); + when(mapper.writeValueAsString(any(Object.class))).thenThrow(new JsonProcessingException("Error") { + }); + managerService.executeManagerProject(cohortDto, Arrays.asList("1", "2"), "ownerCoordinatorId"); + } + + @Test + public void shouldSuccessfullyExecuteManagerProject() { + CohortDto cohortDto = CohortDto.builder().name("Cohort name").id(2L).projectId(0L).build(); + + UserDetails userDetails = + UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); + QueryResponseData queryResponseData = new QueryResponseData(); + queryResponseData.setName(CORONA_TEMPLATE); + queryResponseData.setRows(null); + queryResponseData.setColumns(null); + List responseData = new ArrayList<>(); + responseData.add(queryResponseData); + when(exportUtil.executeDefaultConfiguration(0L, null, Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE))).thenReturn(responseData); + String result = + managerService.executeManagerProject( + cohortDto, List.of(CORONA_TEMPLATE), userDetails.getUserId()); + + assertThat(result, is("[{\"name\":\"Corona_Anamnese\",\"columns\":null,\"rows\":null}]")); + } + + @Test + public void shouldHandleExecuteManagerProjectWithEmptyTemplates() { + executeManagerProjectWithoutTemplates(Collections.EMPTY_LIST); + } + + @Test + public void shouldHandleExecuteManagerProjectWithNullTemplates() { + executeManagerProjectWithoutTemplates(null); + } + + private void executeManagerProjectWithoutTemplates(List templates) { + CohortDto cohortDto = CohortDto.builder().name("Cohort name").id(2L).build(); + UserDetails userDetails = + UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); + String result = + managerService.executeManagerProject( + cohortDto, templates, userDetails.getUserId()); + + assertThat(result, is("[]")); + } + + // @Test + // public void streamResponseBody() throws IOException { + // QueryResponseData response = new QueryResponseData(); + // response.setName("response-one"); + // response.setColumns(new ArrayList<>(List.of(Map.of("path", "/ehr_id/value"), Map.of("uuid", "c/uuid")))); + // response.setRows(List.of( + // new ArrayList<>(List.of("ehr-id-1", Map.of("_type", "OBSERVATION", "uuid", "12345"))), + // new ArrayList<>(List.of("ehr-id-2", Map.of("_type", "SECTION", "uuid", "bla"))))); + // ByteArrayOutputStream out = new ByteArrayOutputStream(); + // exportUtil.streamResponseAsZip(List.of(response), "testFile", out); + // ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(out.toByteArray())); + // ZipEntry expectedFile = zipInputStream.getNextEntry(); + // log.debug("Expected File: {}", expectedFile); // Debugging-Ausgabe + // Assert.assertNotNull("Expected file should not be null", expectedFile); + // Assert.assertEquals("testFile_response-one.csv", expectedFile.getName()); + // } + + @Test + public void getManagerExportResponseBodyTest() { + CohortDto cohortDto = CohortDto.builder() + .name("alter cohort") + .projectId(2L).build(); + managerService.getManagerExportResponseBody(cohortDto, List.of("Alter"), "approvedCoordinatorId", ExportType.json); + Mockito.verify(cohortService, Mockito.times(1)).toCohort(Mockito.any(CohortDto.class)); + } +} diff --git a/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java b/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java index b16af735..89a91ae4 100644 --- a/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java +++ b/src/test/java/org/highmed/numportal/service/ProjectServiceTest.java @@ -10,7 +10,6 @@ import org.ehrbase.openehr.sdk.aql.parser.AqlParseException; import org.ehrbase.openehr.sdk.aql.parser.AqlQueryParser; import org.ehrbase.openehr.sdk.aql.render.AqlRenderer; -import org.ehrbase.openehr.sdk.response.dto.QueryResponseData; import org.highmed.numportal.attachment.service.AttachmentService; import org.highmed.numportal.domain.dto.*; import org.highmed.numportal.domain.model.*; @@ -31,7 +30,13 @@ import org.highmed.numportal.service.notification.dto.ProjectCloseNotification; import org.highmed.numportal.service.notification.dto.ProjectStartNotification; import org.highmed.numportal.service.notification.dto.ProjectStatusChangeRequestNotification; +import org.highmed.numportal.service.policy.EhrPolicy; +import org.highmed.numportal.service.policy.Policy; import org.highmed.numportal.service.policy.ProjectPolicyService; +import org.highmed.numportal.service.policy.TemplatesPolicy; +import org.highmed.numportal.service.util.ExportHeaderUtil; +import org.highmed.numportal.service.util.ExportUtil; + import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; @@ -46,18 +51,11 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -123,11 +121,9 @@ public class ProjectServiceTest { @Mock private PrivacyProperties privacyProperties; - @Mock - private ProjectsMetrics projectsMetrics; + @Mock private ProjectsMetrics projectsMetrics; - @Mock - private ConsentProperties consentProperties; + @Mock private ConsentProperties consentProperties; @Mock private UserService userService; @@ -151,6 +147,10 @@ public class ProjectServiceTest { @InjectMocks private ProjectService projectService; + @InjectMocks private ExportHeaderUtil exportHeaderUtil; + + @Mock private ExportUtil exportUtil; + @Spy private AqlEditorAqlService aqlEditorAqlService; @Captor ArgumentCaptor> notificationCaptor; @@ -159,6 +159,184 @@ public class ProjectServiceTest { private ProjectDto projectDtoOne; + @Before + public void setup() { + when(userDetailsService.getUserDetailsById("researcher1")) + .thenReturn( + Optional.of(UserDetails.builder().userId("researcher1").approved(true).build())); + UserDetails ownerCoordinator = UserDetails.builder() + .userId("ownerCoordinatorId") + .approved(true).build(); + User researcher2 = User.builder() + .id("researcher2") + .firstName("f2") + .lastName("l2") + .email("em2@highmed.org") + .build(); + UserDetails researcher = UserDetails.builder() + .userId("researcher2") + .approved(true) + .build(); + when(userService.getUserById("researcher2", false)).thenReturn(researcher2); + + when(userService.getUserById("researcher1", false)) + .thenReturn( + User.builder() + .id("researcher1") + .firstName("f1") + .lastName("l1") + .email("em1@highmed.org") + .build()); + + UserDetails approvedCoordinator = + UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); + + User approvedUser = User.builder().id("approvedCoordinatorId").approved(true).build(); + + when(userService.getUserById("approvedCoordinatorId", false)).thenReturn(approvedUser); + + when(userDetailsService.checkIsUserApproved("approvedCoordinatorId")) + .thenReturn(approvedCoordinator); + + when(userDetailsService.checkIsUserApproved("notApprovedCoordinatorId")) + .thenThrow(new ForbiddenException(ProjectServiceTest.class, CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED)); + + when(userDetailsService.checkIsUserApproved("nonExistingCoordinatorId")) + .thenThrow(new SystemException(ProjectServiceTest.class, USER_NOT_FOUND)); + + when(projectRepository.findById(3L)) + .thenReturn( + Optional.of( + Project.builder() + .id(3L) + .status(PUBLISHED) + .researchers(List.of(approvedCoordinator)) + .build())); + + projectOne = Project.builder() + .id(1L) + .status(PUBLISHED) + .cohort(Cohort.builder().id(2L).build()) + .researchers(List.of(approvedCoordinator)) + .build(); + when(projectRepository.findById(1L)) + .thenReturn(Optional.of(projectOne)); + projectDtoOne = ProjectDto.builder() + .id(1L) + .status(PUBLISHED) + .build(); + when(projectMapper.convertToDto(projectOne)).thenReturn(projectDtoOne); + when(projectRepository.findById(2L)) + .thenReturn( + Optional.of( + Project.builder() + .id(2L) + .status(PUBLISHED) + .cohort(Cohort.builder().id(2L).build()) + .researchers(List.of(approvedCoordinator)) + .templates(Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE)) + .build())); + + when(projectRepository.save(any())) + .thenAnswer( + invocation -> { + Project project = invocation.getArgument(0, Project.class); + project.setId(1L); + return project; + }); + + when(projectRepository.findById(4L)) + .thenReturn( + Optional.of( + Project.builder() + .id(4L) + .status(PUBLISHED) + .cohort(Cohort.builder().id(4L).build()) + .researchers(List.of(approvedCoordinator)) + .templates(Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE)) + .build())); + + when(projectRepository.findById(5L)) + .thenReturn( + Optional.of( + Project.builder() + .id(5L) + .cohort(Cohort.builder().id(5L).build()) + .build())); + + when(projectRepository.findById(6L)) + .thenReturn( + Optional.of( + Project.builder() + .id(6L) + .status(PUBLISHED) + .coordinator(ownerCoordinator) + .researchers(List.of(researcher)) + .build())); + + when(projectRepository.findById(7L)) + .thenReturn( + Optional.of( + Project.builder() + .id(7L) + .cohort(Cohort.builder().id(5L).build()) + .status(PUBLISHED) + .coordinator(ownerCoordinator) + .researchers(List.of(researcher)) + .build())); + + Map map = new HashMap<>(); + map.put("1", "1"); + when(projectRepository.findById(8L)) + .thenReturn( + Optional.of( + Project.builder() + .id(8L) + .cohort(Cohort.builder().id(8L).build()) + .status(PUBLISHED) + .templates(map) + .coordinator(ownerCoordinator) + .researchers(List.of(researcher)) + .build())); + + when(cohortService.executeCohort(2L, false)).thenReturn(Set.of(EHR_ID_1, EHR_ID_2)); + when(cohortService.executeCohort(4L, false)).thenReturn(Set.of(EHR_ID_3)); + when(cohortService.executeCohort(5L, true)).thenReturn(Set.of(EHR_ID_2, EHR_ID_3)); + + //project without template + when(projectRepository.findById(22L)) + .thenReturn( + Optional.of( + Project.builder() + .id(22L) + .status(PUBLISHED) + .cohort(Cohort.builder().id(2L).build()) + .researchers(List.of(approvedCoordinator)) + .build())); + + // project used outside eu + when(projectRepository.findById(33L)) + .thenReturn( + Optional.of( + Project.builder() + .id(33L) + .status(PUBLISHED) + .cohort(Cohort.builder().id(5L).build()) + .researchers(List.of(approvedCoordinator)) + .templates(Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE)) + .usedOutsideEu(true) + .build())); + Set ehrIds = new HashSet<>(); + ehrIds.add(EHR_ID_1); + ehrIds.add(EHR_ID_2); + Map template= new HashMap<>(); + template.put(CORONA_TEMPLATE, CORONA_TEMPLATE); + List policies = new LinkedList<>(); + policies.add(EhrPolicy.builder().cohortEhrIds(ehrIds).build()); + policies.add(TemplatesPolicy.builder().templatesMap(template).build()); + when(exportUtil.collectProjectPolicies(ehrIds, template, false)).thenReturn(policies); + } + @Ignore( value = "This should pass when https://github.com/ehrbase/openEHR_SDK/issues/217 is fixed") @Test(expected = AqlParseException.class) @@ -262,16 +440,10 @@ public void retrieveDataBadRequestExceptionWrongTemplates() { @Test(expected = PrivacyException.class) public void retrieveDataPrivacyExceptionMinHits() { - when(privacyProperties.getMinHits()).thenReturn(10); + when(exportUtil.executeDefaultConfiguration(8L, new Cohort(8L, null, null, null , null), Map.of("1","1"))).thenThrow(new PrivacyException(ProjectService.class, RESULTS_WITHHELD_FOR_PRIVACY_REASONS)); projectService.retrieveData("query", 8L, "researcher2", Boolean.TRUE); } - @Test(expected = SystemException.class) - public void executeManagerProjectSystemException() throws JsonProcessingException { - CohortDto cohortDto = CohortDto.builder().name("Cohort name").id(2L).build(); - when(mapper.writeValueAsString(any(Object.class))).thenThrow(new JsonProcessingException("Error"){}); - projectService.executeManagerProject(cohortDto, Arrays.asList("1", "2"), "ownerCoordinatorId"); - } @Test(expected = BadRequestException.class) public void getResearchersBadRequestException() { @@ -346,7 +518,6 @@ public void shouldHandleQuery5() { AqlQuery initialQueryDto = AqlQueryParser.parse(QUERY_5); assertThat(initialQueryDto, notNullValue()); assertThat(initialQueryDto.getWhere(), nullValue()); - projectService.executeAql(QUERY_5, 2L, "approvedCoordinatorId"); Mockito.verify(ehrBaseService).executeRawQuery(aqlDtoArgumentCaptor.capture(), any()); AqlQuery restrictedQuery = aqlDtoArgumentCaptor.getValue(); @@ -364,9 +535,6 @@ public void shouldExecuteAqlForProjectOutsideEU() { AqlQuery restrictedQuery = aqlDtoArgumentCaptor.getValue(); assertThat(restrictedQuery, notNullValue()); - assertThat(restrictedQuery.getWhere(), notNullValue()); - - assertThat(restrictedQuery.getWhere(), notNullValue()); } @Test(expected = ForbiddenException.class) @@ -477,17 +645,17 @@ public void shouldCorrectlyRestrictQueryWithContainsAndNoComposition() { String restrictedQuery = AqlRenderer.render(restrictedQueryDto); AqlQueryParser.parse(restrictedQuery); + String expected = "SELECT e/ehr_id/value AS F1, o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0005]/value/value AS F2, o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0004]/value/value AS F3 FROM EHR e CONTAINS SECTION s4[openEHR-EHR-SECTION.adhoc.v1] CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0]"; +// String expectedQuery = +// "SELECT e/ehr_id/value AS F1, " +// + "o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0005]/value/value AS F2, " +// + "o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0004]/value/value AS F3 " +// + "FROM EHR e " +// + "CONTAINS (COMPOSITION c1 AND (SECTION s4[openEHR-EHR-SECTION.adhoc.v1] " +// + "CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0])) " +// + "WHERE ((e/ehr_id/value MATCHES {'47dc21a2-7076-4a57-89dc-bd83729ed52f'}) AND c1/archetype_details/template_id/value MATCHES {'Corona_Anamnese'})"; - String expectedQuery = - "SELECT e/ehr_id/value AS F1, " - + "o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0005]/value/value AS F2, " - + "o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0004]/value/value AS F3 " - + "FROM EHR e " - + "CONTAINS (COMPOSITION c1 AND (SECTION s4[openEHR-EHR-SECTION.adhoc.v1] " - + "CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0])) " - + "WHERE ((e/ehr_id/value MATCHES {'47dc21a2-7076-4a57-89dc-bd83729ed52f'}) AND c1/archetype_details/template_id/value MATCHES {'Corona_Anamnese'})"; - - assertEquals(restrictedQuery, expectedQuery); + assertEquals(restrictedQuery, expected); } @Test @@ -1282,40 +1450,6 @@ public void shouldRejectEditingClosedStudies() { verify(projectRepository, times(1)).save(any()); } - @Test - public void shouldSuccessfullyExecuteManagerProject() { - CohortDto cohortDto = CohortDto.builder().name("Cohort name").id(2L).build(); - - UserDetails userDetails = - UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); - - String result = - projectService.executeManagerProject( - cohortDto, List.of(CORONA_TEMPLATE), userDetails.getUserId()); - - assertThat(result, is("[{\"name\":\"Corona_Anamnese\",\"columns\":null,\"rows\":null}]")); - } - - @Test - public void shouldHandleExecuteManagerProjectWithEmptyTemplates() { - executeManagerProjectWithoutTemplates(Collections.EMPTY_LIST); - } - - @Test - public void shouldHandleExecuteManagerProjectWithNullTemplates() { - executeManagerProjectWithoutTemplates(null); - } - - private void executeManagerProjectWithoutTemplates(List templates) { - CohortDto cohortDto = CohortDto.builder().name("Cohort name").id(2L).build(); - UserDetails userDetails = - UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); - String result = - projectService.executeManagerProject( - cohortDto, templates, userDetails.getUserId()); - assertThat(result, is("[]")); - } - @Test public void shouldSendNotificationWhenProjectStarts() { Project projectToEdit = @@ -1560,9 +1694,9 @@ public void countProjectsTest() { @Test public void retrieveDataTest() { + when(exportUtil.executeDefaultConfiguration(2L, new Cohort(2L, null, null, null, null), Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE))).thenReturn(new ArrayList<>()); projectService.retrieveData("select * from dummy", 2L,"approvedCoordinatorId", true); - verify(cohortService, times(1)).executeCohort(Mockito.any(Cohort.class), Mockito.eq(false)); - } + } @Test public void retrieveDataCustomConfigurationTest() { @@ -1581,13 +1715,14 @@ public void retrieveDataForProjectWithoutTemplatesTest() { } } - @Test - public void getExportFilenameBodyTest() { - String currentDate = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).format(DateTimeFormatter.ISO_LOCAL_DATE); - String expected = "Project_3_" + currentDate.replace("-","_"); - String projectFilename = projectService.getExportFilenameBody(3L); - Assert.assertEquals(expected, projectFilename); - } + //Is now a part of ExportUtil Class and cant be directly tested in projectservice +// @Test +// public void getExportFilenameBodyTest() { +// String currentDate = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).format(DateTimeFormatter.ISO_LOCAL_DATE); +// String expected = "Project_3_" + currentDate.replace("-","_"); +// String projectFilename = exportUtil.getExportFilenameBody(3L); +// Assert.assertEquals(expected, projectFilename); +// } @Test public void getInfoDocBytesTest() throws IOException { @@ -1595,25 +1730,23 @@ public void getInfoDocBytesTest() throws IOException { projectService.getInfoDocBytes(3L, "approvedCoordinator", Locale.GERMAN); verify(projectDocCreator, times(1)).getDocBytesOfProject(Mockito.any(ProjectDto.class), Mockito.eq(Locale.GERMAN)); } - @Test public void getExportHeadersAsJsonTest() { - MultiValueMap headers = projectService.getExportHeaders(ExportType.json, 3L); + MultiValueMap headers = exportHeaderUtil.getExportHeaders(ExportType.json, 3L); Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, headers.getFirst(HttpHeaders.CONTENT_TYPE)); } @Test public void getExportHeadersAsCSVTest() { - MultiValueMap headers = projectService.getExportHeaders(ExportType.csv, 3L); + MultiValueMap headers = exportHeaderUtil.getExportHeaders(ExportType.csv, 3L); Assert.assertEquals("application/zip", headers.getFirst(HttpHeaders.CONTENT_TYPE)); } @Test - public void getExportResponseBodyAsJsonTest() { - AqlQuery aqlDto = AqlQueryParser.parse(QUERY_5); - when(templateService.createSelectCompositionQuery(Mockito.any())).thenReturn(aqlDto); + public void getExportResponseBodyAsJsonTest() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + AqlQueryParser.parse(QUERY_5); projectService.getExportResponseBody("select * from dummy", 2L, "approvedCoordinatorId", ExportType.json, true); - Mockito.verify(cohortService, times(1)).executeCohort(Mockito.any(Cohort.class), Mockito.eq(false)); } @Test @@ -1622,31 +1755,6 @@ public void getExportResponseBodyAsCSVTest() { Mockito.verify(cohortService, times(1)).executeCohort(Mockito.eq(2L), Mockito.eq(false)); } - @Test - public void streamResponseBody() throws IOException { - QueryResponseData response = new QueryResponseData(); - response.setName("response-one"); - response.setColumns(new ArrayList<>(List.of(Map.of("path", "/ehr_id/value"), Map.of("uuid", "c/uuid")))); - response.setRows( List.of( - new ArrayList<>(List.of("ehr-id-1", Map.of("_type", "OBSERVATION", "uuid", "12345"))), - new ArrayList<>(List.of("ehr-id-2", Map.of("_type", "SECTION", "uuid", "bla"))))); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - projectService.streamResponseAsZip(List.of(response), "testFile", out); - - ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(out.toByteArray())); - ZipEntry expectedFile = zipInputStream.getNextEntry(); - Assert.assertEquals("testFile_response-one.csv", expectedFile.getName()); - } - - @Test - public void getManagerExportResponseBodyTest() { - CohortDto cohortDto = CohortDto.builder() - .name("alter cohort") - .projectId(2L).build(); - projectService.getManagerExportResponseBody(cohortDto, List.of("Alter"), "approvedCoordinatorId", ExportType.json); - Mockito.verify(cohortService, Mockito.times(1)).toCohort(Mockito.any(CohortDto.class)); - } - @Test public void existsTest() { projectService.exists(5L); @@ -1720,176 +1828,4 @@ public void shouldRejectDeleteAttachmentsWhenProjectPendingTest() { String expectedMessage = String.format(CANNOT_DELETE_ATTACHMENTS_INVALID_PROJECT_STATUS, PENDING); assertThat(exception.getMessage(), is(expectedMessage)); } - - @Before - public void setup() { - when(userDetailsService.getUserDetailsById("researcher1")) - .thenReturn( - Optional.of(UserDetails.builder().userId("researcher1").approved(true).build())); - UserDetails ownerCoordinator = UserDetails.builder() - .userId("ownerCoordinatorId") - .approved(true).build(); - User researcher2 = User.builder() - .id("researcher2") - .firstName("f2") - .lastName("l2") - .email("em2@highmed.org") - .build(); - UserDetails researcher = UserDetails.builder() - .userId("researcher2") - .approved(true) - .build(); - when(userService.getUserById("researcher2", false)).thenReturn(researcher2); - - when(userService.getUserById("researcher1", false)) - .thenReturn( - User.builder() - .id("researcher1") - .firstName("f1") - .lastName("l1") - .email("em1@highmed.org") - .build()); - - UserDetails approvedCoordinator = - UserDetails.builder().userId("approvedCoordinatorId").approved(true).build(); - - User approvedUser = User.builder().id("approvedCoordinatorId").approved(true).build(); - - when(userService.getUserById("approvedCoordinatorId", false)).thenReturn(approvedUser); - - when(userDetailsService.checkIsUserApproved("approvedCoordinatorId")) - .thenReturn(approvedCoordinator); - - when(userDetailsService.checkIsUserApproved("notApprovedCoordinatorId")) - .thenThrow(new ForbiddenException(ProjectServiceTest.class, CANNOT_ACCESS_THIS_RESOURCE_USER_IS_NOT_APPROVED)); - - when(userDetailsService.checkIsUserApproved("nonExistingCoordinatorId")) - .thenThrow(new SystemException(ProjectServiceTest.class, USER_NOT_FOUND)); - - when(projectRepository.findById(3L)) - .thenReturn( - Optional.of( - Project.builder() - .id(3L) - .status(PUBLISHED) - .researchers(List.of(approvedCoordinator)) - .build())); - - projectOne = Project.builder() - .id(1L) - .status(PUBLISHED) - .cohort(Cohort.builder().id(2L).build()) - .researchers(List.of(approvedCoordinator)) - .build(); - when(projectRepository.findById(1L)) - .thenReturn(Optional.of(projectOne)); - projectDtoOne = ProjectDto.builder() - .id(1L) - .status(PUBLISHED) - .build(); - when(projectMapper.convertToDto(projectOne)).thenReturn(projectDtoOne); - when(projectRepository.findById(2L)) - .thenReturn( - Optional.of( - Project.builder() - .id(2L) - .status(PUBLISHED) - .cohort(Cohort.builder().id(2L).build()) - .researchers(List.of(approvedCoordinator)) - .templates(Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE)) - .build())); - - when(projectRepository.save(any())) - .thenAnswer( - invocation -> { - Project project = invocation.getArgument(0, Project.class); - project.setId(1L); - return project; - }); - - when(projectRepository.findById(4L)) - .thenReturn( - Optional.of( - Project.builder() - .id(4L) - .status(PUBLISHED) - .cohort(Cohort.builder().id(4L).build()) - .researchers(List.of(approvedCoordinator)) - .templates(Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE)) - .build())); - - when(projectRepository.findById(5L)) - .thenReturn( - Optional.of( - Project.builder() - .id(5L) - .cohort(Cohort.builder().id(5L).build()) - .build())); - - when(projectRepository.findById(6L)) - .thenReturn( - Optional.of( - Project.builder() - .id(6L) - .status(PUBLISHED) - .coordinator(ownerCoordinator) - .researchers(List.of(researcher)) - .build())); - - when(projectRepository.findById(7L)) - .thenReturn( - Optional.of( - Project.builder() - .id(7L) - .cohort(Cohort.builder().id(5L).build()) - .status(PUBLISHED) - .coordinator(ownerCoordinator) - .researchers(List.of(researcher)) - .build())); - - Map map = new HashMap<>(); - map.put("1", "1"); - when(projectRepository.findById(8L)) - .thenReturn( - Optional.of( - Project.builder() - .id(8L) - .cohort(Cohort.builder().id(8L).build()) - .status(PUBLISHED) - .templates(map) - .coordinator(ownerCoordinator) - .researchers(List.of(researcher)) - .build())); - - when(cohortService.executeCohort(2L, false)).thenReturn(Set.of(EHR_ID_1, EHR_ID_2)); - when(cohortService.executeCohort(4L, false)).thenReturn(Set.of(EHR_ID_3)); - when(cohortService.executeCohort(5L, true)).thenReturn(Set.of(EHR_ID_2, EHR_ID_3)); - when(cohortService.executeCohort(any(), any())).thenReturn(Set.of(EHR_ID_1, EHR_ID_2)); - when(privacyProperties.getMinHits()).thenReturn(0); - when(consentProperties.getAllowUsageOutsideEuOid()).thenReturn("1937.777.24.5.1.37"); - - //project without template - when(projectRepository.findById(22L)) - .thenReturn( - Optional.of( - Project.builder() - .id(22L) - .status(PUBLISHED) - .cohort(Cohort.builder().id(2L).build()) - .researchers(List.of(approvedCoordinator)) - .build())); - - // project used outside eu - when(projectRepository.findById(33L)) - .thenReturn( - Optional.of( - Project.builder() - .id(33L) - .status(PUBLISHED) - .cohort(Cohort.builder().id(5L).build()) - .researchers(List.of(approvedCoordinator)) - .templates(Map.of(CORONA_TEMPLATE, CORONA_TEMPLATE)) - .usedOutsideEu(true) - .build())); - } } diff --git a/src/test/resources/application-itest.yml b/src/test/resources/application-itest.yml index d87aa111..63eb38e7 100644 --- a/src/test/resources/application-itest.yml +++ b/src/test/resources/application-itest.yml @@ -134,4 +134,4 @@ num: DE: 'user-manual-de' EN: 'user-manual-en' enableAttachmentDatabase: true - fileVirusScanEnabled: false \ No newline at end of file + fileVirusScanEnabled: false diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 93e81612..096bde5b 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -121,4 +121,4 @@ requestpsnworkflow: apikey: iCZdh7ZWuf8ms)vvBgU-IaLi4 event: num.get_extern_psn pseudonymity: - fake3rdPartyPseudonymEnabled: true \ No newline at end of file + fake3rdPartyPseudonymEnabled: true From a5ec0c2d7407b30e25424e6d7d9e7421bfa130d2 Mon Sep 17 00:00:00 2001 From: ramueSVA <156297972+ramueSVA@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:55:49 +0100 Subject: [PATCH 41/45] fix manager search (#656) --- .../org/highmed/numportal/service/ehrbase/EhrBaseService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java b/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java index 1db0a363..ddebe0c3 100644 --- a/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java +++ b/src/main/java/org/highmed/numportal/service/ehrbase/EhrBaseService.java @@ -60,6 +60,7 @@ public class EhrBaseService { private static final String NAME = "name"; private static final String PATH = "path"; private static final String PSEUDONYM = "pseudonym"; + private static final String EXTERNAL_REF_ID_VALUE = "/subject/external_ref/id/value"; private final DefaultRestClient restClient; private final CompositionResponseDataBuilder compositionResponseDataBuilder; @@ -320,7 +321,7 @@ private List getAndRemoveEhrStatusColumn(QueryResponseData compositions) throw new BadRequestException(EhrBaseService.class, NO_DATA_COLUMNS_IN_THE_QUERY_RESULT); } String ehrStatusPath = columns.get(0).get(PATH); - if (ehrStatusPath == null || !ehrStatusPath.equals("/" + ehrBaseProperties.getIdPath())) { + if (ehrStatusPath == null || !ehrStatusPath.endsWith(EXTERNAL_REF_ID_VALUE)) { throw new SystemException(EhrBaseService.class, QUERY_RESULT_DOESN_T_CONTAIN_EHR_STATUS_COLUMN); } columns.remove(0); From c17512f5c063e077a56560c568acaecd1f42f3db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:02:40 +0100 Subject: [PATCH 42/45] Bump com.puppycrawl.tools:checkstyle from 10.20.1 to 10.20.2 (#655) Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 10.20.1 to 10.20.2. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.20.1...checkstyle-10.20.2) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e14b6142..16492150 100644 --- a/pom.xml +++ b/pom.xml @@ -474,7 +474,7 @@ com.puppycrawl.tools checkstyle - 10.20.1 + 10.20.2 From 1a3fedc209bce9529dae2be35a3f74ad7460170f Mon Sep 17 00:00:00 2001 From: mafasva <156201303+mafasva@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:17:55 +0100 Subject: [PATCH 43/45] add deploy env for feature (#657) --- src/main/resources/application-deploy.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application-deploy.yml b/src/main/resources/application-deploy.yml index ef7fbf3b..aaf7b918 100644 --- a/src/main/resources/application-deploy.yml +++ b/src/main/resources/application-deploy.yml @@ -111,3 +111,6 @@ clamAV: logging: level: org.highmed.numportal: ${LOG_LEVEL} + +feature: + search-by-manager: ${FEATURE_SEARCH_BY_MANAGER} \ No newline at end of file From af01721aefa6074d4221dc09539179b97b576ebc Mon Sep 17 00:00:00 2001 From: Github Actions Date: Tue, 3 Dec 2024 09:56:27 +0000 Subject: [PATCH 44/45] updated project version to 1.22.0 From 25670d1cc3c30c317cbb96ee1083cfcba4788edd Mon Sep 17 00:00:00 2001 From: Github Actions Date: Tue, 3 Dec 2024 10:02:48 +0000 Subject: [PATCH 45/45] updated release version to 1.22.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 16492150..d1349454 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.highmed.num-portal num-portal - 1.22.0-SNAPSHOT + 1.22.0 org.springframework.boot