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