diff --git a/.docs/changelog.md b/.docs/changelog.md index 8c114c004061347b51db487ddafdae19a57721fe..941b3ccc59758e4bf709514ac296b738e13158cc 100644 --- a/.docs/changelog.md +++ b/.docs/changelog.md @@ -2,18 +2,20 @@ author: Martin Weise --- -## v1.7.1 (2025-03-04) - -#### Fixes - -* Fixed a bug where quick interaction with the UI caused the user to trigger the brute-force login detection - in [#501](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/issues/501). +## v1.7.1 (2025-03-06) #### Features +* Added support to download `pandas` DataFrame by PID + in [#503](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/issues/503). * Added the possibility to create and fill a table from a `pandas` DataFrame (or optionally just create the schema) in [#496](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/issues/496). +#### Fixes + +* Fixed a bug where quick interaction with the UI caused the user to trigger the brute-force login detection + in [#501](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/issues/501). + ## v1.7.0 (2025-03-03) [:simple-gitlab: GitLab Release](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/tags/v1.7.1) diff --git a/dbrepo-analyse-service/Pipfile.lock b/dbrepo-analyse-service/Pipfile.lock index 94704d5c24469e8669f7037f90a622361f87a2cb..995400fd16e50f79ff7fe5b0b4114ef6c407041e 100644 --- a/dbrepo-analyse-service/Pipfile.lock +++ b/dbrepo-analyse-service/Pipfile.lock @@ -425,7 +425,7 @@ }, "dbrepo": { "hashes": [ - "sha256:4f5ee48e9a68a44c2d1184923b90729d724553862742738d8f942273a586eb3d" + "sha256:e70ea4f7030191eb80116e5d0a4b17b041c94c80359d5d8e707d62218edd9a54" ], "path": "./lib/dbrepo-1.7.1.tar.gz" }, diff --git a/dbrepo-analyse-service/lib/dbrepo-1.7.1-py3-none-any.whl b/dbrepo-analyse-service/lib/dbrepo-1.7.1-py3-none-any.whl index 8e77c6e1f6a01b3d9739280f29539985c359d2c8..61f52896c18ecbec8090177b38b8dbaeb0e1a95e 100644 Binary files a/dbrepo-analyse-service/lib/dbrepo-1.7.1-py3-none-any.whl and b/dbrepo-analyse-service/lib/dbrepo-1.7.1-py3-none-any.whl differ diff --git a/dbrepo-analyse-service/lib/dbrepo-1.7.1.tar.gz b/dbrepo-analyse-service/lib/dbrepo-1.7.1.tar.gz index ede846d6168615c3e3bac49872362163b0a9f005..6708e1d892771d6cdf9293a6e9f5197f4dd9e304 100644 Binary files a/dbrepo-analyse-service/lib/dbrepo-1.7.1.tar.gz and b/dbrepo-analyse-service/lib/dbrepo-1.7.1.tar.gz differ diff --git a/dbrepo-auth-service/listeners/target/create-event-listener.jar b/dbrepo-auth-service/listeners/target/create-event-listener.jar index b117c50b6646de91273c95dc5749cd54868012be..0410b9e37b8fedf3956c870e2faf7aa9e8ab89b6 100644 Binary files a/dbrepo-auth-service/listeners/target/create-event-listener.jar and b/dbrepo-auth-service/listeners/target/create-event-listener.jar differ diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java index 7ca50f8aa864f88bd75d873c93e9caa736fde258..eb9971869f51a0291eec0b69a494656cc308b984 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java @@ -11,7 +11,6 @@ import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.mapper.MariaDbMapper; -import at.tuwien.mapper.MetadataMapper; import at.tuwien.service.CacheService; import at.tuwien.service.DatabaseService; import at.tuwien.service.StorageService; @@ -33,10 +32,7 @@ import lombok.extern.log4j.Log4j2; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -44,7 +40,6 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.UUID; @Log4j2 @@ -56,7 +51,6 @@ public class SubsetEndpoint extends RestEndpoint { private final CacheService cacheService; private final MariaDbMapper mariaDbMapper; private final SubsetService subsetService; - private final MetadataMapper metadataMapper; private final StorageService storageService; private final DatabaseService databaseService; private final EndpointValidator endpointValidator; @@ -64,12 +58,11 @@ public class SubsetEndpoint extends RestEndpoint { @Autowired public SubsetEndpoint(CacheService cacheService, MariaDbMapper mariaDbMapper, SubsetService subsetService, - MetadataMapper metadataMapper, StorageService storageService, DatabaseService databaseService, + StorageService storageService, DatabaseService databaseService, EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.cacheService = cacheService; this.mariaDbMapper = mariaDbMapper; this.subsetService = subsetService; - this.metadataMapper = metadataMapper; this.storageService = storageService; this.databaseService = databaseService; this.endpointValidator = endpointValidator; @@ -162,14 +155,12 @@ public class SubsetEndpoint extends RestEndpoint { }) public ResponseEntity<?> findById(@NotNull @PathVariable("databaseId") UUID databaseId, @NotNull @PathVariable("subsetId") UUID subsetId, - @NotNull @RequestHeader("Accept") String accept, @RequestParam(required = false) Instant timestamp, Principal principal) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, - QueryNotFoundException, FormatNotAvailableException, StorageUnavailableException, UserNotFoundException, - MetadataServiceException, TableNotFoundException, QueryMalformedException, NotAllowedException { - log.debug("endpoint find subset in database, databaseId={}, subsetId={}, accept={}, timestamp={}", databaseId, - subsetId, accept, timestamp); + QueryNotFoundException, UserNotFoundException, MetadataServiceException, NotAllowedException { + log.debug("endpoint find subset in database, databaseId={}, subsetId={}, timestamp={}", databaseId, + subsetId, timestamp); final DatabaseDto database = cacheService.getDatabase(databaseId); endpointValidator.validateOnlyPrivateSchemaAccess(database, principal); final QueryDto subset; @@ -185,27 +176,7 @@ public class SubsetEndpoint extends RestEndpoint { timestamp = Instant.now(); log.debug("timestamp not set: default to {}", timestamp); } - if (accept == null || accept.isBlank()) { - accept = MediaType.APPLICATION_JSON_VALUE; - log.debug("accept header not set: default to {}", accept); - } - switch (accept) { - case MediaType.APPLICATION_JSON_VALUE: - log.trace("accept header matches json"); - return ResponseEntity.ok(subset); - case "text/csv": - log.trace("accept header matches csv"); - final String query = mariaDbMapper.rawSelectQuery(subset.getQuery(), timestamp, null, null); - final Dataset<Row> dataset = subsetService.getData(database, query); - final ExportResourceDto resource = storageService.transformDataset(dataset); - final HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); - log.trace("export table resulted in resource {}", resource); - return ResponseEntity.ok() - .headers(headers) - .body(resource.getResource()); - } - throw new FormatNotAvailableException("Must provide either application/json or text/csv value for header 'Accept': provided " + accept + " instead"); + return ResponseEntity.ok(subset); } @PostMapping @@ -250,18 +221,18 @@ public class SubsetEndpoint extends RestEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<List<Map<String, Object>>> create(@NotNull @PathVariable("databaseId") UUID databaseId, - @Valid @RequestBody SubsetDto data, - Principal principal, - @NotNull HttpServletRequest request, - @RequestParam(required = false) Instant timestamp, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size) + public ResponseEntity<?> create(@NotNull @PathVariable("databaseId") UUID databaseId, + @Valid @RequestBody SubsetDto data, + Principal principal, + @NotNull HttpServletRequest request, + @RequestParam(required = false) Instant timestamp, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, QueryNotFoundException, StorageUnavailableException, QueryMalformedException, StorageNotFoundException, QueryStoreInsertException, TableMalformedException, PaginationException, QueryNotSupportedException, NotAllowedException, UserNotFoundException, MetadataServiceException, TableNotFoundException, - ViewMalformedException, ViewNotFoundException, ImageNotFoundException { + ViewMalformedException, ViewNotFoundException, ImageNotFoundException, FormatNotAvailableException { log.debug("endpoint create subset in database, databaseId={}, page={}, size={}, timestamp={}", databaseId, page, size, timestamp); /* check */ @@ -291,7 +262,7 @@ public class SubsetEndpoint extends RestEndpoint { endpointValidator.validateOnlyPrivateSchemaAccess(database, principal); try { final UUID subsetId = subsetService.create(database, data, timestamp, userId); - return getData(databaseId, subsetId, principal, request, timestamp, page, size); + return getData(databaseId, subsetId, principal, "application/json", request, timestamp, page, size); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -312,7 +283,8 @@ public class SubsetEndpoint extends RestEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Reverse proxy exposing of custom headers", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = List.class))}), + schema = @Schema(implementation = List.class)), + @Content(mediaType = "text/csv")}), @ApiResponse(responseCode = "400", description = "Invalid pagination", content = {@Content( @@ -334,18 +306,20 @@ public class SubsetEndpoint extends RestEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<List<Map<String, Object>>> getData(@NotNull @PathVariable("databaseId") UUID databaseId, - @NotNull @PathVariable("subsetId") UUID subsetId, - Principal principal, - @NotNull HttpServletRequest request, - @RequestParam(required = false) Instant timestamp, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size) + public ResponseEntity<?> getData(@NotNull @PathVariable("databaseId") UUID databaseId, + @NotNull @PathVariable("subsetId") UUID subsetId, + Principal principal, + @NotNull @RequestHeader("Accept") String accept, + @NotNull HttpServletRequest request, + @RequestParam(required = false) Instant timestamp, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size) throws PaginationException, DatabaseNotFoundException, RemoteUnavailableException, NotAllowedException, QueryNotFoundException, DatabaseUnavailableException, QueryMalformedException, UserNotFoundException, - MetadataServiceException, TableNotFoundException, ViewNotFoundException, ViewMalformedException { - log.debug("endpoint get subset data, databaseId={}, subsetId={}, principal.name={} page={}, size={}", - databaseId, subsetId, principal != null ? principal.getName() : null, page, size); + MetadataServiceException, TableNotFoundException, ViewNotFoundException, ViewMalformedException, + FormatNotAvailableException, StorageUnavailableException { + log.debug("endpoint get subset data, databaseId={}, subsetId={}, accept={} page={}, size={}", databaseId, + subsetId, accept, page, size); endpointValidator.validateDataParams(page, size); final DatabaseDto database = cacheService.getDatabase(databaseId); if (!database.getIsPublic()) { @@ -353,7 +327,9 @@ public class SubsetEndpoint extends RestEndpoint { log.error("Failed to re-execute query: no authentication found"); throw new NotAllowedException("Failed to re-execute query: no authentication found"); } - cacheService.getAccess(databaseId, getId(principal)); + if (!isSystem(principal)) { + cacheService.getAccess(databaseId, getId(principal)); + } } log.trace("visibility for database: is_public={}, is_schema_public={}", database.getIsPublic(), database.getIsSchemaPublic()); /* parameters */ @@ -369,6 +345,10 @@ public class SubsetEndpoint extends RestEndpoint { timestamp = Instant.now(); log.debug("timestamp not set: default to {}", timestamp); } + if (accept == null || accept.isBlank()) { + accept = MediaType.APPLICATION_JSON_VALUE; + log.debug("accept header not set: default to {}", accept); + } try { final HttpHeaders headers = new HttpHeaders(); headers.set("X-Id", "" + subsetId); @@ -382,16 +362,32 @@ public class SubsetEndpoint extends RestEndpoint { .build(); } subset.setIdentifiers(metadataServiceGateway.getIdentifiers(database.getId(), subset.getId())); - final String query = mariaDbMapper.rawSelectQuery(subset.getQuery(), timestamp, page, size); + final String query = mariaDbMapper.rawSelectQuery(subset.getQuery(), timestamp, + accept.equals("text/csv") ? null : page, + accept.equals("text/csv") ? null : size); final Dataset<Row> dataset = subsetService.getData(database, query); - final String viewName = metadataMapper.queryDtoToViewName(subset); + final String viewName = subset.getQueryHash(); databaseService.createView(database, viewName, subset.getQuery()); final ViewDto view = databaseService.inspectView(database, viewName); headers.set("Access-Control-Expose-Headers", "X-Id X-Headers"); headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); - return ResponseEntity.status(request.getMethod().equals("POST") ? HttpStatus.CREATED : HttpStatus.OK) - .headers(headers) - .body(transform(dataset)); + final HttpStatusCode statusCode = request.getMethod().equals("POST") ? HttpStatus.CREATED : HttpStatus.OK; + switch (accept) { + case MediaType.APPLICATION_JSON_VALUE: + log.trace("accept header matches json"); + return ResponseEntity.status(statusCode) + .headers(headers) + .body(transform(dataset)); + case "text/csv": + log.trace("accept header matches csv"); + final ExportResourceDto resource = storageService.transformDataset(dataset); + headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); + return ResponseEntity.status(statusCode) + .headers(headers) + .body(storageService.transformDataset(dataset) + .getResource()); + } + throw new FormatNotAvailableException("Must provide either application/json or text/csv value for header 'Accept': provided " + accept + " instead"); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 6914726cb84a886d97210dce9f1f60ac19db53b5..00dc146332f4eb0daf3f2a8cacde65c1d2535fec 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -29,9 +29,9 @@ import lombok.extern.log4j.Log4j2; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.InputStreamResource; 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.web.bind.annotation.*; @@ -40,7 +40,6 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.UUID; @Log4j2 @@ -228,7 +227,8 @@ public class TableEndpoint extends RestEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Expose `X-Count` custom header", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = List.class))}), + schema = @Schema(implementation = List.class)), + @Content(mediaType = "text/csv")}), @ApiResponse(responseCode = "400", description = "Request pagination or table data select query is malformed", content = {@Content( @@ -250,17 +250,19 @@ public class TableEndpoint extends RestEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<List<Map<String, Object>>> getData(@NotNull @PathVariable("databaseId") UUID databaseId, - @NotNull @PathVariable("tableId") UUID tableId, - @RequestParam(required = false) Instant timestamp, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size, - @NotNull HttpServletRequest request, - Principal principal) + public ResponseEntity<?> getData(@NotNull @PathVariable("databaseId") UUID databaseId, + @NotNull @PathVariable("tableId") UUID tableId, + @RequestParam(required = false) Instant timestamp, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size, + @NotNull @RequestHeader("Accept") String accept, + @NotNull HttpServletRequest request, + Principal principal) throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException, - PaginationException, MetadataServiceException, NotAllowedException, DatabaseNotFoundException { - log.debug("endpoint get table data, databaseId={}, tableId={}, timestamp={}, page={}, size={}", databaseId, - tableId, timestamp, page, size); + PaginationException, MetadataServiceException, NotAllowedException, DatabaseNotFoundException, + FormatNotAvailableException, StorageUnavailableException { + log.debug("endpoint get table data, databaseId={}, tableId={}, timestamp={}, page={}, size={}, accept={}", + databaseId, tableId, timestamp, page, size, accept); endpointValidator.validateDataParams(page, size); /* parameters */ if (page == null) { @@ -281,7 +283,9 @@ public class TableEndpoint extends RestEndpoint { log.error("Failed find table data: authentication required"); throw new NotAllowedException("Failed to find table data: authentication required"); } - cacheService.getAccess(databaseId, getId(principal)); + if (!isSystem(principal)) { + cacheService.getAccess(databaseId, getId(principal)); + } } final DatabaseDto database = cacheService.getDatabase(databaseId); try { @@ -296,11 +300,26 @@ public class TableEndpoint extends RestEndpoint { headers.set("Access-Control-Expose-Headers", "X-Headers"); headers.set("X-Headers", String.join(",", table.getColumns().stream().map(ColumnDto::getInternalName).toList())); final String query = mariaDbMapper.defaultRawSelectQuery(database.getInternalName(), - table.getInternalName(), timestamp, page, size); + table.getInternalName(), timestamp, + accept.equals("text/csv") ? null : page, + accept.equals("text/csv") ? null : size); final Dataset<Row> dataset = subsetService.getData(database, query); - return ResponseEntity.ok() - .headers(headers) - .body(transform(dataset)); + switch (accept) { + case MediaType.APPLICATION_JSON_VALUE: + log.trace("accept header matches json"); + return ResponseEntity.ok() + .headers(headers) + .body(transform(dataset)); + case "text/csv": + log.trace("accept header matches csv"); + final ExportResourceDto resource = storageService.transformDataset(dataset); + headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); + return ResponseEntity.status(HttpStatus.OK) + .headers(headers) + .body(storageService.transformDataset(dataset) + .getResource()); + } + throw new FormatNotAvailableException("Must provide either application/json or text/csv value for header 'Accept': provided " + accept + " instead"); } catch (SQLException | QueryMalformedException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -586,72 +605,6 @@ public class TableEndpoint extends RestEndpoint { } } - @GetMapping("/{tableId}/export") - @Observed(name = "dbrepo_table_data_export") - @Operation(summary = "Get table data", - description = "Gets data from table with id as downloadable file. For tables in private databases, the user needs to have at least *READ* access to the associated database.", - security = {@SecurityRequirement(name = "basicAuth"), @SecurityRequirement(name = "bearerAuth")}) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", - description = "Exported table data", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = InputStreamResource.class))}), - @ApiResponse(responseCode = "400", - description = "Request pagination or table data select query is malformed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "403", - description = "Export table data not allowed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "404", - description = "Failed to find table in metadata database", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "503", - description = "Failed to establish connection with the metadata service", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - }) - public ResponseEntity<InputStreamResource> exportDataset(@NotNull @PathVariable("databaseId") UUID databaseId, - @NotNull @PathVariable("tableId") UUID tableId, - @RequestParam(required = false) Instant timestamp, - Principal principal) - throws RemoteUnavailableException, TableNotFoundException, NotAllowedException, StorageUnavailableException, - QueryMalformedException, MetadataServiceException, DatabaseNotFoundException { - log.debug("endpoint export table data, databaseId={}, tableId={}, timestamp={}", databaseId, tableId, timestamp); - /* parameters */ - if (timestamp == null) { - timestamp = Instant.now(); - log.debug("timestamp not set: default to {}", timestamp); - } - final TableDto table = cacheService.getTable(databaseId, tableId); - if (!table.getIsPublic()) { - if (principal == null) { - log.error("Failed to export private table: principal is null"); - throw new NotAllowedException("Failed to export private table: principal is null"); - } - cacheService.getAccess(databaseId, getId(principal)); - } - final DatabaseDto database = cacheService.getDatabase(databaseId); - final String query = mariaDbMapper.defaultRawSelectQuery(database.getInternalName(), - table.getInternalName(), timestamp, null, null); - final Dataset<Row> dataset = subsetService.getData(cacheService.getDatabase(table.getDatabaseId()), - query); - final ExportResourceDto resource = storageService.transformDataset(dataset); - final HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); - log.trace("export table resulted in resource {}", resource); - return ResponseEntity.ok() - .headers(headers) - .body(resource.getResource()); - } - @PostMapping("/{tableId}/data/import") @Observed(name = "dbrepo_table_data_import") @PreAuthorize("hasAuthority('insert-table-data')") diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java index 5c1fdb57dbc30b428d104e4c145f4a0064105f78..3a06b2fe7c4148ef4a53dcb9626de125aaf619e1 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java @@ -25,9 +25,9 @@ import lombok.extern.log4j.Log4j2; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.InputStreamResource; 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.web.bind.annotation.*; @@ -36,7 +36,6 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.UUID; @Log4j2 @@ -225,7 +224,8 @@ public class ViewEndpoint extends RestEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Expose `X-Count` custom header", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = List.class))}), + schema = @Schema(implementation = List.class)), + @Content(mediaType = "text/csv")}), @ApiResponse(responseCode = "400", description = "Request pagination is malformed", content = {@Content( @@ -252,17 +252,20 @@ public class ViewEndpoint extends RestEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<List<Map<String, Object>>> getData(@NotNull @PathVariable("databaseId") UUID databaseId, - @NotNull @PathVariable("viewId") UUID viewId, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size, - @RequestParam(required = false) Instant timestamp, - @NotNull HttpServletRequest request, - Principal principal) + public ResponseEntity<?> getData(@NotNull @PathVariable("databaseId") UUID databaseId, + @NotNull @PathVariable("viewId") UUID viewId, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size, + @RequestParam(required = false) Instant timestamp, + @NotNull HttpServletRequest request, + @NotNull @RequestHeader("Accept") String accept, + Principal principal) throws DatabaseUnavailableException, RemoteUnavailableException, ViewNotFoundException, PaginationException, - QueryMalformedException, NotAllowedException, MetadataServiceException, TableNotFoundException, DatabaseNotFoundException { - log.debug("endpoint get view data, databaseId={}, viewId={}, page={}, size={}, timestamp={}", databaseId, - viewId, page, size, timestamp); + QueryMalformedException, NotAllowedException, MetadataServiceException, TableNotFoundException, + DatabaseNotFoundException, ViewMalformedException, StorageUnavailableException, + FormatNotAvailableException { + log.debug("endpoint get view data, databaseId={}, viewId={}, page={}, size={}, accept={}, timestamp={}", + databaseId, viewId, page, size, accept, timestamp); endpointValidator.validateDataParams(page, size); /* parameters */ if (page == null) { @@ -283,7 +286,9 @@ public class ViewEndpoint extends RestEndpoint { log.error("Failed to get data from view: unauthorized"); throw new NotAllowedException("Failed to get data from view: unauthorized"); } - cacheService.getAccess(databaseId, getId(principal)); + if (!isSystem(principal)) { + cacheService.getAccess(databaseId, getId(principal)); + } } final DatabaseDto database = cacheService.getDatabase(databaseId); try { @@ -297,84 +302,32 @@ public class ViewEndpoint extends RestEndpoint { } headers.set("Access-Control-Expose-Headers", "X-Headers"); headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); - final String query = mariaDbMapper.defaultRawSelectQuery(database.getInternalName(), - view.getInternalName(), timestamp, page, size); - final Dataset<Row> dataset = subsetService.getData(cacheService.getDatabase(databaseId), - query); - return ResponseEntity.ok() - .headers(headers) - .body(transform(dataset)); + final String query = mariaDbMapper.rawSelectQuery(view.getQuery(), timestamp, + accept.equals("text/csv") ? null : page, + accept.equals("text/csv") ? null : size); + final Dataset<Row> dataset = subsetService.getData(database, query); + final String viewName = view.getQueryHash(); + databaseService.createView(database, viewName, view.getQuery()); + switch (accept) { + case MediaType.APPLICATION_JSON_VALUE: + log.trace("accept header matches json"); + return ResponseEntity.ok() + .headers(headers) + .body(transform(dataset)); + case "text/csv": + log.trace("accept header matches csv"); + final ExportResourceDto resource = storageService.transformDataset(dataset); + headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); + return ResponseEntity.ok() + .headers(headers) + .body(storageService.transformDataset(dataset) + .getResource()); + } + throw new FormatNotAvailableException("Must provide either application/json or text/csv value for header 'Accept': provided " + accept + " instead"); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); } } - @GetMapping("/{viewId}/export") - @Observed(name = "dbrepo_view_data_export") - @Operation(summary = "Get view data", - description = "Gets data from view with id as downloadable file. For tables in private databases, the user needs to have at least *READ* access to the associated database.", - security = {@SecurityRequirement(name = "basicAuth"), @SecurityRequirement(name = "bearerAuth")}) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", - description = "Exported view data", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = InputStreamResource.class))}), - @ApiResponse(responseCode = "400", - description = "Request pagination or view data select query is malformed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "403", - description = "Export view data not allowed", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "404", - description = "Failed to find view in metadata database or export dataset", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - @ApiResponse(responseCode = "503", - description = "Failed to establish connection with the metadata service", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ApiErrorDto.class))}), - }) - public ResponseEntity<InputStreamResource> exportDataset(@NotNull @PathVariable("databaseId") UUID databaseId, - @NotNull @PathVariable("viewId") UUID viewId, - @RequestParam(required = false) Instant timestamp, - Principal principal) - throws RemoteUnavailableException, ViewNotFoundException, NotAllowedException, MetadataServiceException, - StorageUnavailableException, QueryMalformedException, TableNotFoundException, DatabaseNotFoundException { - log.debug("endpoint export view data, databaseId={}, viewId={}", databaseId, viewId); - /* parameters */ - if (timestamp == null) { - timestamp = Instant.now(); - log.debug("timestamp not set: default to {}", timestamp); - } - /* parameters */ - final ViewDto view = cacheService.getView(databaseId, viewId); - if (!view.getIsPublic()) { - if (principal == null) { - log.error("Failed to export private view: principal is null"); - throw new NotAllowedException("Failed to export private view: principal is null"); - } - cacheService.getAccess(databaseId, getId(principal)); - } - final DatabaseDto database = cacheService.getDatabase(databaseId); - final String query = mariaDbMapper.defaultRawSelectQuery(database.getInternalName(), - view.getInternalName(), timestamp, null, null); - final Dataset<Row> dataset = subsetService.getData(cacheService.getDatabase(databaseId), - query); - final ExportResourceDto resource = storageService.transformDataset(dataset); - final HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); - log.trace("export table resulted in resource {}", resource); - return ResponseEntity.ok() - .headers(headers) - .body(resource.getResource()); - } - } diff --git a/dbrepo-data-service/rest-service/src/main/resources/application.yml b/dbrepo-data-service/rest-service/src/main/resources/application.yml index b5f592d570cb73677baaa00666af945901e1d256..53c0858bbd98acd3bb250b0def751534bc920548 100644 --- a/dbrepo-data-service/rest-service/src/main/resources/application.yml +++ b/dbrepo-data-service/rest-service/src/main/resources/application.yml @@ -74,6 +74,7 @@ dbrepo: default: read: "${GRANT_DEFAULT_READ:SELECT}" write: "${GRANT_DEFAULT_WRITE:SELECT, CREATE, CREATE VIEW, CREATE ROUTINE, CREATE TEMPORARY TABLES, LOCK TABLES, INDEX, TRIGGER, INSERT, UPDATE, DELETE}" + website: "${BASE_URL:http://localhost}" credentialCacheTimeout: "${CREDENTIAL_CACHE_TIMEOUT:60}" minConcurrent: "${MIN_CONCURRENT_CONSUMERS:2}" maxConcurrent: "${MAX_CONCURRENT_CONSUMERS:6}" diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java index 9cdb0d99c82e399c1d8297c1c71dd1f6dd963a31..ca97569f3c00b54b466af97cd99d44aea4f31d0e 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java @@ -33,7 +33,6 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; -import java.util.Map; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @@ -145,16 +144,15 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - generic_findById(DATABASE_1_ID, QUERY_1_ID, "application/json", null, null); + generic_findById(DATABASE_1_ID, QUERY_1_ID, null, null); }); } @Test @WithMockUser(username = USER_1_USERNAME) public void findById_privateDataPrivateSchema_succeeds() throws DatabaseNotFoundException, SQLException, - RemoteUnavailableException, UserNotFoundException, QueryNotFoundException, MetadataServiceException, - DatabaseUnavailableException, TableNotFoundException, StorageUnavailableException, NotAllowedException, - QueryMalformedException, FormatNotAvailableException { + RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, + QueryNotFoundException, MetadataServiceException { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) @@ -163,15 +161,14 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(QUERY_1_DTO); /* test */ - generic_findById(DATABASE_1_ID, QUERY_1_ID, "application/json", null, USER_1_PRINCIPAL); + generic_findById(DATABASE_1_ID, QUERY_1_ID, null, USER_1_PRINCIPAL); } @Test @WithMockUser(username = USER_1_USERNAME) public void findById_privateDataPrivateSchemaAcceptEmpty_succeeds() throws DatabaseNotFoundException, SQLException, - RemoteUnavailableException, UserNotFoundException, QueryNotFoundException, MetadataServiceException, - DatabaseUnavailableException, TableNotFoundException, StorageUnavailableException, NotAllowedException, - QueryMalformedException, FormatNotAvailableException { + RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, + QueryNotFoundException, MetadataServiceException { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) @@ -180,15 +177,14 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(QUERY_1_DTO); /* test */ - generic_findById(DATABASE_1_ID, QUERY_1_ID, null, null, USER_1_PRINCIPAL); + generic_findById(DATABASE_1_ID, QUERY_1_ID, null, USER_1_PRINCIPAL); } @Test @WithMockUser(username = USER_3_USERNAME) public void findById_publicDataPrivateSchema_succeeds() throws DatabaseNotFoundException, SQLException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, - StorageUnavailableException, QueryMalformedException, QueryNotFoundException, - FormatNotAvailableException, TableNotFoundException, MetadataServiceException { + QueryNotFoundException, MetadataServiceException { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) @@ -197,7 +193,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(QUERY_5_DTO); /* test */ - generic_findById(DATABASE_3_ID, QUERY_5_ID, "application/json", null, USER_3_PRINCIPAL); + generic_findById(DATABASE_3_ID, QUERY_5_ID, null, USER_3_PRINCIPAL); } @Test @@ -213,16 +209,16 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(FormatNotAvailableException.class, () -> { - generic_findById(DATABASE_4_ID, QUERY_7_ID, "application/pdf", null, null); + generic_findById(DATABASE_4_ID, QUERY_7_ID, null, null); }); } @Test @WithMockUser(username = USER_1_USERNAME) - public void findById_privateDataPrivateSchemaAcceptCsv_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, - UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, - QueryNotFoundException, FormatNotAvailableException, SQLException, MetadataServiceException, - TableNotFoundException, NotAllowedException { + public void findById_privateDataPrivateSchemaAcceptCsv_succeeds() throws DatabaseNotFoundException, + RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, + StorageUnavailableException, QueryMalformedException, QueryNotFoundException, SQLException, + MetadataServiceException, TableNotFoundException, NotAllowedException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -236,7 +232,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(EXPORT_RESOURCE_DTO); /* test */ - generic_findById(DATABASE_1_ID, QUERY_1_ID, "text/csv", null, USER_1_PRINCIPAL); + generic_findById(DATABASE_1_ID, QUERY_1_ID, null, USER_1_PRINCIPAL); } @Test @@ -250,7 +246,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - generic_findById(DATABASE_3_ID, QUERY_5_ID, "text/csv", Instant.now(), null); + generic_findById(DATABASE_3_ID, QUERY_5_ID, Instant.now(), null); }); } @@ -273,7 +269,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - generic_findById(DATABASE_3_ID, QUERY_5_ID, "text/csv", Instant.now(), null); + generic_findById(DATABASE_3_ID, QUERY_5_ID, Instant.now(), null); }); } @@ -289,7 +285,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseNotFoundException.class, () -> { - generic_findById(DATABASE_3_ID, QUERY_5_ID, "application/json", null, null); + generic_findById(DATABASE_3_ID, QUERY_5_ID, null, null); }); } @@ -307,7 +303,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseUnavailableException.class, () -> { - generic_findById(DATABASE_3_ID, QUERY_5_ID, "application/json", null, USER_3_PRINCIPAL); + generic_findById(DATABASE_3_ID, QUERY_5_ID, null, USER_3_PRINCIPAL); }); } @@ -318,7 +314,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { StorageNotFoundException, DatabaseUnavailableException, StorageUnavailableException, SQLException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException, TableNotFoundException, ViewMalformedException, ViewNotFoundException, - ImageNotFoundException { + ImageNotFoundException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -348,7 +344,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { PaginationException, StorageNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, SQLException, MetadataServiceException, TableNotFoundException, ViewMalformedException, - ViewNotFoundException, ImageNotFoundException { + ViewNotFoundException, ImageNotFoundException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -398,7 +394,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { TableMalformedException, NotAllowedException, SQLException, QueryNotFoundException, PaginationException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotSupportedException, StorageNotFoundException, TableNotFoundException, ViewMalformedException, - ViewNotFoundException, ImageNotFoundException { + ViewNotFoundException, ImageNotFoundException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -426,7 +422,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { NotAllowedException, SQLException, QueryNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotSupportedException, PaginationException, StorageNotFoundException, TableNotFoundException, ViewMalformedException, ViewNotFoundException, - ImageNotFoundException { + ImageNotFoundException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -478,7 +474,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { public void getData_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, NotAllowedException, SQLException, QueryNotFoundException, QueryMalformedException, DatabaseUnavailableException, PaginationException, MetadataServiceException, TableNotFoundException, - ViewNotFoundException, ViewMalformedException { + ViewNotFoundException, ViewMalformedException, StorageUnavailableException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -498,16 +494,17 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("GET"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null, null); + final ResponseEntity<?> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, + null, "GET", httpServletRequest, null, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @Test public void getData_head_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, - UserNotFoundException, NotAllowedException, SQLException, QueryNotFoundException, TableMalformedException, - QueryMalformedException, DatabaseUnavailableException, PaginationException, MetadataServiceException, - TableNotFoundException, ViewNotFoundException, ViewMalformedException { + UserNotFoundException, NotAllowedException, SQLException, QueryNotFoundException, QueryMalformedException, + DatabaseUnavailableException, PaginationException, MetadataServiceException, TableNotFoundException, + ViewNotFoundException, ViewMalformedException, StorageUnavailableException, FormatNotAvailableException { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) @@ -520,7 +517,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("HEAD"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null, null); + final ResponseEntity<?> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, + null, "GET", httpServletRequest, null, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("X-Count")); assertEquals(1, response.getHeaders().get("X-Count").size()); @@ -532,7 +530,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { public void getData_private_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, QueryMalformedException, QueryNotFoundException, PaginationException, SQLException, MetadataServiceException, - TableNotFoundException, ViewNotFoundException, ViewMalformedException { + TableNotFoundException, ViewNotFoundException, ViewMalformedException, StorageUnavailableException, + FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -552,7 +551,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("GET"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null, null); + final ResponseEntity<?> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, + USER_1_PRINCIPAL, "GET", httpServletRequest, null, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @@ -568,7 +568,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, null, httpServletRequest, null, null, null); + subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, null, "GET", httpServletRequest, null, null, null); }); } @@ -586,7 +586,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null, null); + subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, "GET", httpServletRequest, null, null, null); }); } @@ -595,7 +595,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { public void getData_privateHead_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, QueryMalformedException, QueryNotFoundException, PaginationException, SQLException, MetadataServiceException, - TableNotFoundException, ViewNotFoundException, ViewMalformedException { + TableNotFoundException, ViewNotFoundException, ViewMalformedException, StorageUnavailableException, + FormatNotAvailableException { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) @@ -608,7 +609,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("HEAD"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null, null); + final ResponseEntity<?> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, + USER_1_PRINCIPAL, "GET", httpServletRequest, null, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("X-Count")); assertEquals(1, response.getHeaders().get("X-Count").size()); @@ -734,14 +736,12 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { return response.getBody(); } - protected void generic_findById(UUID databaseId, UUID subsetId, String accept, Instant timestamp, - Principal principal) throws UserNotFoundException, DatabaseUnavailableException, - StorageUnavailableException, NotAllowedException, QueryMalformedException, QueryNotFoundException, - DatabaseNotFoundException, RemoteUnavailableException, FormatNotAvailableException, - MetadataServiceException, TableNotFoundException { + protected void generic_findById(UUID databaseId, UUID subsetId, Instant timestamp, Principal principal) + throws UserNotFoundException, DatabaseUnavailableException, NotAllowedException, QueryNotFoundException, + DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException { /* test */ - final ResponseEntity<?> response = subsetEndpoint.findById(databaseId, subsetId, accept, timestamp, principal); + final ResponseEntity<?> response = subsetEndpoint.findById(databaseId, subsetId, timestamp, principal); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java index 25f3b60ceac1f47225e73c237cefc215c21f1696..3a5a62731bc595c2e971a382cf6c3a1c1b5791aa 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java @@ -39,7 +39,6 @@ import java.sql.SQLException; import java.time.Instant; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -292,7 +291,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void getData_publicDataPrivateSchema_succeeds() throws DatabaseUnavailableException, TableNotFoundException, QueryMalformedException, RemoteUnavailableException, PaginationException, MetadataServiceException, NotAllowedException, - DatabaseNotFoundException { + DatabaseNotFoundException, StorageUnavailableException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -306,7 +305,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { .thenReturn("GET"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_4_ID, null, null, null, httpServletRequest, null); + final ResponseEntity<?> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_4_ID, null, null, null, "application/json", httpServletRequest, null); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -315,7 +314,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void getData_head_succeeds() throws DatabaseUnavailableException, TableNotFoundException, SQLException, QueryMalformedException, RemoteUnavailableException, PaginationException, - MetadataServiceException, NotAllowedException, DatabaseNotFoundException { + MetadataServiceException, NotAllowedException, DatabaseNotFoundException, StorageUnavailableException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -331,7 +330,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { .thenReturn("HEAD"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = tableEndpoint.getData(DATABASE_2_ID, TABLE_5_ID, null, null, null, httpServletRequest, null); + final ResponseEntity<?> response = tableEndpoint.getData(DATABASE_2_ID, TABLE_5_ID, null, null, null, "application/json", httpServletRequest, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); @@ -351,7 +350,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, null); + tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "application/json", httpServletRequest, null); }); } @@ -369,14 +368,14 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, USER_2_PRINCIPAL); + tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "application/json", httpServletRequest, USER_2_PRINCIPAL); }); } @Test @WithAnonymousUser public void getData_notAllowed_fails() throws TableNotFoundException, RemoteUnavailableException, - MetadataServiceException{ + MetadataServiceException { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) @@ -386,7 +385,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, httpServletRequest, null); + tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, "application/json", httpServletRequest, null); }); } @@ -408,7 +407,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseUnavailableException.class, () -> { - tableEndpoint.getData(DATABASE_2_ID, TABLE_5_ID, null, null, null, httpServletRequest, null); + tableEndpoint.getData(DATABASE_2_ID, TABLE_5_ID, null, null, null, "application/json", httpServletRequest, null); }); } @@ -426,7 +425,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(RemoteUnavailableException.class, () -> { - tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, USER_2_PRINCIPAL); + tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "application/json", httpServletRequest, USER_2_PRINCIPAL); }); } @@ -435,7 +434,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @MethodSource("anyAccess_parameters") public void getData_private_succeeds(String name, DatabaseAccessDto access) throws DatabaseUnavailableException, TableNotFoundException, QueryMalformedException, RemoteUnavailableException, PaginationException, - MetadataServiceException, NotAllowedException, DatabaseNotFoundException { + MetadataServiceException, NotAllowedException, DatabaseNotFoundException, StorageUnavailableException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -451,7 +450,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { .thenReturn("GET"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, USER_2_PRINCIPAL); + final ResponseEntity<?> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "application/json", httpServletRequest, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -467,7 +466,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(TableNotFoundException.class, () -> { - tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, httpServletRequest, null); + tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, "application/json", httpServletRequest, null); }); } @@ -1323,8 +1322,9 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void exportData_publicDataPrivateSchema_succeeds() throws TableNotFoundException, NotAllowedException, StorageUnavailableException, - QueryMalformedException, RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException { + public void getData_publicDataPrivateSchemaTextCsv_succeeds() throws TableNotFoundException, NotAllowedException, + StorageUnavailableException, QueryMalformedException, RemoteUnavailableException, MetadataServiceException, + DatabaseNotFoundException, DatabaseUnavailableException, FormatNotAvailableException, PaginationException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -1334,18 +1334,21 @@ public class TableEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_PRIVILEGED_DTO); when(subsetService.getData(any(DatabaseDto.class), anyString())) .thenReturn(mock); + when(httpServletRequest.getMethod()) + .thenReturn("GET"); /* test */ - final ResponseEntity<InputStreamResource> response = tableEndpoint.exportDataset(DATABASE_1_ID, TABLE_4_ID, null, null); + final ResponseEntity<?> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_4_ID, null, null, null, "text/csv", httpServletRequest, null); assertEquals(HttpStatus.OK, response.getStatusCode()); } @ParameterizedTest @WithMockUser(username = USER_2_USERNAME) @MethodSource("anyAccess_parameters") - public void exportData_privateDataPrivateSchema_succeeds(String name, DatabaseAccessDto access) + public void getData_privateDataPrivateSchemaTextCsv_succeeds(String name, DatabaseAccessDto access) throws TableNotFoundException, NotAllowedException, StorageUnavailableException, QueryMalformedException, - RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException { + RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException, + DatabaseUnavailableException, FormatNotAvailableException, PaginationException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -1359,9 +1362,11 @@ public class TableEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_DTO); when(subsetService.getData(any(DatabaseDto.class), anyString())) .thenReturn(mock); + when(httpServletRequest.getMethod()) + .thenReturn("GET"); /* test */ - final ResponseEntity<InputStreamResource> response = tableEndpoint.exportDataset(DATABASE_1_ID, TABLE_1_ID, null, USER_2_PRINCIPAL); + final ResponseEntity<?> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "text/csv", httpServletRequest, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -1381,7 +1386,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - tableEndpoint.exportDataset(DATABASE_1_ID, TABLE_1_ID, null, null); + tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "text/csv", httpServletRequest, null); }); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java index 0393be3dad8da280f29cc1be9133ffa42c9f6cb2..23ad1b73e3db99c355d08aa8011ab658fb48e728 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java @@ -29,7 +29,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.sql.SQLException; import java.time.Instant; import java.util.List; -import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; @@ -253,7 +252,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_PRIVILEGED_DTO); doNothing() .when(viewService) - .delete(DATABASE_1_PRIVILEGED_DTO,VIEW_1_DTO); + .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_DTO); /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { @@ -281,7 +280,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) public void getData_privateDataPrivateSchema_succeeds() throws RemoteUnavailableException, ViewNotFoundException, DatabaseUnavailableException, QueryMalformedException, PaginationException, NotAllowedException, - MetadataServiceException, TableNotFoundException, DatabaseNotFoundException { + MetadataServiceException, TableNotFoundException, DatabaseNotFoundException, ViewMalformedException, StorageUnavailableException, FormatNotAvailableException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ @@ -297,7 +296,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { .thenReturn("GET"); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); + final ResponseEntity<?> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "application/json", USER_1_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @@ -323,7 +322,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, null); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "application/json", null); }); } @@ -331,7 +330,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) public void getData_privateHead_succeeds() throws RemoteUnavailableException, ViewNotFoundException, SQLException, DatabaseUnavailableException, QueryMalformedException, PaginationException, - NotAllowedException, MetadataServiceException, TableNotFoundException, DatabaseNotFoundException { + NotAllowedException, MetadataServiceException, TableNotFoundException, DatabaseNotFoundException, ViewMalformedException, StorageUnavailableException, FormatNotAvailableException { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) @@ -346,7 +345,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { .thenReturn(VIEW_3_DATA_COUNT); /* test */ - final ResponseEntity<List<Map<String, Object>>> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_3_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); + final ResponseEntity<?> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_3_ID, null, null, null, httpServletRequest, "application/json", USER_1_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("X-Count")); assertEquals(1, response.getHeaders().get("X-Count").size()); @@ -373,13 +372,13 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_4_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "application/json", USER_4_PRINCIPAL); }); } @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) - public void getData_viewNotFound_fails() throws RemoteUnavailableException, ViewNotFoundException, + public void getData_viewNotFoundTextCsv_fails() throws RemoteUnavailableException, ViewNotFoundException, MetadataServiceException { /* mock */ @@ -389,7 +388,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(ViewNotFoundException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_4_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "text/csv", USER_4_PRINCIPAL); }); } @@ -407,13 +406,13 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_4_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "application/json", USER_4_PRINCIPAL); }); } @Test @WithMockUser(username = USER_3_USERNAME, authorities = {"view-database-view-data"}) - public void exportDataset_privateNoAccess_fails() throws RemoteUnavailableException, ViewNotFoundException, + public void getData_privateNoAccessTextCsv_fails() throws RemoteUnavailableException, ViewNotFoundException, NotAllowedException, MetadataServiceException { /* mock */ @@ -425,13 +424,13 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, null, USER_4_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "text/csv", USER_4_PRINCIPAL); }); } @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) - public void exportDataset_viewNotFound_fails() throws RemoteUnavailableException, ViewNotFoundException, + public void getData_viewNotFound_fails() throws RemoteUnavailableException, ViewNotFoundException, MetadataServiceException { /* mock */ @@ -441,25 +440,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(ViewNotFoundException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, null, USER_4_PRINCIPAL); - }); - } - - @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) - public void exportDataset_privateNoAccess_succeeds() throws RemoteUnavailableException, ViewNotFoundException, - NotAllowedException, MetadataServiceException { - - /* mock */ - when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_DTO); - doThrow(NotAllowedException.class) - .when(credentialService) - .getAccess(DATABASE_1_ID, USER_4_ID); - - /* test */ - assertThrows(NotAllowedException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, null, USER_4_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, "application/json", USER_4_PRINCIPAL); }); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java index ae03fdbf68cefc1db05455d0ec228ea00be1c12f..33f77b7cce1f3fc29f70073bc9bbc6e5acd5869e 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java @@ -1,8 +1,6 @@ package at.tuwien.mvc; -import at.tuwien.api.database.query.ExecuteStatementDto; import at.tuwien.api.database.query.ImportDto; -import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.query.QueryPersistDto; import at.tuwien.api.database.table.TupleDeleteDto; import at.tuwien.api.database.table.TupleDto; @@ -116,7 +114,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* ignore */ } try { - subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, 0L, 10L); + subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, "application/json", httpServletRequest, null, 0L, 10L); } catch (Exception e) { /* ignore */ } @@ -126,7 +124,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* ignore */ } try { - subsetEndpoint.findById(DATABASE_1_ID, QUERY_1_ID, "application/json", null, USER_1_PRINCIPAL); + subsetEndpoint.findById(DATABASE_1_ID, QUERY_1_ID, null, USER_1_PRINCIPAL); } catch (Exception e) { /* ignore */ } @@ -145,7 +143,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* mock */ try { - tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, null); + tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, "application/json", httpServletRequest, null); } catch (Exception e) { /* ignore */ } @@ -169,11 +167,6 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { } catch (Exception e) { /* ignore */ } - try { - tableEndpoint.exportDataset(DATABASE_1_ID, TABLE_1_ID, null, USER_1_PRINCIPAL); - } catch (Exception e) { - /* ignore */ - } try { tableEndpoint.importDataset(DATABASE_1_ID, TABLE_1_ID, ImportDto.builder().build(), USER_1_PRINCIPAL, TOKEN_ACCESS_TOKEN); } catch (Exception e) { @@ -195,7 +188,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* mock */ try { - viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, 0L, 10L, null, httpServletRequest, USER_1_PRINCIPAL); + viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, 0L, 10L, null, httpServletRequest, "application/json", USER_1_PRINCIPAL); } catch (Exception e) { /* ignore */ } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java index 7bdf8fa14118f7f6cca621cf01e7567ce608dfdc..0bd399d454d4427440684a57d3f2611079279bca 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -6,7 +6,6 @@ import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.columns.ColumnDto; @@ -27,10 +26,6 @@ public interface MetadataMapper { org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); - default String queryDtoToViewName(QueryDto subset) { - return subset.getQueryHash(); - } - ContainerDto containerDtoToContainerDto(ContainerDto data); DatabaseBriefDto databaseDtoToDatabaseBriefDto(DatabaseDto data); diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java new file mode 100644 index 0000000000000000000000000000000000000000..b8448528bda815a6f0fa0faba462b2a37fae1b95 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java @@ -0,0 +1,7 @@ +package at.tuwien.service; + +import at.tuwien.api.keycloak.TokenDto; + +public interface CredentialService { + TokenDto getAccessToken(String username, String password); +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7cf7d1eff4394ae174b0e724fa8e34455b363578 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java @@ -0,0 +1,44 @@ +package at.tuwien.service.impl; + +import at.tuwien.api.keycloak.TokenDto; +import at.tuwien.gateway.KeycloakGateway; +import at.tuwien.service.CredentialService; +import com.github.benmanes.caffeine.cache.Cache; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +public class CredentialServiceImpl implements CredentialService { + + private final KeycloakGateway keycloakGateway; + private final Cache<String, TokenDto> tokenCache; + + @Autowired + public CredentialServiceImpl(KeycloakGateway keycloakGateway, Cache<String, TokenDto> tokenCache) { + this.tokenCache = tokenCache; + this.keycloakGateway = keycloakGateway; + } + + @Override + public TokenDto getAccessToken(String username, String password) { + final TokenDto cacheAccessToken = tokenCache.getIfPresent(username); + if (cacheAccessToken != null) { + log.trace("found access token for user with username {} in cache", username); + return cacheAccessToken; + } + log.debug("access token for user with username {} not it cache (anymore): request new", username); + final TokenDto token = keycloakGateway.obtainUserToken(username, password); + tokenCache.put(username, token); + return token; + } + + /** + * Method for test cases to remove all caches. + */ + public void invalidateAll() { + tokenCache.invalidateAll(); + } + +} diff --git a/dbrepo-gateway-service/dbrepo.conf b/dbrepo-gateway-service/dbrepo.conf index 165aba7643fcda32b94784d757d93e253814a1e0..f181fdf7a70945280453f345f6a639921967abca 100644 --- a/dbrepo-gateway-service/dbrepo.conf +++ b/dbrepo-gateway-service/dbrepo.conf @@ -123,7 +123,7 @@ server { proxy_read_timeout 90; } - location ~ "/api/database/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/table/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/(data|history|export)" { + location ~ "/api/database/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/table/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/(data|history)" { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -132,7 +132,7 @@ server { proxy_read_timeout 90; } - location ~ "/api/database/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/view/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/(data|export)" { + location ~ "/api/database/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/view/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/data" { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -168,6 +168,15 @@ server { proxy_read_timeout 90; } + location ~ "/api/identifier/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})" { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://metadata; + proxy_read_timeout 90; + } + location ~ "/pid/([0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})" { rewrite /pid/(.*) /api/identifier/$1 break; proxy_set_header Host $host; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java index 57ecb5ad625017fec1571a1da53a83cbd5fcf852..0bc16d61cd9a1dc45c129184ec18cfb49ed0d754 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierDto.java @@ -46,6 +46,9 @@ public class IdentifierDto { @Schema(example = "null") private UUID viewId; + @NotNull + private LinksDto links; + @NotNull @Schema(example = "database") private IdentifierTypeDto type; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/LinksDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/LinksDto.java new file mode 100644 index 0000000000000000000000000000000000000000..fcef2a659750d9cf6b78bdc54b4a627dbcc0935d --- /dev/null +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/LinksDto.java @@ -0,0 +1,32 @@ +package at.tuwien.api.identifier; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import lombok.extern.jackson.Jacksonized; + + +@Getter +@Setter +@Builder +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +@Jacksonized +@ToString +public class LinksDto { + + @NotNull + @Schema(example = "http://example.com/api/") + private String self; + + @NotNull + @JsonProperty("self_html") + @Schema(example = "http://example.com") + private String selfHtml; + + @Schema(example = "http://example.com") + private String data; + +} diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java index 2d18ca0c49124539a6b9aa6072396aa247eb8167..74284789247ee20deac7df99c49a4f60e8cfa765 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -318,10 +318,27 @@ public interface MetadataMapper { Identifier identifierDtoToIdentifier(IdentifierDto data); @Mappings({ - @Mapping(target = "databaseId", source = "database.id") + @Mapping(target = "databaseId", source = "database.id"), + @Mapping(target = "links", expression = "java(identifierToLinksDto(data))"), }) IdentifierDto identifierToIdentifierDto(Identifier data); + default LinksDto identifierToLinksDto(Identifier data) { + final LinksDto links = LinksDto.builder() + .self("/api/identifier/" + data.getId()) + .selfHtml("/pid/" + data.getId()) + .build(); + switch (data.getType()) { + case VIEW -> + links.setData("/api/database/" + data.getDatabase().getId() + "/view/" + data.getViewId() + "/data"); + case TABLE -> + links.setData("/api/database/" + data.getDatabase().getId() + "/table/" + data.getTableId() + "/data"); + case SUBSET -> + links.setData("/api/database/" + data.getDatabase().getId() + "/subset/" + data.getQueryId() + "/data"); + } + return links; + } + @Mappings({ @Mapping(target = "databaseId", source = "database.id"), @Mapping(target = "ownedBy", source = "owner.id") diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java index 31dd1e542d53112ff42b49a6e5f6269255088ac6..96deaea775defd1562aba3601d9ba513b771f401 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java @@ -9,7 +9,6 @@ import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; import at.tuwien.entities.identifier.Identifier; import at.tuwien.entities.identifier.IdentifierStatusType; -import at.tuwien.entities.identifier.IdentifierType; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.MetadataMapper; @@ -126,7 +125,7 @@ public class IdentifierEndpoint extends AbstractEndpoint { } @GetMapping(value = "/{identifierId}", produces = {MediaType.APPLICATION_JSON_VALUE, "application/ld+json", - MediaType.TEXT_XML_VALUE, "text/csv", "text/bibliography", "text/bibliography; style=apa", + MediaType.TEXT_XML_VALUE, "text/bibliography", "text/bibliography; style=apa", "text/bibliography; style=ieee", "text/bibliography; style=bibtex"}) @Transactional(readOnly = true) @Observed(name = "dbrepo_identifier_find") @@ -138,7 +137,6 @@ public class IdentifierEndpoint extends AbstractEndpoint { content = { @Content(mediaType = "application/json", schema = @Schema(implementation = IdentifierDto.class)), @Content(mediaType = "application/ld+json", schema = @Schema(implementation = LdDatasetDto.class)), - @Content(mediaType = "text/csv"), @Content(mediaType = "text/xml"), @Content(mediaType = "text/bibliography"), @Content(mediaType = "text/bibliography; style=apa"), @@ -190,7 +188,7 @@ public class IdentifierEndpoint extends AbstractEndpoint { @RequestHeader(HttpHeaders.ACCEPT) String accept, Principal principal) throws IdentifierNotFoundException, DataServiceException, DataServiceConnectionException, MalformedException, FormatNotAvailableException, - QueryNotFoundException, NotAllowedException { + QueryNotFoundException, NotAllowedException, TableNotFoundException, ViewNotFoundException { log.debug("endpoint find identifier, identifierId={}, accept={}", identifierId, accept); if (accept == null) { accept = ""; @@ -212,13 +210,6 @@ public class IdentifierEndpoint extends AbstractEndpoint { case "application/ld+json": log.trace("accept header matches json-ld"); return ResponseEntity.ok(metadataMapper.identifierToLdDatasetDto(identifier, endpointConfig.getWebsiteUrl())); - case "text/csv": - log.trace("accept header matches csv"); - if (identifier.getType().equals(IdentifierType.DATABASE)) { - log.error("Failed to export dataset: identifier type is database"); - throw new FormatNotAvailableException("Failed to export dataset: identifier type is database"); - } - return ResponseEntity.ok(identifierService.exportResource(identifier)); case "text/xml": log.trace("accept header matches xml"); return ResponseEntity.ok(identifierService.exportMetadata(identifier)); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java index 1e003041027ed22072d167eae81fab3a5d32c813..4f6bd37fd41dabb1bdb73eadc15fddf06aa2f93c 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/IdentifierEndpointUnitTest.java @@ -410,7 +410,8 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_2_USERNAME) public void find_draft_succeeds() throws IdentifierNotFoundException, MalformedException, NotAllowedException, - DataServiceException, QueryNotFoundException, DataServiceConnectionException, FormatNotAvailableException { + DataServiceException, QueryNotFoundException, DataServiceConnectionException, FormatNotAvailableException, + TableNotFoundException, ViewNotFoundException { /* mock */ when(identifierService.find(IDENTIFIER_5_ID)) @@ -424,7 +425,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_defaultHtmlRespondsJson_succeeds() throws IdentifierNotFoundException, MalformedException, NotAllowedException, DataServiceException, QueryNotFoundException, DataServiceConnectionException, - FormatNotAvailableException { + FormatNotAvailableException, TableNotFoundException, ViewNotFoundException { /* mock */ when(identifierService.find(IDENTIFIER_1_ID)) @@ -438,7 +439,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_4_USERNAME) public void find_json0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "application/json"; final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata0.json"), StandardCharsets.UTF_8), IdentifierDto.class); @@ -468,7 +469,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_json1_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "application/json"; final IdentifierDto compare = objectMapper.readValue(FileUtils.readFileToString(new File("src/test/resources/json/metadata1.json"), StandardCharsets.UTF_8), IdentifierDto.class); @@ -514,34 +515,11 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { assertEquals(compare.getCreators().get(0).getNameIdentifierScheme(), creator0.getNameIdentifierScheme()); } - @Test - @WithAnonymousUser - public void find_csv_succeeds() throws IOException, MalformedException, DataServiceException, - DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { - final String accept = "text/csv"; - final InputStreamResource compare = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv"))); - final InputStreamResource mock = new InputStreamResource(FileUtils.openInputStream(new File("src/test/resources/csv/keyboard.csv"))); - - /* mock */ - when(identifierService.find(IDENTIFIER_2_ID)) - .thenReturn(IDENTIFIER_2); - when(identifierService.exportResource(IDENTIFIER_2)) - .thenReturn(mock); - - /* test */ - final ResponseEntity<?> response = identifierEndpoint.find(IDENTIFIER_2_ID, accept, null); - assertEquals(HttpStatus.OK, response.getStatusCode()); - final InputStreamResource body = (InputStreamResource) response.getBody(); - assertNotNull(body); - assertEquals(inputStreamToString(compare.getInputStream()), inputStreamToString(body.getInputStream())); - } - @Test @WithAnonymousUser public void find_bibliography_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa1.txt"), StandardCharsets.UTF_8); @@ -584,7 +562,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_4_USERNAME) public void find_bibliographyApa0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa0.txt"), StandardCharsets.UTF_8); @@ -607,7 +585,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyApa1_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa1.txt"), StandardCharsets.UTF_8); @@ -630,7 +608,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_2_USERNAME) public void find_draftBibliographyApa2_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa2.txt"), StandardCharsets.UTF_8); @@ -653,7 +631,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyApa3_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa3.txt"), StandardCharsets.UTF_8); @@ -676,7 +654,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyApa4_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=apa"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_apa4.txt"), StandardCharsets.UTF_8); @@ -699,7 +677,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_4_USERNAME) public void find_bibliographyIeee0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee0.txt"), StandardCharsets.UTF_8); @@ -722,7 +700,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyIeee1_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee1.txt"), StandardCharsets.UTF_8); @@ -745,7 +723,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_2_USERNAME) public void find_bibliographyIeee2_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee2.txt"), StandardCharsets.UTF_8); @@ -768,7 +746,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyIeee3_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=ieee"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_ieee3.txt"), StandardCharsets.UTF_8); @@ -791,7 +769,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_4_USERNAME) public void find_bibliographyBibtex0_succeeds() throws IOException, MalformedException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex0.txt"), StandardCharsets.UTF_8); @@ -814,7 +792,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyBibtex1_succeeds() throws MalformedException, IOException, DataServiceException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex1.txt"), StandardCharsets.UTF_8); @@ -837,7 +815,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_2_USERNAME) public void find_bibliographyBibtex2_succeeds() throws MalformedException, DataServiceException, IOException, DataServiceConnectionException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex2.txt"), StandardCharsets.UTF_8); @@ -860,7 +838,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_bibliographyBibtex3_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, IOException, QueryNotFoundException, IdentifierNotFoundException, - FormatNotAvailableException, NotAllowedException { + FormatNotAvailableException, NotAllowedException, TableNotFoundException, ViewNotFoundException { final String accept = "text/bibliography; style=bibtex"; final String compare = FileUtils.readFileToString(new File("src/test/resources/bibliography/style_bibtex3.txt"), StandardCharsets.UTF_8); @@ -882,7 +860,8 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void find_jsonLd_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException { + QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException, + TableNotFoundException, ViewNotFoundException { final String accept = "application/ld+json"; /* mock */ @@ -914,7 +893,8 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void find_move_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException { + QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, NotAllowedException, + TableNotFoundException, ViewNotFoundException { /* mock */ when(identifierService.find(IDENTIFIER_1_ID)) @@ -1065,7 +1045,8 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void find_json_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, - FormatNotAvailableException, QueryNotFoundException, IdentifierNotFoundException, NotAllowedException { + FormatNotAvailableException, QueryNotFoundException, IdentifierNotFoundException, NotAllowedException, + TableNotFoundException, ViewNotFoundException { final String accept = "application/json"; /* mock */ @@ -1093,7 +1074,7 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void find_xml_succeeds() throws MalformedException, DataServiceException, DataServiceConnectionException, IOException, QueryNotFoundException, IdentifierNotFoundException, FormatNotAvailableException, - NotAllowedException { + NotAllowedException, TableNotFoundException, ViewNotFoundException { final InputStreamResource resource = new InputStreamResource(FileUtils.openInputStream( new File("src/test/resources/xml/datacite-example-dataset-v4.xml"))); @@ -1108,9 +1089,9 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void find_httpRedirect_succeeds() throws MalformedException, DataServiceException, - DataServiceConnectionException, FormatNotAvailableException, QueryNotFoundException, - IdentifierNotFoundException, NotAllowedException { + public void find_httpRedirect_succeeds() throws MalformedException, DataServiceException, QueryNotFoundException, + DataServiceConnectionException, FormatNotAvailableException, IdentifierNotFoundException, + NotAllowedException, TableNotFoundException, ViewNotFoundException { /* test */ final ResponseEntity<?> response = generic_find(null, null); @@ -1509,14 +1490,13 @@ public class IdentifierEndpointUnitTest extends AbstractUnitTest { protected ResponseEntity<?> generic_find(String accept, InputStreamResource resource) throws MalformedException, DataServiceException, DataServiceConnectionException, FormatNotAvailableException, - QueryNotFoundException, IdentifierNotFoundException, NotAllowedException { + QueryNotFoundException, IdentifierNotFoundException, NotAllowedException, TableNotFoundException, + ViewNotFoundException { /* mock */ when(identifierService.find(IDENTIFIER_1_ID)) .thenReturn(IDENTIFIER_1); if (resource != null) { - when(identifierService.exportResource(IDENTIFIER_1)) - .thenReturn(resource); when(identifierService.exportMetadata(IDENTIFIER_1)) .thenReturn(resource); } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java index 5508a6245e40021cf307d474bc056dfc64861920..af1ecc9f1c69b6f2acaf3176575045bf14ad8322 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java @@ -1,6 +1,5 @@ package at.tuwien.gateway; -import at.tuwien.ExportResourceDto; import at.tuwien.api.database.AccessTypeDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewDto; @@ -812,75 +811,6 @@ public class DataServiceGatewayUnitTest extends AbstractUnitTest { }); } - @Test - public void exportQuery_succeeds() throws DataServiceException, DataServiceConnectionException, - QueryNotFoundException { - - /* mock */ - when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(ExportResourceDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .build()); - - /* test */ - dataServiceGateway.exportQuery(DATABASE_1_ID, QUERY_1_ID); - } - - @Test - public void exportQuery_connection_fails() { - - /* mock */ - doThrow(HttpServerErrorException.class) - .when(dataServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(ExportResourceDto.class)); - - /* test */ - assertThrows(DataServiceConnectionException.class, () -> { - dataServiceGateway.exportQuery(DATABASE_1_ID, QUERY_1_ID); - }); - } - - @Test - public void exportQuery_unauthorized_fails() { - - /* mock */ - doThrow(HttpClientErrorException.Unauthorized.class) - .when(dataServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(ExportResourceDto.class)); - - /* test */ - assertThrows(DataServiceException.class, () -> { - dataServiceGateway.exportQuery(DATABASE_1_ID, QUERY_1_ID); - }); - } - - @Test - public void exportQuery_notFound_fails() { - - /* mock */ - doThrow(HttpClientErrorException.NotFound.class) - .when(dataServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(ExportResourceDto.class)); - - /* test */ - assertThrows(QueryNotFoundException.class, () -> { - dataServiceGateway.exportQuery(DATABASE_1_ID, QUERY_1_ID); - }); - } - - @Test - public void exportQuery_responseCode_fails() { - - /* mock */ - when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(ExportResourceDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) - .build()); - - /* test */ - assertThrows(DataServiceException.class, () -> { - dataServiceGateway.exportQuery(DATABASE_1_ID, QUERY_1_ID); - }); - } - @Test public void getTableSchemas_succeeds() throws DataServiceException, DataServiceConnectionException, TableNotFoundException { diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java index d6b5da373cd08436c1ab1a42031952f05faf6d12..6d7480ba8034a9108492ab0816005f4a92f8046c 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/auth/InternalRequestInterceptor.java @@ -29,7 +29,9 @@ public class InternalRequestInterceptor implements ClientHttpRequestInterceptor public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { final HttpHeaders headers = request.getHeaders(); - headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (headers.get("Accept") == null) { + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + } final TokenDto token = credentialService.getAccessToken(gatewayConfig.getSystemUsername(), gatewayConfig.getSystemPassword()); headers.setBearerAuth(token.getAccessToken()); diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/DataServiceGateway.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/DataServiceGateway.java index 5bb0303e16c14145f1e08ee913b4c4dcabdead61..3996448d212bed80364bdc2ac4d5ab62da3e1863 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/DataServiceGateway.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/DataServiceGateway.java @@ -1,9 +1,8 @@ package at.tuwien.gateway; -import at.tuwien.ExportResourceDto; import at.tuwien.api.database.AccessTypeDto; -import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.CreateViewDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.internal.CreateDatabaseDto; import at.tuwien.api.database.query.QueryDto; @@ -145,19 +144,6 @@ public interface DataServiceGateway { QueryDto findQuery(UUID databaseId, UUID queryId) throws DataServiceConnectionException, DataServiceException, QueryNotFoundException; - /** - * Exports a given query. - * - * @param databaseId The database id. - * @param queryId The query id. - * @return The exported resource, if successful. - * @throws DataServiceConnectionException The connection to the data service could not be established. - * @throws DataServiceException The data service responded unexpectedly. - * @throws QueryNotFoundException The given query was not found in the query store. - */ - ExportResourceDto exportQuery(UUID databaseId, UUID queryId) throws DataServiceConnectionException, - DataServiceException, QueryNotFoundException; - /** * Obtain table schemas from a given database. * diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/DataServiceGatewayImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/DataServiceGatewayImpl.java index 8feed3b291444ea674317673d74fa59bba4b5cad..ba855feda32000f1c4f0265c2b194c7e9e4e8237 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/DataServiceGatewayImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/DataServiceGatewayImpl.java @@ -1,6 +1,5 @@ package at.tuwien.gateway.impl; -import at.tuwien.ExportResourceDto; import at.tuwien.api.database.*; import at.tuwien.api.database.internal.CreateDatabaseDto; import at.tuwien.api.database.query.QueryDto; @@ -315,31 +314,6 @@ public class DataServiceGatewayImpl implements DataServiceGateway { return response.getBody(); } - @Override - public ExportResourceDto exportQuery(UUID databaseId, UUID queryId) throws DataServiceConnectionException, - DataServiceException, QueryNotFoundException { - final ResponseEntity<ExportResourceDto> response; - final String path = "/api/database/" + databaseId + "/subset/" + queryId; - log.trace("export subset at endpoint {} with path {}", gatewayConfig.getDataEndpoint(), path); - try { - response = restTemplate.exchange(path, HttpMethod.GET, HttpEntity.EMPTY, ExportResourceDto.class); - } catch (HttpServerErrorException e) { - log.error("Failed to export query: {}", e.getMessage()); - throw new DataServiceConnectionException("Failed to export query: " + e.getMessage(), e); - } catch (HttpClientErrorException.NotFound e) { - log.error("Failed to export query: not found: {}", e.getMessage()); - throw new QueryNotFoundException("Failed to export query: not found: " + e.getMessage(), e); - } catch (HttpClientErrorException.Unauthorized e) { - log.error("Failed to export query: {}", e.getMessage()); - throw new DataServiceException("Failed to export query: " + e.getMessage(), e); - } - if (!response.getStatusCode().equals(HttpStatus.OK)) { - log.error("Failed to export query: wrong http code: {}", response.getStatusCode()); - throw new DataServiceException("Failed to export query: wrong http code: " + response.getStatusCode()); - } - return response.getBody(); - } - @Override public List<TableDto> getTableSchemas(UUID databaseId) throws DataServiceConnectionException, DataServiceException, TableNotFoundException { diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/IdentifierService.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/IdentifierService.java index 605ded7d37faad52fc7119f4dd0a958c199b68b8..8c9a3088fe96844df0628ba95d71c0356ebc2b33 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/IdentifierService.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/IdentifierService.java @@ -161,19 +161,6 @@ public interface IdentifierService { */ String exportBibliography(Identifier identifier, BibliographyTypeDto style) throws MalformedException; - /** - * Exports an identifier to XML - * - * @param identifier The identifier. - * @return The XML resource, if successful. - * @throws DataServiceException - * @throws DataServiceConnectionException - * @throws IdentifierNotFoundException - * @throws QueryNotFoundException - */ - InputStreamResource exportResource(Identifier identifier) throws DataServiceException, DataServiceConnectionException, - IdentifierNotFoundException, QueryNotFoundException; - /** * Soft-deletes an identifier for a given id in the metadata database. Does not actually remove the entity from the * database, but sets it as deleted. diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java index 0c8ce27d1214b1ac47c6756d7f5d3fbe4c77adcd..5ab9cc26a226374d817966e880a684393c36cf08 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/DataCiteIdentifierServiceImpl.java @@ -196,13 +196,6 @@ public class DataCiteIdentifierServiceImpl implements IdentifierService { return identifierService.exportBibliography(identifier, style); } - @Override - @Transactional(readOnly = true) - public InputStreamResource exportResource(Identifier identifier) throws DataServiceException, - DataServiceConnectionException, IdentifierNotFoundException, QueryNotFoundException { - return identifierService.exportResource(identifier); - } - @Override @Transactional public void delete(Identifier identifier) throws DataServiceException, DataServiceConnectionException, diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java index b1d1f45932c61bb755a615f22a6a919297ffec46..8af7023c8cb48b64078457c409b8e14ece6187b7 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java @@ -1,6 +1,5 @@ package at.tuwien.service.impl; -import at.tuwien.ExportResourceDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.identifier.BibliographyTypeDto; import at.tuwien.api.identifier.CreateIdentifierDto; @@ -356,14 +355,6 @@ public class IdentifierServiceImpl implements IdentifierService { return body; } - @Override - @Transactional(readOnly = true) - public InputStreamResource exportResource(Identifier identifier) throws DataServiceException, - DataServiceConnectionException, QueryNotFoundException { - final ExportResourceDto exportResource = dataServiceGateway.exportQuery(identifier.getDatabase().getId(), identifier.getQueryId()); - return exportResource.getResource(); - } - @Override @Transactional public void delete(Identifier identifier) throws DataServiceException, DataServiceConnectionException, diff --git a/dbrepo-search-service/Pipfile.lock b/dbrepo-search-service/Pipfile.lock index ac2d0e3f70431e171ffda6524d193727056840b4..e520c5d0f8890461117ca6d7ab6645de932c2261 100644 --- a/dbrepo-search-service/Pipfile.lock +++ b/dbrepo-search-service/Pipfile.lock @@ -373,7 +373,7 @@ }, "dbrepo": { "hashes": [ - "sha256:4f5ee48e9a68a44c2d1184923b90729d724553862742738d8f942273a586eb3d" + "sha256:e70ea4f7030191eb80116e5d0a4b17b041c94c80359d5d8e707d62218edd9a54" ], "path": "./lib/dbrepo-1.7.1.tar.gz" }, diff --git a/dbrepo-search-service/init/Pipfile.lock b/dbrepo-search-service/init/Pipfile.lock index 39a52462e0daa22c8052517b0617cb350e9a9408..7e637809c29b2223d18dc64896ae84432efbf7c5 100644 --- a/dbrepo-search-service/init/Pipfile.lock +++ b/dbrepo-search-service/init/Pipfile.lock @@ -259,7 +259,7 @@ }, "dbrepo": { "hashes": [ - "sha256:4f5ee48e9a68a44c2d1184923b90729d724553862742738d8f942273a586eb3d" + "sha256:e70ea4f7030191eb80116e5d0a4b17b041c94c80359d5d8e707d62218edd9a54" ], "path": "./lib/dbrepo-1.7.1.tar.gz" }, diff --git a/dbrepo-search-service/init/lib/dbrepo-1.7.1-py3-none-any.whl b/dbrepo-search-service/init/lib/dbrepo-1.7.1-py3-none-any.whl index 8e77c6e1f6a01b3d9739280f29539985c359d2c8..61f52896c18ecbec8090177b38b8dbaeb0e1a95e 100644 Binary files a/dbrepo-search-service/init/lib/dbrepo-1.7.1-py3-none-any.whl and b/dbrepo-search-service/init/lib/dbrepo-1.7.1-py3-none-any.whl differ diff --git a/dbrepo-search-service/init/lib/dbrepo-1.7.1.tar.gz b/dbrepo-search-service/init/lib/dbrepo-1.7.1.tar.gz index ede846d6168615c3e3bac49872362163b0a9f005..6708e1d892771d6cdf9293a6e9f5197f4dd9e304 100644 Binary files a/dbrepo-search-service/init/lib/dbrepo-1.7.1.tar.gz and b/dbrepo-search-service/init/lib/dbrepo-1.7.1.tar.gz differ diff --git a/dbrepo-search-service/lib/dbrepo-1.7.1-py3-none-any.whl b/dbrepo-search-service/lib/dbrepo-1.7.1-py3-none-any.whl index 8e77c6e1f6a01b3d9739280f29539985c359d2c8..61f52896c18ecbec8090177b38b8dbaeb0e1a95e 100644 Binary files a/dbrepo-search-service/lib/dbrepo-1.7.1-py3-none-any.whl and b/dbrepo-search-service/lib/dbrepo-1.7.1-py3-none-any.whl differ diff --git a/dbrepo-search-service/lib/dbrepo-1.7.1.tar.gz b/dbrepo-search-service/lib/dbrepo-1.7.1.tar.gz index ede846d6168615c3e3bac49872362163b0a9f005..6708e1d892771d6cdf9293a6e9f5197f4dd9e304 100644 Binary files a/dbrepo-search-service/lib/dbrepo-1.7.1.tar.gz and b/dbrepo-search-service/lib/dbrepo-1.7.1.tar.gz differ diff --git a/dbrepo-ui/composables/query-service.ts b/dbrepo-ui/composables/query-service.ts index 5a86c09d1b1b7017e362a62c0cc5df33c545fd6c..2b33fa8849c6d7bba4509720c2083f4d65562ff1 100644 --- a/dbrepo-ui/composables/query-service.ts +++ b/dbrepo-ui/composables/query-service.ts @@ -64,7 +64,7 @@ export const useQueryService = (): any => { } console.debug('export query with id', queryId, 'in database with id', databaseId) return new Promise<any>((resolve, reject) => { - axios.get<any>(`/api/database/${databaseId}/subset/${queryId}`, config) + axios.get<any>(`/api/database/${databaseId}/subset/${queryId}/data`, config) .then((response) => { console.info('Exported query with id', queryId, 'in database with id', databaseId) resolve(response.data) diff --git a/dbrepo-ui/composables/table-service.ts b/dbrepo-ui/composables/table-service.ts index 45ee0a7b934cb92c2d275b3cb1a23738d4fc0809..c9dfeb62fcd42c603ef86d67b52332fea04ac2a2 100644 --- a/dbrepo-ui/composables/table-service.ts +++ b/dbrepo-ui/composables/table-service.ts @@ -132,7 +132,7 @@ export const useTableService = (): any => { } console.debug('export data for table with id', tableId, 'in database with id', databaseId); return new Promise<QueryResultDto>((resolve, reject) => { - axios.get<QueryResultDto>(`/api/database/${databaseId}/table/${tableId}/export`, config) + axios.get<QueryResultDto>(`/api/database/${databaseId}/table/${tableId}/data`, config) .then((response) => { console.info('Exported data for table') resolve(response.data) diff --git a/dbrepo-ui/composables/view-service.ts b/dbrepo-ui/composables/view-service.ts index 9029a553ac782248d33707380f2ed72efcfc8032..3e775e2b14d51b83c7332bf7311de99a7a25d24e 100644 --- a/dbrepo-ui/composables/view-service.ts +++ b/dbrepo-ui/composables/view-service.ts @@ -114,7 +114,7 @@ export const useViewService = (): any => { } console.debug('export data for view with id', viewId, 'in database with id', databaseId); return new Promise<QueryResultDto>((resolve, reject) => { - axios.get<QueryResultDto>(`/api/database/${databaseId}/view/${viewId}/export`, config) + axios.get<QueryResultDto>(`/api/database/${databaseId}/view/${viewId}/data`, config) .then((response) => { console.info('Exported data for view with id', viewId, 'in database with id', databaseId) resolve(response.data) diff --git a/docker-compose.yml b/docker-compose.yml index e8c5cd8de7795d1bcced37da11b80b94dc404315..84905a7d057f6ea27dae62f7402eaa53f0d92cb7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -539,6 +539,7 @@ services: AUTH_SERVICE_CLIENT: "${AUTH_SERVICE_CLIENT:-dbrepo-client}" AUTH_SERVICE_CLIENT_SECRET: "${AUTH_SERVICE_CLIENT:-MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG}" AUTH_SERVICE_ENDPOINT: "${AUTH_SERVICE_ENDPOINT:-http://auth-service:8080}" + BASE_URL: "${BASE_URL:-http://localhost}" BROKER_EXCHANGE_NAME: "${BROKER_EXCHANGE_NAME:-dbrepo}" BROKER_QUEUE_NAME: "${BROKER_QUEUE_NAME:-dbrepo}" BROKER_HOST: "${BROKER_ENDPOINT:-broker-service}" diff --git a/helm/dbrepo/files/create-event-listener.jar b/helm/dbrepo/files/create-event-listener.jar index b117c50b6646de91273c95dc5749cd54868012be..0410b9e37b8fedf3956c870e2faf7aa9e8ab89b6 100644 Binary files a/helm/dbrepo/files/create-event-listener.jar and b/helm/dbrepo/files/create-event-listener.jar differ diff --git a/lib/python/README.md b/lib/python/README.md index 11506a7bf059630b65ed703d4674cde8cf3642e9..1a15df15c79acd1dba9ea26ab14c09cbb2419927 100644 --- a/lib/python/README.md +++ b/lib/python/README.md @@ -44,6 +44,7 @@ client = RestClient(endpoint="https://test.dbrepo.tuwien.ac.at", username="foo", password="bar") df = pd.DataFrame(data={'x_coord': 16.52617, 'component': 'Feinstaub (PM10)', 'unit': 'µg/m³', ...}) +df = df.set_index(['x_coord']) client.create_table(database_id="e0d82287-9099-4077-8f69-3c19fc3bc145", name="Sensor", is_public=True, is_schema_public=True, dataframe=df) @@ -51,6 +52,14 @@ client.create_table(database_id="e0d82287-9099-4077-8f69-3c19fc3bc145", ... or just create the table schema by setting `create_table(..., withData=False)`. +In both cases it is important to set the index to existing columns that uniquely +identify a row. You can specify multiple columns: + +```python +... +df = df.set_index(['some_column', 'some_other_column']) +``` + ## Supported Features & Best-Practices - Manage user diff --git a/lib/python/dbrepo/RestClient.py b/lib/python/dbrepo/RestClient.py index f2a93151ecff15ece92618ddf30d9d7bfa0f1dcd..3459543d838cc76ee125f2ad84165caf9bced574 100644 --- a/lib/python/dbrepo/RestClient.py +++ b/lib/python/dbrepo/RestClient.py @@ -826,7 +826,7 @@ class RestClient: if page is not None and size is not None: params.append(('page', page)) params.append(('size', size)) - response = self._wrapper(method="get", url=url, params=params) + response = self._wrapper(method="get", url=url, params=params, headers={'Accept': 'application/json'}) if response.status_code == 200: return DataFrame.from_records(response.json()) if response.status_code == 400: @@ -869,7 +869,7 @@ class RestClient: params.append(('size', size)) if timestamp is not None: params.append(('timestamp', timestamp.strftime("%Y-%m-%dT%H:%M:%SZ"))) - response = self._wrapper(method="get", url=url, params=params) + response = self._wrapper(method="get", url=url, params=params, headers={'Accept': 'application/json'}) if response.status_code == 200: return DataFrame.from_records(response.json()) if response.status_code == 400: @@ -1393,11 +1393,10 @@ class RestClient: :raises ServiceError: If something went wrong with obtaining the information in the data service. :raises ResponseCodeError: If something went wrong with the retrieval. """ - headers = {} url = f'/api/database/{database_id}/subset/{subset_id}/data' if page is not None and size is not None: url += f'?page={page}&size={size}' - response = self._wrapper(method="get", url=url, headers=headers) + response = self._wrapper(method="get", url=url, headers={'Accept': 'application/json'}) if response.status_code == 200: return DataFrame.from_records(response.json()) if response.status_code == 400: @@ -1786,7 +1785,7 @@ class RestClient: def get_identifier(self, identifier_id: str) -> Identifier: """ - Get list of identifiers, filter by the remaining optional arguments. + Get the identifier by given id. :param identifier_id: The identifier id. @@ -1805,6 +1804,39 @@ class RestClient: raise ResponseCodeError(f'Failed to get identifier: response code: {response.status_code} is not ' f'200 (OK): {response.text}') + def get_identifier_data(self, identifier_id: str) -> DataFrame: + """ + Get the identifier data by given id. + + :param identifier_id: The identifier id. + + :returns: The identifier, if successful. + + :raises NotExistsError: If the identifier does not exist. + :raises ResponseCodeError: If something went wrong with the retrieval of the identifier. + """ + url = f'/api/identifier/{identifier_id}' + response = self._wrapper(method="get", url=url, headers={'Accept': 'application/json'}) + if response.status_code == 200: + body = response.json() + identifier = Identifier.model_validate(body) + if identifier.type == IdentifierType.VIEW: + return self.get_view_data(database_id=identifier.database_id, view_id=identifier.view_id, page=0, + size=10000) + elif identifier.type == IdentifierType.TABLE: + return self.get_table_data(database_id=identifier.database_id, table_id=identifier.table_id, page=0, + size=10000) + elif identifier.type == IdentifierType.SUBSET: + return self.get_subset_data(database_id=identifier.database_id, subset_id=identifier.query_id, page=0, + size=10000) + raise FormatNotAvailable(f'Failed to get identifier data: type is database') + if response.status_code == 404: + raise NotExistsError(f'Failed to get identifier data: not found') + if response.status_code == 406: + raise NotExistsError(f'Failed to get identifier data: type database') + raise ResponseCodeError(f'Failed to get identifier data: response code: {response.status_code} is not ' + f'200 (OK): {response.text}') + def get_image(self, image_id: str) -> Image: """ Get container image. diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py index 526ba29bf7d4dfea5983bce99c84c2e67e8befe4..356f9b220095970886e524e8cccefdd49ea823c7 100644 --- a/lib/python/dbrepo/api/dto.py +++ b/lib/python/dbrepo/api/dto.py @@ -653,6 +653,7 @@ class IdentifierSave(CreateIdentifier): class Identifier(BaseModel): id: str database_id: str + links: Links type: IdentifierType owner: UserBrief status: IdentifierStatusType @@ -988,6 +989,12 @@ class Query(BaseModel): identifiers: List[IdentifierBrief] = field(default_factory=list) +class Links(BaseModel): + self: str + self_html: str + data: Optional[str] = None + + class UpdateQuery(BaseModel): persist: bool