From 1ebccc2f05e5ddef48b79106fcbfb165618f1269 Mon Sep 17 00:00:00 2001 From: KFilippopolitis Date: Thu, 2 Apr 2026 10:01:16 +0300 Subject: [PATCH] feat: add custom exceptions for pathology access and availability and update DataModelService to handle empty results. --- AGENTS.md | 44 +++++++++++++++++++ .../hbp/mip/datamodel/DataModelService.java | 23 +++++++++- .../mip/utils/ControllerExceptionHandler.java | 28 ++++++++++++ .../NoAuthorizedPathologiesException.java | 8 ++++ .../NoPathologiesAvailableException.java | 8 ++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 AGENTS.md create mode 100644 src/main/java/hbp/mip/utils/Exceptions/NoAuthorizedPathologiesException.java create mode 100644 src/main/java/hbp/mip/utils/Exceptions/NoPathologiesAvailableException.java diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..7fe3311a8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# Repository Guidelines + +## Project Structure & Module Organization +This repository is a Spring Boot backend (`java.version=21`) built with Maven. + +- `src/main/java/hbp/mip`: application code. +- `src/main/java/hbp/mip/configurations`: security, persistence, OpenAPI, and web filters. +- `src/main/java/hbp/mip/{algorithm,datamodel,experiment,user}`: feature modules (API, service, repository/DAO, DTOs). +- `src/main/java/hbp/mip/utils`: shared helpers and exception handling. +- `src/main/resources`: runtime config (`application.yml`, `log4j2.yml`) and Flyway migrations in `db/migration`. +- `config/`: container templated config (`application.tmpl`) and static runtime assets. +- `.github/workflows`: release image publishing and mirror automation. + +## Build, Test, and Development Commands +- `mvn clean package`: compile and build `target/platform-backend.jar`. +- `mvn spring-boot:run`: run locally using `src/main/resources/application.yml`. +- `mvn test`: run test phase (also useful as a smoke check when no tests are present). +- `docker build -t hbpmip/platform-backend:testing .`: build container image. +- `pre-commit run --all-files`: run repository hooks (whitespace, YAML/JSON checks, JSON formatting). + +Local development expects PostgreSQL (default `127.0.0.1:5433/portal`) and reachable external services configured in `application.yml`. + +## Coding Style & Naming Conventions +- Follow existing Java style: 4-space indentation, clear constructor injection, and minimal field mutability (`final` where possible). +- Keep package names lowercase under `hbp.mip`. +- Use established type suffixes: `*API` (controllers), `*Service`, `*Repository`, `*DAO`, `*DTO`. +- Preserve current logging approach (`hbp.mip.utils.Logger` + Log4j2 config). +- Keep migration filenames in Flyway format: `V{number}__Description.sql`. + +## Testing Guidelines +There is currently no committed `src/test/java` tree. For new functionality: + +- Add unit/integration tests under `src/test/java`, mirroring production packages. +- Name tests `*Test` (unit) and `*IT` (integration). +- Run `mvn test` before opening a PR. +- For DB changes, verify migrations apply cleanly against a local PostgreSQL instance. + +## Commit & Pull Request Guidelines +Recent history follows Conventional Commit style (for example: `fix(user): ...`, `chore(build): ...`, `refactor(...): ...`). + +- Use `type(scope): short imperative summary`. +- Keep commits focused and atomic. +- In PRs, include: purpose, linked issue, config/env changes, migration impact, and validation steps run (`mvn test`, local run, or Docker build as relevant). +- If API behavior changes, include example request/response snippets. diff --git a/src/main/java/hbp/mip/datamodel/DataModelService.java b/src/main/java/hbp/mip/datamodel/DataModelService.java index 687e472dc..5c41a737b 100644 --- a/src/main/java/hbp/mip/datamodel/DataModelService.java +++ b/src/main/java/hbp/mip/datamodel/DataModelService.java @@ -3,6 +3,8 @@ import com.google.gson.reflect.TypeToken; import hbp.mip.utils.ClaimUtils; import hbp.mip.utils.Exceptions.InternalServerError; +import hbp.mip.utils.Exceptions.NoAuthorizedPathologiesException; +import hbp.mip.utils.Exceptions.NoPathologiesAvailableException; import hbp.mip.utils.HTTPUtil; import hbp.mip.utils.JsonConverters; import hbp.mip.utils.Logger; @@ -37,11 +39,26 @@ public DataModelService(ClaimUtils claimUtils) { public List getDataModels(Authentication authentication, Logger logger) { List allDataModelDTOS = getAggregatedDataModelDTOs(logger); + if (allDataModelDTOS.isEmpty()) { + logger.warn("No pathologies are available for this federation."); + throw new NoPathologiesAvailableException("No pathologies are available for this federation."); + } if (!authenticationIsEnabled) { return allDataModelDTOS; } - return claimUtils.getAuthorizedDataModels(logger, authentication, allDataModelDTOS); + + List authorizedDataModels = + claimUtils.getAuthorizedDataModels(logger, authentication, allDataModelDTOS); + + if (authorizedDataModels.isEmpty()) { + logger.warn("User does not have access to any federation pathology."); + throw new NoAuthorizedPathologiesException( + "You do not have access to any of the pathologies in this federation." + ); + } + + return authorizedDataModels; } private List getAggregatedDataModelDTOs(Logger logger) { @@ -55,7 +72,9 @@ private List getAggregatedDataModelDTOs(Logger logger) { try { StringBuilder response = new StringBuilder(); HTTPUtil.sendGet(exaflowAttributesUrl, response); - exaflowDataModelAttributes = JsonConverters.convertJsonStringToObject(response.toString(), pathologyAttributesType); + Map convertedResponse = + JsonConverters.convertJsonStringToObject(response.toString(), pathologyAttributesType); + exaflowDataModelAttributes = convertedResponse != null ? convertedResponse : Collections.emptyMap(); } catch (Exception e) { logger.error("Could not fetch exaflow dataModels' metadata: " + e.getMessage()); throw new InternalServerError(e.getMessage()); diff --git a/src/main/java/hbp/mip/utils/ControllerExceptionHandler.java b/src/main/java/hbp/mip/utils/ControllerExceptionHandler.java index eb368572d..a86cfb492 100644 --- a/src/main/java/hbp/mip/utils/ControllerExceptionHandler.java +++ b/src/main/java/hbp/mip/utils/ControllerExceptionHandler.java @@ -50,6 +50,34 @@ public ResponseEntity handleUnauthorizedException(UnauthorizedException return new ResponseEntity<>(message, HttpStatus.UNAUTHORIZED); } + @ExceptionHandler(NoAuthorizedPathologiesException.class) + public ResponseEntity handleNoAuthorizedPathologiesException( + NoAuthorizedPathologiesException ex, + WebRequest request + ) { + ErrorMessage message = new ErrorMessage( + HttpStatus.FORBIDDEN.value(), + new Date(), + ex.getMessage(), + request.getDescription(false)); + + return new ResponseEntity<>(message, HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(NoPathologiesAvailableException.class) + public ResponseEntity handleNoPathologiesAvailableException( + NoPathologiesAvailableException ex, + WebRequest request + ) { + ErrorMessage message = new ErrorMessage( + HttpStatus.NOT_FOUND.value(), + new Date(), + ex.getMessage(), + request.getDescription(false)); + + return new ResponseEntity<>(message, HttpStatus.NOT_FOUND); + } + @ExceptionHandler(NoContent.class) public ResponseEntity handleNoContent(NoContent nc, WebRequest request) { return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); diff --git a/src/main/java/hbp/mip/utils/Exceptions/NoAuthorizedPathologiesException.java b/src/main/java/hbp/mip/utils/Exceptions/NoAuthorizedPathologiesException.java new file mode 100644 index 000000000..34f766392 --- /dev/null +++ b/src/main/java/hbp/mip/utils/Exceptions/NoAuthorizedPathologiesException.java @@ -0,0 +1,8 @@ +package hbp.mip.utils.Exceptions; + +public class NoAuthorizedPathologiesException extends RuntimeException { + + public NoAuthorizedPathologiesException(String msg) { + super(msg); + } +} diff --git a/src/main/java/hbp/mip/utils/Exceptions/NoPathologiesAvailableException.java b/src/main/java/hbp/mip/utils/Exceptions/NoPathologiesAvailableException.java new file mode 100644 index 000000000..77c18a988 --- /dev/null +++ b/src/main/java/hbp/mip/utils/Exceptions/NoPathologiesAvailableException.java @@ -0,0 +1,8 @@ +package hbp.mip.utils.Exceptions; + +public class NoPathologiesAvailableException extends RuntimeException { + + public NoPathologiesAvailableException(String msg) { + super(msg); + } +}