From 0fc45036fb84a7ee83de3be20e410b1a87e3862e Mon Sep 17 00:00:00 2001 From: southeo Date: Mon, 19 Aug 2024 13:41:54 +0200 Subject: [PATCH 1/6] add tombstoning event --- .../core/datacitepublisher/Profiles.java | 3 +- .../component/XmlLocReader.java | 6 +- .../configuration/ApplicationConfig.java | 5 +- .../datacitepublisher/domain/EventType.java | 4 +- .../domain/TombstoneEvent.java | 9 + .../domain/datacite/DcAttributes.java | 5 + .../domain/datacite/DcContributor.java | 2 + .../domain/datacite/DcCreator.java | 2 + .../domain/datacite/DcData.java | 2 + .../domain/datacite/DcDate.java | 2 +- .../domain/datacite/DcDescription.java | 2 + .../domain/datacite/DcTitle.java | 2 + .../domain/datacite/DcType.java | 4 + .../InvalidFdoProfileReceivedException.java | 10 + .../InvalidFdoProfileRecievedException.java | 10 - .../exceptions/InvalidRequestException.java | 8 + .../kafka/KafkaConsumerService.java | 40 ++-- .../service/DataCitePublisherService.java | 56 ++++- .../datacitepublisher/web/DataCiteClient.java | 33 ++- .../core/datacitepublisher/TestUtils.java | 89 +++++++- .../component/XmlLocReaderTest.java | 4 +- .../kafka/KafkaConsumerServiceTest.java | 31 ++- .../service/DataCitePublisherServiceTest.java | 110 +++++----- .../web/DataCiteClientTest.java | 207 +++++++++++++++++- 24 files changed, 528 insertions(+), 118 deletions(-) create mode 100644 src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java create mode 100644 src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileReceivedException.java delete mode 100644 src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileRecievedException.java create mode 100644 src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidRequestException.java diff --git a/src/main/java/eu/dissco/core/datacitepublisher/Profiles.java b/src/main/java/eu/dissco/core/datacitepublisher/Profiles.java index bb114ed..c9733ed 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/Profiles.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/Profiles.java @@ -8,6 +8,7 @@ public class Profiles { private Profiles() { } - public static final String TEST = "test"; + public static final String SANDBOX = "sandbox"; + public static final String PRODUCTION = "production"; } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/component/XmlLocReader.java b/src/main/java/eu/dissco/core/datacitepublisher/component/XmlLocReader.java index 1fd1d6b..20f7f14 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/component/XmlLocReader.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/component/XmlLocReader.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import eu.dissco.core.datacitepublisher.exceptions.InvalidFdoProfileRecievedException; +import eu.dissco.core.datacitepublisher.exceptions.InvalidFdoProfileReceivedException; import java.util.Collections; import java.util.List; import lombok.Getter; @@ -22,7 +22,7 @@ public class XmlLocReader { @Qualifier("xmlMapper") private final XmlMapper xmlMapper; - public List getLocationsFromXml(String xmlDoc) throws InvalidFdoProfileRecievedException { + public List getLocationsFromXml(String xmlDoc) throws InvalidFdoProfileReceivedException { if (xmlDoc == null) { log.warn("No url provided"); return Collections.emptyList(); @@ -32,7 +32,7 @@ public List getLocationsFromXml(String xmlDoc) throws InvalidFdoProfileR return locations.getLocation().stream().map(LocationXml::getHref).toList(); } catch (JsonProcessingException e){ log.error("Unable to parse 10320/loc field for fdo", e); - throw new InvalidFdoProfileRecievedException(); + throw new InvalidFdoProfileReceivedException(); } } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/configuration/ApplicationConfig.java b/src/main/java/eu/dissco/core/datacitepublisher/configuration/ApplicationConfig.java index 67808fb..22d5434 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/configuration/ApplicationConfig.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/configuration/ApplicationConfig.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.time.Instant; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import lombok.RequiredArgsConstructor; @@ -17,8 +18,8 @@ public class ApplicationConfig { public static final DateTimeFormatter FDO_FORMATTER = DateTimeFormatter.ofPattern( "yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneOffset.UTC); - public static final DateTimeFormatter DATACITE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - + public static final DateTimeFormatter DATACITE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd") + .withZone(ZoneId.of("UTC")); @Bean public ObjectMapper objectMapper() { diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/EventType.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/EventType.java index 3f686cd..444e5e3 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/EventType.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/EventType.java @@ -1,10 +1,10 @@ package eu.dissco.core.datacitepublisher.domain; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; public enum EventType { @JsonProperty("create") CREATE, - @JsonPropertyOrder("update") UPDATE; + @JsonProperty("update") UPDATE, + @JsonProperty("tombstone") TOMBSTONE; } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java new file mode 100644 index 0000000..081fef4 --- /dev/null +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java @@ -0,0 +1,9 @@ +package eu.dissco.core.datacitepublisher.domain; + +import eu.dissco.core.datacitepublisher.domain.datacite.DcRelatedIdentifiers; +import java.util.List; + +public record TombstoneEvent(String handle, List tombstonePids) { + + +} diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java index 5d5a8f5..ad54488 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java @@ -5,19 +5,24 @@ import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.SCHEMA_VERSION; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.Setter; +import lombok.extern.jackson.Jacksonized; @Builder @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter @JsonInclude(NON_EMPTY) +@Jacksonized +@JsonIgnoreProperties(ignoreUnknown = true) public class DcAttributes { private String suffix; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcContributor.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcContributor.java index 9be4d9e..3d23fca 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcContributor.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcContributor.java @@ -1,5 +1,6 @@ package eu.dissco.core.datacitepublisher.domain.datacite; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -11,6 +12,7 @@ @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter +@JsonIgnoreProperties(ignoreUnknown = true) public class DcContributor { private String name; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcCreator.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcCreator.java index 57fcffa..c9c0416 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcCreator.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcCreator.java @@ -1,5 +1,6 @@ package eu.dissco.core.datacitepublisher.domain.datacite; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -11,6 +12,7 @@ @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter +@JsonIgnoreProperties(ignoreUnknown = true) public class DcCreator { private final String nameType= "Organizational"; private String name; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcData.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcData.java index 0b988ad..653e5d0 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcData.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcData.java @@ -1,5 +1,6 @@ package eu.dissco.core.datacitepublisher.domain.datacite; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -10,6 +11,7 @@ @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter +@JsonIgnoreProperties(ignoreUnknown = true) public class DcData { private final String type = "dois"; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDate.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDate.java index af0e46b..4e53491 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDate.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDate.java @@ -14,6 +14,6 @@ @NoArgsConstructor public class DcDate { private String date; - private final String dateType = "Issued"; + private String dateType; } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDescription.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDescription.java index 258285c..e2aea3b 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDescription.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcDescription.java @@ -4,12 +4,14 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Builder @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter +@NoArgsConstructor public class DcDescription { private String description; private final String descriptionType = "TechnicalInfo"; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcTitle.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcTitle.java index 77413d4..dc86411 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcTitle.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcTitle.java @@ -4,12 +4,14 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Builder @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter +@NoArgsConstructor public class DcTitle { private String title; } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java index f8e4f72..aaecd85 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java @@ -2,16 +2,20 @@ import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.RESOURCE_TYPE_GENERAL; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Builder @AllArgsConstructor @Setter(value = AccessLevel.PACKAGE) @Getter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class DcType { private String resourceType; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileReceivedException.java b/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileReceivedException.java new file mode 100644 index 0000000..7380b4f --- /dev/null +++ b/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileReceivedException.java @@ -0,0 +1,10 @@ +package eu.dissco.core.datacitepublisher.exceptions; + +public class InvalidFdoProfileReceivedException extends InvalidRequestException { + + public InvalidFdoProfileReceivedException() { + super(); + } + + +} diff --git a/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileRecievedException.java b/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileRecievedException.java deleted file mode 100644 index 1ac623e..0000000 --- a/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidFdoProfileRecievedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package eu.dissco.core.datacitepublisher.exceptions; - -public class InvalidFdoProfileRecievedException extends Exception { - - public InvalidFdoProfileRecievedException() { - super(); - } - - -} diff --git a/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidRequestException.java b/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidRequestException.java new file mode 100644 index 0000000..ea081d1 --- /dev/null +++ b/src/main/java/eu/dissco/core/datacitepublisher/exceptions/InvalidRequestException.java @@ -0,0 +1,8 @@ +package eu.dissco.core.datacitepublisher.exceptions; + +public class InvalidRequestException extends Exception { + public InvalidRequestException(){ + super(); + } + +} diff --git a/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java b/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java index c686df5..3368fbe 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java @@ -4,7 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dissco.core.datacitepublisher.domain.DigitalSpecimenEvent; import eu.dissco.core.datacitepublisher.domain.MediaObjectEvent; +import eu.dissco.core.datacitepublisher.domain.TombstoneEvent; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; +import eu.dissco.core.datacitepublisher.exceptions.InvalidRequestException; import eu.dissco.core.datacitepublisher.service.DataCitePublisherService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -32,33 +34,45 @@ public class KafkaConsumerService { attempts = "1", dltStrategy = DltStrategy.FAIL_ON_ERROR) @KafkaListener(topics = "${kafka.consumer.topic.specimen}", - groupId = "${spring.kafka.consumer.group-id}" ) - public void getSpecimenMessages(@Payload String message) throws DataCiteApiException { - DigitalSpecimenEvent event; + groupId = "${spring.kafka.consumer.group-id}") + public void getSpecimenMessages(@Payload String message) throws DataCiteApiException, InvalidRequestException { try { - event = mapper.readValue(message, DigitalSpecimenEvent.class); + var event = mapper.readValue(message, DigitalSpecimenEvent.class); + service.handleMessages(event); } catch (JsonProcessingException e) { log.error(ERROR_MSG, e); log.info("Message: {}", message); - throw new DataCiteApiException(ERROR_MSG); + throw new InvalidRequestException(); } - service.handleMessages(event); } @RetryableTopic( attempts = "1", dltStrategy = DltStrategy.FAIL_ON_ERROR) - @KafkaListener(topics = "${kafka.consumer.topic.media}", groupId = "${spring.kafka.consumer.group-id}") - public void getMediaMessages(@Payload String message) throws DataCiteApiException { - MediaObjectEvent event; + @KafkaListener(topics = "${kafka.consumer.topic.media}", groupId = "${spring.kafka.consumer.group-id}") + public void getMediaMessages(@Payload String message) throws DataCiteApiException, InvalidRequestException { try { - event = mapper.readValue(message, MediaObjectEvent.class); + var event = mapper.readValue(message, MediaObjectEvent.class); + service.handleMessages(event); } catch (JsonProcessingException e) { log.error(ERROR_MSG); log.info("Message: {}", message); - throw new DataCiteApiException(ERROR_MSG); + throw new InvalidRequestException(); + } + } + + @RetryableTopic( + attempts = "1", + dltStrategy = DltStrategy.FAIL_ON_ERROR) + @KafkaListener(topics = "tombstone", groupId = "${spring.kafka.consumer.group-id}") + public void tombstoneDois(@Payload String message) throws DataCiteApiException, InvalidRequestException { + try { + var event = mapper.readValue(message, TombstoneEvent.class); + service.tombstoneRecord(event); + } catch (JsonProcessingException e){ + log.error(ERROR_MSG + ". Message: {}", message, e); + throw new InvalidRequestException(); } - service.handleMessages(event); } @DltHandler @@ -66,6 +80,4 @@ public void dltHandler(String message, @Header(KafkaHeaders.RECEIVED_TOPIC) String receivedTopic) { log.info("Message {} received in dlt handler at topic {} ", message, receivedTopic); } - - } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java index df9dcb8..a293d2a 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java @@ -13,6 +13,7 @@ import eu.dissco.core.datacitepublisher.domain.DigitalSpecimenEvent; import eu.dissco.core.datacitepublisher.domain.EventType; import eu.dissco.core.datacitepublisher.domain.MediaObjectEvent; +import eu.dissco.core.datacitepublisher.domain.TombstoneEvent; import eu.dissco.core.datacitepublisher.domain.datacite.DcAlternateIdentifier; import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; import eu.dissco.core.datacitepublisher.domain.datacite.DcContributor; @@ -29,12 +30,13 @@ import eu.dissco.core.datacitepublisher.domain.datacite.UriScheme; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; import eu.dissco.core.datacitepublisher.exceptions.DataCiteMappingException; -import eu.dissco.core.datacitepublisher.exceptions.InvalidFdoProfileRecievedException; +import eu.dissco.core.datacitepublisher.exceptions.InvalidFdoProfileReceivedException; import eu.dissco.core.datacitepublisher.properties.DoiProperties; import eu.dissco.core.datacitepublisher.schemas.DigitalSpecimen; import eu.dissco.core.datacitepublisher.schemas.MediaObject; import eu.dissco.core.datacitepublisher.web.DataCiteClient; import java.time.DateTimeException; +import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; @@ -62,6 +64,36 @@ public void handleMessages(DigitalSpecimenEvent digitalSpecimenEvent) publishToDataCite(dcRequest, digitalSpecimenEvent.eventType()); } + public void tombstoneRecord(TombstoneEvent event) throws DataCiteApiException { + var dcRecord = dataCiteClient.getDoiRecord(event.handle()); + var dcRequest = buildDataCiteTombstoneRequest(dcRecord, event); + publishToDataCite(dcRequest, EventType.TOMBSTONE); + } + + private DcRequest buildDataCiteTombstoneRequest(DcAttributes dcAttributes, TombstoneEvent event) { + var description = new ArrayList<>(dcAttributes.getDescriptions()); + description.add(DcDescription.builder() + .description("This DOI has been tombstoned") + .build()); + var relatedIdentifiers = new ArrayList<>(dcAttributes.getRelatedIdentifiers()); + relatedIdentifiers.addAll(event.tombstonePids()); + var dates = new ArrayList<>(dcAttributes.getDates()); + dates.add(DcDate.builder() + .date(DATACITE_FORMATTER.format(Instant.now())) + .dateType("Withdrawn") + .build()); + return DcRequest.builder() + .data(DcData.builder() + .attributes(DcAttributes.builder() + .descriptions(description) + .relatedIdentifiers(relatedIdentifiers) + .dates(dates) + .doi(getDoi(event.handle())) + .build()) + .build()) + .build(); + } + public void handleMessages(MediaObjectEvent mediaObjectEvent) throws DataCiteApiException { var dcRequest = buildDcRequest(mediaObjectEvent.pidRecord()); publishToDataCite(dcRequest, mediaObjectEvent.eventType()); @@ -71,10 +103,12 @@ private void publishToDataCite(DcRequest request, EventType eventType) throws DataCiteApiException { var body = mapper.valueToTree(request); var method = eventType.equals(EventType.CREATE) ? HttpMethod.POST : HttpMethod.PUT; - log.info("Publishing DOI {} to datacite", request.getData().getAttributes().getDoi()); - var response = dataCiteClient.sendDoiRequest(body, method, request.getData().getAttributes().getDoi()); + log.info("Sending {} request to datacite with DOI {}", eventType.name(), + request.getData().getAttributes().getDoi()); + var response = dataCiteClient.sendDoiRequest(body, method, + request.getData().getAttributes().getDoi()); log.debug("received response from datacite: {}", response); - log.info("Successfully published DOI {} to datacite", + log.info("Successfully {}D DOI {} to datacite", eventType.name(), request.getData().getAttributes().getDoi()); } @@ -146,7 +180,7 @@ private DcRequest buildDcRequest(String xmlLoc, String landingPage, String altId .build()) .build() ).build(); - } catch (InvalidFdoProfileRecievedException e) { + } catch (InvalidFdoProfileReceivedException e) { throw new DataCiteMappingException(); } } @@ -191,7 +225,7 @@ private List getNameIdentifiers(String id) { .build()); } - private ZonedDateTime getDate(String pidIssueDate) throws InvalidFdoProfileRecievedException { + private ZonedDateTime getDate(String pidIssueDate) throws InvalidFdoProfileReceivedException { if (pidIssueDate == null) { return null; } @@ -200,7 +234,7 @@ private ZonedDateTime getDate(String pidIssueDate) throws InvalidFdoProfileRecie return ZonedDateTime.parse(pidIssueDate); } catch (DateTimeException e) { log.error("Unable to parse date {}", pidIssueDate, e); - throw new InvalidFdoProfileRecievedException(); + throw new InvalidFdoProfileReceivedException(); } } @@ -208,7 +242,10 @@ private List getDates(ZonedDateTime pidIssueDate) { if (pidIssueDate == null) { return Collections.emptyList(); } - return List.of(DcDate.builder().date(pidIssueDate.format(DATACITE_FORMATTER)).build()); + return List.of(DcDate.builder() + .date(pidIssueDate.format(DATACITE_FORMATTER)) + .dateType("Issued") + .build()); } private Integer getPublicationYear(ZonedDateTime pidIssueDate) { @@ -243,7 +280,8 @@ private List getDescriptionForMedia(MediaObject mediaObject) { } if (mediaObject.getLinkedDigitalObjectType() != null) { descriptionList.add(DcDescription.builder() - .description("Is media for an object of type " + mediaObject.getLinkedDigitalObjectType() + ".") + .description( + "Is media for an object of type " + mediaObject.getLinkedDigitalObjectType() + ".") .build()); } return descriptionList; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java b/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java index c5035a8..87db97d 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java @@ -1,6 +1,9 @@ package eu.dissco.core.datacitepublisher.web; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; import java.time.Duration; import java.util.concurrent.ExecutionException; @@ -11,16 +14,20 @@ import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; import reactor.util.retry.Retry; @RequiredArgsConstructor @Component @Slf4j public class DataCiteClient { + @Qualifier(value = "datacite") private final WebClient webClient; + private final ObjectMapper mapper; - public JsonNode sendDoiRequest(JsonNode requestBody, HttpMethod method, String doi) throws DataCiteApiException { + public JsonNode sendDoiRequest(JsonNode requestBody, HttpMethod method, String doi) + throws DataCiteApiException { String uri = method.equals(HttpMethod.PUT) ? "/" + doi : ""; @@ -34,6 +41,30 @@ public JsonNode sendDoiRequest(JsonNode requestBody, HttpMethod method, String d .filter(WebClientUtils::is5xxServerError) .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> new DataCiteApiException( "External Service failed to process after max retries"))); + return getResponse(response); + } + + public DcAttributes getDoiRecord(String doi) throws DataCiteApiException { + var uri = "/dois/" + doi; + var response = webClient.get() + .uri(uri) + .retrieve() + .bodyToMono(JsonNode.class) + .retryWhen( + Retry.fixedDelay(3, Duration.ofSeconds(2)) + .filter(WebClientUtils::is5xxServerError) + .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> new DataCiteApiException( + "External Service failed to process after max retries"))); + var jsonNodeResponse = getResponse(response); + try { + return mapper.treeToValue(jsonNodeResponse.get("data").get("attributes"), DcAttributes.class); + } catch (JsonProcessingException e){ + log.error("Unable to parse response from DataCite: {}", jsonNodeResponse, e); + throw new DataCiteApiException("Unexpected response from DataCite"); + } + } + + private JsonNode getResponse(Mono response) throws DataCiteApiException { try { return response.toFuture().get(); } catch (InterruptedException e) { diff --git a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java index 3ca25c5..d39e78c 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java @@ -12,15 +12,18 @@ import eu.dissco.core.datacitepublisher.domain.EventType; import eu.dissco.core.datacitepublisher.domain.FdoType; import eu.dissco.core.datacitepublisher.domain.RecoveryEvent; +import eu.dissco.core.datacitepublisher.domain.TombstoneEvent; import eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants; import eu.dissco.core.datacitepublisher.domain.datacite.DcAlternateIdentifier; import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; import eu.dissco.core.datacitepublisher.domain.datacite.DcContributor; import eu.dissco.core.datacitepublisher.domain.datacite.DcCreator; +import eu.dissco.core.datacitepublisher.domain.datacite.DcData; import eu.dissco.core.datacitepublisher.domain.datacite.DcDate; import eu.dissco.core.datacitepublisher.domain.datacite.DcDescription; import eu.dissco.core.datacitepublisher.domain.datacite.DcNameIdentifiers; import eu.dissco.core.datacitepublisher.domain.datacite.DcRelatedIdentifiers; +import eu.dissco.core.datacitepublisher.domain.datacite.DcRequest; import eu.dissco.core.datacitepublisher.domain.datacite.DcSubject; import eu.dissco.core.datacitepublisher.domain.datacite.DcTitle; import eu.dissco.core.datacitepublisher.domain.datacite.DcType; @@ -51,6 +54,7 @@ private TestUtils() { public static final String ROR = "https://ror.org/0566bfb96"; public static final String HOST_NAME = "Naturalis Biodiversity Center"; public static final String REFERENT_NAME = "New digital object"; + public static final Instant TOMBSTONED = Instant.parse("2024-04-09T09:59:24.00Z"); public static final String PID_ISSUE_DATE = "2024-03-08'T'11:17:13Z"; public static final String LOCS = ""; public static final List LOCS_ARR = List.of( @@ -78,7 +82,7 @@ private TestUtils() { XML_MAPPER = new XmlMapper(); } - public static DcAttributes givenSpecimenDataCiteAttributes(){ + public static DcAttributes givenSpecimenDataCiteAttributes() { return givenSpecimenDataCiteAttributes(DOI); } @@ -100,22 +104,31 @@ public static DcAttributes givenSpecimenDataCiteAttributes(String doi) { .alternateIdentifiers(List.of(DcAlternateIdentifier.builder() .alternateIdentifierType("primarySpecimenObjectId") .alternateIdentifier(LOCAL_ID).build())) - .dates(List.of(DcDate.builder() - .date("2024-03-08") - .build())) - .relatedIdentifiers(List.of( - DcRelatedIdentifiers.builder() - .relationType("IsVariantFormOf") - .relatedIdentifier( - "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") - .relatedIdentifierType("URL") - .build())) + .dates(List.of(givenDcIssueDate())) + .relatedIdentifiers(List.of(givenDcRelatedIdentifiers())) .descriptions(givenSpecimenDescription()) .types(givenType(DataCiteConstants.TYPE_DS)) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") .build(); } + private static DcDate givenDcIssueDate() { + return DcDate.builder() + .dateType("Issued") + .date("2024-03-08") + .build(); + } + + private static DcRelatedIdentifiers givenDcRelatedIdentifiers() { + return DcRelatedIdentifiers.builder() + .relationType("IsVariantFormOf") + .relatedIdentifier( + "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") + .relatedIdentifierType("URL") + .build(); + } + + public static DcType givenType(String resourceType) { return DcType.builder() .resourceType(resourceType) @@ -142,6 +155,7 @@ public static DcAttributes givenSpecimenDataCiteAttributesFull() { .alternateIdentifier(LOCAL_ID).build())) .dates(List.of(DcDate.builder() .date("2024-03-08") + .dateType("Issued") .build())) .relatedIdentifiers(List.of( DcRelatedIdentifiers.builder() @@ -178,6 +192,15 @@ public static JsonNode givenSpecimenJson(String doi) { return MAPPER.valueToTree(givenSpecimenDataCiteAttributes(doi)); } + public static JsonNode givenDcRequest(DcAttributes attributes){ + return MAPPER.valueToTree( + DcRequest.builder() + .data(DcData.builder() + .attributes(attributes) + .build()) + .build()); + } + public static DcAttributes givenMediaAttributes() { return DcAttributes.builder() .suffix(SUFFIX) @@ -205,6 +228,7 @@ public static DcAttributes givenMediaAttributes() { .alternateIdentifier(LOCAL_ID).build())) .dates(List.of(DcDate.builder() .date("2024-03-08") + .dateType("Issued") .build())) .relatedIdentifiers(List.of( DcRelatedIdentifiers.builder() @@ -239,6 +263,7 @@ public static DcAttributes givenMediaAttributesFull() { .alternateIdentifierType("primaryMediaId") .alternateIdentifier(LOCAL_ID).build())) .dates(List.of(DcDate.builder() + .dateType("Issued") .date("2024-03-08") .build())) .relatedIdentifiers(List.of( @@ -291,7 +316,7 @@ private static List givenMediaDescriptionFull() { ); } - public static DigitalSpecimen givenDigitalSpecimen(){ + public static DigitalSpecimen givenDigitalSpecimen() { return givenDigitalSpecimen(PID); } @@ -340,6 +365,46 @@ public static MediaObject givenMediaObjectFull() { .withMediaFormat(MediaFormat.IMAGE); } + public static TombstoneEvent givenTombstoneEvent() { + return new TombstoneEvent( + DOI, + List.of(givenDcRelatedIdentifiersTombstone()) + ); + } + + private static DcRelatedIdentifiers givenDcRelatedIdentifiersTombstone() { + return DcRelatedIdentifiers.builder() + .relatedIdentifier(PID_ALT) + .relatedIdentifierType("DOI") + .relationType("Superseded By") + .build(); + } + + public static DcRequest givenDcRequestTombstone() { + var description = new ArrayList<>(givenSpecimenDescription()); + description.add(DcDescription.builder() + .description("This DOI has been tombstoned") + .build()); + return DcRequest.builder() + .data(DcData.builder() + .attributes(DcAttributes.builder() + .doi(DOI) + .relatedIdentifiers( + List.of(givenDcRelatedIdentifiers(), givenDcRelatedIdentifiersTombstone())) + .dates(List.of( + givenDcIssueDate(), + DcDate.builder() + .date("2024-04-09") + .dateType("Withdrawn") + .build() + )) + .descriptions(description) + .build()) + .build()) + .build(); + } + + public static RecoveryEvent givenRecoveryEvent() { return new RecoveryEvent(List.of(DOI, DOI_ALT), EventType.CREATE); } diff --git a/src/test/java/eu/dissco/core/datacitepublisher/component/XmlLocReaderTest.java b/src/test/java/eu/dissco/core/datacitepublisher/component/XmlLocReaderTest.java index 8500b71..2a1837e 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/component/XmlLocReaderTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/component/XmlLocReaderTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertThrows; -import eu.dissco.core.datacitepublisher.exceptions.InvalidFdoProfileRecievedException; +import eu.dissco.core.datacitepublisher.exceptions.InvalidFdoProfileReceivedException; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,7 +36,7 @@ void testGetLocationsFromXml() throws Exception { @Test void testGetLocationsBadLocs() { // When / Then - assertThrows(InvalidFdoProfileRecievedException.class, + assertThrows(InvalidFdoProfileReceivedException.class, () -> xmlLocReader.getLocationsFromXml("bad document")); } diff --git a/src/test/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerServiceTest.java b/src/test/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerServiceTest.java index 2932d12..64e0316 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerServiceTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerServiceTest.java @@ -3,7 +3,9 @@ import static eu.dissco.core.datacitepublisher.TestUtils.MAPPER; import static eu.dissco.core.datacitepublisher.TestUtils.givenDigitalSpecimen; import static eu.dissco.core.datacitepublisher.TestUtils.givenMediaObject; +import static eu.dissco.core.datacitepublisher.TestUtils.givenTombstoneEvent; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; @@ -11,7 +13,9 @@ import eu.dissco.core.datacitepublisher.domain.DigitalSpecimenEvent; import eu.dissco.core.datacitepublisher.domain.EventType; import eu.dissco.core.datacitepublisher.domain.MediaObjectEvent; +import eu.dissco.core.datacitepublisher.domain.TombstoneEvent; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; +import eu.dissco.core.datacitepublisher.exceptions.InvalidRequestException; import eu.dissco.core.datacitepublisher.service.DataCitePublisherService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -51,7 +55,7 @@ void testHandleSpecimenMessagesBadRequest() throws Exception { var message = MAPPER.writeValueAsString(event); // When / Then - assertThrows(DataCiteApiException.class, + assertThrows(InvalidRequestException.class, () -> kafkaConsumerService.getSpecimenMessages(message)); } @@ -75,10 +79,33 @@ void testHandleMediaMessageBadRequest() throws Exception { var message = MAPPER.writeValueAsString(event); // When / Then - assertThrows(DataCiteApiException.class, + assertThrows(InvalidRequestException.class, () -> kafkaConsumerService.getMediaMessages(message)); } + @Test + void testHandleTombstoneMessages() throws Exception { + // Given + var event = givenTombstoneEvent(); + var message = MAPPER.writeValueAsString(event); + + // When + kafkaConsumerService.tombstoneDois(message); + + // Then + then(dataCiteService).should().tombstoneRecord(any(TombstoneEvent.class)); + } + + @Test + void testHandleTombstoneMessagesBadRequest() { + // Given + var message = ""; + + // When / Then + assertThrows(InvalidRequestException.class, + () -> kafkaConsumerService.tombstoneDois(message)); + } + @Test void testDlt() throws Exception { // Given diff --git a/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java b/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java index 52cc192..144026b 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java @@ -7,6 +7,9 @@ import static eu.dissco.core.datacitepublisher.TestUtils.PID; import static eu.dissco.core.datacitepublisher.TestUtils.PREFIX; import static eu.dissco.core.datacitepublisher.TestUtils.SUFFIX; +import static eu.dissco.core.datacitepublisher.TestUtils.TOMBSTONED; +import static eu.dissco.core.datacitepublisher.TestUtils.givenDcRequest; +import static eu.dissco.core.datacitepublisher.TestUtils.givenDcRequestTombstone; import static eu.dissco.core.datacitepublisher.TestUtils.givenDigitalSpecimen; import static eu.dissco.core.datacitepublisher.TestUtils.givenDigitalSpecimenFull; import static eu.dissco.core.datacitepublisher.TestUtils.givenMediaAttributes; @@ -15,13 +18,15 @@ import static eu.dissco.core.datacitepublisher.TestUtils.givenMediaObjectFull; import static eu.dissco.core.datacitepublisher.TestUtils.givenSpecimenDataCiteAttributes; import static eu.dissco.core.datacitepublisher.TestUtils.givenSpecimenDataCiteAttributesFull; +import static eu.dissco.core.datacitepublisher.TestUtils.givenTombstoneEvent; import static eu.dissco.core.datacitepublisher.TestUtils.givenType; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mockStatic; -import eu.dissco.core.datacitepublisher.TestUtils; import eu.dissco.core.datacitepublisher.component.XmlLocReader; import eu.dissco.core.datacitepublisher.domain.DigitalSpecimenEvent; import eu.dissco.core.datacitepublisher.domain.EventType; @@ -36,12 +41,15 @@ import eu.dissco.core.datacitepublisher.schemas.DigitalSpecimen; import eu.dissco.core.datacitepublisher.schemas.MediaObject; import eu.dissco.core.datacitepublisher.web.DataCiteClient; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.core.env.Environment; import org.springframework.http.HttpMethod; @ExtendWith(MockitoExtension.class) @@ -52,10 +60,10 @@ class DataCitePublisherServiceTest { @Mock private DataCiteClient dataCiteClient; @Mock - private Environment environment; - @Mock DoiProperties properties; private DataCitePublisherService service; + private MockedStatic mockedInstant; + private MockedStatic mockedClock; @BeforeEach void setup() { @@ -67,12 +75,7 @@ void setup() { void testHandleDigitalSpecimenMessage() throws Exception { // Given var event = new DigitalSpecimenEvent(givenDigitalSpecimen(), EventType.CREATE); - var expected = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(givenSpecimenDataCiteAttributes()) - .build()) - .build()); + var expected = givenDcRequest(givenSpecimenDataCiteAttributes()); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); // When @@ -86,12 +89,7 @@ void testHandleDigitalSpecimenMessage() throws Exception { void testHandleDigitalSpecimenMessageUpdate() throws Exception { // Given var event = new DigitalSpecimenEvent(givenDigitalSpecimen(), EventType.UPDATE); - var expected = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(givenSpecimenDataCiteAttributes()) - .build()) - .build()); + var expected = givenDcRequest(givenSpecimenDataCiteAttributes()); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); // When @@ -105,12 +103,7 @@ void testHandleDigitalSpecimenMessageUpdate() throws Exception { void testHandleDigitalSpecimenApiException() throws Exception { // Given var event = new DigitalSpecimenEvent(givenDigitalSpecimen(), EventType.CREATE); - var requestBody = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(givenSpecimenDataCiteAttributes()) - .build()) - .build()); + var requestBody = givenDcRequest(givenSpecimenDataCiteAttributes()); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); given(dataCiteClient.sendDoiRequest(requestBody, HttpMethod.POST, DOI)).willThrow( DataCiteApiException.class); @@ -123,12 +116,7 @@ void testHandleDigitalSpecimenApiException() throws Exception { void testHandleDigitalSpecimenMessageFull() throws Exception { // Given var event = new DigitalSpecimenEvent(givenDigitalSpecimenFull(), EventType.CREATE); - var expected = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(givenSpecimenDataCiteAttributesFull()) - .build()) - .build()); + var expected = givenDcRequest(givenSpecimenDataCiteAttributesFull()); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); // When @@ -142,12 +130,7 @@ void testHandleDigitalSpecimenMessageFull() throws Exception { void testHandleMediaObjectMessage() throws Exception { // Given var event = new MediaObjectEvent(givenMediaObject(), EventType.CREATE); - var expected = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(givenMediaAttributes()) - .build()) - .build()); + var expected = givenDcRequest(givenMediaAttributes()); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); // When @@ -161,12 +144,7 @@ void testHandleMediaObjectMessage() throws Exception { void testHandleMediaObjectMessageFull() throws Exception { // Given var event = new MediaObjectEvent(givenMediaObjectFull(), EventType.CREATE); - var expected = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(givenMediaAttributesFull()) - .build()) - .build()); + var expected = givenDcRequest(givenMediaAttributesFull()); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); // When @@ -191,12 +169,7 @@ void testHandleMessageBadDate() throws Exception { void testHandleDigitalSpecimenMessageTestEnv() throws Exception { // Given var event = new DigitalSpecimenEvent(givenDigitalSpecimen(), EventType.CREATE); - var expected = MAPPER.valueToTree( - DcRequest.builder() - .data(DcData.builder() - .attributes(TestUtils.givenSpecimenDataCiteAttributes(PREFIX + "/" + SUFFIX)) - .build()) - .build()); + var expected = givenDcRequest(givenSpecimenDataCiteAttributes(PREFIX + "/" + SUFFIX)); given(xmlLocReader.getLocationsFromXml(LOCS)).willReturn(LOCS_ARR); // When @@ -242,18 +215,12 @@ void testHandleMediaObjectMessageNulls() throws Exception { .withPid(PID), EventType.UPDATE ); - var expected = MAPPER.valueToTree(DcRequest.builder() - .data(DcData.builder() - .attributes( - DcAttributes.builder() - .doi(DOI) - .suffix(SUFFIX) - .types(givenType(DataCiteConstants.TYPE_MO)) - .build() - ) - .build() - ).build() - ); + var attributes = DcAttributes.builder() + .doi(DOI) + .suffix(SUFFIX) + .types(givenType(DataCiteConstants.TYPE_MO)) + .build(); + var expected = givenDcRequest(attributes); // When service.handleMessages(event); @@ -262,4 +229,31 @@ void testHandleMediaObjectMessageNulls() throws Exception { then(dataCiteClient).should().sendDoiRequest(expected, HttpMethod.PUT, DOI); } + @Test + void testTombstoneRecord() throws Exception{ + // Given + given(dataCiteClient.getDoiRecord(DOI)).willReturn(givenSpecimenDataCiteAttributes()); + var expected = MAPPER.valueToTree(givenDcRequestTombstone()); + initTime(); + + // When + service.tombstoneRecord(givenTombstoneEvent()); + + // Then + then(dataCiteClient).should().sendDoiRequest(expected, HttpMethod.PUT, DOI); + mockedInstant.close(); + mockedClock.close(); + } + + private void initTime(){ + Clock clock = Clock.fixed(TOMBSTONED, ZoneOffset.UTC); + Instant instant = Instant.now(clock); + mockedInstant = mockStatic(Instant.class); + mockedInstant.when(Instant::now).thenReturn(instant); + mockedInstant.when(() -> Instant.from(any())).thenReturn(instant); + mockedInstant.when(() -> Instant.parse(any())).thenReturn(instant); + mockedClock = mockStatic(Clock.class); + mockedClock.when(Clock::systemUTC).thenReturn(clock); + } + } diff --git a/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java b/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java index 0f3dcc6..d237a3c 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java @@ -7,8 +7,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertThrows; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; import java.io.IOException; import okhttp3.mockwebserver.MockResponse; @@ -50,7 +53,7 @@ void setup() { .filter(errorResponseFilter) .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.api+json") .build(); - dataCiteClient = new DataCiteClient(webClient); + dataCiteClient = new DataCiteClient(webClient, MAPPER); } @AfterAll @@ -192,6 +195,21 @@ void testDataCiteOther() throws Exception { assertThat(e.getMessage()).contains(expectedMessage); } + @Test + void testGetDoiRecord() throws Exception { + // Given + mockDataCiteServer.enqueue(new MockResponse() + .setResponseCode(HttpStatus.OK.value()) + .setBody(MAPPER.writeValueAsString(givenDataCiteResponse())) + .addHeader("Content-Type", "application/json")); + + // When + var result = dataCiteClient.getDoiRecord(DOI); + + // Then + assertThat(result).isInstanceOf(DcAttributes.class); + } + private static String givenDataCiteErrorResponse(boolean conflict) throws Exception { ArrayNode errors = MAPPER.createArrayNode(); ObjectNode message = MAPPER.createObjectNode(); @@ -208,4 +226,191 @@ private static String givenDataCiteErrorResponse(boolean conflict) throws Except .set("errors", errors)); } + private JsonNode givenDataCiteResponse() throws JsonProcessingException { + return MAPPER.readTree(""" + { + "data": { + "id": "", + "type": "dois", + "attributes": { + "doi": "10.3535/qr1-p21-9fw", + "prefix": "10.82621", + "suffix": "y3w-byd-2cm", + "identifiers": [ + { + "identifier": "dfc36908-9949-4438-893d-927425c789a4:01na82s61", + "identifierType": "primarySpecimenObjectId" + }, + { + "identifier": "20.5000.1025/Y3W-BYD-2CM", + "identifierType": "Handle" + } + ], + "alternateIdentifiers": [ + { + "alternateIdentifierType": "primarySpecimenObjectId", + "alternateIdentifier": "dfc36908-9949-4438-893d-927425c789a4:01na82s61" + }, + { + "alternateIdentifierType": "Handle", + "alternateIdentifier": "20.5000.1025/Y3W-BYD-2CM" + } + ], + "creators": [ + { + "name": "Digital System of Scientific Collections", + "nameIdentifiers": [ + { + "schemeUri": "https://ror.org", + "nameIdentifier": "https://ror.org/0566bfb96", + "nameIdentifierScheme": "ROR" + } + ], + "affiliation": [] + } + ], + "titles": [ + { + "title": "Lamium amplexicaule" + } + ], + "publisher": "Distributed System of Scientific Collections", + "container": {}, + "publicationYear": 2023, + "subjects": [ + { + "subject": "Botany", + "subjectScheme": "topicDiscipline" + } + ], + "contributors": [ + { + "name": "United States Department of Agriculture", + "nameIdentifiers": [ + { + "schemeUri": "https://ror.org", + "nameIdentifier": "https://ror.org/01na82s61", + "nameIdentifierScheme": "ROR" + } + ], + "affiliation": [] + } + ], + "dates": [ + { + "date": "2023-08-03", + "dateType": "Issued" + } + ], + "language": null, + "types": { + "ris": "DATA", + "bibtex": "misc", + "citeproc": "dataset", + "schemaOrg": "Dataset", + "resourceType": "digitalSpecimen", + "resourceTypeGeneral": "Dataset" + }, + "relatedIdentifiers": [ + { + "relationType": "IsVariantFormOf", + "relatedIdentifier": "https://sandbox.dissco.tech/api/v1/specimens/20.5000.1025/Y3W-BYD-2CM", + "relatedIdentifierType": "URL" + } + ], + "relatedItems": [], + "sizes": [], + "formats": [], + "version": null, + "rightsList": [], + "descriptions": [ + { + "description": "Digital Specimen hosted at United States Department of Agriculture" + } + ], + "geoLocations": [], + "fundingReferences": [], + "xml": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHJlc291cmNlIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCIgeHNpOnNjaGVtYUxvY2F0aW9uPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCBodHRwOi8vc2NoZW1hLmRhdGFjaXRlLm9yZy9tZXRhL2tlcm5lbC00L21ldGFkYXRhLnhzZCI+CiAgPGlkZW50aWZpZXIgaWRlbnRpZmllclR5cGU9IkRPSSI+MTAuODI2MjEvWTNXLUJZRC0yQ008L2lkZW50aWZpZXI+CiAgPGNyZWF0b3JzPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZT5EaWdpdGFsIFN5c3RlbSBvZiBTY2llbnRpZmljIENvbGxlY3Rpb25zPC9jcmVhdG9yTmFtZT4KICAgICAgPG5hbWVJZGVudGlmaWVyIG5hbWVJZGVudGlmaWVyU2NoZW1lPSJST1IiIHNjaGVtZVVSST0iaHR0cHM6Ly9yb3Iub3JnIj5odHRwczovL3Jvci5vcmcvMDU2NmJmYjk2PC9uYW1lSWRlbnRpZmllcj4KICAgIDwvY3JlYXRvcj4KICA8L2NyZWF0b3JzPgogIDx0aXRsZXM+CiAgICA8dGl0bGU+TGFtaXVtIGFtcGxleGljYXVsZTwvdGl0bGU+CiAgPC90aXRsZXM+CiAgPHB1Ymxpc2hlcj5EaXN0cmlidXRlZCBTeXN0ZW0gb2YgU2NpZW50aWZpYyBDb2xsZWN0aW9uczwvcHVibGlzaGVyPgogIDxwdWJsaWNhdGlvblllYXI+MjAyMzwvcHVibGljYXRpb25ZZWFyPgogIDxyZXNvdXJjZVR5cGUgcmVzb3VyY2VUeXBlR2VuZXJhbD0iRGF0YXNldCI+ZGlnaXRhbFNwZWNpbWVuPC9yZXNvdXJjZVR5cGU+CiAgPHN1YmplY3RzPgogICAgPHN1YmplY3Qgc3ViamVjdFNjaGVtZT0idG9waWNEaXNjaXBsaW5lIj5Cb3Rhbnk8L3N1YmplY3Q+CiAgPC9zdWJqZWN0cz4KICA8Y29udHJpYnV0b3JzPgogICAgPGNvbnRyaWJ1dG9yIGNvbnRyaWJ1dG9yVHlwZT0iT3RoZXIiPgogICAgICA8Y29udHJpYnV0b3JOYW1lPlVuaXRlZCBTdGF0ZXMgRGVwYXJ0bWVudCBvZiBBZ3JpY3VsdHVyZTwvY29udHJpYnV0b3JOYW1lPgogICAgICA8bmFtZUlkZW50aWZpZXIgbmFtZUlkZW50aWZpZXJTY2hlbWU9IlJPUiIgc2NoZW1lVVJJPSJodHRwczovL3Jvci5vcmciPmh0dHBzOi8vcm9yLm9yZy8wMW5hODJzNjE8L25hbWVJZGVudGlmaWVyPgogICAgPC9jb250cmlidXRvcj4KICA8L2NvbnRyaWJ1dG9ycz4KICA8ZGF0ZXM+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iSXNzdWVkIj4yMDIzLTA4LTAzPC9kYXRlPgogIDwvZGF0ZXM+CiAgPGFsdGVybmF0ZUlkZW50aWZpZXJzPgogICAgPGFsdGVybmF0ZUlkZW50aWZpZXIgYWx0ZXJuYXRlSWRlbnRpZmllclR5cGU9InByaW1hcnlTcGVjaW1lbk9iamVjdElkIj5kZmMzNjkwOC05OTQ5LTQ0MzgtODkzZC05Mjc0MjVjNzg5YTQ6MDFuYTgyczYxPC9hbHRlcm5hdGVJZGVudGlmaWVyPgogICAgPGFsdGVybmF0ZUlkZW50aWZpZXIgYWx0ZXJuYXRlSWRlbnRpZmllclR5cGU9IkhhbmRsZSI+MjAuNTAwMC4xMDI1L1kzVy1CWUQtMkNNPC9hbHRlcm5hdGVJZGVudGlmaWVyPgogIDwvYWx0ZXJuYXRlSWRlbnRpZmllcnM+CiAgPHJlbGF0ZWRJZGVudGlmaWVycz4KICAgIDxyZWxhdGVkSWRlbnRpZmllciByZWxhdGVkSWRlbnRpZmllclR5cGU9IlVSTCIgcmVsYXRpb25UeXBlPSJJc1ZhcmlhbnRGb3JtT2YiPmh0dHBzOi8vc2FuZGJveC5kaXNzY28udGVjaC9hcGkvdjEvc3BlY2ltZW5zLzIwLjUwMDAuMTAyNS9ZM1ctQllELTJDTTwvcmVsYXRlZElkZW50aWZpZXI+CiAgPC9yZWxhdGVkSWRlbnRpZmllcnM+CiAgPHNpemVzLz4KICA8Zm9ybWF0cy8+CiAgPHZlcnNpb24vPgogIDxkZXNjcmlwdGlvbnM+CiAgICA8ZGVzY3JpcHRpb24gZGVzY3JpcHRpb25UeXBlPSJBYnN0cmFjdCI+RGlnaXRhbCBTcGVjaW1lbiBob3N0ZWQgYXQgVW5pdGVkIFN0YXRlcyBEZXBhcnRtZW50IG9mIEFncmljdWx0dXJlPC9kZXNjcmlwdGlvbj4KICA8L2Rlc2NyaXB0aW9ucz4KPC9yZXNvdXJjZT4K", + "url": "https://sandbox.dissco.tech/ds/20.5000.1025/Y3W-BYD-2CM", + "contentUrl": null, + "metadataVersion": 0, + "schemaVersion": "http://datacite.org/schema/kernel-4.4", + "source": "api", + "isActive": true, + "state": "findable", + "reason": null, + "landingPage": { + "url": "https://sandbox.dissco.tech/ds/20.5000.1025/Y3W-BYD-2CM", + "error": "", + "status": 200, + "checked": "2023-08-29 07:02:28", + "bodyHasPid": false, + "citationDoi": null, + "contentType": "text/html", + "schemaOrgId": null, + "dcIdentifier": null, + "hasSchemaOrg": false, + "redirectUrls": [], + "redirectCount": 0, + "downloadLatency": 1647 + }, + "viewCount": 0, + "viewsOverTime": [], + "downloadCount": 0, + "downloadsOverTime": [], + "referenceCount": 0, + "citationCount": 0, + "citationsOverTime": [], + "partCount": 0, + "partOfCount": 0, + "versionCount": 0, + "versionOfCount": 0, + "created": "2023-08-03T14:21:00.000Z", + "registered": "2023-08-03T14:21:00.000Z", + "published": "2023", + "updated": "2024-08-18T23:32:39.000Z" + }, + "relationships": { + "client": { + "data": { + "id": "znlx.aadziy", + "type": "clients" + } + }, + "provider": { + "data": { + "id": "znlx", + "type": "providers" + } + }, + "media": { + "data": { + "id": "10.3535/qr1-p21-9fw", + "type": "media" + } + }, + "references": { + "data": [] + }, + "citations": { + "data": [] + }, + "parts": { + "data": [] + }, + "partOf": { + "data": [] + }, + "versions": { + "data": [] + }, + "versionOf": { + "data": [] + } + } + } + } + """); + } + } From 90ff077f7d2fa43319a7b7917fc1c3d9f0cdae47 Mon Sep 17 00:00:00 2001 From: southeo Date: Mon, 19 Aug 2024 13:51:23 +0200 Subject: [PATCH 2/6] upgrade spring --- pom.xml | 2 +- .../core/datacitepublisher/domain/datacite/DcAttributes.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b6d30e1..59277c4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.5 + 3.3.2 eu.dissco.core diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java index ad54488..c174c74 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; From 3e580bab71d0f7b336323a6ea573c118268c16b7 Mon Sep 17 00:00:00 2001 From: southeo Date: Mon, 19 Aug 2024 15:23:25 +0200 Subject: [PATCH 3/6] Add relationtype --- .../configuration/WebClientConfig.java | 3 -- .../domain/datacite/DcRelatedIdentifiers.java | 2 +- .../domain/datacite/RelationType.java | 40 +++++++++++++++++++ .../service/DataCitePublisherService.java | 3 +- .../service/RecoveryService.java | 4 +- .../datacitepublisher/web/DataCiteClient.java | 5 ++- .../core/datacitepublisher/TestUtils.java | 11 ++--- .../web/HandleClientTest.java | 4 -- 8 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/RelationType.java diff --git a/src/main/java/eu/dissco/core/datacitepublisher/configuration/WebClientConfig.java b/src/main/java/eu/dissco/core/datacitepublisher/configuration/WebClientConfig.java index f11b7b8..7924d10 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/configuration/WebClientConfig.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/configuration/WebClientConfig.java @@ -32,11 +32,8 @@ public WebClient dataciteClient() { @Bean("handle") public WebClient handleClient() { - ExchangeFilterFunction errorResponseFilter = ExchangeFilterFunction - .ofResponseProcessor(WebClientUtils::exchangeFilterResponseProcessor); return WebClient.builder() .baseUrl(handleProperties.getEndpoint()) - .filter(errorResponseFilter) .build(); } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcRelatedIdentifiers.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcRelatedIdentifiers.java index 87bfc12..96b8dc2 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcRelatedIdentifiers.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcRelatedIdentifiers.java @@ -11,7 +11,7 @@ @Setter(value = AccessLevel.PACKAGE) @Getter public class DcRelatedIdentifiers { - private String relationType; + private RelationType relationType; private String relatedIdentifier; private String relatedIdentifierType; } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/RelationType.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/RelationType.java new file mode 100644 index 0000000..6fdc1a3 --- /dev/null +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/RelationType.java @@ -0,0 +1,40 @@ +package eu.dissco.core.datacitepublisher.domain.datacite; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum RelationType { + @JsonProperty("IsCitedBy") IS_CITED_BY, + @JsonProperty("Cites") CITES, + @JsonProperty("IsSupplementTo") IS_SUPPLEMENT_TO, + @JsonProperty("IsSupplementedBy") IS_SUPPLEMENTED_BY, + @JsonProperty("IsContinuedBy") IS_CONTINUED_BY, + @JsonProperty("Continues") CONTINUES, + @JsonProperty("IsDescribedBy") IS_DESCRIBED_BY, + @JsonProperty("Describes") DESCRIBES, + @JsonProperty("HasMetadata") HAS_METADATA, + @JsonProperty("IsMetadataFor") IS_METADATA_FOR, + @JsonProperty("HasVersion") HAS_VERSION, + @JsonProperty("IsVersionOf") IS_VERSION_OF, + @JsonProperty("IsNewVersionOf") IS_NEW_VERSION_OF, + @JsonProperty("IsPreviousVersionOf") IS_PREVIOUS_VERSION_OF, + @JsonProperty("IsPartOf") IS_PART_OF, + @JsonProperty("HasPart") HAS_PART, + @JsonProperty("IsPublishedIn") IS_PUBLISHED_IN, + @JsonProperty("IsReferencedBy") IS_REFERENCED_BY, + @JsonProperty("References") REFERENCES, + @JsonProperty("IsDocumentedBy") IS_DOCUMENTED_BY, + @JsonProperty("Documents") DOCUMENTS, + @JsonProperty("IsCompiledBy") IS_COMPILED_BY, + @JsonProperty("Compiles") COMPILES, + @JsonProperty("IsVariantFormOf") IS_VARIANT_FORM_OF, + @JsonProperty("IsOriginalFormOf") IS_ORIGINAL_FORM_OF, + @JsonProperty("IsIdenticalTo") IS_IDENTICAL_TO, + @JsonProperty("IsReviewedBy") IS_REVIEWED_BY, + @JsonProperty("Reviews") REVIEWS, + @JsonProperty("IsDerivedFrom") IS_DERIVED_FROM, + @JsonProperty("IsSourceOf") IS_SOURCE_OF, + @JsonProperty("IsRequiredBy") IS_REQUIRED_BY, + @JsonProperty("Requires") REQUIRES, + @JsonProperty("IsObsoletedBy") IS_OBSOLETED_BY, + @JsonProperty("Obsoletes") OBSOLETES; +} diff --git a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java index a293d2a..ec9705a 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java @@ -27,6 +27,7 @@ import eu.dissco.core.datacitepublisher.domain.datacite.DcSubject; import eu.dissco.core.datacitepublisher.domain.datacite.DcTitle; import eu.dissco.core.datacitepublisher.domain.datacite.DcType; +import eu.dissco.core.datacitepublisher.domain.datacite.RelationType; import eu.dissco.core.datacitepublisher.domain.datacite.UriScheme; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; import eu.dissco.core.datacitepublisher.exceptions.DataCiteMappingException; @@ -361,7 +362,7 @@ private List getRelatedIdentifiers(List xmlLocatio locs.remove(landingPage); for (var location : locs) { relatedIdentifiersList.add(DcRelatedIdentifiers.builder() - .relationType("IsVariantFormOf") + .relationType(RelationType.IS_VARIANT_FORM_OF) .relatedIdentifier(location) .relatedIdentifierType("URL") .build()); diff --git a/src/main/java/eu/dissco/core/datacitepublisher/service/RecoveryService.java b/src/main/java/eu/dissco/core/datacitepublisher/service/RecoveryService.java index 7fb23e5..f623762 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/service/RecoveryService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/service/RecoveryService.java @@ -17,6 +17,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service @@ -26,6 +27,7 @@ public class RecoveryService { private final HandleClient handleClient; private final DataCitePublisherService dataCitePublisherService; + @Qualifier("objectMapper") private final ObjectMapper mapper; private final HandleConnectionProperties handleConnectionProperties; @@ -67,8 +69,6 @@ private void processResolvedHandles(List handles, EventType eventType) } } - - private void recoverDigitalSpecimen(JsonNode pidRecordAttributes, EventType eventType) throws JsonProcessingException, DataCiteApiException { var digitalSpecimen = mapper.treeToValue(pidRecordAttributes, DigitalSpecimen.class); diff --git a/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java b/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java index 87db97d..3688b07 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java @@ -24,6 +24,7 @@ public class DataCiteClient { @Qualifier(value = "datacite") private final WebClient webClient; + @Qualifier("objectMapper") private final ObjectMapper mapper; public JsonNode sendDoiRequest(JsonNode requestBody, HttpMethod method, String doi) @@ -45,7 +46,7 @@ public JsonNode sendDoiRequest(JsonNode requestBody, HttpMethod method, String d } public DcAttributes getDoiRecord(String doi) throws DataCiteApiException { - var uri = "/dois/" + doi; + var uri = "/" + doi; var response = webClient.get() .uri(uri) .retrieve() @@ -58,7 +59,7 @@ public DcAttributes getDoiRecord(String doi) throws DataCiteApiException { var jsonNodeResponse = getResponse(response); try { return mapper.treeToValue(jsonNodeResponse.get("data").get("attributes"), DcAttributes.class); - } catch (JsonProcessingException e){ + } catch (JsonProcessingException e) { log.error("Unable to parse response from DataCite: {}", jsonNodeResponse, e); throw new DataCiteApiException("Unexpected response from DataCite"); } diff --git a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java index d39e78c..8739602 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java @@ -27,6 +27,7 @@ import eu.dissco.core.datacitepublisher.domain.datacite.DcSubject; import eu.dissco.core.datacitepublisher.domain.datacite.DcTitle; import eu.dissco.core.datacitepublisher.domain.datacite.DcType; +import eu.dissco.core.datacitepublisher.domain.datacite.RelationType; import eu.dissco.core.datacitepublisher.domain.datacite.UriScheme; import eu.dissco.core.datacitepublisher.schemas.DigitalSpecimen; import eu.dissco.core.datacitepublisher.schemas.DigitalSpecimen.MaterialSampleType; @@ -121,7 +122,7 @@ private static DcDate givenDcIssueDate() { private static DcRelatedIdentifiers givenDcRelatedIdentifiers() { return DcRelatedIdentifiers.builder() - .relationType("IsVariantFormOf") + .relationType(RelationType.IS_VARIANT_FORM_OF) .relatedIdentifier( "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL") @@ -159,7 +160,7 @@ public static DcAttributes givenSpecimenDataCiteAttributesFull() { .build())) .relatedIdentifiers(List.of( DcRelatedIdentifiers.builder() - .relationType("IsVariantFormOf") + .relationType(RelationType.IS_VARIANT_FORM_OF) .relatedIdentifier( "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL") @@ -232,7 +233,7 @@ public static DcAttributes givenMediaAttributes() { .build())) .relatedIdentifiers(List.of( DcRelatedIdentifiers.builder() - .relationType("IsVariantFormOf") + .relationType(RelationType.IS_VARIANT_FORM_OF) .relatedIdentifier( "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL").build())) @@ -268,7 +269,7 @@ public static DcAttributes givenMediaAttributesFull() { .build())) .relatedIdentifiers(List.of( DcRelatedIdentifiers.builder() - .relationType("IsVariantFormOf") + .relationType(RelationType.IS_VARIANT_FORM_OF) .relatedIdentifier( "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL").build())) @@ -376,7 +377,7 @@ private static DcRelatedIdentifiers givenDcRelatedIdentifiersTombstone() { return DcRelatedIdentifiers.builder() .relatedIdentifier(PID_ALT) .relatedIdentifierType("DOI") - .relationType("Superseded By") + .relationType(RelationType.OBSOLETES) .build(); } diff --git a/src/test/java/eu/dissco/core/datacitepublisher/web/HandleClientTest.java b/src/test/java/eu/dissco/core/datacitepublisher/web/HandleClientTest.java index 6e55749..6b1193a 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/web/HandleClientTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/web/HandleClientTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; @ExtendWith(MockitoExtension.class) @@ -37,12 +36,9 @@ static void init() throws IOException { @BeforeEach void setup() { - ExchangeFilterFunction errorResponseFilter = ExchangeFilterFunction - .ofResponseProcessor(WebClientUtils::exchangeFilterResponseProcessor); var client = WebClient.builder() .baseUrl(String.format("http://%s:%s", mockHandleServer.getHostName(), mockHandleServer.getPort())) - .filter(errorResponseFilter) .build(); handleClient = new HandleClient(client); } From c6fb697a714090a0cb63c903a991f55e71d5b648 Mon Sep 17 00:00:00 2001 From: southeo Date: Tue, 20 Aug 2024 09:40:50 +0200 Subject: [PATCH 4/6] publisher as an object --- .../domain/TombstoneEvent.java | 2 +- .../domain/datacite/DcAttributes.java | 3 +-- .../domain/datacite/DcPublisher.java | 16 +++++++++---- .../service/DataCitePublisherService.java | 4 +++- .../datacitepublisher/web/DataCiteClient.java | 2 +- .../core/datacitepublisher/TestUtils.java | 5 ++++ .../service/DataCitePublisherServiceTest.java | 24 ++++++++----------- .../web/DataCiteClientTest.java | 4 +++- 8 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java index 081fef4..56203e2 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/TombstoneEvent.java @@ -3,7 +3,7 @@ import eu.dissco.core.datacitepublisher.domain.datacite.DcRelatedIdentifiers; import java.util.List; -public record TombstoneEvent(String handle, List tombstonePids) { +public record TombstoneEvent(String handle, List dcRelatedIdentifiersTombstone) { } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java index c174c74..2216d6a 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java @@ -2,7 +2,6 @@ import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.DC_EVENT; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.SCHEMA_VERSION; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -37,7 +36,7 @@ public class DcAttributes { private List relatedIdentifiers; // tombstone pids; primary specimenObjectId; primaryMediaId private List descriptions; // Specimen: Host + materialSampleType, Media: host + linked object type private String url; // human readable landing page - private final DcPublisher publisher = PUBLISHER; + private DcPublisher publisher; private final String schemaVersion = SCHEMA_VERSION; private final String event = DC_EVENT; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java index 16b17a5..4f2de0c 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java @@ -4,8 +4,16 @@ @Value public class DcPublisher { - String name = "Distributed System of Scientific Collections"; - String publisherIdentifier = "https://ror.org/0566bfb96"; - String publisherIdentifierScheme = UriScheme.ROR.getSchemeName(); - String schemeUri = UriScheme.ROR.getUri(); + String name; + String publisherIdentifier; + String publisherIdentifierScheme; + String schemeUri; + + public DcPublisher(){ + name = "Distributed System of Scientific Collections"; + publisherIdentifier = "https://ror.org/0566bfb96"; + publisherIdentifierScheme = UriScheme.ROR.getSchemeName(); + schemeUri = UriScheme.ROR.getUri(); + } + } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java index ec9705a..891b665 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java @@ -5,6 +5,7 @@ import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.ALT_ID_TYPE_MO; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.LANDING_PAGE_DS; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.LANDING_PAGE_MO; +import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.TYPE_DS; import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.TYPE_MO; @@ -77,7 +78,7 @@ private DcRequest buildDataCiteTombstoneRequest(DcAttributes dcAttributes, Tombs .description("This DOI has been tombstoned") .build()); var relatedIdentifiers = new ArrayList<>(dcAttributes.getRelatedIdentifiers()); - relatedIdentifiers.addAll(event.tombstonePids()); + relatedIdentifiers.addAll(event.dcRelatedIdentifiersTombstone()); var dates = new ArrayList<>(dcAttributes.getDates()); dates.add(DcDate.builder() .date(DATACITE_FORMATTER.format(Instant.now())) @@ -178,6 +179,7 @@ private DcRequest buildDcRequest(String xmlLoc, String landingPage, String altId .titles(getTitles(referentName)) .types(getDcType(dcType)) .url(url) + .publisher(PUBLISHER) .build()) .build() ).build(); diff --git a/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java b/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java index 3688b07..7cceb78 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/web/DataCiteClient.java @@ -46,7 +46,7 @@ public JsonNode sendDoiRequest(JsonNode requestBody, HttpMethod method, String d } public DcAttributes getDoiRecord(String doi) throws DataCiteApiException { - var uri = "/" + doi; + var uri = "/" + doi + "?publisher=true"; var response = webClient.get() .uri(uri) .retrieve() diff --git a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java index 8739602..7695579 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java @@ -1,5 +1,6 @@ package eu.dissco.core.datacitepublisher; +import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; import static eu.dissco.core.datacitepublisher.schemas.DigitalSpecimen.MaterialSampleType.ORGANISM_PART; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -110,6 +111,7 @@ public static DcAttributes givenSpecimenDataCiteAttributes(String doi) { .descriptions(givenSpecimenDescription()) .types(givenType(DataCiteConstants.TYPE_DS)) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") + .publisher(PUBLISHER) .build(); } @@ -181,6 +183,7 @@ public static DcAttributes givenSpecimenDataCiteAttributesFull() { .subjectScheme("topicCategory") .build()) ) + .publisher(PUBLISHER) .descriptions(givenSpecimenDescriptionFull()) .build(); } @@ -239,6 +242,7 @@ public static DcAttributes givenMediaAttributes() { .relatedIdentifierType("URL").build())) .descriptions(givenMediaDescriptionFull()) .types(givenType(DataCiteConstants.TYPE_MO)) + .publisher(PUBLISHER) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") .build(); } @@ -276,6 +280,7 @@ public static DcAttributes givenMediaAttributesFull() { .descriptions(givenMediaDescriptionFull()) .types(givenType(DataCiteConstants.TYPE_MO)) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") + .publisher(PUBLISHER) .subjects(List.of( DcSubject.builder() .subjectScheme("mediaFormat") diff --git a/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java b/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java index 144026b..37d6440 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java @@ -20,6 +20,7 @@ import static eu.dissco.core.datacitepublisher.TestUtils.givenSpecimenDataCiteAttributesFull; import static eu.dissco.core.datacitepublisher.TestUtils.givenTombstoneEvent; import static eu.dissco.core.datacitepublisher.TestUtils.givenType; +import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -33,8 +34,6 @@ import eu.dissco.core.datacitepublisher.domain.MediaObjectEvent; import eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants; import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; -import eu.dissco.core.datacitepublisher.domain.datacite.DcData; -import eu.dissco.core.datacitepublisher.domain.datacite.DcRequest; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; import eu.dissco.core.datacitepublisher.exceptions.DataCiteMappingException; import eu.dissco.core.datacitepublisher.properties.DoiProperties; @@ -187,17 +186,13 @@ void testHandleDigitalSpecimenMessageNulls() throws Exception { .withPid(PID), EventType.UPDATE ); - var expected = MAPPER.valueToTree(DcRequest.builder() - .data(DcData.builder() - .attributes( - DcAttributes.builder() - .doi(DOI) - .suffix(SUFFIX) - .types(givenType(DataCiteConstants.TYPE_DS)) - .build() - ) + var expected = givenDcRequest( + DcAttributes.builder() + .doi(DOI) + .suffix(SUFFIX) + .types(givenType(DataCiteConstants.TYPE_DS)) + .publisher(PUBLISHER) .build() - ).build() ); // When @@ -219,6 +214,7 @@ void testHandleMediaObjectMessageNulls() throws Exception { .doi(DOI) .suffix(SUFFIX) .types(givenType(DataCiteConstants.TYPE_MO)) + .publisher(PUBLISHER) .build(); var expected = givenDcRequest(attributes); @@ -230,7 +226,7 @@ void testHandleMediaObjectMessageNulls() throws Exception { } @Test - void testTombstoneRecord() throws Exception{ + void testTombstoneRecord() throws Exception { // Given given(dataCiteClient.getDoiRecord(DOI)).willReturn(givenSpecimenDataCiteAttributes()); var expected = MAPPER.valueToTree(givenDcRequestTombstone()); @@ -245,7 +241,7 @@ void testTombstoneRecord() throws Exception{ mockedClock.close(); } - private void initTime(){ + private void initTime() { Clock clock = Clock.fixed(TOMBSTONED, ZoneOffset.UTC); Instant instant = Instant.now(clock); mockedInstant = mockStatic(Instant.class); diff --git a/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java b/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java index d237a3c..df46072 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/web/DataCiteClientTest.java @@ -274,7 +274,9 @@ private JsonNode givenDataCiteResponse() throws JsonProcessingException { "title": "Lamium amplexicaule" } ], - "publisher": "Distributed System of Scientific Collections", + "publisher": { + "name":"Distributed System of Scientific Collections" + }, "container": {}, "publicationYear": 2023, "subjects": [ From 01b64fe3cf7359c5f844dacc86fa5bc1eaefdcc2 Mon Sep 17 00:00:00 2001 From: southeo Date: Fri, 23 Aug 2024 15:37:17 +0200 Subject: [PATCH 5/6] properties --- .../domain/datacite/DataCiteConstants.java | 21 --------------- .../domain/datacite/DcAttributes.java | 4 +-- .../domain/datacite/DcPublisher.java | 8 +----- .../domain/datacite/DcType.java | 3 ++- .../properties/DoiProperties.java | 21 +++++++++++++++ .../service/DataCitePublisherService.java | 25 ++++++++---------- .../core/datacitepublisher/TestUtils.java | 26 ++++++++++++------- .../service/DataCitePublisherServiceTest.java | 18 ++++++++----- 8 files changed, 65 insertions(+), 61 deletions(-) delete mode 100644 src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DataCiteConstants.java diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DataCiteConstants.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DataCiteConstants.java deleted file mode 100644 index 03a471e..0000000 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DataCiteConstants.java +++ /dev/null @@ -1,21 +0,0 @@ -package eu.dissco.core.datacitepublisher.domain.datacite; - -public class DataCiteConstants { - private DataCiteConstants(){} - public static final String ALT_ID_TYPE_DS = "primarySpecimenObjectId"; - public static final String ALT_ID_TYPE_MO = "primaryMediaId"; - public static final String LANDING_PAGE_DS = "https://sandbox.dissco.tech/ds/"; - public static final String LANDING_PAGE_MO = "https://sandbox.dissco.tech/dm/"; - public static final String TYPE_DS = "Digital Specimen"; - public static final String TYPE_MO = "Media Object"; - public static final String RESOURCE_TYPE_GENERAL = "Dataset"; - public static final String DC_EVENT = "publish"; - public static final String SCHEMA_VERSION = "https://datacite.org/schema/kernel-4.4"; - public static final DcPublisher PUBLISHER; - - static { - PUBLISHER = new DcPublisher(); - } - - -} diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java index 2216d6a..3b15a5c 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcAttributes.java @@ -1,8 +1,8 @@ package eu.dissco.core.datacitepublisher.domain.datacite; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.DC_EVENT; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.SCHEMA_VERSION; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.DC_EVENT; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.SCHEMA_VERSION; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java index 4f2de0c..5bc2e10 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcPublisher.java @@ -1,5 +1,6 @@ package eu.dissco.core.datacitepublisher.domain.datacite; + import lombok.Value; @Value @@ -9,11 +10,4 @@ public class DcPublisher { String publisherIdentifierScheme; String schemeUri; - public DcPublisher(){ - name = "Distributed System of Scientific Collections"; - publisherIdentifier = "https://ror.org/0566bfb96"; - publisherIdentifierScheme = UriScheme.ROR.getSchemeName(); - schemeUri = UriScheme.ROR.getUri(); - } - } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java index aaecd85..f4f2612 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/domain/datacite/DcType.java @@ -1,6 +1,7 @@ package eu.dissco.core.datacitepublisher.domain.datacite; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.RESOURCE_TYPE_GENERAL; + +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.RESOURCE_TYPE_GENERAL; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AccessLevel; diff --git a/src/main/java/eu/dissco/core/datacitepublisher/properties/DoiProperties.java b/src/main/java/eu/dissco/core/datacitepublisher/properties/DoiProperties.java index 15bc203..a4e8db0 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/properties/DoiProperties.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/properties/DoiProperties.java @@ -1,5 +1,7 @@ package eu.dissco.core.datacitepublisher.properties; +import eu.dissco.core.datacitepublisher.domain.datacite.DcPublisher; +import eu.dissco.core.datacitepublisher.domain.datacite.UriScheme; import jakarta.validation.constraints.NotBlank; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -11,5 +13,24 @@ public class DoiProperties { @NotBlank private String prefix; + @NotBlank + private String publisherName = "Distributed System of Scientific Collections"; + @NotBlank + private String publisherIdentifier = "https://ror.org/0566bfb96"; + @NotBlank + private String landingPageSpecimen = "https://sandbox.dissco.tech/ds/"; + @NotBlank + private String landingPageMedia = "https://sandbox.dissco.tech/dm/"; + private final DcPublisher defaultPublisher = new DcPublisher(publisherName, publisherIdentifier, + UriScheme.ROR.getSchemeName(), UriScheme.ROR.getUri()); + + public static final String SPECIMEN_ALT_ID_TYPE = "primarySpecimenObjectId"; + public static final String MEDIA_ALT_ID_TYPE = "primaryMediaId"; + public static final String SPECIMEN_TYPE = "Digital Specimen"; + public static final String MEDIA_TYPE = "Media Object"; + public static final String RESOURCE_TYPE_GENERAL = "Dataset"; + public static final String DC_EVENT = "publish"; + public static final String SCHEMA_VERSION = "https://datacite.org/schema/kernel-4.4"; + } diff --git a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java index 891b665..6ed74a7 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherService.java @@ -1,13 +1,10 @@ package eu.dissco.core.datacitepublisher.service; import static eu.dissco.core.datacitepublisher.configuration.ApplicationConfig.DATACITE_FORMATTER; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.ALT_ID_TYPE_DS; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.ALT_ID_TYPE_MO; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.LANDING_PAGE_DS; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.LANDING_PAGE_MO; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.TYPE_DS; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.TYPE_MO; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.MEDIA_ALT_ID_TYPE; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.MEDIA_TYPE; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.SPECIMEN_ALT_ID_TYPE; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.SPECIMEN_TYPE; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dissco.core.datacitepublisher.component.XmlLocReader; @@ -117,8 +114,8 @@ private void publishToDataCite(DcRequest request, EventType eventType) private DcRequest buildDcRequest(DigitalSpecimen digitalSpecimen) { return buildDcRequest( digitalSpecimen.get10320Loc(), - LANDING_PAGE_DS, - ALT_ID_TYPE_DS, + properties.getLandingPageSpecimen(), + SPECIMEN_ALT_ID_TYPE, digitalSpecimen.getPrimarySpecimenObjectId(), digitalSpecimen.getSpecimenHostName(), digitalSpecimen.getSpecimenHost(), @@ -127,7 +124,7 @@ private DcRequest buildDcRequest(DigitalSpecimen digitalSpecimen) { digitalSpecimen.getPidRecordIssueDate(), digitalSpecimen.getPid(), digitalSpecimen.getReferentName(), - TYPE_DS, + SPECIMEN_TYPE, getDescriptionForSpecimen(digitalSpecimen), getSubjectsForSpecimen(digitalSpecimen)); } @@ -135,8 +132,8 @@ private DcRequest buildDcRequest(DigitalSpecimen digitalSpecimen) { private DcRequest buildDcRequest(MediaObject mediaObject) { return buildDcRequest( mediaObject.get10320Loc(), - LANDING_PAGE_MO, - ALT_ID_TYPE_MO, + properties.getLandingPageMedia(), + MEDIA_ALT_ID_TYPE, mediaObject.getPrimaryMediaId(), mediaObject.getMediaHostName(), mediaObject.getMediaHost(), @@ -145,7 +142,7 @@ private DcRequest buildDcRequest(MediaObject mediaObject) { mediaObject.getPidRecordIssueDate(), mediaObject.getPid(), mediaObject.getReferentName(), - TYPE_MO, + MEDIA_TYPE, getDescriptionForMedia(mediaObject), getSubjectsForMedia(mediaObject)); } @@ -179,7 +176,7 @@ private DcRequest buildDcRequest(String xmlLoc, String landingPage, String altId .titles(getTitles(referentName)) .types(getDcType(dcType)) .url(url) - .publisher(PUBLISHER) + .publisher(properties.getDefaultPublisher()) .build()) .build() ).build(); diff --git a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java index 7695579..f6da48a 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/TestUtils.java @@ -1,6 +1,7 @@ package eu.dissco.core.datacitepublisher; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.MEDIA_TYPE; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.SPECIMEN_TYPE; import static eu.dissco.core.datacitepublisher.schemas.DigitalSpecimen.MaterialSampleType.ORGANISM_PART; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -14,7 +15,6 @@ import eu.dissco.core.datacitepublisher.domain.FdoType; import eu.dissco.core.datacitepublisher.domain.RecoveryEvent; import eu.dissco.core.datacitepublisher.domain.TombstoneEvent; -import eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants; import eu.dissco.core.datacitepublisher.domain.datacite.DcAlternateIdentifier; import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; import eu.dissco.core.datacitepublisher.domain.datacite.DcContributor; @@ -23,6 +23,7 @@ import eu.dissco.core.datacitepublisher.domain.datacite.DcDate; import eu.dissco.core.datacitepublisher.domain.datacite.DcDescription; import eu.dissco.core.datacitepublisher.domain.datacite.DcNameIdentifiers; +import eu.dissco.core.datacitepublisher.domain.datacite.DcPublisher; import eu.dissco.core.datacitepublisher.domain.datacite.DcRelatedIdentifiers; import eu.dissco.core.datacitepublisher.domain.datacite.DcRequest; import eu.dissco.core.datacitepublisher.domain.datacite.DcSubject; @@ -69,6 +70,11 @@ private TestUtils() { public static final String LOCAL_ID = "PLANT-123"; public static final ObjectMapper MAPPER; public static final XmlMapper XML_MAPPER; + public static final DcPublisher DEFAULT_PUBLISHER = new DcPublisher("Distributed System of Scientific Collections", + "https://ror.org/0566bfb96", UriScheme.ROR.getSchemeName(), UriScheme.ROR.getUri()); + + public static final String SPECIMEN_PAGE = "https://sandbox.dissco.tech/ds/"; + public static final String MEDIA_PAGE = "https://sandbox.dissco.tech/dm/"; static { var mapper = new ObjectMapper().findAndRegisterModules(); @@ -109,9 +115,9 @@ public static DcAttributes givenSpecimenDataCiteAttributes(String doi) { .dates(List.of(givenDcIssueDate())) .relatedIdentifiers(List.of(givenDcRelatedIdentifiers())) .descriptions(givenSpecimenDescription()) - .types(givenType(DataCiteConstants.TYPE_DS)) + .types(givenType(SPECIMEN_TYPE)) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") - .publisher(PUBLISHER) + .publisher(DEFAULT_PUBLISHER) .build(); } @@ -167,7 +173,7 @@ public static DcAttributes givenSpecimenDataCiteAttributesFull() { "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL") .build())) - .types(givenType(DataCiteConstants.TYPE_DS)) + .types(givenType(SPECIMEN_TYPE)) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") .subjects(List.of( DcSubject.builder() @@ -183,7 +189,7 @@ public static DcAttributes givenSpecimenDataCiteAttributesFull() { .subjectScheme("topicCategory") .build()) ) - .publisher(PUBLISHER) + .publisher(DEFAULT_PUBLISHER) .descriptions(givenSpecimenDescriptionFull()) .build(); } @@ -241,8 +247,8 @@ public static DcAttributes givenMediaAttributes() { "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL").build())) .descriptions(givenMediaDescriptionFull()) - .types(givenType(DataCiteConstants.TYPE_MO)) - .publisher(PUBLISHER) + .types(givenType(MEDIA_TYPE)) + .publisher(DEFAULT_PUBLISHER) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") .build(); } @@ -278,9 +284,9 @@ public static DcAttributes givenMediaAttributesFull() { "https://sandbox.dissco.tech/api/v1/specimens/10.3535/QR1-P21-9FW") .relatedIdentifierType("URL").build())) .descriptions(givenMediaDescriptionFull()) - .types(givenType(DataCiteConstants.TYPE_MO)) + .types(givenType(MEDIA_TYPE)) .url("https://sandbox.dissco.tech/ds/10.3535/QR1-P21-9FW") - .publisher(PUBLISHER) + .publisher(DEFAULT_PUBLISHER) .subjects(List.of( DcSubject.builder() .subjectScheme("mediaFormat") diff --git a/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java b/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java index 37d6440..aa465e9 100644 --- a/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java +++ b/src/test/java/eu/dissco/core/datacitepublisher/service/DataCitePublisherServiceTest.java @@ -1,11 +1,14 @@ package eu.dissco.core.datacitepublisher.service; +import static eu.dissco.core.datacitepublisher.TestUtils.DEFAULT_PUBLISHER; import static eu.dissco.core.datacitepublisher.TestUtils.DOI; import static eu.dissco.core.datacitepublisher.TestUtils.LOCS; import static eu.dissco.core.datacitepublisher.TestUtils.LOCS_ARR; import static eu.dissco.core.datacitepublisher.TestUtils.MAPPER; +import static eu.dissco.core.datacitepublisher.TestUtils.MEDIA_PAGE; import static eu.dissco.core.datacitepublisher.TestUtils.PID; import static eu.dissco.core.datacitepublisher.TestUtils.PREFIX; +import static eu.dissco.core.datacitepublisher.TestUtils.SPECIMEN_PAGE; import static eu.dissco.core.datacitepublisher.TestUtils.SUFFIX; import static eu.dissco.core.datacitepublisher.TestUtils.TOMBSTONED; import static eu.dissco.core.datacitepublisher.TestUtils.givenDcRequest; @@ -20,7 +23,8 @@ import static eu.dissco.core.datacitepublisher.TestUtils.givenSpecimenDataCiteAttributesFull; import static eu.dissco.core.datacitepublisher.TestUtils.givenTombstoneEvent; import static eu.dissco.core.datacitepublisher.TestUtils.givenType; -import static eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants.PUBLISHER; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.MEDIA_TYPE; +import static eu.dissco.core.datacitepublisher.properties.DoiProperties.SPECIMEN_TYPE; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -32,7 +36,6 @@ import eu.dissco.core.datacitepublisher.domain.DigitalSpecimenEvent; import eu.dissco.core.datacitepublisher.domain.EventType; import eu.dissco.core.datacitepublisher.domain.MediaObjectEvent; -import eu.dissco.core.datacitepublisher.domain.datacite.DataCiteConstants; import eu.dissco.core.datacitepublisher.domain.datacite.DcAttributes; import eu.dissco.core.datacitepublisher.exceptions.DataCiteApiException; import eu.dissco.core.datacitepublisher.exceptions.DataCiteMappingException; @@ -68,6 +71,9 @@ class DataCitePublisherServiceTest { void setup() { service = new DataCitePublisherService(xmlLocReader, MAPPER, dataCiteClient, properties); lenient().when(properties.getPrefix()).thenReturn(PREFIX); + lenient().when(properties.getDefaultPublisher()).thenReturn(DEFAULT_PUBLISHER); + lenient().when(properties.getLandingPageSpecimen()).thenReturn(SPECIMEN_PAGE); + lenient().when(properties.getLandingPageMedia()).thenReturn(MEDIA_PAGE); } @Test @@ -190,8 +196,8 @@ void testHandleDigitalSpecimenMessageNulls() throws Exception { DcAttributes.builder() .doi(DOI) .suffix(SUFFIX) - .types(givenType(DataCiteConstants.TYPE_DS)) - .publisher(PUBLISHER) + .types(givenType(SPECIMEN_TYPE)) + .publisher(DEFAULT_PUBLISHER) .build() ); @@ -213,8 +219,8 @@ void testHandleMediaObjectMessageNulls() throws Exception { var attributes = DcAttributes.builder() .doi(DOI) .suffix(SUFFIX) - .types(givenType(DataCiteConstants.TYPE_MO)) - .publisher(PUBLISHER) + .types(givenType(MEDIA_TYPE)) + .publisher(DEFAULT_PUBLISHER) .build(); var expected = givenDcRequest(attributes); From b4e12d60abad10a0a192c6cba2a0595a9ee05d1f Mon Sep 17 00:00:00 2001 From: southeo Date: Fri, 23 Aug 2024 15:39:21 +0200 Subject: [PATCH 6/6] code review --- .../core/datacitepublisher/kafka/KafkaConsumerService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java b/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java index 3368fbe..405944f 100644 --- a/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java +++ b/src/main/java/eu/dissco/core/datacitepublisher/kafka/KafkaConsumerService.java @@ -64,7 +64,7 @@ public void getMediaMessages(@Payload String message) throws DataCiteApiExceptio @RetryableTopic( attempts = "1", dltStrategy = DltStrategy.FAIL_ON_ERROR) - @KafkaListener(topics = "tombstone", groupId = "${spring.kafka.consumer.group-id}") + @KafkaListener(topics = "${kafka.consumer.topic.tombstone}", groupId = "${spring.kafka.consumer.group-id}") public void tombstoneDois(@Payload String message) throws DataCiteApiException, InvalidRequestException { try { var event = mapper.readValue(message, TombstoneEvent.class);