diff --git a/.env b/.env index 2f08850..543be1c 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ ZK_HOSTS=zookeeper-solr-openaire-dev-1:2181,zookeeper-solr-openaire-dev-2:2181,zookeeper-solr-openaire-dev-3:2181 SOLR_COLLECTION=public OPENAPI_SERVER_BASE_URL=http://localhost:8080/graph -CONTEXT_PATH=/graph \ No newline at end of file +CONTEXT_PATH=/graph +SCHOLIX_SERVER_BASE_URL=https://test.api.scholexplorer.openaire.eu \ No newline at end of file diff --git a/pom.xml b/pom.xml index a7a5640..50acf19 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,6 @@ org.springframework.boot spring-boot-configuration-processor - true org.apache.solr diff --git a/src/main/java/eu/openaire/api/config/ScholixClientConfig.java b/src/main/java/eu/openaire/api/config/ScholixClientConfig.java new file mode 100644 index 0000000..fee68e1 --- /dev/null +++ b/src/main/java/eu/openaire/api/config/ScholixClientConfig.java @@ -0,0 +1,19 @@ +package eu.openaire.api.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +@Configuration +public class ScholixClientConfig { + @Value("${scholix.server-base-url}") + private String baseUrl; + + @Bean + public RestClient restClient() { + return RestClient.builder() + .baseUrl(baseUrl) + .build(); + } +} diff --git a/src/main/java/eu/openaire/api/controllers/ScholixController.java b/src/main/java/eu/openaire/api/controllers/ScholixController.java new file mode 100644 index 0000000..8313cb4 --- /dev/null +++ b/src/main/java/eu/openaire/api/controllers/ScholixController.java @@ -0,0 +1,54 @@ +package eu.openaire.api.controllers; + +import eu.dnetlib.dhp.schema.sx.api.model.v2.PageResultType; +import eu.openaire.api.dto.request.validators.PaginationValidator; +import eu.openaire.api.errors.ErrorResponse; +import eu.openaire.api.services.ScholixService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Scholix Relations", description = "API endpoints to explore scholix") +public class ScholixController { + private final ScholixService scholixService; + + // common validator to check pagination parameters + private final PaginationValidator paginationValidator; + + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.addValidators(paginationValidator); + } + + @Operation( + summary = "Retrieve scholix links by sourcePid and sourceType", + description = "Retrieve scholix links by sourcePid and sourceType" + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", content = { @Content(schema = @Schema(implementation = PageResultType.class), mediaType = "application/json") }), + @ApiResponse(responseCode = "404", content = { @Content(schema = @Schema(implementation = ErrorResponse.class), mediaType = "application/json") }), + @ApiResponse(responseCode = "500", content = { @Content(schema = @Schema(implementation = ErrorResponse.class), mediaType = "application/json") }) + }) + @GetMapping("/links") + public PageResultType getLinks(@RequestParam + @Parameter(description = "The OpenAIRE Id") String sourcePid, + @RequestParam(required = false) + @Parameter(description = "The OpenAIRE Source Type") String sourceType, + @RequestParam(defaultValue = "0") + @Parameter(description = "Page number of the results") int page) { + + return scholixService.getLinks(sourcePid, sourceType, page); + } +} diff --git a/src/main/java/eu/openaire/api/errors/ServiceExceptionHandler.java b/src/main/java/eu/openaire/api/errors/ServiceExceptionHandler.java index 3616da5..97e4ade 100644 --- a/src/main/java/eu/openaire/api/errors/ServiceExceptionHandler.java +++ b/src/main/java/eu/openaire/api/errors/ServiceExceptionHandler.java @@ -5,10 +5,12 @@ import eu.openaire.api.errors.exceptions.NotFoundException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.RestClientResponseException; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.resource.NoResourceFoundException; @@ -60,6 +62,22 @@ public class ServiceExceptionHandler { return this.handleException(e.getMessage(), request, HttpStatus.INTERNAL_SERVER_ERROR); } + @ExceptionHandler(RestClientResponseException.class) + public ResponseEntity handleRestClientResponseException(RestClientResponseException e, WebRequest request) { + HttpStatusCode status = e.getStatusCode(); + String path = ((ServletWebRequest) request).getRequest().getRequestURI(); + + ErrorResponse response = ErrorResponse.builder() + .message(e.getMessage()) + .error(status.toString()) + .code(status.value()) + .timestamp(new Date()) + .path(path) + .build(); + + return ResponseEntity.status(status.value()).body(response); + } + private ResponseEntity handleException(String message, WebRequest request, HttpStatus httpStatus) { var req = ((ServletWebRequest)request).getRequest(); String path = String.format("%s?%s", req.getRequestURI(), req.getQueryString()); diff --git a/src/main/java/eu/openaire/api/services/ScholixService.java b/src/main/java/eu/openaire/api/services/ScholixService.java new file mode 100644 index 0000000..5ed192b --- /dev/null +++ b/src/main/java/eu/openaire/api/services/ScholixService.java @@ -0,0 +1,42 @@ +package eu.openaire.api.services; + +import eu.dnetlib.dhp.schema.sx.api.model.v2.PageResultType; +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +@RequiredArgsConstructor +public class ScholixService { + + private final RestClient restClient; + private final Logger log = LogManager.getLogger(this.getClass()); + private static final String OPENAIRE_ID_PREFIX = "50|"; + + @Timed + public PageResultType getLinks(String sourcePid, String sourceType, int page) { + try { + if (!sourcePid.startsWith(OPENAIRE_ID_PREFIX)) { + sourcePid = OPENAIRE_ID_PREFIX + sourcePid; + } + + String finalSourcePid = sourcePid; + + return restClient.get() + .uri(uriBuilder -> uriBuilder + .path("/v2/Links") + .queryParam("sourcePid", finalSourcePid) + .queryParam("sourceType", sourceType) + .queryParam("page", page) + .build()) + .retrieve() + .body(PageResultType.class); + } catch (Exception e) { + log.error(e.getMessage()); + throw new RuntimeException("Unexpected error: " + e.getMessage(), e); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d62d9de..b72bcb7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,6 +7,8 @@ openapi.description=The Graph API allows developers to access metadata records o solr.collection=${SOLR_COLLECTION} solr.zkHosts=${ZK_HOSTS} +scholix.server-base-url=${SCHOLIX_SERVER_BASE_URL} + logging.level.org.springframework.web=DEBUG #removes 'trace' field from error responses; alternatively remove 'spring-boot-devtools' dependency