diff --git a/.github/workflows/.trivyignore b/.github/workflows/.trivyignore index 546adac9..6d205aed 100644 --- a/.github/workflows/.trivyignore +++ b/.github/workflows/.trivyignore @@ -1,3 +1,3 @@ -# April 17 -# Spring boot needs to update its version of spring -CVE-2024-22262 \ No newline at end of file +# July 17 +# Spring boot needs to update its version of Apache Tomcat +CVE-2024-34750 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 844b0ba3..868ca982 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ target/ .idea -src/main/resources/* +src/main/resources/application.properties .mvn/ diff --git a/pom.xml b/pom.xml index c207c821..4ea42527 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ spring-boot-starter-parent org.springframework.boot - 3.2.4 + 3.3.1 handle-manager 0.0.1-SNAPSHOT @@ -18,17 +18,17 @@ 17 UTF-8 - 1.17.6 + 1.19.8 4.3 5.2.0 + 5.1.1 4.10.0 + 4.35.0 ../app-it/target/site/jacoco-aggregate/jacoco.xml https://sonarcloud.io dissco - 6.2.3 - 4.28.0 @@ -97,21 +97,17 @@ - org.postgresql - postgresql - ${postgresql.version} + org.apache.commons + commons-lang3 - org.jooq - jooq + org.springframework.boot + spring-boot-starter-data-mongodb - org.springframework - spring-jdbc - - - org.apache.commons - commons-lang3 + org.mongodb + mongodb-driver-sync + ${mongodb-driver.version} @@ -177,6 +173,11 @@ org.springframework.boot test + + org.testcontainers + mongodb + test + com.squareup.okhttp3 okhttp diff --git a/src/main/java/eu/dissco/core/handlemanager/configuration/AppConfig.java b/src/main/java/eu/dissco/core/handlemanager/configuration/AppConfig.java index 3059535e..1350c1d6 100644 --- a/src/main/java/eu/dissco/core/handlemanager/configuration/AppConfig.java +++ b/src/main/java/eu/dissco/core/handlemanager/configuration/AppConfig.java @@ -1,6 +1,8 @@ package eu.dissco.core.handlemanager.configuration; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.time.Instant; import java.util.Random; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; @@ -13,6 +15,8 @@ @Configuration public class AppConfig { + public static final String DATE_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + @Bean public DocumentBuilderFactory documentBuilderFactory() throws ParserConfigurationException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -31,7 +35,13 @@ public TransformerFactory transformerFactory() throws TransformerConfigurationEx @Bean public ObjectMapper objectMapper() { - return new ObjectMapper().findAndRegisterModules(); + var mapper = new ObjectMapper() + .findAndRegisterModules(); + SimpleModule dateModule = new SimpleModule(); + dateModule.addSerializer(Instant.class, new InstantSerializer()); + dateModule.addDeserializer(Instant.class, new InstantDeserializer()); + mapper.registerModule(dateModule); + return mapper; } @Bean diff --git a/src/main/java/eu/dissco/core/handlemanager/configuration/BatchInserterConfig.java b/src/main/java/eu/dissco/core/handlemanager/configuration/BatchInserterConfig.java deleted file mode 100644 index 8b641eb8..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/configuration/BatchInserterConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package eu.dissco.core.handlemanager.configuration; - -import java.sql.DriverManager; -import java.sql.SQLException; -import lombok.RequiredArgsConstructor; -import org.postgresql.copy.CopyManager; -import org.postgresql.core.BaseConnection; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@RequiredArgsConstructor -public class BatchInserterConfig { - - private final DataSourceProperties properties; - - @Bean - public CopyManager copyManager() throws SQLException { - var connection = DriverManager.getConnection(properties.getUrl(), properties.getUsername(), - properties.getPassword()); - return new CopyManager((BaseConnection) connection); - } - -} diff --git a/src/main/java/eu/dissco/core/handlemanager/configuration/InstantDeserializer.java b/src/main/java/eu/dissco/core/handlemanager/configuration/InstantDeserializer.java new file mode 100644 index 00000000..454fa05d --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/configuration/InstantDeserializer.java @@ -0,0 +1,30 @@ +package eu.dissco.core.handlemanager.configuration; + +import static eu.dissco.core.handlemanager.configuration.AppConfig.DATE_STRING; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InstantDeserializer extends JsonDeserializer { + + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_STRING).withZone( + ZoneOffset.UTC); + + @Override + public Instant deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { + try { + return Instant.from(formatter.parse(jsonParser.getText())); + } catch (IOException e) { + log.error("An error has occurred deserializing a date. More information: {}", e.getMessage()); + return null; + } + } + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/configuration/InstantSerializer.java b/src/main/java/eu/dissco/core/handlemanager/configuration/InstantSerializer.java new file mode 100644 index 00000000..fc2e9846 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/configuration/InstantSerializer.java @@ -0,0 +1,30 @@ +package eu.dissco.core.handlemanager.configuration; + +import static eu.dissco.core.handlemanager.configuration.AppConfig.DATE_STRING; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InstantSerializer extends JsonSerializer { + + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_STRING).withZone( + ZoneOffset.UTC); + + @Override + public void serialize(Instant value, JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) { + try { + jsonGenerator.writeString(formatter.format(value)); + } catch (IOException e) { + log.error("An error has occurred serializing a date. More information: {}", e.getMessage()); + } + } + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/configuration/MongoConfig.java b/src/main/java/eu/dissco/core/handlemanager/configuration/MongoConfig.java new file mode 100644 index 00000000..e1b7b833 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/configuration/MongoConfig.java @@ -0,0 +1,23 @@ +package eu.dissco.core.handlemanager.configuration; + +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import eu.dissco.core.handlemanager.properties.MongoProperties; +import lombok.RequiredArgsConstructor; +import org.bson.Document; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class MongoConfig { + + private final MongoProperties properties; + + @Bean + public MongoCollection getHandleCollection() { + var client = MongoClients.create(properties.getConnectionString()); + var database = client.getDatabase(properties.getDatabase()); + return database.getCollection("handles"); + } +} diff --git a/src/main/java/eu/dissco/core/handlemanager/controller/PidController.java b/src/main/java/eu/dissco/core/handlemanager/controller/PidController.java index 074f6d11..a8722e02 100644 --- a/src/main/java/eu/dissco/core/handlemanager/controller/PidController.java +++ b/src/main/java/eu/dissco/core/handlemanager/controller/PidController.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.JsonNode; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperRead; -import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperReadSingle; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperWrite; import eu.dissco.core.handlemanager.domain.requests.RollbackRequest; import eu.dissco.core.handlemanager.domain.validation.JsonSchemaValidator; @@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; @@ -54,13 +52,13 @@ public class PidController { // Getters @Operation(summary = "Resolve single PID record") @GetMapping("/{prefix}/{suffix}") - public ResponseEntity resolvePid(@PathVariable("prefix") String prefix, + public ResponseEntity resolvePid(@PathVariable("prefix") String prefix, @PathVariable("suffix") String suffix, HttpServletRequest r) throws PidResolutionException { - String path = applicationProperties.getUiUrl() + r.getRequestURI(); + String link = applicationProperties.getUiUrl() + "/" + r.getRequestURI(); String handle = prefix + "/" + suffix; if (prefix.equals(applicationProperties.getPrefix())) { - var node = service.resolveSingleRecord(handle.getBytes(StandardCharsets.UTF_8), path); + var node = service.resolveSingleRecord(handle, link); return ResponseEntity.status(HttpStatus.OK).body(node); } throw new PidResolutionException( @@ -73,17 +71,15 @@ public ResponseEntity resolvePid(@PathVariable("prefix public ResponseEntity resolvePids( @RequestParam List handles, HttpServletRequest r) throws InvalidRequestException { - String path = applicationProperties.getUiUrl() + r.getRequestURI(); + String link = applicationProperties.getUiUrl() + "/" + r.getRequestURI(); if (handles.size() > applicationProperties.getMaxHandles()) { throw new InvalidRequestException( "Attempting to resolve more than maximum permitted PIDs in a single request. Maximum handles: " + applicationProperties.getMaxHandles()); } - List handleBytes = new ArrayList<>(); - handles.forEach(h -> handleBytes.add(h.getBytes(StandardCharsets.UTF_8))); - return ResponseEntity.status(HttpStatus.OK).body(service.resolveBatchRecord(handleBytes, path)); + return ResponseEntity.status(HttpStatus.OK).body(service.resolveBatchRecord(handles, link)); } @Operation(summary = "Given a physical identifier (i.e. local identifier), resolve PID record") @@ -124,18 +120,14 @@ public ResponseEntity updateRecord(@PathVariable("prefix") log.info("Received single update request for PID {}/{} from user {}", prefix, suffix, authentication.getName()); schemaValidator.validatePatchRequest(request); - - JsonNode data = request.get(NODE_DATA); - byte[] handle = (prefix + "/" + suffix).getBytes(StandardCharsets.UTF_8); - byte[] handleData = data.get(NODE_ID).asText().getBytes(StandardCharsets.UTF_8); - - if (!Arrays.equals(handle, handleData)) { + var handle = (prefix + "/" + suffix); + var handleData = request.get(NODE_DATA).get(NODE_ID).asText(); + if (!handle.equals(handleData)) { throw new InvalidRequestException(String.format( "Handle in request path does not match id in request body. Path: %s, Body: %s", - new String(handle, StandardCharsets.UTF_8), - new String(handleData, StandardCharsets.UTF_8))); + handle, + handleData)); } - return ResponseEntity.status(HttpStatus.OK).body(service.updateRecords(List.of(request), true)); } @@ -171,7 +163,8 @@ public ResponseEntity archiveRecord(@PathVariable("prefix") + new String(handle, StandardCharsets.UTF_8) + ". Body: " + new String(handleRequest, StandardCharsets.UTF_8)); } - return ResponseEntity.status(HttpStatus.OK).body(service.archiveRecordBatch(List.of(request))); + return ResponseEntity.status(HttpStatus.OK) + .body(service.tombstoneRecords(List.of(request))); } @Operation(summary = "rollback handle creation") @@ -227,7 +220,7 @@ public ResponseEntity archiveRecords(@RequestBody ListDEFAULT_CATALOG - */ - public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog(); - - /** - * The schema public. - */ - public final Public PUBLIC = Public.PUBLIC; - - /** - * No further instances allowed - */ - private DefaultCatalog() { - super(""); - } - - @Override - public final List getSchemas() { - return Arrays.asList( - Public.PUBLIC); - } -} diff --git a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Indexes.java b/src/main/java/eu/dissco/core/handlemanager/database/jooq/Indexes.java deleted file mode 100644 index 9bcf7052..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Indexes.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package eu.dissco.core.handlemanager.database.jooq; - - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import org.jooq.Index; -import org.jooq.OrderField; -import org.jooq.impl.DSL; -import org.jooq.impl.Internal; - - -/** - * A class modelling indexes of tables in public. - */ -@SuppressWarnings({"all", "unchecked", "rawtypes"}) -public class Indexes { - - // ------------------------------------------------------------------------- - // INDEX definitions - // ------------------------------------------------------------------------- - - public static final Index DATAINDEX = Internal.createIndex(DSL.name("dataindex"), Handles.HANDLES, - new OrderField[]{Handles.HANDLES.DATA}, false); - public static final Index HANDLEINDEX = Internal.createIndex(DSL.name("handleindex"), - Handles.HANDLES, new OrderField[]{Handles.HANDLES.HANDLE}, false); -} diff --git a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Keys.java b/src/main/java/eu/dissco/core/handlemanager/database/jooq/Keys.java deleted file mode 100644 index ad4139e1..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Keys.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package eu.dissco.core.handlemanager.database.jooq; - - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import eu.dissco.core.handlemanager.database.jooq.tables.records.HandlesRecord; -import org.jooq.TableField; -import org.jooq.UniqueKey; -import org.jooq.impl.DSL; -import org.jooq.impl.Internal; - - -/** - * A class modelling foreign key relationships and constraints of tables in public. - */ -@SuppressWarnings({"all", "unchecked", "rawtypes"}) -public class Keys { - - // ------------------------------------------------------------------------- - // UNIQUE and PRIMARY KEY definitions - // ------------------------------------------------------------------------- - - public static final UniqueKey HANDLES_PKEY = Internal.createUniqueKey( - Handles.HANDLES, DSL.name("handles_pkey"), - new TableField[]{Handles.HANDLES.HANDLE, Handles.HANDLES.IDX}, true); -} diff --git a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Public.java b/src/main/java/eu/dissco/core/handlemanager/database/jooq/Public.java deleted file mode 100644 index fe5ae76b..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Public.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package eu.dissco.core.handlemanager.database.jooq; - - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import java.util.Arrays; -import java.util.List; -import org.jooq.Catalog; -import org.jooq.Table; -import org.jooq.impl.SchemaImpl; - - -/** - * This class is generated by jOOQ. - */ -@SuppressWarnings({"all", "unchecked", "rawtypes"}) -public class Public extends SchemaImpl { - - private static final long serialVersionUID = 1L; - - /** - * The reference instance of public - */ - public static final Public PUBLIC = new Public(); - - /** - * The table public.handles. - */ - public final Handles HANDLES = Handles.HANDLES; - - /** - * No further instances allowed - */ - private Public() { - super("public", null); - } - - - @Override - public Catalog getCatalog() { - return DefaultCatalog.DEFAULT_CATALOG; - } - - @Override - public final List> getTables() { - return Arrays.>asList( - Handles.HANDLES); - } -} diff --git a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Tables.java b/src/main/java/eu/dissco/core/handlemanager/database/jooq/Tables.java deleted file mode 100644 index 488e0242..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/database/jooq/Tables.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package eu.dissco.core.handlemanager.database.jooq; - - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; - - -/** - * Convenience access to all tables in public. - */ -@SuppressWarnings({"all", "unchecked", "rawtypes"}) -public class Tables { - - /** - * The table public.handles. - */ - public static final Handles HANDLES = Handles.HANDLES; -} diff --git a/src/main/java/eu/dissco/core/handlemanager/database/jooq/tables/Handles.java b/src/main/java/eu/dissco/core/handlemanager/database/jooq/tables/Handles.java deleted file mode 100644 index 7772dabf..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/database/jooq/tables/Handles.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package eu.dissco.core.handlemanager.database.jooq.tables; - - -import eu.dissco.core.handlemanager.database.jooq.Indexes; -import eu.dissco.core.handlemanager.database.jooq.Keys; -import eu.dissco.core.handlemanager.database.jooq.Public; -import eu.dissco.core.handlemanager.database.jooq.tables.records.HandlesRecord; -import java.util.Arrays; -import java.util.List; -import org.jooq.Field; -import org.jooq.ForeignKey; -import org.jooq.Index; -import org.jooq.Name; -import org.jooq.Record; -import org.jooq.Row12; -import org.jooq.Schema; -import org.jooq.Table; -import org.jooq.TableField; -import org.jooq.TableOptions; -import org.jooq.UniqueKey; -import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; -import org.jooq.impl.TableImpl; - - -/** - * This class is generated by jOOQ. - */ -@SuppressWarnings({"all", "unchecked", "rawtypes"}) -public class Handles extends TableImpl { - - private static final long serialVersionUID = 1L; - - /** - * The reference instance of public.handles - */ - public static final Handles HANDLES = new Handles(); - - /** - * The class holding records for this type - */ - @Override - public Class getRecordType() { - return HandlesRecord.class; - } - - /** - * The column public.handles.handle. - */ - public final TableField HANDLE = createField(DSL.name("handle"), - SQLDataType.BLOB.nullable(false), this, ""); - - /** - * The column public.handles.idx. - */ - public final TableField IDX = createField(DSL.name("idx"), - SQLDataType.INTEGER.nullable(false), this, ""); - - /** - * The column public.handles.type. - */ - public final TableField TYPE = createField(DSL.name("type"), - SQLDataType.BLOB, this, ""); - - /** - * The column public.handles.data. - */ - public final TableField DATA = createField(DSL.name("data"), - SQLDataType.BLOB, this, ""); - - /** - * The column public.handles.ttl_type. - */ - public final TableField TTL_TYPE = createField(DSL.name("ttl_type"), - SQLDataType.SMALLINT, this, ""); - - /** - * The column public.handles.ttl. - */ - public final TableField TTL = createField(DSL.name("ttl"), - SQLDataType.INTEGER, this, ""); - - /** - * The column public.handles.timestamp. - */ - public final TableField TIMESTAMP = createField(DSL.name("timestamp"), - SQLDataType.BIGINT, this, ""); - - /** - * The column public.handles.refs. - */ - public final TableField REFS = createField(DSL.name("refs"), - SQLDataType.CLOB, this, ""); - - /** - * The column public.handles.admin_read. - */ - public final TableField ADMIN_READ = createField(DSL.name("admin_read"), - SQLDataType.BOOLEAN, this, ""); - - /** - * The column public.handles.admin_write. - */ - public final TableField ADMIN_WRITE = createField(DSL.name("admin_write"), - SQLDataType.BOOLEAN, this, ""); - - /** - * The column public.handles.pub_read. - */ - public final TableField PUB_READ = createField(DSL.name("pub_read"), - SQLDataType.BOOLEAN, this, ""); - - /** - * The column public.handles.pub_write. - */ - public final TableField PUB_WRITE = createField(DSL.name("pub_write"), - SQLDataType.BOOLEAN, this, ""); - - private Handles(Name alias, Table aliased) { - this(alias, aliased, null); - } - - private Handles(Name alias, Table aliased, Field[] parameters) { - super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); - } - - /** - * Create an aliased public.handles table reference - */ - public Handles(String alias) { - this(DSL.name(alias), HANDLES); - } - - /** - * Create an aliased public.handles table reference - */ - public Handles(Name alias) { - this(alias, HANDLES); - } - - /** - * Create a public.handles table reference - */ - public Handles() { - this(DSL.name("handles"), null); - } - - public Handles(Table child, ForeignKey key) { - super(child, key, HANDLES); - } - - @Override - public Schema getSchema() { - return Public.PUBLIC; - } - - @Override - public List getIndexes() { - return Arrays.asList(Indexes.DATAINDEX, Indexes.HANDLEINDEX); - } - - @Override - public UniqueKey getPrimaryKey() { - return Keys.HANDLES_PKEY; - } - - @Override - public List> getKeys() { - return Arrays.>asList(Keys.HANDLES_PKEY); - } - - @Override - public Handles as(String alias) { - return new Handles(DSL.name(alias), this); - } - - @Override - public Handles as(Name alias) { - return new Handles(alias, this); - } - - /** - * Rename this table - */ - @Override - public Handles rename(String name) { - return new Handles(DSL.name(name), null); - } - - /** - * Rename this table - */ - @Override - public Handles rename(Name name) { - return new Handles(name, null); - } - - // ------------------------------------------------------------------------- - // Row12 type methods - // ------------------------------------------------------------------------- - - @Override - public Row12 fieldsRow() { - return (Row12) super.fieldsRow(); - } -} diff --git a/src/main/java/eu/dissco/core/handlemanager/database/jooq/tables/records/HandlesRecord.java b/src/main/java/eu/dissco/core/handlemanager/database/jooq/tables/records/HandlesRecord.java deleted file mode 100644 index d0a1aea8..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/database/jooq/tables/records/HandlesRecord.java +++ /dev/null @@ -1,518 +0,0 @@ -/* - * This file is generated by jOOQ. - */ -package eu.dissco.core.handlemanager.database.jooq.tables.records; - - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import org.jooq.Field; -import org.jooq.Record12; -import org.jooq.Record2; -import org.jooq.Row12; -import org.jooq.impl.UpdatableRecordImpl; - - -/** - * This class is generated by jOOQ. - */ -@SuppressWarnings({"all", "unchecked", "rawtypes"}) -public class HandlesRecord extends UpdatableRecordImpl implements - Record12 { - - private static final long serialVersionUID = 1L; - - /** - * Setter for public.handles.handle. - */ - public void setHandle(byte[] value) { - set(0, value); - } - - /** - * Getter for public.handles.handle. - */ - public byte[] getHandle() { - return (byte[]) get(0); - } - - /** - * Setter for public.handles.idx. - */ - public void setIdx(Integer value) { - set(1, value); - } - - /** - * Getter for public.handles.idx. - */ - public Integer getIdx() { - return (Integer) get(1); - } - - /** - * Setter for public.handles.type. - */ - public void setType(byte[] value) { - set(2, value); - } - - /** - * Getter for public.handles.type. - */ - public byte[] getType() { - return (byte[]) get(2); - } - - /** - * Setter for public.handles.data. - */ - public void setData(byte[] value) { - set(3, value); - } - - /** - * Getter for public.handles.data. - */ - public byte[] getData() { - return (byte[]) get(3); - } - - /** - * Setter for public.handles.ttl_type. - */ - public void setTtlType(Short value) { - set(4, value); - } - - /** - * Getter for public.handles.ttl_type. - */ - public Short getTtlType() { - return (Short) get(4); - } - - /** - * Setter for public.handles.ttl. - */ - public void setTtl(Integer value) { - set(5, value); - } - - /** - * Getter for public.handles.ttl. - */ - public Integer getTtl() { - return (Integer) get(5); - } - - /** - * Setter for public.handles.timestamp. - */ - public void setTimestamp(Long value) { - set(6, value); - } - - /** - * Getter for public.handles.timestamp. - */ - public Long getTimestamp() { - return (Long) get(6); - } - - /** - * Setter for public.handles.refs. - */ - public void setRefs(String value) { - set(7, value); - } - - /** - * Getter for public.handles.refs. - */ - public String getRefs() { - return (String) get(7); - } - - /** - * Setter for public.handles.admin_read. - */ - public void setAdminRead(Boolean value) { - set(8, value); - } - - /** - * Getter for public.handles.admin_read. - */ - public Boolean getAdminRead() { - return (Boolean) get(8); - } - - /** - * Setter for public.handles.admin_write. - */ - public void setAdminWrite(Boolean value) { - set(9, value); - } - - /** - * Getter for public.handles.admin_write. - */ - public Boolean getAdminWrite() { - return (Boolean) get(9); - } - - /** - * Setter for public.handles.pub_read. - */ - public void setPubRead(Boolean value) { - set(10, value); - } - - /** - * Getter for public.handles.pub_read. - */ - public Boolean getPubRead() { - return (Boolean) get(10); - } - - /** - * Setter for public.handles.pub_write. - */ - public void setPubWrite(Boolean value) { - set(11, value); - } - - /** - * Getter for public.handles.pub_write. - */ - public Boolean getPubWrite() { - return (Boolean) get(11); - } - - // ------------------------------------------------------------------------- - // Primary key information - // ------------------------------------------------------------------------- - - @Override - public Record2 key() { - return (Record2) super.key(); - } - - // ------------------------------------------------------------------------- - // Record12 type implementation - // ------------------------------------------------------------------------- - - @Override - public Row12 fieldsRow() { - return (Row12) super.fieldsRow(); - } - - @Override - public Row12 valuesRow() { - return (Row12) super.valuesRow(); - } - - @Override - public Field field1() { - return Handles.HANDLES.HANDLE; - } - - @Override - public Field field2() { - return Handles.HANDLES.IDX; - } - - @Override - public Field field3() { - return Handles.HANDLES.TYPE; - } - - @Override - public Field field4() { - return Handles.HANDLES.DATA; - } - - @Override - public Field field5() { - return Handles.HANDLES.TTL_TYPE; - } - - @Override - public Field field6() { - return Handles.HANDLES.TTL; - } - - @Override - public Field field7() { - return Handles.HANDLES.TIMESTAMP; - } - - @Override - public Field field8() { - return Handles.HANDLES.REFS; - } - - @Override - public Field field9() { - return Handles.HANDLES.ADMIN_READ; - } - - @Override - public Field field10() { - return Handles.HANDLES.ADMIN_WRITE; - } - - @Override - public Field field11() { - return Handles.HANDLES.PUB_READ; - } - - @Override - public Field field12() { - return Handles.HANDLES.PUB_WRITE; - } - - @Override - public byte[] component1() { - return getHandle(); - } - - @Override - public Integer component2() { - return getIdx(); - } - - @Override - public byte[] component3() { - return getType(); - } - - @Override - public byte[] component4() { - return getData(); - } - - @Override - public Short component5() { - return getTtlType(); - } - - @Override - public Integer component6() { - return getTtl(); - } - - @Override - public Long component7() { - return getTimestamp(); - } - - @Override - public String component8() { - return getRefs(); - } - - @Override - public Boolean component9() { - return getAdminRead(); - } - - @Override - public Boolean component10() { - return getAdminWrite(); - } - - @Override - public Boolean component11() { - return getPubRead(); - } - - @Override - public Boolean component12() { - return getPubWrite(); - } - - @Override - public byte[] value1() { - return getHandle(); - } - - @Override - public Integer value2() { - return getIdx(); - } - - @Override - public byte[] value3() { - return getType(); - } - - @Override - public byte[] value4() { - return getData(); - } - - @Override - public Short value5() { - return getTtlType(); - } - - @Override - public Integer value6() { - return getTtl(); - } - - @Override - public Long value7() { - return getTimestamp(); - } - - @Override - public String value8() { - return getRefs(); - } - - @Override - public Boolean value9() { - return getAdminRead(); - } - - @Override - public Boolean value10() { - return getAdminWrite(); - } - - @Override - public Boolean value11() { - return getPubRead(); - } - - @Override - public Boolean value12() { - return getPubWrite(); - } - - @Override - public HandlesRecord value1(byte[] value) { - setHandle(value); - return this; - } - - @Override - public HandlesRecord value2(Integer value) { - setIdx(value); - return this; - } - - @Override - public HandlesRecord value3(byte[] value) { - setType(value); - return this; - } - - @Override - public HandlesRecord value4(byte[] value) { - setData(value); - return this; - } - - @Override - public HandlesRecord value5(Short value) { - setTtlType(value); - return this; - } - - @Override - public HandlesRecord value6(Integer value) { - setTtl(value); - return this; - } - - @Override - public HandlesRecord value7(Long value) { - setTimestamp(value); - return this; - } - - @Override - public HandlesRecord value8(String value) { - setRefs(value); - return this; - } - - @Override - public HandlesRecord value9(Boolean value) { - setAdminRead(value); - return this; - } - - @Override - public HandlesRecord value10(Boolean value) { - setAdminWrite(value); - return this; - } - - @Override - public HandlesRecord value11(Boolean value) { - setPubRead(value); - return this; - } - - @Override - public HandlesRecord value12(Boolean value) { - setPubWrite(value); - return this; - } - - @Override - public HandlesRecord values(byte[] value1, Integer value2, byte[] value3, byte[] value4, - Short value5, Integer value6, Long value7, String value8, Boolean value9, Boolean value10, - Boolean value11, Boolean value12) { - value1(value1); - value2(value2); - value3(value3); - value4(value4); - value5(value5); - value6(value6); - value7(value7); - value8(value8); - value9(value9); - value10(value10); - value11(value11); - value12(value12); - return this; - } - - // ------------------------------------------------------------------------- - // Constructors - // ------------------------------------------------------------------------- - - /** - * Create a detached HandlesRecord - */ - public HandlesRecord() { - super(Handles.HANDLES); - } - - /** - * Create a detached, initialised HandlesRecord - */ - public HandlesRecord(byte[] handle, Integer idx, byte[] type, byte[] data, Short ttlType, - Integer ttl, Long timestamp, String refs, Boolean adminRead, Boolean adminWrite, - Boolean pubRead, Boolean pubWrite) { - super(Handles.HANDLES); - - setHandle(handle); - setIdx(idx); - setType(type); - setData(data); - setTtlType(ttlType); - setTtl(ttl); - setTimestamp(timestamp); - setRefs(refs); - setAdminRead(adminRead); - setAdminWrite(adminWrite); - setPubRead(pubRead); - setPubWrite(pubWrite); - } -} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalMediaRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalMediaRequest.java index d1a19f28..30dacf94 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalMediaRequest.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalMediaRequest.java @@ -23,7 +23,7 @@ public class DigitalMediaRequest extends DoiRecordRequest { private final String mediaHostName; @Nullable @JsonProperty(value = "dcterms:format") - private final MediaFormat mediaFormat; + private final MediaFormat dctermsFormat; @JsonProperty(required = true) private final Boolean isDerivedFromSpecimen; @JsonProperty(required = true) @@ -32,6 +32,7 @@ public class DigitalMediaRequest extends DoiRecordRequest { private final LinkedDigitalObjectType linkedDigitalObjectType; @Nullable private final String linkedAttribute; + @JsonProperty(required = true) private final String primaryMediaId; @Nullable private final PrimarySpecimenObjectIdType primaryMediaObjectIdType; @@ -41,13 +42,14 @@ public class DigitalMediaRequest extends DoiRecordRequest { @JsonProperty(value = "dcterms:type") private final DcTermsType dcTermsType; @Nullable - private final String mediaMimeType; + @JsonProperty(value = "dcterms:subject") + private final String dctermsSubject; @Nullable private final String derivedFromEntity; @Nullable private final String licenseName; @Nullable - private final String license; + private final String licenseUrl; @Nullable private final String rightsholderName; @Nullable @@ -68,7 +70,7 @@ public DigitalMediaRequest( // Media String mediaHost, String mediaHostName, - MediaFormat mediaFormat, + MediaFormat dctermsFormat, Boolean isDerivedFromSpecimen, String linkedDigitalObjectPid, LinkedDigitalObjectType linkedDigitalObjectType, @@ -77,10 +79,10 @@ public DigitalMediaRequest( PrimarySpecimenObjectIdType primaryMediaObjectIdType, String primaryMediaObjectIdName, DcTermsType dcTermsType, - String mediaMimeType, + String dctermsSubject, String derivedFromEntity, String licenseName, - String license, + String licenseUrl, String rightsholderPid, String rightsholderName, PrimarySpecimenObjectIdType rightsholderPidType, @@ -91,7 +93,7 @@ public DigitalMediaRequest( referentName, FdoType.DIGITAL_MEDIA.getDigitalObjectName(), primaryReferentType); this.mediaHost = mediaHost; this.mediaHostName = mediaHostName; - this.mediaFormat = mediaFormat; + this.dctermsFormat = dctermsFormat; this.isDerivedFromSpecimen = isDerivedFromSpecimen; this.linkedDigitalObjectPid = linkedDigitalObjectPid; this.linkedDigitalObjectType = linkedDigitalObjectType; @@ -100,10 +102,10 @@ public DigitalMediaRequest( this.primaryMediaObjectIdType = primaryMediaObjectIdType; this.primaryMediaObjectIdName = primaryMediaObjectIdName; this.dcTermsType = dcTermsType; - this.mediaMimeType = mediaMimeType; + this.dctermsSubject = dctermsSubject; this.derivedFromEntity = derivedFromEntity; this.licenseName = licenseName; - this.license = license; + this.licenseUrl = licenseUrl; this.rightsholderPid = rightsholderPid == null ? mediaHost : rightsholderPid; this.rightsholderName = rightsholderName; this.rightsholderPidType = rightsholderPidType; diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalSpecimenRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalSpecimenRequest.java index 1c3fccdc..de6e3032 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalSpecimenRequest.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DigitalSpecimenRequest.java @@ -43,7 +43,7 @@ public class DigitalSpecimenRequest extends DoiRecordRequest { @JsonProperty(required = true) private final String normalisedPrimarySpecimenObjectId; @Nullable - private final String primarySpecimenObjectIdAbsenceReason; + private final String specimenObjectIdAbsenceReason; @Nullable private final List otherSpecimenIds; @Nullable @@ -86,7 +86,7 @@ public DigitalSpecimenRequest( PrimarySpecimenObjectIdType primarySpecimenObjectIdType, String primarySpecimenObjectIdName, String normalisedPrimarySpecimenObjetId, - String primarySpecimenObjectIdAbsenceReason, + String specimenObjectIdAbsenceReason, List otherSpecimenIds, TopicOrigin topicOrigin, TopicDomain topicDomain, @@ -109,7 +109,7 @@ public DigitalSpecimenRequest( primarySpecimenObjectIdType == null ? LOCAL : primarySpecimenObjectIdType; this.primarySpecimenObjectIdName = primarySpecimenObjectIdName; this.normalisedPrimarySpecimenObjectId = normalisedPrimarySpecimenObjetId; - this.primarySpecimenObjectIdAbsenceReason = primarySpecimenObjectIdAbsenceReason; + this.specimenObjectIdAbsenceReason = specimenObjectIdAbsenceReason; this.otherSpecimenIds = otherSpecimenIds; this.topicOrigin = topicOrigin; this.topicDomain = topicDomain; @@ -130,7 +130,7 @@ public DigitalSpecimenRequest( } private void idXorAbsence() throws InvalidRequestException { - if ((this.primarySpecimenObjectId == null) == (this.primarySpecimenObjectIdAbsenceReason + if ((this.primarySpecimenObjectId == null) == (this.specimenObjectIdAbsenceReason == null)) { throw new InvalidRequestException( "Request must contain exactly one of: [primarySpecimenObjectId, primarySpecimenObjectIdAbsenceReason]"); diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DoiRecordRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DoiRecordRequest.java index 28ecbec8..1ef5e92b 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DoiRecordRequest.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/DoiRecordRequest.java @@ -11,8 +11,6 @@ @EqualsAndHashCode(callSuper = true) public class DoiRecordRequest extends HandleRecordRequest { - private static final String PLACEHOLDER = "{This value is a placeholder}"; - private final String referentType; @JsonPropertyDescription("Local name of the object (human-readable)") diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoProfile.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoProfile.java index 667d94d3..f389c9da 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoProfile.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoProfile.java @@ -1,6 +1,5 @@ package eu.dissco.core.handlemanager.domain.fdo; -import java.util.Arrays; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -21,8 +20,9 @@ public enum FdoProfile { PID_STATUS("pidStatus", 13), // Tombstone - TOMBSTONE_TEXT("tombstoneText", 30), - TOMBSTONE_PIDS("tombstonePids", 31), + TOMBSTONED_TEXT("ods:tombstonedText", 30), + HAS_RELATED_PID("hasRelatedPID", 31), + TOMBSTONED_DATE("tombstonedDate", 32), // DOI REFERENT_TYPE("referentType", 40), @@ -55,7 +55,6 @@ public enum FdoProfile { // Media MEDIA_HOST("mediaHost", 400), MEDIA_HOST_NAME("mediaHostName", 401), - MEDIA_FORMAT("mediaFormat", 402), IS_DERIVED_FROM_SPECIMEN("isDerivedFromSpecimen", 403), LINKED_DO_PID("linkedDigitalObjectPid", 404), LINKED_DO_TYPE("linkedDigitalObjectType", 405), @@ -63,8 +62,9 @@ public enum FdoProfile { PRIMARY_MEDIA_ID("primaryMediaId", 407), PRIMARY_MO_ID_TYPE("primaryMediaObjectIdType", 408), PRIMARY_MO_ID_NAME("primaryMediaObjectIdName", 409), - DCTERMS_TYPE("dcterms:type", 411), - MEDIA_MIME_TYPE("mediaMimeType", 412), + DCTERMS_TYPE("dcterms:type", 410), + DCTERMS_SUBJECT("dcterms:subject", 411), + DCTERMS_FORMAT("dcterms:format", 412), DERIVED_FROM_ENTITY("derivedFromEntity", 413), LICENSE_NAME("licenseName", 414), LICENSE_URL("licenseUrl", 415), @@ -111,15 +111,4 @@ public int index() { return this.index; } - public static int retrieveIndex(String searchAttribute) { - var fdoProfile = Arrays.stream(FdoProfile.values()) - .filter(fdoRow -> fdoRow.attribute.equals(searchAttribute)) - .findFirst(); - if (fdoProfile.isPresent()) { - return fdoProfile.get().index; - } - log.error("Unable to locate index for requested attribute {}", searchAttribute); - throw new IllegalStateException(searchAttribute + " not valid fdo attribute"); - } - } diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoType.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoType.java index 3f75b5b1..d821850a 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoType.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/FdoType.java @@ -1,5 +1,8 @@ package eu.dissco.core.handlemanager.domain.fdo; +import static eu.dissco.core.handlemanager.service.FdoRecordService.DOI_DOMAIN; +import static eu.dissco.core.handlemanager.service.FdoRecordService.HANDLE_DOMAIN; + import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -10,53 +13,65 @@ public enum FdoType { @JsonProperty("https://hdl.handle.net/21.T11148/532ce6796e2828dd2be6") HANDLE( "Handle Kernel", "https://hdl.handle.net/21.T11148/532ce6796e2828dd2be6", - "https://hdl.handle.net/21.T11148/532ce6796e2828dd2be6"), + "https://hdl.handle.net/21.T11148/532ce6796e2828dd2be6", + HANDLE_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/527856fd709ec8c5bc8c") DOI( "DOI Kernel", "https://hdl.handle.net/21.T11148/527856fd709ec8c5bc8c", - "https://hdl.handle.net/21.T11148/527856fd709ec8c5bc8c"), + "https://hdl.handle.net/21.T11148/527856fd709ec8c5bc8c", + DOI_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/894b1e6cad57e921764e") DIGITAL_SPECIMEN( "DigitalSpecimen", "https://hdl.handle.net/21.T11148/894b1e6cad57e921764e", - "https://hdl.handle.net/21.T11148/894b1e6cad57e921764e"), + "https://hdl.handle.net/21.T11148/894b1e6cad57e921764e", + DOI_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/bbad8c4e101e8af01115") DIGITAL_MEDIA( - "DigitalMedia", + "MediaObject", + "https://hdl.handle.net/21.T11148/bbad8c4e101e8af01115", "https://hdl.handle.net/21.T11148/bbad8c4e101e8af01115", - "https://hdl.handle.net/21.T11148/bbad8c4e101e8af01115"), + DOI_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/cf458ca9ee1d44a5608f") ANNOTATION( "Annotation", "https://hdl.handle.net/21.T11148/cf458ca9ee1d44a5608f", - "https://hdl.handle.net/21.T11148/cf458ca9ee1d44a5608f"), + "https://hdl.handle.net/21.T11148/cf458ca9ee1d44a5608f", + HANDLE_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/417a4f472f60f7974c12") SOURCE_SYSTEM( "sourceSystem", "https://hdl.handle.net/21.T11148/417a4f472f60f7974c12", - "https://hdl.handle.net/21.T11148/417a4f472f60f7974c12"), + "https://hdl.handle.net/21.T11148/417a4f472f60f7974c12", + HANDLE_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/ce794a6f4df42eb7e77e") DATA_MAPPING( - "Data Mapping", + "Mapping", + "https://hdl.handle.net/21.T11148/ce794a6f4df42eb7e77e", "https://hdl.handle.net/21.T11148/ce794a6f4df42eb7e77e", - "https://hdl.handle.net/21.T11148/ce794a6f4df42eb7e77e"), + HANDLE_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/413c00cbd83ae33d1ac0") ORGANISATION( "Organisation", "https://hdl.handle.net/21.T11148/413c00cbd83ae33d1ac0", - "https://hdl.handle.net/21.T11148/413c00cbd83ae33d1ac0"), + "https://hdl.handle.net/21.T11148/413c00cbd83ae33d1ac0", + HANDLE_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/d7570227982f70256af3") TOMBSTONE( "Tombstone", "https://hdl.handle.net/21.T11148/d7570227982f70256af3", - "https://hdl.handle.net/21.T11148/d7570227982f70256af3"), + "https://hdl.handle.net/21.T11148/d7570227982f70256af3", + HANDLE_DOMAIN), @JsonProperty("https://hdl.handle.net/21.T11148/22e71a0015cbcfba8ffa") MAS( "Machine Annotation Service", "https://hdl.handle.net/21.T11148/22e71a0015cbcfba8ffa", - "https://hdl.handle.net/21.T11148/22e71a0015cbcfba8ffa"); + "https://hdl.handle.net/21.T11148/22e71a0015cbcfba8ffa", + HANDLE_DOMAIN); private final String digitalObjectName; private final String digitalObjectType; private final String fdoProfile; + private final String domain; FdoType(@JsonProperty("type") String digitalObjectName, String digitalObjectType, - String fdoProfile) { + String fdoProfile, String domain) { this.digitalObjectName = digitalObjectName; this.digitalObjectType = digitalObjectType; this.fdoProfile = fdoProfile; + this.domain = domain; } @Override diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/TombstoneRecordRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/TombstoneRecordRequest.java index 5d23044f..6340d9e4 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/TombstoneRecordRequest.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/TombstoneRecordRequest.java @@ -1,6 +1,8 @@ package eu.dissco.core.handlemanager.domain.fdo; import com.fasterxml.jackson.annotation.JsonProperty; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.tombstone.HasRelatedPid; +import java.util.List; import lombok.Getter; import lombok.ToString; import org.springframework.lang.Nullable; @@ -9,20 +11,15 @@ @ToString public class TombstoneRecordRequest { - @JsonProperty(required = true) - private final String tombstoneText; - + @JsonProperty(required = true, value = "ods:tombstonedText") + private final String tombstonedText; @Nullable - private final String[] tombstonePids; - - public TombstoneRecordRequest(String tombstoneText) { - this.tombstoneText = tombstoneText; - this.tombstonePids = new String[]{}; - } + @JsonProperty("ods:hasRelatedPID") + private final List hasRelatedPID; - public TombstoneRecordRequest(String tombstoneText, String[] tombstonePids) { - this.tombstoneText = tombstoneText; - this.tombstonePids = tombstonePids; + public TombstoneRecordRequest(String tombstoneText, List hasRelatedPID) { + this.tombstonedText = tombstoneText; + this.hasRelatedPID = hasRelatedPID; } } diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/PidStatus.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/PidStatus.java new file mode 100644 index 00000000..5404ee99 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/PidStatus.java @@ -0,0 +1,9 @@ +package eu.dissco.core.handlemanager.domain.fdo.vocabulary; + +public enum PidStatus { + TOMBSTONED, + ACTIVE, + DRAFT, + FAILED, + TEST +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/specimen/StructuralType.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/specimen/StructuralType.java index 4be772fd..2f4d8036 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/specimen/StructuralType.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/specimen/StructuralType.java @@ -7,11 +7,11 @@ public enum StructuralType { @JsonProperty("digital") DIGITAL("digital"), @JsonProperty("physical") PHYSICAL("physical"), @JsonProperty("performance") PERFORM("performance"), - @JsonProperty("performance") ABSTRACT("abstraction"); + @JsonProperty("abstraction") ABSTRACT("abstraction"); private final String state; - private StructuralType(String state) { + StructuralType(String state) { this.state = state; } diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/tombstone/HasRelatedPid.java b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/tombstone/HasRelatedPid.java new file mode 100644 index 00000000..9018d6f4 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/fdo/vocabulary/tombstone/HasRelatedPid.java @@ -0,0 +1,12 @@ +package eu.dissco.core.handlemanager.domain.fdo.vocabulary.tombstone; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record HasRelatedPid( + @JsonProperty("ods:ID") + String odsId, + @JsonProperty("ods:relationshipType") + String odsRelationshipType +) { + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/AdminHandleData.java b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/AdminHandleData.java new file mode 100644 index 00000000..9f1ed05e --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/AdminHandleData.java @@ -0,0 +1,23 @@ +package eu.dissco.core.handlemanager.domain.repsitoryobjects; + +import lombok.Value; + +@Value +public class AdminHandleData implements HandleData { + + String format = "admin"; + AdminValue value; + + public AdminHandleData(String prefix) { + this.value = new AdminValue("0.NA/" + prefix); + } + + @Value + protected static class AdminValue { + + String handle; + int index = 200; + String permissions = "011111110011"; + } + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/FdoAttribute.java b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/FdoAttribute.java new file mode 100644 index 00000000..18655358 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/FdoAttribute.java @@ -0,0 +1,66 @@ +package eu.dissco.core.handlemanager.domain.repsitoryobjects; + +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; +import java.time.Instant; +import java.util.LinkedHashMap; +import lombok.Value; +import org.bson.Document; + +@Value +public class FdoAttribute { + + int index; + String type; + HandleData data; + int ttl; + Instant timestamp; + + public FdoAttribute(FdoProfile fdoAttribute, Instant timestamp, Object value) { + this.index = fdoAttribute.index(); + this.type = fdoAttribute.get(); + this.timestamp = timestamp; + value = value == null ? "" : value; + this.data = new StringHandleData(value.toString()); + this.ttl = 86400; + } + + public FdoAttribute(Instant timestamp, String prefix) { + this.index = HS_ADMIN.index(); + this.type = HS_ADMIN.get(); + this.data = new AdminHandleData(prefix); + this.timestamp = timestamp; + this.ttl = 86400; + } + + @JsonCreator + public FdoAttribute(Integer index, String type, Integer ttl, Instant timestamp, Document data) { + this.index = index; + this.type = type; + var value = data.get("value", Object.class); + if (value instanceof String valueString) { + this.data = new StringHandleData(valueString); + } else if (value == null) { + this.data = new StringHandleData(null); + } else { + var map = data.get("value", LinkedHashMap.class); + var prefix = map.get("handle").toString().replace("0.NA/", ""); + this.data = new AdminHandleData(prefix); + } + this.ttl = ttl; + this.timestamp = timestamp; + } + + @JsonIgnore + public String getValue() { + if (data instanceof StringHandleData d) { + return (d.getValue()); + } else { + return "HS_ADMIN"; + } + } + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/FdoRecord.java b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/FdoRecord.java new file mode 100644 index 00000000..c6b5a55f --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/FdoRecord.java @@ -0,0 +1,18 @@ +package eu.dissco.core.handlemanager.domain.repsitoryobjects; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import eu.dissco.core.handlemanager.domain.fdo.FdoType; +import java.util.List; + +public record FdoRecord( + @JsonProperty("_id") + String handle, + @JsonIgnore + FdoType fdoType, + @JsonProperty("values") + List attributes, + @JsonIgnore + String primaryLocalId) { + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/HandleAttribute.java b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/HandleAttribute.java deleted file mode 100644 index dffc52e4..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/HandleAttribute.java +++ /dev/null @@ -1,61 +0,0 @@ -package eu.dissco.core.handlemanager.domain.repsitoryobjects; - -import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Objects; -import lombok.Data; - -@Data -public class HandleAttribute { - - private final int index; - private final byte[] handle; - private final String type; - private final byte[] data; - - public HandleAttribute(int index, byte[] handle, String type, byte[] data) { - this.index = index; - this.handle = handle; - this.type = type; - this.data = data; - } - - public HandleAttribute(FdoProfile fdoAttribute, byte[] handle, String data) { - this.index = fdoAttribute.index(); - this.handle = handle; - this.data = data.getBytes(StandardCharsets.UTF_8); - this.type = fdoAttribute.get(); - } - - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - HandleAttribute that = (HandleAttribute) o; - return index == that.index && Objects.equals(type, that.type) - && Arrays.equals(data, that.data); - } - - @Override - public int hashCode() { - int result = Objects.hash(index, type); - result = 31 * result + Arrays.hashCode(data); - return result; - } - - @Override - public String toString() { - return "HandleAttribute{" + - "handle=" + new String(handle, StandardCharsets.UTF_8) + - ", index=" + index + - ", type='" + type + '\'' + - ", data=" + new String(data, StandardCharsets.UTF_8) + - '}'; - } -} \ No newline at end of file diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/HandleData.java b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/HandleData.java new file mode 100644 index 00000000..655fb0e3 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/HandleData.java @@ -0,0 +1,5 @@ +package eu.dissco.core.handlemanager.domain.repsitoryobjects; + +public interface HandleData { + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/StringHandleData.java b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/StringHandleData.java new file mode 100644 index 00000000..fa07a9eb --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/repsitoryobjects/StringHandleData.java @@ -0,0 +1,10 @@ +package eu.dissco.core.handlemanager.domain.repsitoryobjects; + +import lombok.Value; + +@Value +public class StringHandleData implements HandleData { + + String format = "string"; + String value; +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequest.java index a7e6b876..081dc237 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequest.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequest.java @@ -2,9 +2,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public record PatchRequest( - @JsonProperty(required = true) - PatchRequestData data -) { +public record PatchRequest(@JsonProperty(required = true) PutRequestData data) { } diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequestData.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequestData.java deleted file mode 100644 index 7801dfb4..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PatchRequestData.java +++ /dev/null @@ -1,19 +0,0 @@ -package eu.dissco.core.handlemanager.domain.requests; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import com.fasterxml.jackson.databind.JsonNode; -import eu.dissco.core.handlemanager.domain.fdo.FdoType; - -public record PatchRequestData( - @JsonProperty(required = true) - @JsonPropertyDescription("Type of object") - FdoType type, - @JsonProperty(required = true) - @JsonPropertyDescription("DiSSCo Identifier of object") - String id, - @JsonProperty(required = true) - JsonNode attributes -) { - -} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PostRequestData.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PostRequestData.java index bfd85bf6..ecafde3e 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PostRequestData.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PostRequestData.java @@ -6,7 +6,6 @@ import eu.dissco.core.handlemanager.domain.fdo.FdoType; public record PostRequestData( - @JsonProperty(required = true) @JsonPropertyDescription("type of object") FdoType type, @JsonProperty(required = true) diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequest.java deleted file mode 100644 index 0b51d4f2..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package eu.dissco.core.handlemanager.domain.requests; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public record PutRequest(@JsonProperty(required = true) PutRequestData data) { - -} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequestData.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequestData.java index 100e2296..1fca81a3 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequestData.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/requests/PutRequestData.java @@ -3,9 +3,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.databind.JsonNode; +import eu.dissco.core.handlemanager.domain.fdo.FdoType; public record PutRequestData( @JsonProperty(required = true) @JsonPropertyDescription("DiSSCo Identifier of object") String id, - @JsonProperty(required = true) JsonNode attributes) { + @JsonProperty(required = true) JsonNode attributes, + FdoType type) { } diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/TombstoneRequest.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/TombstoneRequest.java new file mode 100644 index 00000000..2eb6de11 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/requests/TombstoneRequest.java @@ -0,0 +1,5 @@ +package eu.dissco.core.handlemanager.domain.requests; + +public record TombstoneRequest(TombstoneRequestData data) { + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/requests/TombstoneRequestData.java b/src/main/java/eu/dissco/core/handlemanager/domain/requests/TombstoneRequestData.java new file mode 100644 index 00000000..5bc52403 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/domain/requests/TombstoneRequestData.java @@ -0,0 +1,12 @@ +package eu.dissco.core.handlemanager.domain.requests; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.databind.JsonNode; + +public record TombstoneRequestData( + @JsonProperty(required = true) @JsonPropertyDescription("DiSSCo Identifier of object") String id, + @JsonProperty(required = true) JsonNode attributes +) { + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/domain/validation/JsonSchemaValidator.java b/src/main/java/eu/dissco/core/handlemanager/domain/validation/JsonSchemaValidator.java index a04c3fc7..bafbf895 100644 --- a/src/main/java/eu/dissco/core/handlemanager/domain/validation/JsonSchemaValidator.java +++ b/src/main/java/eu/dissco/core/handlemanager/domain/validation/JsonSchemaValidator.java @@ -1,5 +1,6 @@ package eu.dissco.core.handlemanager.domain.validation; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.TOMBSTONE; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ATTRIBUTES; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_DATA; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_TYPE; @@ -30,7 +31,7 @@ import eu.dissco.core.handlemanager.domain.fdo.TombstoneRecordRequest; import eu.dissco.core.handlemanager.domain.requests.PatchRequest; import eu.dissco.core.handlemanager.domain.requests.PostRequest; -import eu.dissco.core.handlemanager.domain.requests.PutRequest; +import eu.dissco.core.handlemanager.domain.requests.TombstoneRequest; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import jakarta.validation.constraints.NotEmpty; import java.util.Arrays; @@ -49,69 +50,50 @@ public class JsonSchemaValidator { private JsonNode postReqJsonNode; private JsonNode patchReqJsonNode; private JsonNode putReqJsonNode; - private JsonNode handlePostReqJsonNode; - private JsonNode handlePatchReqJsonNode; - private JsonNode doiPostReqJsonNode; - private JsonNode doiPatchReqJsonNode; - private JsonNode digitalSpecimenPostReqJsonNode; - private JsonNode digitalSpecimenPatchReqJsonNode; - private JsonNode digitalMediaPostReqJsonNode; - private JsonNode digitalMediaPatchReqJsonNode; + private JsonNode handleJsonNode; + private JsonNode doiJsonNode; + private JsonNode digitalSpecimenJsonNode; + private JsonNode digitalMediaJsonNode; private JsonNode tombstoneReqJsonNode; - private JsonNode annotationPostReqJsonNode; - private JsonNode annotationPatchReqJsonNode; - private JsonNode mappingPostReqJsonNode; - private JsonNode mappingPatchReqJsonNode; - private JsonNode sourceSystemPostReqJsonNode; - private JsonNode sourceSystemPatchReqJsonNode; - private JsonNode organisationPostReqJsonNode; - private JsonNode organisationPatchReqJsonNode; - private JsonNode masPostReqJsonNode; - private JsonNode masPatchReqJsonNode; + private JsonNode annotationJsonNode; + private JsonNode mappingJsonNode; + private JsonNode sourceSystemJsonNode; + private JsonNode organisationJsonNode; + private JsonNode masJsonNode; // Schemas private JsonSchema postReqSchema; private JsonSchema patchReqSchema; private JsonSchema putReqSchema; - private JsonSchema handlePostReqSchema; - private JsonSchema handlePatchReqSchema; - private JsonSchema doiPostReqSchema; - private JsonSchema doiPatchReqSchema; - private JsonSchema digitalSpecimenPostReqSchema; - private JsonSchema digitalSpecimenPatchReqSchema; - private JsonSchema digitalMediaPostReqSchema; - private JsonSchema digitalMediaPatchReqSchema; + private JsonSchema handleSchema; + private JsonSchema doiSchema; + private JsonSchema digitalSpecimenSchema; + private JsonSchema digitalMediaSchema; private JsonSchema tombstoneReqSchema; - private JsonSchema annotationPostReqSchema; - private JsonSchema annotationPatchReqSchema; - private JsonSchema mappingPostReqSchema; - private JsonSchema mappingPatchReqSchema; - private JsonSchema sourceSystemPostReqSchema; - private JsonSchema sourceSystemPatchReqSchema; - private JsonSchema organisationPostReqSchema; - private JsonSchema organisationPatchReqSchema; - private JsonSchema masPostReqSchema; - private JsonSchema masPatchReqSchema; + private JsonSchema annotationSchema; + private JsonSchema dataMappingSchema; + private JsonSchema sourceSystemSchema; + private JsonSchema organisationSchema; + private JsonSchema masSchema; public JsonSchemaValidator() { setPostRequestAttributesJsonNodes(); - setPatchRequestAttributesJsonNodes(); setRequestJsonNodes(); setJsonSchemas(); } private void setPostRequestAttributesJsonNodes() { var schemaGenerator = new SchemaGenerator(jacksonModuleSchemaConfig()); - handlePostReqJsonNode = schemaGenerator.generateSchema(HandleRecordRequest.class); - doiPostReqJsonNode = schemaGenerator.generateSchema(DoiRecordRequest.class); - digitalSpecimenPostReqJsonNode = schemaGenerator.generateSchema(DigitalSpecimenRequest.class); - digitalMediaPostReqJsonNode = schemaGenerator.generateSchema(DigitalMediaRequest.class); + handleJsonNode = schemaGenerator.generateSchema(HandleRecordRequest.class); + doiJsonNode = schemaGenerator.generateSchema(DoiRecordRequest.class); + digitalSpecimenJsonNode = schemaGenerator.generateSchema(DigitalSpecimenRequest.class); + digitalMediaJsonNode = schemaGenerator.generateSchema(DigitalMediaRequest.class); tombstoneReqJsonNode = schemaGenerator.generateSchema(TombstoneRecordRequest.class); - annotationPostReqJsonNode = schemaGenerator.generateSchema(AnnotationRequest.class); - mappingPostReqJsonNode = schemaGenerator.generateSchema(DataMappingRequest.class); - sourceSystemPostReqJsonNode = schemaGenerator.generateSchema(SourceSystemRequest.class); - organisationPostReqJsonNode = schemaGenerator.generateSchema(OrganisationRequest.class); - masPostReqJsonNode = schemaGenerator.generateSchema(MasRequest.class); + annotationJsonNode = schemaGenerator.generateSchema(AnnotationRequest.class); + mappingJsonNode = schemaGenerator.generateSchema(DataMappingRequest.class); + sourceSystemJsonNode = schemaGenerator.generateSchema(SourceSystemRequest.class); + organisationJsonNode = schemaGenerator.generateSchema(OrganisationRequest.class); + masJsonNode = schemaGenerator.generateSchema(MasRequest.class); } private SchemaGeneratorConfig jacksonModuleSchemaConfig() { @@ -151,49 +133,11 @@ private SchemaGeneratorConfig jacksonModuleSchemaConfig() { return configBuilder.build(); } - private void setPatchRequestAttributesJsonNodes() { - var schemaGenerator = new SchemaGenerator(attributesSchemaConfig()); - handlePatchReqJsonNode = schemaGenerator.generateSchema(HandleRecordRequest.class); - doiPatchReqJsonNode = schemaGenerator.generateSchema(DoiRecordRequest.class); - digitalSpecimenPatchReqJsonNode = schemaGenerator.generateSchema(DigitalSpecimenRequest.class); - digitalMediaPatchReqJsonNode = schemaGenerator.generateSchema(DigitalMediaRequest.class); - annotationPatchReqJsonNode = schemaGenerator.generateSchema(AnnotationRequest.class); - mappingPatchReqJsonNode = schemaGenerator.generateSchema(DataMappingRequest.class); - sourceSystemPatchReqJsonNode = schemaGenerator.generateSchema(SourceSystemRequest.class); - organisationPatchReqJsonNode = schemaGenerator.generateSchema(OrganisationRequest.class); - masPatchReqJsonNode = schemaGenerator.generateSchema(MasRequest.class); - } - - private SchemaGeneratorConfig attributesSchemaConfig() { - // Secondary Configuration for schemas of Request Objects - // In these schemas not every field is required, but no unknown properties are allowed - // e.g. PATCH update attributes - - SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder( - SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON) - .with(Option.FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT) - .with( - Option.FLATTENED_ENUMS_FROM_TOSTRING); // Uses the output of toString() as the enum vocabulary - - configBuilder.forTypesInGeneral() - .withEnumResolver(scope -> scope.getType().getErasedType().isEnum() - ? Stream.of(scope.getType().getErasedType().getEnumConstants()) - .map(v -> ((Enum) v).name()).toList() - : null); - - // Min - configBuilder.forFields() - .withArrayMinItemsResolver(field -> field - .getAnnotationConsideringFieldAndGetterIfSupported(NotEmpty.class) == null ? null : 1); - - return configBuilder.build(); - } - private void setRequestJsonNodes() { var schemaGenerator = new SchemaGenerator(requestSchemaConfig()); postReqJsonNode = schemaGenerator.generateSchema(PostRequest.class); patchReqJsonNode = schemaGenerator.generateSchema(PatchRequest.class); - putReqJsonNode = schemaGenerator.generateSchema(PutRequest.class); + putReqJsonNode = schemaGenerator.generateSchema(TombstoneRequest.class); } public SchemaGeneratorConfig requestSchemaConfig() { @@ -224,110 +168,72 @@ public SchemaGeneratorConfig requestSchemaConfig() { private void setJsonSchemas() { JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); - postReqSchema = factory.getSchema(postReqJsonNode); patchReqSchema = factory.getSchema(patchReqJsonNode); putReqSchema = factory.getSchema(putReqJsonNode); - - handlePostReqSchema = factory.getSchema(handlePostReqJsonNode); - doiPostReqSchema = factory.getSchema(doiPostReqJsonNode); - digitalSpecimenPostReqSchema = factory.getSchema(digitalSpecimenPostReqJsonNode); - digitalMediaPostReqSchema = factory.getSchema(digitalMediaPostReqJsonNode); - annotationPostReqSchema = factory.getSchema(annotationPostReqJsonNode); - mappingPostReqSchema = factory.getSchema(mappingPostReqJsonNode); - sourceSystemPostReqSchema = factory.getSchema(sourceSystemPostReqJsonNode); - organisationPostReqSchema = factory.getSchema(organisationPostReqJsonNode); - masPostReqSchema = factory.getSchema(masPostReqJsonNode); - - handlePatchReqSchema = factory.getSchema(handlePatchReqJsonNode); - doiPatchReqSchema = factory.getSchema(doiPatchReqJsonNode); - digitalSpecimenPatchReqSchema = factory.getSchema(digitalSpecimenPatchReqJsonNode); - digitalMediaPatchReqSchema = factory.getSchema(digitalMediaPatchReqJsonNode); - annotationPatchReqSchema = factory.getSchema(annotationPatchReqJsonNode); - mappingPatchReqSchema = factory.getSchema(mappingPatchReqJsonNode); - sourceSystemPatchReqSchema = factory.getSchema(sourceSystemPatchReqJsonNode); - organisationPatchReqSchema = factory.getSchema(organisationPatchReqJsonNode); - masPatchReqSchema = factory.getSchema(masPatchReqJsonNode); - + handleSchema = factory.getSchema(handleJsonNode); + doiSchema = factory.getSchema(doiJsonNode); + digitalSpecimenSchema = factory.getSchema(digitalSpecimenJsonNode); + digitalMediaSchema = factory.getSchema(digitalMediaJsonNode); + annotationSchema = factory.getSchema(annotationJsonNode); + dataMappingSchema = factory.getSchema(mappingJsonNode); + sourceSystemSchema = factory.getSchema(sourceSystemJsonNode); + organisationSchema = factory.getSchema(organisationJsonNode); + masSchema = factory.getSchema(masJsonNode); tombstoneReqSchema = factory.getSchema(tombstoneReqJsonNode); - } public void validatePostRequest(JsonNode requestRoot) throws InvalidRequestException { var validationErrors = postReqSchema.validate(requestRoot); if (!validationErrors.isEmpty()) { - throw new InvalidRequestException(setErrorMessage(validationErrors, "POST")); - } - - FdoType type = FdoType.fromString(requestRoot.get(NODE_DATA).get(NODE_TYPE).asText()); - var attributes = requestRoot.get(NODE_DATA).get(NODE_ATTRIBUTES); - switch (type) { - case HANDLE -> validateRequestAttributes(attributes, handlePostReqSchema, type); - case DOI -> validateRequestAttributes(attributes, doiPostReqSchema, type); - case DIGITAL_SPECIMEN -> - validateRequestAttributes(attributes, digitalSpecimenPostReqSchema, type); - case DIGITAL_MEDIA -> validateRequestAttributes(attributes, digitalMediaPostReqSchema, type); - case ANNOTATION -> validateRequestAttributes(attributes, annotationPostReqSchema, type); - case DATA_MAPPING -> validateRequestAttributes(attributes, mappingPostReqSchema, type); - case SOURCE_SYSTEM -> validateRequestAttributes(attributes, sourceSystemPostReqSchema, type); - case ORGANISATION -> validateRequestAttributes(attributes, organisationPostReqSchema, type); - case MAS -> validateRequestAttributes(attributes, masPostReqSchema, type); - default -> - throw new InvalidRequestException("Invalid Request. Reason: Invalid type: " + type); + throw new InvalidRequestException(setErrorMessage(validationErrors, "CREATE")); } + var fdoType = FdoType.fromString(requestRoot.get(NODE_DATA).get(NODE_TYPE).asText()); + validateAttributes(requestRoot, fdoType); } public void validatePatchRequest(JsonNode requestRoot) throws InvalidRequestException { var validationErrors = patchReqSchema.validate(requestRoot); if (!validationErrors.isEmpty()) { throw new InvalidRequestException( - setErrorMessage(validationErrors, "PATCH (update)")); - } - FdoType type = FdoType.fromString(requestRoot.get(NODE_DATA).get(NODE_TYPE).asText()); - var attributes = requestRoot.get(NODE_DATA).get(NODE_ATTRIBUTES); - switch (type) { - case HANDLE -> validateRequestAttributes(attributes, handlePatchReqSchema, type); - case DOI -> validateRequestAttributes(attributes, doiPatchReqSchema, type); - case DIGITAL_SPECIMEN -> - validateRequestAttributes(attributes, digitalSpecimenPatchReqSchema, type); - case DIGITAL_MEDIA -> validateRequestAttributes(attributes, digitalMediaPatchReqSchema, type); - case ANNOTATION -> validateRequestAttributes(attributes, annotationPatchReqSchema, type); - case DATA_MAPPING -> validateRequestAttributes(attributes, mappingPatchReqSchema, type); - case SOURCE_SYSTEM -> validateRequestAttributes(attributes, sourceSystemPatchReqSchema, type); - case ORGANISATION -> validateRequestAttributes(attributes, organisationPatchReqSchema, type); - case MAS -> validateRequestAttributes(attributes, masPatchReqSchema, type); - default -> - throw new InvalidRequestException("Invalid Request. Reason: Invalid type: " + type); + setErrorMessage(validationErrors, "UPDATE")); } + var fdoType = FdoType.fromString(requestRoot.get(NODE_DATA).get(NODE_TYPE).asText()); + validateAttributes(requestRoot, fdoType); } public void validatePutRequest(JsonNode requestRoot) throws InvalidRequestException { var validationErrors = putReqSchema.validate(requestRoot); if (!validationErrors.isEmpty()) { throw new InvalidRequestException( - setErrorMessage(validationErrors, "PUT (tombstone)")); + setErrorMessage(validationErrors, "TOMBSTONE")); } - var attributes = requestRoot.get(NODE_DATA).get(NODE_ATTRIBUTES); - validateTombstoneRequestAttributes(attributes); + validateAttributes(requestRoot, TOMBSTONE); } - - private void validateTombstoneRequestAttributes(JsonNode requestAttributes) + private void validateAttributes(JsonNode requestRoot, FdoType fdoType) throws InvalidRequestException { - var validationErrors = tombstoneReqSchema.validate(requestAttributes); - if (!validationErrors.isEmpty()) { - throw new InvalidRequestException( - setErrorMessage(validationErrors, FdoType.TOMBSTONE.getDigitalObjectName(), - requestAttributes)); + var requestAttributes = requestRoot.get(NODE_DATA).get(NODE_ATTRIBUTES); + JsonSchema schema; + switch (fdoType) { + case HANDLE -> schema = handleSchema; + case DOI -> schema = doiSchema; + case DIGITAL_SPECIMEN -> schema = digitalSpecimenSchema; + case DIGITAL_MEDIA -> schema = digitalMediaSchema; + case ANNOTATION -> schema = annotationSchema; + case DATA_MAPPING -> schema = dataMappingSchema; + case SOURCE_SYSTEM -> schema = sourceSystemSchema; + case ORGANISATION -> schema = organisationSchema; + case MAS -> schema = masSchema; + case TOMBSTONE -> schema = tombstoneReqSchema; + default -> + throw new InvalidRequestException("Invalid Request. Reason: Invalid type: " + fdoType); } - } - - private void validateRequestAttributes(JsonNode requestAttributes, JsonSchema schema, - FdoType type) throws InvalidRequestException { var validationErrors = schema.validate(requestAttributes); if (!validationErrors.isEmpty()) { throw new InvalidRequestException( - setErrorMessage(validationErrors, type.getDigitalObjectName(), requestAttributes)); + setErrorMessage(validationErrors, fdoType.getDigitalObjectName(), + requestAttributes)); } } diff --git a/src/main/java/eu/dissco/core/handlemanager/properties/MongoProperties.java b/src/main/java/eu/dissco/core/handlemanager/properties/MongoProperties.java new file mode 100644 index 00000000..fcd53cf0 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/properties/MongoProperties.java @@ -0,0 +1,19 @@ +package eu.dissco.core.handlemanager.properties; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Data +@Validated +@ConfigurationProperties(prefix = "mongo") +public class MongoProperties { + + @NotBlank + private String connectionString; + + @NotBlank + private String database; + +} diff --git a/src/main/java/eu/dissco/core/handlemanager/repository/BatchInserter.java b/src/main/java/eu/dissco/core/handlemanager/repository/BatchInserter.java deleted file mode 100644 index 485590c4..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/repository/BatchInserter.java +++ /dev/null @@ -1,51 +0,0 @@ -package eu.dissco.core.handlemanager.repository; - -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; -import eu.dissco.core.handlemanager.exceptions.DatabaseCopyException; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.sql.SQLException; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.postgresql.copy.CopyManager; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -@Slf4j -public class BatchInserter { - - private final CopyManager copyManager; - - public void batchCopy(long recordTimestamp, List handleAttributes) { - try (var outputStream = new ByteArrayOutputStream()) { - for (var row : handleAttributes) { - outputStream.write(getCsvRow(recordTimestamp, row)); - } - var inputStream = new ByteArrayInputStream(outputStream.toByteArray()); - copyManager.copyIn("COPY handles FROM stdin DELIMITER ','", inputStream); - } catch (IOException | SQLException e) { - log.error("Sql error: ", e); - throw new DatabaseCopyException("Unable to insert new handles into database."); - } - } - - private static byte[] getCsvRow(Long recordTimestamp, HandleAttribute handleAttribute) { - return (new String(handleAttribute.getHandle(), StandardCharsets.UTF_8) + "," - + handleAttribute.getIndex() + "," - + handleAttribute.getType() + "," - + new String(handleAttribute.getData(), StandardCharsets.UTF_8).replace(",", "\\,") - + "," - + "0," - + "86400," - + recordTimestamp + "," - + "\\N," // Leave refs null - + true + "," - + true + "," - + true + "," - + false + "\n").getBytes(StandardCharsets.UTF_8); - } -} diff --git a/src/main/java/eu/dissco/core/handlemanager/repository/MongoRepository.java b/src/main/java/eu/dissco/core/handlemanager/repository/MongoRepository.java new file mode 100644 index 00000000..701ea37b --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/repository/MongoRepository.java @@ -0,0 +1,118 @@ +package eu.dissco.core.handlemanager.repository; + +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.in; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DIGITAL_OBJECT_TYPE; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_MEDIA_ID; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.ReplaceOneModel; +import eu.dissco.core.handlemanager.domain.fdo.FdoType; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +@Slf4j +public class MongoRepository { + + private final MongoCollection collection; + private final ObjectMapper mapper; + private static final String ID = "_id"; + + public List getHandleRecords(List ids) throws JsonProcessingException { + var results = collection.find(in(ID, ids)); + return formatResults(results); + } + + public List getExistingHandles(List ids) { + var results = collection.find(in(ID, ids)); + if (results.first() == null) { + return Collections.emptyList(); + } + var existingHandles = new ArrayList(); + results.forEach(r -> existingHandles.add(r.get(ID).toString())); + return existingHandles; + } + + public void postHandleRecords(List handleRecords) { + collection.insertMany(handleRecords); + } + + public void updateHandleRecords(List handleRecords) { + var queryList = handleRecords.stream().map(doc -> { + var filter = eq(doc.get(ID)); + return new ReplaceOneModel<>(filter, doc); + }).toList(); + collection.bulkWrite(queryList); + } + + public List searchByPrimaryLocalId(String localIdField, List localIds) + throws JsonProcessingException { + var results = collection.find(in(localIdField, localIds)); + return formatResults(results); + } + + public void rollbackHandles(List ids) { + var filter = in(ID, ids); + collection.deleteMany(filter); + } + + public void rollbackHandles(String localIdField, List localIds) { + var filter = in(localIdField, localIds); + collection.deleteMany(filter); + } + + private List formatResults(FindIterable results) + throws JsonProcessingException { + var handleRecords = new ArrayList(); + for (var result : results) { + var jsonRecord = mapper.readValue(result.toJson(), JsonNode.class); + if (jsonRecord.get("values") == null) { + log.warn("Unable to read handle record values \n {}", + mapper.writeValueAsString(jsonRecord)); + } else { + var attributes = mapper.convertValue(jsonRecord.get("values"), + new TypeReference>() { + }); + var fdoType = getFdoType(attributes, jsonRecord.get("_id").asText()); + handleRecords.add(new FdoRecord(jsonRecord.get("_id").asText(), + fdoType, attributes, getLocalId(jsonRecord, fdoType))); + } + } + return handleRecords; + } + + private FdoType getFdoType(List fdoAttributes, String id) { + for (var fdoAttribute : fdoAttributes) { + if (DIGITAL_OBJECT_TYPE.index() == fdoAttribute.getIndex()) { + return FdoType.fromString(fdoAttribute.getValue()); + } + } + log.error("Unable to determine Fdo Type for record of handle {}", id); + throw new IllegalStateException(); + } + + private String getLocalId(JsonNode jsonRecord, FdoType fdoType) { + if (FdoType.DIGITAL_SPECIMEN.equals(fdoType)) { + return jsonRecord.get(NORMALISED_SPECIMEN_OBJECT_ID.get()).asText(); + } + if (FdoType.DIGITAL_MEDIA.equals(fdoType)) { + return jsonRecord.get(PRIMARY_MEDIA_ID.get()).asText(); + } + return null; + } +} diff --git a/src/main/java/eu/dissco/core/handlemanager/repository/PidRepository.java b/src/main/java/eu/dissco/core/handlemanager/repository/PidRepository.java deleted file mode 100644 index 96819a70..00000000 --- a/src/main/java/eu/dissco/core/handlemanager/repository/PidRepository.java +++ /dev/null @@ -1,268 +0,0 @@ -package eu.dissco.core.handlemanager.repository; - -import static eu.dissco.core.handlemanager.database.jooq.tables.Handles.HANDLES; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID_RECORD_ISSUE_NUMBER; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID_STATUS; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_SPECIMEN_OBJECT_ID; -import static org.jooq.impl.DSL.select; - -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.jooq.DSLContext; -import org.jooq.Query; -import org.jooq.Record4; -import org.jooq.SelectConditionStep; -import org.springframework.stereotype.Repository; - -@Slf4j -@Repository -@RequiredArgsConstructor -public class PidRepository { - - private static final int TTL = 86400; - private final DSLContext context; - private final BatchInserter batchInserter; - - // For Handle Name Generation - public List getHandlesExist(List handles) { - return context.selectDistinct(HANDLES.HANDLE).from(HANDLES).where(HANDLES.HANDLE.in(handles)) - .fetch().getValues(HANDLES.HANDLE, byte[].class); - } - - public List checkHandlesWritable(List handles) { - return context.selectDistinct(HANDLES.HANDLE).from(HANDLES).where(HANDLES.HANDLE.in(handles)) - .and(HANDLES.TYPE.eq(PID_STATUS.get().getBytes(StandardCharsets.UTF_8))) - .and(HANDLES.DATA.notEqual("ARCHIVED".getBytes())).fetch() - .getValues(HANDLES.HANDLE, byte[].class); - } - - public List resolveHandleAttributes(byte[] handle) { - return context.select(HANDLES.IDX, HANDLES.HANDLE, HANDLES.TYPE, HANDLES.DATA).from(HANDLES) - .where(HANDLES.HANDLE.eq(handle)) - .and(HANDLES.TYPE.notEqual(HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) - .fetch(this::mapToAttribute); - } - - public List resolveHandleAttributes(List handles) { - return context.select(HANDLES.IDX, HANDLES.HANDLE, HANDLES.TYPE, HANDLES.DATA).from(HANDLES) - .where(HANDLES.HANDLE.in(handles)) - .and(HANDLES.TYPE.notEqual(HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) - .fetch(this::mapToAttribute); - } - - public List searchByNormalisedPhysicalIdentifier( - List normalisedPhysicalIdentifiers) { - return context.select(HANDLES.IDX, HANDLES.HANDLE, HANDLES.TYPE, HANDLES.DATA).from(HANDLES) - .where(HANDLES.HANDLE.in(select(HANDLES.HANDLE).from(HANDLES) - .where(HANDLES.TYPE.eq(PID_STATUS.get().getBytes(StandardCharsets.UTF_8))) - .and(HANDLES.DATA.notEqual("ARCHIVED".getBytes(StandardCharsets.UTF_8))).and( - HANDLES.HANDLE.in( - select(HANDLES.HANDLE).from(HANDLES).where(HANDLES.TYPE.eq( - NORMALISED_SPECIMEN_OBJECT_ID.get() - .getBytes(StandardCharsets.UTF_8))) - .and(HANDLES.DATA.in(normalisedPhysicalIdentifiers)))))) - .and(HANDLES.TYPE.eq( - NORMALISED_SPECIMEN_OBJECT_ID.get().getBytes(StandardCharsets.UTF_8))) - .fetch(this::mapToAttribute); - } - - public List getPrimarySpecimenObjectId(List handles) { - return context.select(HANDLES.IDX, HANDLES.HANDLE, HANDLES.TYPE, HANDLES.DATA).from(HANDLES) - .where(HANDLES.HANDLE.in(handles)) - .and(HANDLES.TYPE.eq(PRIMARY_SPECIMEN_OBJECT_ID.get().getBytes(StandardCharsets.UTF_8))) - .fetch(this::mapToAttribute); - } - - public List searchByNormalisedPhysicalIdentifierFullRecord( - List normalisedPhysicalIdentifiers) { - var normalisedPhysicalIdentifierTable = searchByNormalisedPhysicalIdentifierQuery( - normalisedPhysicalIdentifiers).asTable("normalisedPhysicalIdentifierTable"); - - return context.select(HANDLES.IDX, HANDLES.HANDLE, HANDLES.TYPE, HANDLES.DATA).from(HANDLES) - .join(normalisedPhysicalIdentifierTable) - .on(HANDLES.HANDLE.eq(normalisedPhysicalIdentifierTable.field(HANDLES.HANDLE))) - .where(HANDLES.TYPE.notEqual(HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) - .fetch(this::mapToAttribute); - } - - private SelectConditionStep> searchByNormalisedPhysicalIdentifierQuery( - List normalisedPhysicalIdentifiers) { - return context.select(HANDLES.IDX, HANDLES.HANDLE, HANDLES.TYPE, HANDLES.DATA).from(HANDLES) - .where( - HANDLES.TYPE.eq(NORMALISED_SPECIMEN_OBJECT_ID.get().getBytes(StandardCharsets.UTF_8))) - .and((HANDLES.DATA).in(normalisedPhysicalIdentifiers)); - } - - // Get List of Pids - public List getAllHandles(byte[] pidStatus, int pageNum, int pageSize) { - int offset = getOffset(pageNum, pageSize); - - return context.selectDistinct(HANDLES.HANDLE).from(HANDLES) - .where(HANDLES.TYPE.eq(PID_STATUS.get().getBytes(StandardCharsets.UTF_8))) - .and(HANDLES.DATA.eq(pidStatus)).limit(pageSize).offset(offset).fetch() - .getValues(HANDLES.HANDLE, String.class); - } - - public List getAllHandles(int pageNum, int pageSize) { - int offset = getOffset(pageNum, pageSize); - return context.selectDistinct(HANDLES.HANDLE).from(HANDLES).limit(pageSize).offset(offset) - .fetch().getValues(HANDLES.HANDLE, String.class); - } - - private HandleAttribute mapToAttribute(Record4 row) { - return new HandleAttribute(row.get(HANDLES.IDX), row.get(HANDLES.HANDLE), - new String(row.get(HANDLES.TYPE), StandardCharsets.UTF_8), row.get(HANDLES.DATA)); - } - - // Post - public void postAttributesToDb(long recordTimestamp, List handleAttributes) { - batchInserter.batchCopy(recordTimestamp, handleAttributes); - } - - private List prepareBatchPostQuery(long recordTimestamp, - List handleAttributes) { - var queryList = new ArrayList(); - - for (var handleAttribute : handleAttributes) { - var query = context.insertInto(HANDLES).set(HANDLES.HANDLE, handleAttribute.getHandle()) - .set(HANDLES.IDX, handleAttribute.getIndex()) - .set(HANDLES.TYPE, handleAttribute.getType().getBytes(StandardCharsets.UTF_8)) - .set(HANDLES.DATA, handleAttribute.getData()).set(HANDLES.TTL, TTL) - .set(HANDLES.TIMESTAMP, recordTimestamp).set(HANDLES.ADMIN_READ, true) - .set(HANDLES.ADMIN_WRITE, true).set(HANDLES.PUB_READ, true) - .set(HANDLES.PUB_WRITE, false); - queryList.add(query); - } - return queryList; - } - - // Archive - public void archiveRecords(long recordTimestamp, List handleAttributes, - List handles) { - updateRecord(recordTimestamp, handleAttributes, true); - deleteNonTombstoneAttributes(handles); - } - - private void deleteNonTombstoneAttributes(List handles) { - context.delete(HANDLES).where(HANDLES.HANDLE.in(handles)).and(HANDLES.IDX.notBetween(1).and(39)) - .and(HANDLES.IDX.notBetween(100).and(101)).execute(); - } - - public void postAndUpdateHandles(long recordTimestamp, List createAttributes, - List> updateAttributes) { - var queryList = prepareBatchUpdateQuery(recordTimestamp, updateAttributes, true); - queryList.addAll(prepareBatchPostQuery(recordTimestamp, createAttributes)); - context.batch(queryList).execute(); - } - - // Update - public void updateRecord(long recordTimestamp, List handleAttributes, - boolean incrementVersion) { - var queryList = prepareUpdateQuery(recordTimestamp, handleAttributes); - if (incrementVersion) { - queryList.addAll( - getVersionIncrementQuery(Set.of(handleAttributes.get(0).getHandle()), - recordTimestamp)); - } - context.batch(queryList).execute(); - } - - public void updateRecordBatch(long recordTimestamp, List> handleRecords, - boolean incrementVersion) { - var queryList = prepareBatchUpdateQuery(recordTimestamp, handleRecords, incrementVersion); - context.batch(queryList).execute(); - } - - private List prepareBatchUpdateQuery(long recordTimestamp, - List> handleRecords, boolean incrementVersion) { - List queryList = new ArrayList<>(); - for (List handleRecord : handleRecords) { - queryList.addAll(prepareUpdateQuery(recordTimestamp, handleRecord)); - } - if (incrementVersion) { - var handles = handleRecords.stream().map(h -> h.get(0).getHandle()) - .collect(Collectors.toSet()); - queryList.addAll(getVersionIncrementQuery(handles, recordTimestamp)); - } - return queryList; - } - - private ArrayList prepareUpdateQuery(long recordTimestamp, - List handleAttributes) { - var queryList = new ArrayList(); - for (var handleAttribute : handleAttributes) { - var query = context.insertInto(HANDLES).set(HANDLES.HANDLE, handleAttribute.getHandle()) - .set(HANDLES.IDX, handleAttribute.getIndex()) - .set(HANDLES.TYPE, handleAttribute.getType().getBytes(StandardCharsets.UTF_8)) - .set(HANDLES.DATA, handleAttribute.getData()).set(HANDLES.TTL, TTL) - .set(HANDLES.TIMESTAMP, recordTimestamp).set(HANDLES.ADMIN_READ, true) - .set(HANDLES.ADMIN_WRITE, true).set(HANDLES.PUB_READ, true) - .set(HANDLES.PUB_WRITE, false) - .onDuplicateKeyUpdate().set(HANDLES.HANDLE, handleAttribute.getHandle()) - .set(HANDLES.IDX, handleAttribute.getIndex()) - .set(HANDLES.TYPE, handleAttribute.getType().getBytes(StandardCharsets.UTF_8)) - .set(HANDLES.DATA, handleAttribute.getData()).set(HANDLES.TTL, TTL) - .set(HANDLES.TIMESTAMP, recordTimestamp).set(HANDLES.ADMIN_READ, true) - .set(HANDLES.ADMIN_WRITE, true).set(HANDLES.PUB_READ, true) - .set(HANDLES.PUB_WRITE, false); - queryList.add(query); - } - return queryList; - } - - private List getVersionIncrementQuery(Set handles, long recordTimestamp) { - var versions = getIncrementedVersions(handles); - var handleStr = handles.stream().map(h -> new String(h, StandardCharsets.UTF_8)).toList(); - List queryList = new ArrayList<>(); - for (var handle : handleStr) { - queryList.add(context.update(HANDLES) - .set(HANDLES.DATA, versions.get(handle)) - .set(HANDLES.TIMESTAMP, recordTimestamp) - .where(HANDLES.HANDLE.eq(handle.getBytes(StandardCharsets.UTF_8))) - .and(HANDLES.TYPE.eq( - PID_RECORD_ISSUE_NUMBER.get().getBytes(StandardCharsets.UTF_8)))); - } - return queryList; - } - - private Map getIncrementedVersions(Set handles) { - var versions = context.select(HANDLES.HANDLE, HANDLES.DATA) - .from(HANDLES) - .where(HANDLES.HANDLE.in(handles) - .and(HANDLES.TYPE.eq( - PID_RECORD_ISSUE_NUMBER.get().getBytes(StandardCharsets.UTF_8)))) - .fetchMap(HANDLES.HANDLE, HANDLES.DATA); - return versions.entrySet().stream() - .collect(Collectors.toMap( - e -> new String(e.getKey(), StandardCharsets.UTF_8), - e -> incrementByteArray(e.getValue()) - )); - } - - private static byte[] incrementByteArray(byte[] version) { - var originalVersion = Integer.parseInt(new String(version, StandardCharsets.UTF_8)); - return String.valueOf(originalVersion + 1).getBytes(StandardCharsets.UTF_8); - } - - public void rollbackHandles(List handles) { - context.delete(HANDLES).where(HANDLES.HANDLE.in(handles)).execute(); - } - - private int getOffset(int pageNum, int pageSize) { - int offset = 0; - if (pageNum > 1) { - offset = offset + (pageSize * (pageNum - 1)); - } - return offset; - } - -} diff --git a/src/main/java/eu/dissco/core/handlemanager/security/WebSecurityConfig.java b/src/main/java/eu/dissco/core/handlemanager/security/WebSecurityConfig.java index b4b3540f..e2488074 100644 --- a/src/main/java/eu/dissco/core/handlemanager/security/WebSecurityConfig.java +++ b/src/main/java/eu/dissco/core/handlemanager/security/WebSecurityConfig.java @@ -15,7 +15,7 @@ @RequiredArgsConstructor @Configuration @EnableWebSecurity -public class WebSecurityConfig { +public class WebSecurityConfig { private final JwtAuthConverter jwtAuthConverter; diff --git a/src/main/java/eu/dissco/core/handlemanager/service/DoiService.java b/src/main/java/eu/dissco/core/handlemanager/service/DoiService.java index 941c6486..731c8477 100644 --- a/src/main/java/eu/dissco/core/handlemanager/service/DoiService.java +++ b/src/main/java/eu/dissco/core/handlemanager/service/DoiService.java @@ -1,8 +1,6 @@ package eu.dissco.core.handlemanager.service; -import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_MEDIA; -import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_SPECIMEN; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ATTRIBUTES; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_DATA; @@ -12,20 +10,16 @@ import eu.dissco.core.handlemanager.Profiles; import eu.dissco.core.handlemanager.domain.datacite.DataCiteEvent; import eu.dissco.core.handlemanager.domain.datacite.EventType; -import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; -import eu.dissco.core.handlemanager.domain.fdo.FdoType; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperWrite; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import eu.dissco.core.handlemanager.exceptions.PidResolutionException; import eu.dissco.core.handlemanager.exceptions.UnprocessableEntityException; import eu.dissco.core.handlemanager.properties.ProfileProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.ArrayList; +import eu.dissco.core.handlemanager.repository.MongoRepository; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -36,11 +30,12 @@ public class DoiService extends PidService { private final DataCiteService dataCiteService; - public DoiService(PidRepository pidRepository, - FdoRecordService fdoRecordService, PidNameGeneratorService pidNameGeneratorService, + public DoiService(FdoRecordService fdoRecordService, + PidNameGeneratorService pidNameGeneratorService, ObjectMapper mapper, ProfileProperties profileProperties, - DataCiteService dataCiteService) { - super(pidRepository, fdoRecordService, pidNameGeneratorService, mapper, profileProperties); + DataCiteService dataCiteService, MongoRepository mongoRepository) { + super(fdoRecordService, pidNameGeneratorService, mapper, profileProperties, + mongoRepository); this.dataCiteService = dataCiteService; } @@ -49,68 +44,73 @@ public DoiService(PidRepository pidRepository, @Override public JsonApiWrapperWrite createRecords(List requests) throws InvalidRequestException, UnprocessableEntityException { - var handles = hf.genHandleList(requests.size()).iterator(); + var handles = hf.generateNewHandles(requests.size()).iterator(); var requestAttributes = requests.stream() .map(request -> request.get(NODE_DATA).get(NODE_ATTRIBUTES)).toList(); var type = getObjectTypeFromJsonNode(requests); - List handleAttributes; + List fdoDocuments; + List fdoRecords; try { switch (type) { - case DIGITAL_SPECIMEN -> - handleAttributes = createDigitalSpecimen(requestAttributes, handles); - case DIGITAL_MEDIA -> handleAttributes = createDigitalMedia(requestAttributes, handles); + case DIGITAL_SPECIMEN -> fdoRecords = createDigitalSpecimen(requestAttributes, handles); + case DIGITAL_MEDIA -> fdoRecords = createDigitalMedia(requestAttributes, handles); default -> throw new UnsupportedOperationException( - type + " is not an appropriate Type for DOI endpoint."); + String.format(TYPE_ERROR_MESSAGE, type.getDigitalObjectName())); } + fdoDocuments = toMongoDbDocument(fdoRecords); } catch (JsonProcessingException | PidResolutionException e) { throw new InvalidRequestException( "An error has occurred parsing a record in request. More information: " + e.getMessage()); } - log.info("Persisting new dois to db"); - pidRepository.postAttributesToDb(Instant.now().getEpochSecond(), handleAttributes); + log.info("Persisting new DOIs to Document Store"); + mongoRepository.postHandleRecords(fdoDocuments); log.info("Publishing to DataCite"); - publishToDataCite(handleAttributes, EventType.CREATE, type); - return new JsonApiWrapperWrite(formatCreateRecords(handleAttributes, type)); + publishToDataCite(fdoRecords, EventType.CREATE); + return new JsonApiWrapperWrite(formatFdoRecord(fdoRecords, type)); } @Override public JsonApiWrapperWrite updateRecords(List requests, boolean incrementVersion) throws InvalidRequestException, UnprocessableEntityException { - var type = getObjectTypeFromJsonNode(requests); - if (!DIGITAL_SPECIMEN.equals(type) && !DIGITAL_MEDIA.equals(type)) { - throw new InvalidRequestException(TYPE_ERROR_MESSAGE); + var updateRequests = requests.stream() + .map(request -> request.get(NODE_DATA)).toList(); + var fdoRecordMap = processUpdateRequest(updateRequests); + var fdoType = getObjectTypeFromJsonNode(requests); + List fdoRecords; + List fdoDocuments; + try { + switch (fdoType) { + case DIGITAL_SPECIMEN -> + fdoRecords = updateDigitalSpecimen(updateRequests, fdoRecordMap, incrementVersion); + case DIGITAL_MEDIA -> + fdoRecords = updateDigitalMedia(updateRequests, fdoRecordMap, incrementVersion); + default -> throw new UnsupportedOperationException( + String.format(TYPE_ERROR_MESSAGE, fdoType.getDigitalObjectName())); + } + fdoDocuments = toMongoDbDocument(fdoRecords); + mongoRepository.updateHandleRecords(fdoDocuments); + publishToDataCite(fdoRecords, EventType.UPDATE); + return new JsonApiWrapperWrite(formatFdoRecord(fdoRecords, fdoType)); + } catch (JsonProcessingException e) { + log.error("An error has occurred processing JSON data", e); + throw new UnprocessableEntityException("Json Processing Error"); } - var attributesToUpdate = getAttributesToUpdate(requests); - var response = updateRecords(attributesToUpdate, incrementVersion, type); - log.info("Publishing to datacite"); - var flatList = attributesToUpdate.stream().flatMap(List::stream).toList(); - publishToDataCite(flatList, EventType.UPDATE, type); - return response; } - private void publishToDataCite(List handleAttributes, EventType eventType, - FdoType objectType) throws UnprocessableEntityException { - var handleMap = mapRecords(handleAttributes); - var eventList = new ArrayList(); - handleMap.forEach( - (key, value) -> { - if (eventType.equals(EventType.UPDATE)) { - value.add( - new HandleAttribute(FdoProfile.PID, key.getBytes(StandardCharsets.UTF_8), key)); - } - eventList.add( - new DataCiteEvent(jsonFormatSingleRecord(value), eventType)); - }); - + private void publishToDataCite(List fdoRecords, EventType eventType) + throws UnprocessableEntityException { + var eventList = fdoRecords.stream() + .map(fdoRecord -> new DataCiteEvent(jsonFormatSingleRecord(fdoRecord.attributes()), + eventType)).toList(); for (var event : eventList) { try { - dataCiteService.publishToDataCite(event, objectType); + dataCiteService.publishToDataCite(event, fdoRecords.get(0).fdoType()); } catch (JsonProcessingException e) { log.error("Critical error: Unable to publish datacite event to queue", e); - log.info("Rolling back handles"); if (eventType.equals(EventType.CREATE)) { - rollbackHandles(new ArrayList<>(handleMap.keySet())); + log.info("Rolling back handles"); + rollbackHandles(fdoRecords.stream().map(FdoRecord::handle).toList()); } throw new UnprocessableEntityException("Unable to publish datacite event to queue"); } diff --git a/src/main/java/eu/dissco/core/handlemanager/service/FdoRecordService.java b/src/main/java/eu/dissco/core/handlemanager/service/FdoRecordService.java index 3c533b2f..ebb4c9d1 100644 --- a/src/main/java/eu/dissco/core/handlemanager/service/FdoRecordService.java +++ b/src/main/java/eu/dissco/core/handlemanager/service/FdoRecordService.java @@ -1,9 +1,12 @@ package eu.dissco.core.handlemanager.service; +import static eu.dissco.core.handlemanager.configuration.AppConfig.DATE_STRING; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ANNOTATION_HASH; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.BASE_TYPE_OF_SPECIMEN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.CATALOG_IDENTIFIER; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DCTERMS_FORMAT; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DCTERMS_SUBJECT; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DCTERMS_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DC_TERMS_CONFORMS; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DERIVED_FROM_ENTITY; @@ -11,6 +14,7 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DIGITAL_OBJECT_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.FDO_PROFILE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.FDO_RECORD_LICENSE; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HAS_RELATED_PID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.INFORMATION_ARTEFACT_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ISSUED_FOR_AGENT; @@ -27,10 +31,8 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MAS_NAME; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MATERIAL_OR_DIGITAL_ENTITY; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MATERIAL_SAMPLE_TYPE; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_FORMAT; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_HOST; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_HOST_NAME; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_MIME_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MOTIVATION; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ORGANISATION_ID; @@ -64,42 +66,43 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.STRUCTURAL_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TARGET_PID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TARGET_TYPE; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONED_DATE; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONED_TEXT; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_CATEGORY; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_DISCIPLINE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_DOMAIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_ORIGIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.WAS_DERIVED_FROM_ENTITY; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.ANNOTATION; import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DATA_MAPPING; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_MEDIA; import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_SPECIMEN; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.MAS; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.ORGANISATION; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.SOURCE_SYSTEM; +import static eu.dissco.core.handlemanager.service.ServiceUtils.getField; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.BaseJsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import eu.dissco.core.handlemanager.domain.fdo.AnnotationRequest; import eu.dissco.core.handlemanager.domain.fdo.DataMappingRequest; import eu.dissco.core.handlemanager.domain.fdo.DigitalMediaRequest; import eu.dissco.core.handlemanager.domain.fdo.DigitalSpecimenRequest; import eu.dissco.core.handlemanager.domain.fdo.DoiRecordRequest; -import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; import eu.dissco.core.handlemanager.domain.fdo.FdoType; import eu.dissco.core.handlemanager.domain.fdo.HandleRecordRequest; import eu.dissco.core.handlemanager.domain.fdo.MasRequest; import eu.dissco.core.handlemanager.domain.fdo.OrganisationRequest; import eu.dissco.core.handlemanager.domain.fdo.SourceSystemRequest; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; +import eu.dissco.core.handlemanager.domain.fdo.TombstoneRecordRequest; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.PidStatus; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; -import eu.dissco.core.handlemanager.exceptions.InvalidRequestRuntimeException; -import eu.dissco.core.handlemanager.exceptions.PidResolutionException; import eu.dissco.core.handlemanager.properties.ApplicationProperties; -import eu.dissco.core.handlemanager.properties.ProfileProperties; import eu.dissco.core.handlemanager.web.PidResolver; -import java.io.IOException; +import jakarta.annotation.Nullable; import java.io.StringWriter; -import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -108,7 +111,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -120,7 +122,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.w3c.dom.Document; + @RequiredArgsConstructor @Service @@ -131,20 +133,30 @@ public class FdoRecordService { private final DocumentBuilderFactory dbf; private final PidResolver pidResolver; private final ObjectMapper mapper; - private final ApplicationProperties appProperties; - private final ProfileProperties profileProperties; - private static final String HANDLE_DOMAIN = "https://hdl.handle.net/"; - private static final String DOI_DOMAIN = "https://doi.org/"; + private final ApplicationProperties applicationProperties; + public static final String HANDLE_DOMAIN = "https://hdl.handle.net/"; + public static final String DOI_DOMAIN = "https://doi.org/"; private static final String ROR_API_DOMAIN = "https://api.ror.org/organizations/"; private static final String ROR_DOMAIN = "https://ror.org/"; private static final String WIKIDATA_DOMAIN = "https://www.wikidata.org/wiki/"; private static final String WIKIDATA_API = "https://wikidata.org/w/rest.php/wikibase/v0/entities/items/"; private static final String PROXY_ERROR = "Invalid attribute: %s must contain proxy: %s"; private static final String PID_KERNEL_METADATA_LICENSE = "https://creativecommons.org/publicdomain/zero/1.0/"; - private static final byte[] ADMIN_HEX = "\\\\x0FFF000000153330303A302E4E412F32302E353030302E31303235000000C8".getBytes( - StandardCharsets.UTF_8); private static final String LOC_REQUEST = "locations"; public static final Map RESOLVABLE_KEYS; + public static final List GENERATED_KEYS; + public static final List TOMBSTONE_KEYS; + + static { + GENERATED_KEYS = List.of(FDO_RECORD_LICENSE.index(), PID.index(), PID_RECORD_ISSUE_DATE.index(), + PID_STATUS.index(), HS_ADMIN.index()); + } + + static { + TOMBSTONE_KEYS = List.of(FDO_RECORD_LICENSE.index(), PID.index(), PID_RECORD_ISSUE_DATE.index(), + PID_ISSUER.index(), PID_ISSUER_NAME.index(), ISSUED_FOR_AGENT.index(), + ISSUED_FOR_AGENT_NAME.index(), STRUCTURAL_TYPE.index(), HS_ADMIN.index()); + } static { HashMap hashMap = new HashMap<>(); @@ -155,510 +167,620 @@ public class FdoRecordService { RESOLVABLE_KEYS = Collections.unmodifiableMap(hashMap); } - private final DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") + private final DateTimeFormatter dt = DateTimeFormatter.ofPattern(DATE_STRING) .withZone(ZoneId.of("UTC")); - public HandleAttribute genHsAdmin(byte[] handle) { - return new HandleAttribute(HS_ADMIN.index(), handle, HS_ADMIN.get(), ADMIN_HEX); + /* Handle Record Creation */ + public FdoRecord prepareNewHandleRecord(HandleRecordRequest request, String handle, + FdoType fdoType, Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewHandleAttributes(request, handle, fdoType, timestamp); + return new FdoRecord(handle, fdoType, fdoAttributes, null); } - public List prepareHandleRecordAttributes(HandleRecordRequest request, - byte[] handle, FdoType type) throws InvalidRequestException { - List fdoRecord = new ArrayList<>(); - - // 100: Admin Handle - fdoRecord.add(genHsAdmin(handle)); - - // 101: 10320/loc - if (type != FdoType.ORGANISATION) { - byte[] loc = setLocations(request.getLocations(), new String(handle, StandardCharsets.UTF_8), - type); - fdoRecord.add(new HandleAttribute(LOC.index(), handle, LOC.get(), loc)); - } - - // 1: FDO Profile - fdoRecord.add(new HandleAttribute(FDO_PROFILE, handle, type.getFdoProfile())); - - // 2: FDO Record License - fdoRecord.add(new HandleAttribute(FDO_RECORD_LICENSE, handle, PID_KERNEL_METADATA_LICENSE)); - - // 3: DigitalObjectType - fdoRecord.add(new HandleAttribute(DIGITAL_OBJECT_TYPE, handle, type.getDigitalObjectType())); - - // 4: DigitalObjectName - fdoRecord.add(new HandleAttribute(DIGITAL_OBJECT_NAME, handle, type.getDigitalObjectName())); - - // 5: Pid - var pid = profileProperties.getDomain() + new String(handle, StandardCharsets.UTF_8); - fdoRecord.add(new HandleAttribute(PID, handle, pid)); - - // 6: PidIssuer - fdoRecord.add(new HandleAttribute(PID_ISSUER, handle, request.getPidIssuer())); - - // 7: pidIssuerName - String pidIssuerName = getObjectName(request.getPidIssuer()); - fdoRecord.add(new HandleAttribute(PID_ISSUER_NAME, handle, pidIssuerName)); - - // 8: issuedForAgent - fdoRecord.add(new HandleAttribute(ISSUED_FOR_AGENT, handle, request.getIssuedForAgent())); - - // 9: issuedForAgentName - var agentNameRor = getRor(request.getIssuedForAgent()); - var issuedForAgentName = pidResolver.getObjectName(agentNameRor); - fdoRecord.add(new HandleAttribute(ISSUED_FOR_AGENT_NAME, handle, issuedForAgentName)); - - // 10: pidRecordIssueDate - fdoRecord.add(new HandleAttribute(PID_RECORD_ISSUE_DATE, handle, getDate())); - - // 11: pidRecordIssueNumber - fdoRecord.add(new HandleAttribute(PID_RECORD_ISSUE_NUMBER, handle, "1")); - - // 12: structuralType - fdoRecord.add( - new HandleAttribute(STRUCTURAL_TYPE, handle, request.getStructuralType().toString())); - - // 13: PidStatus - fdoRecord.add(new HandleAttribute(PID_STATUS, handle, "TEST")); - - return fdoRecord; + public FdoRecord prepareUpdatedHandleRecord(HandleRecordRequest recordRequest, + FdoType fdoType, Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) + throws InvalidRequestException { + var fdoAttributes = prepareUpdatedHandleAttributes(recordRequest, + previousVersion.handle(), fdoType, + timestamp, previousVersion, incrementVersion); + return new FdoRecord(previousVersion.handle(), fdoType, fdoAttributes, null); } - private String getObjectName(String url) throws InvalidRequestException { - if (url.contains(ROR_DOMAIN)) { - return pidResolver.getObjectName(getRor(url)); - } else if (url.contains(HANDLE_DOMAIN) || url.contains(DOI_DOMAIN)) { - return pidResolver.getObjectName(url); - } - throw new InvalidRequestException(String.format(PROXY_ERROR, url, - (ROR_DOMAIN + ", " + HANDLE_DOMAIN + ", or " + DOI_DOMAIN))); + private List prepareNewHandleAttributes(HandleRecordRequest request, + String handle, + FdoType fdoType, Instant timestamp) throws InvalidRequestException { + var handleAttributes = prepareHandleAttributesFromRequest(request, handle, fdoType, timestamp); + handleAttributes.addAll(prepareHandleAttributesGenerated(handle, fdoType, timestamp)); + return handleAttributes; } - private static String getRor(String url) throws InvalidRequestException { - if (!url.contains(ROR_DOMAIN)) { - throw new InvalidRequestException(String.format(PROXY_ERROR, url, ROR_DOMAIN)); + private List prepareUpdatedHandleAttributes(HandleRecordRequest request, + String handle, FdoType fdoType, Instant timestamp, FdoRecord previousVersion, + boolean incrementVersion) + throws InvalidRequestException { + var previousAttributes = new ArrayList<>(previousVersion.attributes()); + var updatedAttributes = prepareHandleAttributesFromRequest(request, handle, fdoType, timestamp); + updatedAttributes.addAll(previousAttributes.stream() + .filter(previousAttribute -> GENERATED_KEYS.contains(previousAttribute.getIndex())) + .toList()); + var previousIssueNumber = getField(previousVersion.attributes(), PID_RECORD_ISSUE_NUMBER); + if (incrementVersion) { + updatedAttributes.add(incrementIssueNumber(previousIssueNumber, timestamp)); + } else { + updatedAttributes.add(previousIssueNumber); } - return url.replace(ROR_DOMAIN, ROR_API_DOMAIN); + return updatedAttributes; } - public List prepareDoiRecordAttributes(DoiRecordRequest request, byte[] handle, - FdoType type) throws InvalidRequestException { - var fdoRecord = prepareHandleRecordAttributes(request, handle, type); - - // 40: referentType - fdoRecord.add(new HandleAttribute(REFERENT_TYPE, handle, request.getReferentType())); - - // 41: referentDoiName - fdoRecord.add( - new HandleAttribute(REFERENT_DOI_NAME.index(), handle, REFERENT_DOI_NAME.get(), handle)); - - // 42: referentName - if (request.getReferentName() != null) { - fdoRecord.add(new HandleAttribute(REFERENT_NAME, handle, request.getReferentName())); - } - // 43: primaryReferentType - fdoRecord.add( - new HandleAttribute(PRIMARY_REFERENT_TYPE, handle, request.getPrimaryReferentType())); - - return fdoRecord; + private FdoAttribute incrementIssueNumber(FdoAttribute previousVersion, Instant timestamp) { + var previousIssueNumber = previousVersion.getValue(); + var incrementedIssueNumber = String.valueOf(Integer.parseInt(previousIssueNumber) + 1); + return new FdoAttribute(PID_RECORD_ISSUE_NUMBER, timestamp, incrementedIssueNumber); } - public List prepareDigitalMediaAttributes(DigitalMediaRequest request, - byte[] handle) throws InvalidRequestException { - var fdoRecord = prepareDoiRecordAttributes(request, handle, FdoType.DIGITAL_MEDIA); - - fdoRecord.add(new HandleAttribute(MEDIA_HOST, handle, request.getMediaHost())); - var mediaHostName = setHostName(request.getMediaHostName(), request.getMediaHost(), handle, - MEDIA_HOST_NAME); - fdoRecord.add(mediaHostName); - if (request.getMediaFormat() != null) { - fdoRecord.add(new HandleAttribute(MEDIA_FORMAT, handle, request.getMediaFormat().toString())); - } - fdoRecord.add(new HandleAttribute(IS_DERIVED_FROM_SPECIMEN, handle, - request.getIsDerivedFromSpecimen().toString())); - fdoRecord.add(new HandleAttribute(LINKED_DO_PID, handle, request.getLinkedDigitalObjectPid())); - fdoRecord.add(new HandleAttribute(LINKED_DO_TYPE, handle, - request.getLinkedDigitalObjectType().toString())); - if (request.getLinkedAttribute() != null) { - fdoRecord.add(new HandleAttribute(LINKED_ATTRIBUTE, handle, request.getLinkedAttribute())); - } - fdoRecord.add(new HandleAttribute(PRIMARY_MEDIA_ID, handle, request.getPrimaryMediaId())); - - if (request.getDcTermsType() != null) { - fdoRecord.add(new HandleAttribute(DCTERMS_TYPE, handle, request.getDcTermsType().toString())); - } - if (request.getPrimaryMediaObjectIdName() != null) { - fdoRecord.add( - new HandleAttribute(PRIMARY_MO_ID_NAME, handle, request.getPrimaryMediaObjectIdName())); - } - if (request.getPrimaryMediaObjectIdType() != null) { - fdoRecord.add(new HandleAttribute(PRIMARY_MO_ID_TYPE, handle, - request.getPrimaryMediaObjectIdType().toString())); - } - if (request.getMediaMimeType() != null) { - fdoRecord.add(new HandleAttribute(MEDIA_MIME_TYPE, handle, request.getMediaMimeType())); - } - if (request.getDerivedFromEntity() != null) { - fdoRecord.add( - new HandleAttribute(DERIVED_FROM_ENTITY, handle, request.getDerivedFromEntity())); - } - if (request.getLicenseName() != null) { - fdoRecord.add(new HandleAttribute(LICENSE_NAME, handle, request.getLicenseName())); - } - if (request.getLicense() != null) { - fdoRecord.add(new HandleAttribute(LICENSE_URL, handle, request.getLicense())); - } - var rightsholderName = setHostName(request.getRightsholderName(), request.getRightsholderPid(), - handle, RIGHTSHOLDER_NAME); - fdoRecord.add(rightsholderName); - if (request.getRightsholderPid() != null) { - fdoRecord.add(new HandleAttribute(RIGHTSHOLDER_PID, handle, request.getRightsholderPid())); - } - if (request.getRightsholderPidType() != null) { - fdoRecord.add(new HandleAttribute(RIGHTSHOLDER_PID_TYPE, handle, - request.getRightsholderPidType().toString())); - } - if (request.getDctermsConformsTo() != null) { - fdoRecord.add(new HandleAttribute(DC_TERMS_CONFORMS, handle, request.getDctermsConformsTo())); + // These attributes may change on an update + private ArrayList prepareHandleAttributesFromRequest( + HandleRecordRequest request, + String handle, + FdoType fdoType, Instant timestamp) + throws InvalidRequestException { + var handleAttributeList = new ArrayList(); + // 101: 10320/Loc + if (!fdoType.equals(ORGANISATION)) { + handleAttributeList.add(new FdoAttribute(LOC, timestamp, + setLocations(request.getLocations(), handle, fdoType))); } - return fdoRecord; + // 1: FDO Profile + handleAttributeList.add( + new FdoAttribute(FDO_PROFILE, timestamp, fdoType.getFdoProfile())); + // 3: Digital Object Type + handleAttributeList.add( + new FdoAttribute(DIGITAL_OBJECT_TYPE, timestamp, fdoType.getDigitalObjectType())); + // 4: Digital ObjectName + handleAttributeList.add( + new FdoAttribute(DIGITAL_OBJECT_NAME, timestamp, fdoType.getDigitalObjectName())); + // 6: PID Issuer + handleAttributeList.add(new FdoAttribute(PID_ISSUER, timestamp, request.getPidIssuer())); + // 7: PID Issuer Name + handleAttributeList.add(new FdoAttribute(PID_ISSUER_NAME, timestamp, + getObjectName(request.getPidIssuer(), null))); + // 8: Issued For Agent + handleAttributeList.add( + new FdoAttribute(ISSUED_FOR_AGENT, timestamp, request.getIssuedForAgent())); + // 9: Issued for Agent Name + handleAttributeList.add(new FdoAttribute(ISSUED_FOR_AGENT_NAME, timestamp, + getObjectName(request.getIssuedForAgent(), null))); + // 12: Structural Type + handleAttributeList.add( + new FdoAttribute(STRUCTURAL_TYPE, timestamp, request.getStructuralType())); + return handleAttributeList; } - public List prepareAnnotationAttributes(AnnotationRequest request, - byte[] handle) throws InvalidRequestException { - var fdoRecord = prepareHandleRecordAttributes(request, handle, FdoType.ANNOTATION); - - // 500 TargetPid - fdoRecord.add(new HandleAttribute(TARGET_PID, handle, request.getTargetPid())); - - // 501 TargetType - fdoRecord.add(new HandleAttribute(TARGET_TYPE, handle, request.getTargetType())); - - // 502 motivation - fdoRecord.add(new HandleAttribute(MOTIVATION, handle, request.getMotivation().toString())); + // These attributes do not depend on the request and do not change on an update (except issue number) + private List prepareHandleAttributesGenerated(String handle, FdoType fdoType, + Instant timestamp) { + var handleAttributeList = new ArrayList(); + // 2: FDO Record License + handleAttributeList.add( + new FdoAttribute(FDO_RECORD_LICENSE, timestamp, PID_KERNEL_METADATA_LICENSE)); + // 5: PID + handleAttributeList.add(new FdoAttribute(PID, timestamp, fdoType.getDomain() + handle)); + // 10: PID Record Issue Date + handleAttributeList.add( + new FdoAttribute(PID_RECORD_ISSUE_DATE, timestamp, getDate(timestamp))); + // 11: Pid Record Issue Number + handleAttributeList.add( + new FdoAttribute(PID_RECORD_ISSUE_NUMBER, timestamp, + "1")); // This gets replaced on an update + // 13: Pid Status + handleAttributeList.add(new FdoAttribute(PID_STATUS, timestamp, PidStatus.ACTIVE.name())); + // 100 HS Admin + handleAttributeList.add(new FdoAttribute(timestamp, applicationProperties.getPrefix())); + return handleAttributeList; + } - // 503 AnnotationHash - if (request.getAnnotationHash() != null) { - fdoRecord.add( - new HandleAttribute(ANNOTATION_HASH, handle, request.getAnnotationHash().toString())); - } - return fdoRecord; + /* DOI Record Creation */ + public FdoRecord prepareNewDoiRecord(DoiRecordRequest request, String handle, + FdoType fdoType, Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewDoiAttributes(request, handle, fdoType, timestamp); + return new FdoRecord(handle, fdoType, fdoAttributes, null); } - public List prepareMasRecordAttributes(MasRequest request, byte[] handle) + public FdoRecord prepareUpdatedDoiRecord(DoiRecordRequest request, + FdoType fdoType, Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) throws InvalidRequestException { - var fdoRecord = prepareHandleRecordAttributes(request, handle, FdoType.MAS); - fdoRecord.add(new HandleAttribute(MAS_NAME, handle, request.getMachineAnnotationServiceName())); - return fdoRecord; + var fdoAttributes = prepareUpdatedDoiAttributes(request, previousVersion.handle(), fdoType, + timestamp, + previousVersion, incrementVersion); + return new FdoRecord(previousVersion.handle(), fdoType, fdoAttributes, null); } - - public List prepareSourceSystemAttributes(SourceSystemRequest request, - byte[] handle) throws InvalidRequestException { - var fdoRecord = prepareHandleRecordAttributes(request, handle, FdoType.SOURCE_SYSTEM); - - // 600 sourceSystemName - fdoRecord.add(new HandleAttribute(SOURCE_SYSTEM_NAME, handle, request.getSourceSystemName())); - - return fdoRecord; + private List prepareNewDoiAttributes(DoiRecordRequest request, + String handle, + FdoType fdoType, Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewHandleAttributes(request, handle, fdoType, timestamp); + fdoAttributes.addAll(prepareDoiAttributesFromRequest(request, handle, timestamp)); + return fdoAttributes; } - public List prepareOrganisationAttributes(OrganisationRequest request, - byte[] handle) throws InvalidRequestException { - var fdoRecord = prepareDoiRecordAttributes(request, handle, FdoType.ORGANISATION); - - //101 10320/loc -> must contain ROR - var objectLocations = new ArrayList<>(List.of(request.getOrganisationIdentifier())); - if (request.getLocations() != null) { - objectLocations.addAll(List.of(request.getLocations())); - } - byte[] loc = setLocations(objectLocations.toArray(new String[0]), - new String(handle, StandardCharsets.UTF_8), FdoType.ORGANISATION); - fdoRecord.add(new HandleAttribute(LOC.index(), handle, LOC.get(), loc)); - - // 601 OrganisationIdentifier - fdoRecord.add( - new HandleAttribute(ORGANISATION_ID, handle, request.getOrganisationIdentifier())); - - // 602 OrganisationIdentifierType - fdoRecord.add( - new HandleAttribute(ORGANISATION_ID_TYPE, handle, request.getOrganisationIdentifierType())); + private List prepareUpdatedDoiAttributes(DoiRecordRequest request, + String handle, + FdoType fdoType, Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) + throws InvalidRequestException { + var fdoAttributes = prepareUpdatedHandleAttributes(request, handle, fdoType, timestamp, + previousVersion, + incrementVersion); + fdoAttributes.addAll(prepareDoiAttributesFromRequest(request, handle, timestamp)); + return fdoAttributes; + } - // 603 OrganisationName - var organisationName = pidResolver.getObjectName(getRor(request.getOrganisationIdentifier())); - fdoRecord.add(new HandleAttribute(ORGANISATION_NAME, handle, organisationName)); + private List prepareDoiAttributesFromRequest(DoiRecordRequest request, + String handle, + Instant timestamp) { + var handleAttributeList = new ArrayList(); + // 40: Referent Type + handleAttributeList.add( + new FdoAttribute(REFERENT_TYPE, timestamp, request.getReferentType())); + // 41: Referent DOI Name + handleAttributeList.add(new FdoAttribute(REFERENT_DOI_NAME, timestamp, handle)); + // 42: Referent Name + handleAttributeList.add( + new FdoAttribute(REFERENT_NAME, timestamp, request.getReferentName())); + // 43: Primary Referent Type + handleAttributeList.add(new FdoAttribute(PRIMARY_REFERENT_TYPE, timestamp, + request.getPrimaryReferentType())); + return handleAttributeList; + } - return fdoRecord; + /* Annotation Record Creation */ + public FdoRecord prepareNewAnnotationRecord(AnnotationRequest request, String handle, + Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewHandleAttributes(request, handle, ANNOTATION, + timestamp); + fdoAttributes.addAll(prepareAnnotationAttributesFromRequest(request, timestamp)); + var localId = + request.getAnnotationHash() != null ? request.getAnnotationHash().toString() : null; + return new FdoRecord(handle, ANNOTATION, fdoAttributes, localId); } - public List prepareDataMappingAttributes(DataMappingRequest request, - byte[] handle) + public FdoRecord prepareUpdatedAnnotationRecord(AnnotationRequest request, + Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) throws InvalidRequestException { - var fdoRecord = prepareHandleRecordAttributes(request, handle, DATA_MAPPING); + var fdoAttributes = prepareUpdatedHandleAttributes(request, previousVersion.handle(), + ANNOTATION, + timestamp, previousVersion, incrementVersion); + fdoAttributes.addAll(prepareAnnotationAttributesFromRequest(request, timestamp)); + var localId = + request.getAnnotationHash() == null ? null : request.getAnnotationHash().toString(); + return new FdoRecord(previousVersion.handle(), ANNOTATION, fdoAttributes, localId); + } - // 700 Source Data Standard - fdoRecord.add( - new HandleAttribute(SOURCE_DATA_STANDARD, handle, request.getSourceDataStandard())); + private List prepareAnnotationAttributesFromRequest( + AnnotationRequest request, + Instant timestamp) { + var handleAttributeList = new ArrayList(); + // 500 Target PID + handleAttributeList.add(new FdoAttribute(TARGET_PID, timestamp, request.getTargetPid())); + // 501 Target Type + handleAttributeList.add( + new FdoAttribute(TARGET_TYPE, timestamp, request.getTargetType())); + // 502 Motivation + handleAttributeList.add(new FdoAttribute(MOTIVATION, timestamp, request.getMotivation())); + // 503 Annotation Hash + handleAttributeList.add( + new FdoAttribute(ANNOTATION_HASH, timestamp, request.getAnnotationHash())); + return handleAttributeList; + } - return fdoRecord; + /* Data Mapping Record Creation */ + public FdoRecord prepareNewDataMappingRecord(DataMappingRequest request, String handle, + Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewHandleAttributes(request, handle, DATA_MAPPING, timestamp); + fdoAttributes.addAll(prepareDataMappingAttributesFromRequest(request, timestamp)); + return new FdoRecord(handle, DATA_MAPPING, fdoAttributes, null); } - public List prepareDigitalSpecimenRecordAttributes( - DigitalSpecimenRequest request, byte[] handle) + public FdoRecord prepareUpdatedDataMappingRecord(DataMappingRequest request, + Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) throws InvalidRequestException { - var fdoRecord = prepareDoiRecordAttributes(request, handle, DIGITAL_SPECIMEN); - - // 200: Specimen Host - fdoRecord.add(new HandleAttribute(SPECIMEN_HOST, handle, request.getSpecimenHost())); - - // 201: Specimen Host name - var specimenHostName = setHostName(request.getSpecimenHostName(), request.getSpecimenHost(), - handle, SPECIMEN_HOST_NAME); - fdoRecord.add(specimenHostName); - - // 202: primarySpecimenObjectId - fdoRecord.add(new HandleAttribute(PRIMARY_SPECIMEN_OBJECT_ID, handle, - request.getPrimarySpecimenObjectId())); + var fdoAttributes = prepareUpdatedHandleAttributes(request, previousVersion.handle(), + DATA_MAPPING, timestamp, + previousVersion, incrementVersion); + fdoAttributes.addAll(prepareDataMappingAttributesFromRequest(request, timestamp)); + return new FdoRecord(previousVersion.handle(), DATA_MAPPING, fdoAttributes, null); + } - // 203: primarySpecimenObjectIdType - fdoRecord.add(new HandleAttribute(PRIMARY_SPECIMEN_OBJECT_ID_TYPE, handle, - request.getPrimarySpecimenObjectIdType().toString())); + private List prepareDataMappingAttributesFromRequest(DataMappingRequest request, + Instant timestamp) { + return List.of( + new FdoAttribute(SOURCE_DATA_STANDARD, timestamp, request.getSourceDataStandard())); + } - // 204-217 are optional + /* Digital Specimen Record Creation */ + public FdoRecord prepareNewDigitalSpecimenRecord(DigitalSpecimenRequest request, + String handle, Instant timestamp) + throws InvalidRequestException, JsonProcessingException { + var fdoAttributes = prepareNewDoiAttributes(request, handle, DIGITAL_SPECIMEN, timestamp); + fdoAttributes.addAll(prepareDigitalSpecimenAttributesFromRequest(request, timestamp)); + return new FdoRecord(handle, DIGITAL_SPECIMEN, fdoAttributes, + request.getNormalisedPrimarySpecimenObjectId()); + } - // 204: primarySpecimenObjectIdName - if (request.getPrimarySpecimenObjectIdName() != null) { - fdoRecord.add(new HandleAttribute(PRIMARY_SPECIMEN_OBJECT_ID_NAME, handle, - request.getPrimarySpecimenObjectIdName())); - } + public FdoRecord prepareUpdatedDigitalSpecimenRecord( + DigitalSpecimenRequest request, Instant timestamp, FdoRecord previousVersion, + boolean incrementVersion) + throws InvalidRequestException, JsonProcessingException { + var fdoAttributes = prepareUpdatedDoiAttributes(request, previousVersion.handle(), + DIGITAL_SPECIMEN, timestamp, previousVersion, incrementVersion); + fdoAttributes.addAll(prepareDigitalSpecimenAttributesFromRequest(request, timestamp)); + return new FdoRecord(previousVersion.handle(), DIGITAL_SPECIMEN, fdoAttributes, + request.getNormalisedPrimarySpecimenObjectId()); + } - // 205 normalisedSpecimenObjectId - fdoRecord.add(new HandleAttribute(NORMALISED_SPECIMEN_OBJECT_ID, handle, + private List prepareDigitalSpecimenAttributesFromRequest( + DigitalSpecimenRequest request, Instant timestamp) + throws InvalidRequestException, JsonProcessingException { + var handleAttributeList = new ArrayList(); + // 200 Specimen Host + handleAttributeList.add( + new FdoAttribute(SPECIMEN_HOST, timestamp, request.getSpecimenHost())); + // 201 Specimen Host Name + handleAttributeList.add(new FdoAttribute(SPECIMEN_HOST_NAME, timestamp, + getObjectName(request.getSpecimenHost(), request.getSpecimenHostName()))); + // 202 Primary Specimen Object ID + handleAttributeList.add(new FdoAttribute(PRIMARY_SPECIMEN_OBJECT_ID, timestamp, + getObjectName(request.getSpecimenHost(), request.getPrimarySpecimenObjectId()))); + // 203 Primary Specimen Object ID Type + handleAttributeList.add(new FdoAttribute(PRIMARY_SPECIMEN_OBJECT_ID_TYPE, timestamp, + getObjectName(request.getSpecimenHost(), + request.getPrimarySpecimenObjectIdType().toString()))); + // 204 Primary Specimen Object ID Name + handleAttributeList.add(new FdoAttribute(PRIMARY_SPECIMEN_OBJECT_ID_NAME, timestamp, + request.getPrimarySpecimenObjectIdName())); + // 205 Normalised Specimen Object Id + handleAttributeList.add(new FdoAttribute(NORMALISED_SPECIMEN_OBJECT_ID, timestamp, request.getNormalisedPrimarySpecimenObjectId())); + // 206 Specimen Object Id Absence Reason + handleAttributeList.add(new FdoAttribute(SPECIMEN_OBJECT_ID_ABSENCE_REASON, timestamp, + request.getSpecimenObjectIdAbsenceReason())); + // 206 Specimen Object Id Absence Reason + handleAttributeList.add(new FdoAttribute(SPECIMEN_OBJECT_ID_ABSENCE_REASON, timestamp, + request.getSpecimenObjectIdAbsenceReason())); + // 207 Other Specimen Ids + if (request.getOtherSpecimenIds() != null && !request.getOtherSpecimenIds().isEmpty()) { + handleAttributeList.add(new FdoAttribute(OTHER_SPECIMEN_IDS, timestamp, + mapper.writeValueAsString(request.getOtherSpecimenIds()))); + } else { + handleAttributeList.add(new FdoAttribute(OTHER_SPECIMEN_IDS, timestamp, + null)); + } + // 208 Topic Origin + handleAttributeList.add(new FdoAttribute(TOPIC_ORIGIN, timestamp, request.getTopicOrigin())); + // 209 Topic Domain + handleAttributeList.add( + new FdoAttribute(TOPIC_DOMAIN, timestamp, request.getTopicDomain())); + // 210 Topic Discipline + handleAttributeList.add(new FdoAttribute(TOPIC_DISCIPLINE, timestamp, + request.getTopicDiscipline())); + // 211 Topic Category + handleAttributeList.add(new FdoAttribute(TOPIC_CATEGORY, timestamp, + request.getTopicCategory())); + // 212 Living or Preserved + handleAttributeList.add(new FdoAttribute(LIVING_OR_PRESERVED, timestamp, + request.getLivingOrPreserved())); + // 213 Base Type of Specimen + handleAttributeList.add(new FdoAttribute(BASE_TYPE_OF_SPECIMEN, timestamp, + request.getBaseTypeOfSpecimen())); + // 214 Information Artefact Type + handleAttributeList.add(new FdoAttribute(INFORMATION_ARTEFACT_TYPE, timestamp, + request.getInformationArtefactType())); + // 215 Material Sample Type + handleAttributeList.add(new FdoAttribute(MATERIAL_SAMPLE_TYPE, timestamp, + request.getMaterialSampleType())); + // 216 Material or Digital Entity + handleAttributeList.add(new FdoAttribute(MATERIAL_OR_DIGITAL_ENTITY, timestamp, + request.getMaterialOrDigitalEntity())); + // 217 Marked as Type + handleAttributeList.add(new FdoAttribute(MARKED_AS_TYPE, timestamp, request.getMarkedAsType())); + // 218 Was Derived From Entity + handleAttributeList.add( + new FdoAttribute(WAS_DERIVED_FROM_ENTITY, timestamp, + String.valueOf(request.getDerivedFromEntity() != null))); + // 219 Catalog ID + handleAttributeList.add( + new FdoAttribute(CATALOG_IDENTIFIER, timestamp, request.getCatalogIdentifier())); + return handleAttributeList; + } - // 206: specimenObjectIdAbsenceReason - if (request.getPrimarySpecimenObjectIdAbsenceReason() != null) { - fdoRecord.add(new HandleAttribute(SPECIMEN_OBJECT_ID_ABSENCE_REASON, handle, - request.getPrimarySpecimenObjectIdAbsenceReason())); - } - - // 207: otherSpecimenIds - if (request.getOtherSpecimenIds() != null) { - try { - var otherSpecimenIds = mapper.writeValueAsString(request.getOtherSpecimenIds()); - fdoRecord.add(new HandleAttribute(OTHER_SPECIMEN_IDS, handle, otherSpecimenIds)); - } catch (JsonProcessingException e) { - log.warn("Unable to parse otherSpecimenIds {} to string", request.getOtherSpecimenIds(), e); - } - } - - // 208: topicOrigin - if (request.getTopicOrigin() != null) { - fdoRecord.add(new HandleAttribute(TOPIC_ORIGIN, handle, request.getTopicOrigin().toString())); - } - - // 209: topicDomain - var topicDomain = request.getTopicDomain(); - if (topicDomain != null) { - fdoRecord.add(new HandleAttribute(TOPIC_DOMAIN, handle, topicDomain.toString())); - } - - // 210: topicDiscipline - var topicDisc = request.getTopicDiscipline(); - if (topicDisc != null) { - fdoRecord.add(new HandleAttribute(TOPIC_DISCIPLINE, handle, topicDisc.toString())); - } + /* MAS Record Creation */ + public FdoRecord prepareNewMasRecord(MasRequest request, String handle, + Instant timestamp) + throws InvalidRequestException { + var fdoAttributes = prepareNewHandleAttributes(request, handle, MAS, timestamp); + fdoAttributes.addAll(prepareMasAttributesFromRequest(request, timestamp)); + return new FdoRecord(handle, MAS, fdoAttributes, null); + } - // 211 topicCategory - var topicCategory = request.getTopicCategory(); - if (topicCategory != null) { - fdoRecord.add(new HandleAttribute(TOPIC_CATEGORY, handle, topicCategory.toString())); - } + public FdoRecord prepareUpdatedMasRecord(MasRequest request, + Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) + throws InvalidRequestException { + var fdoAttributes = prepareUpdatedHandleAttributes(request, previousVersion.handle(), MAS, + timestamp, + previousVersion, incrementVersion); + fdoAttributes.addAll(prepareMasAttributesFromRequest(request, timestamp)); + return new FdoRecord(previousVersion.handle(), MAS, fdoAttributes, null); + } - // 212: livingOrPreserved - var livingOrPres = request.getLivingOrPreserved(); - if (livingOrPres != null) { - fdoRecord.add(new HandleAttribute(LIVING_OR_PRESERVED, handle, livingOrPres.toString())); - } + private List prepareMasAttributesFromRequest(MasRequest request, + Instant timestamp) { + return List.of( + new FdoAttribute(MAS_NAME, timestamp, request.getMachineAnnotationServiceName())); + } - // 213 baseTypeOfSpecimen - var baseType = request.getBaseTypeOfSpecimen(); - if (baseType != null) { - fdoRecord.add(new HandleAttribute(BASE_TYPE_OF_SPECIMEN, handle, baseType.toString())); - } + /* Media Object Record Creation */ + public FdoRecord prepareNewDigitalMediaRecord(DigitalMediaRequest request, + String handle, Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewDoiAttributes(request, handle, DIGITAL_MEDIA, timestamp); + fdoAttributes.addAll(prepareDigitalMediaAttributesFromRequest(request, timestamp)); + return new FdoRecord(handle, DIGITAL_MEDIA, fdoAttributes, request.getPrimaryMediaId()); + } - // 214: informationArtefactType - var artType = request.getInformationArtefactType(); - if (artType != null) { - fdoRecord.add(new HandleAttribute(INFORMATION_ARTEFACT_TYPE, handle, artType.toString())); - } + public FdoRecord prepareUpdatedDigitalMediaRecord(DigitalMediaRequest request, + Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) + throws InvalidRequestException { + var fdoAttributes = prepareUpdatedDoiAttributes(request, previousVersion.handle(), + DIGITAL_MEDIA, timestamp, + previousVersion, incrementVersion); + fdoAttributes.addAll(prepareDigitalMediaAttributesFromRequest(request, timestamp)); + return new FdoRecord(previousVersion.handle(), DIGITAL_MEDIA, fdoAttributes, + request.getPrimaryMediaId()); + } - // 215: materialSampleType - var matSamp = request.getMaterialSampleType(); - if (matSamp != null) { - fdoRecord.add(new HandleAttribute(MATERIAL_SAMPLE_TYPE, handle, matSamp.toString())); - } + private List prepareDigitalMediaAttributesFromRequest( + DigitalMediaRequest request, Instant timestamp) + throws InvalidRequestException { + var handleAttributeList = new ArrayList(); + // 400 Media Host + handleAttributeList.add( + new FdoAttribute(MEDIA_HOST, timestamp, request.getMediaHost())); + // 401 MediaHostName + handleAttributeList.add( + new FdoAttribute(MEDIA_HOST_NAME, timestamp, + getObjectName(request.getMediaHost(), request.getMediaHostName()))); + // 403 Is Derived From Specimen + handleAttributeList.add( + new FdoAttribute(IS_DERIVED_FROM_SPECIMEN, timestamp, + String.valueOf(request.getIsDerivedFromSpecimen()))); + // 404 Linked Digital Object PID + handleAttributeList.add( + new FdoAttribute(LINKED_DO_PID, timestamp, request.getLinkedDigitalObjectPid())); + // 405 Linked Digital Object Type + handleAttributeList.add( + new FdoAttribute(LINKED_DO_TYPE, timestamp, request.getLinkedDigitalObjectType())); + // 406 Linked Attribute + handleAttributeList.add( + new FdoAttribute(LINKED_ATTRIBUTE, timestamp, request.getLinkedAttribute())); + // 407 Primary Media ID + handleAttributeList.add( + new FdoAttribute(PRIMARY_MEDIA_ID, timestamp, request.getPrimaryMediaId())); + // 408 Primary Media Object Id Type + handleAttributeList.add( + new FdoAttribute(PRIMARY_MO_ID_TYPE, timestamp, + request.getPrimaryMediaObjectIdType())); + // 409 Primary Media Object Id Name + handleAttributeList.add( + new FdoAttribute(PRIMARY_MO_ID_NAME, timestamp, + request.getPrimaryMediaObjectIdName())); + // 410 dcterms:type + handleAttributeList.add( + new FdoAttribute(DCTERMS_TYPE, timestamp, request.getDcTermsType())); + // 411 dcterms:subject + handleAttributeList.add( + new FdoAttribute(DCTERMS_SUBJECT, timestamp, request.getDctermsSubject())); + // 412 dcterms:format + handleAttributeList.add( + new FdoAttribute(DCTERMS_FORMAT, timestamp, + request.getDctermsFormat())); + // 413 Derived from Entity + handleAttributeList.add( + new FdoAttribute(DERIVED_FROM_ENTITY, timestamp, request.getDerivedFromEntity())); + // 414 License Name + handleAttributeList.add( + new FdoAttribute(LICENSE_NAME, timestamp, request.getLicenseName())); + // 415 License URL + handleAttributeList.add(new FdoAttribute(LICENSE_URL, timestamp, request.getLicenseName())); + // 416 RightsholderName + handleAttributeList.add( + new FdoAttribute(RIGHTSHOLDER_NAME, timestamp, request.getRightsholderName())); + // 417 Rightsholder PID + handleAttributeList.add( + new FdoAttribute(RIGHTSHOLDER_PID, timestamp, request.getRightsholderPid())); + // 418 RightsholderPidType + handleAttributeList.add(new FdoAttribute(RIGHTSHOLDER_PID_TYPE, timestamp, + request.getRightsholderPidType())); + // 419 dcterms:conformsTo + handleAttributeList.add( + new FdoAttribute(DC_TERMS_CONFORMS, timestamp, request.getDctermsConformsTo())); + return handleAttributeList; + } - // 216: materialOrDigitalEntity - if (request.getMaterialOrDigitalEntity() != null) { - fdoRecord.add(new HandleAttribute(MATERIAL_OR_DIGITAL_ENTITY, handle, - request.getMaterialOrDigitalEntity().toString())); - } + /* Organisation Record Creation */ + public FdoRecord prepareNewOrganisationRecord(OrganisationRequest request, String handle, + Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewDoiAttributes(request, handle, ORGANISATION, timestamp); + fdoAttributes.addAll(prepareOrganisationAttributesFromRequest(request, handle, timestamp)); + return new FdoRecord(handle, ORGANISATION, fdoAttributes, null); + } - // 217: markedAsType - var markedAsType = request.getMarkedAsType(); - if (markedAsType != null) { - fdoRecord.add(new HandleAttribute(MARKED_AS_TYPE, handle, markedAsType.toString())); - } + public FdoRecord prepareUpdatedOrganisationRecord(OrganisationRequest request, + Instant timestamp, FdoRecord previousVersion, boolean incrementVersion) + throws InvalidRequestException { + var fdoAttributes = prepareUpdatedDoiAttributes(request, previousVersion.handle(), + ORGANISATION, timestamp, previousVersion, incrementVersion); + fdoAttributes.addAll( + prepareOrganisationAttributesFromRequest(request, previousVersion.handle(), timestamp)); + return new FdoRecord(previousVersion.handle(), ORGANISATION, fdoAttributes, null); + } - // 218: wasDerivedFromEntity - var wasDerivedFrom = request.getDerivedFromEntity(); - if (wasDerivedFrom != null) { - fdoRecord.add(new HandleAttribute(WAS_DERIVED_FROM_ENTITY, handle, wasDerivedFrom)); - } + private List prepareOrganisationAttributesFromRequest( + OrganisationRequest request, + String handle, + Instant timestamp) throws InvalidRequestException { + var handleAttributeList = new ArrayList(); + // 101 10320/loc -> includes organisation ROR + var userLocations = concatLocations(request.getLocations(), + List.of(request.getOrganisationIdentifier())); + handleAttributeList.add( + new FdoAttribute(LOC, timestamp, setLocations(userLocations, handle, ORGANISATION))); + // 601 Organisation Identifier + handleAttributeList.add( + new FdoAttribute(ORGANISATION_ID, timestamp, request.getOrganisationIdentifier())); + // 602 Organisation Identifier type + handleAttributeList.add(new FdoAttribute(ORGANISATION_ID_TYPE, timestamp, + request.getOrganisationIdentifierType())); + // 603 Organisation Name + handleAttributeList.add(new FdoAttribute(ORGANISATION_NAME, timestamp, + getObjectName(request.getOrganisationIdentifier(), null))); + return handleAttributeList; + } - // 219 catalogId - var catId = request.getCatalogIdentifier(); - if (catId != null) { - fdoRecord.add(new HandleAttribute(CATALOG_IDENTIFIER, handle, catId)); - } + /* Source System Record Creation */ + public FdoRecord prepareNewSourceSystemRecord(SourceSystemRequest request, String handle, + Instant timestamp) throws InvalidRequestException { + var fdoAttributes = prepareNewHandleAttributes(request, handle, SOURCE_SYSTEM, timestamp); + fdoAttributes.addAll(prepareSourceSystemAttributesFromRequest(request, timestamp)); + return new FdoRecord(handle, SOURCE_SYSTEM, fdoAttributes, null); + } - return fdoRecord; + public FdoRecord prepareUpdatedSourceSystemRecord(SourceSystemRequest request, Instant timestamp, + FdoRecord previousVersion, boolean incrementVersion) throws InvalidRequestException { + var fdoAttributes = prepareUpdatedHandleAttributes(request, previousVersion.handle(), + SOURCE_SYSTEM, + timestamp, previousVersion, incrementVersion); + fdoAttributes.addAll(prepareSourceSystemAttributesFromRequest(request, timestamp)); + return new FdoRecord(previousVersion.handle(), SOURCE_SYSTEM, fdoAttributes, null); } - private HandleAttribute setHostName(String hostName, String hostId, byte[] handle, - FdoProfile targetAttribute) throws PidResolutionException { - if (hostName != null) { - return new HandleAttribute(targetAttribute, handle, hostName); - } else { - String hostNameResolved; - if (hostId.contains(ROR_DOMAIN)) { - hostNameResolved = pidResolver.getObjectName(hostId.replace(ROR_DOMAIN, ROR_API_DOMAIN)); - return new HandleAttribute(targetAttribute, handle, hostNameResolved); - } else if (hostId.contains(WIKIDATA_DOMAIN)) { - hostNameResolved = pidResolver.resolveQid(hostId.replace(WIKIDATA_DOMAIN, WIKIDATA_API)); - } else { - log.error("Specimen host ID {} is neither QID nor ROR.", hostId); - throw new PidResolutionException("Invalid host id: " + hostId); - } - return new HandleAttribute(targetAttribute, handle, hostNameResolved); - } + /* Tombstone Record Creation */ + public FdoRecord prepareTombstoneRecord(TombstoneRecordRequest recordRequest, Instant timestamp, + FdoRecord previousVersion) throws JsonProcessingException { + var fdoAttributes = prepareTombstoneAttributes(recordRequest, timestamp, previousVersion); + return new FdoRecord(previousVersion.handle(), previousVersion.fdoType(), fdoAttributes, null); } - public List prepareUpdateAttributes(byte[] handle, JsonNode requestAttributes, - FdoType type) throws InvalidRequestException { - requestAttributes = setLocationXmlFromJson(requestAttributes, - new String(handle, StandardCharsets.UTF_8), type); - Map updateRequestMap = mapper.convertValue(requestAttributes, - new TypeReference>() { - }); - try { - var updatedAttributeList = new ArrayList<>( - updateRequestMap.entrySet().stream() - .filter(entry -> !entry.getValue().isNull()) - .map(entry -> new HandleAttribute(FdoProfile.retrieveIndex(entry.getKey()), handle, - entry.getKey(), - getUpdateAttributeAsByte(entry.getValue()))) - .toList()); - updatedAttributeList.addAll(addResolvedNames(updateRequestMap, handle)); - return updatedAttributeList; - } catch (InvalidRequestRuntimeException e) { - throw new InvalidRequestException("Unable to parse update request"); + private List prepareTombstoneAttributes(TombstoneRecordRequest request, + Instant timestamp, FdoRecord previousVersion) throws JsonProcessingException { + var handleAttributeList = new ArrayList<>(previousVersion.attributes()); + var previousIssueNum = getField(previousVersion.attributes(), PID_RECORD_ISSUE_NUMBER); + var newIssueNum = incrementIssueNumber(previousIssueNum, timestamp); + var previousStatus = getField(previousVersion.attributes(), PID_STATUS); + var newStatus = new FdoAttribute(PID_STATUS, timestamp, PidStatus.TOMBSTONED); + handleAttributeList.set(handleAttributeList.indexOf(previousIssueNum), newIssueNum); + handleAttributeList.set(handleAttributeList.indexOf(previousStatus), newStatus); + // 30: Tombstoned Text + handleAttributeList.add( + new FdoAttribute(TOMBSTONED_TEXT, timestamp, request.getTombstonedText())); + // 31: hasRelatedPID + if (request.getHasRelatedPID() != null && !request.getHasRelatedPID().isEmpty()) { + handleAttributeList.add(new FdoAttribute(HAS_RELATED_PID, timestamp, + mapper.writeValueAsString(request.getHasRelatedPID()))); + } else { + handleAttributeList.add(new FdoAttribute(HAS_RELATED_PID, timestamp, + mapper.writeValueAsString(Collections.emptyList()))); } + // 32: tombstonedDate + handleAttributeList.add(new FdoAttribute(TOMBSTONED_DATE, timestamp, getDate(timestamp))); + return handleAttributeList; } - private byte[] getUpdateAttributeAsByte(BaseJsonNode attribute) { - try { - if (attribute instanceof TextNode) { - return attribute.asText().getBytes(StandardCharsets.UTF_8); - } else { - return mapper.writeValueAsString(attribute).getBytes(StandardCharsets.UTF_8); - } - } catch (JsonProcessingException e) { - log.error("Unable to parse update request", e); - throw new InvalidRequestRuntimeException(); - } + private List prepareSourceSystemAttributesFromRequest( + SourceSystemRequest request, + Instant timestamp) { + return List.of( + new FdoAttribute(SOURCE_SYSTEM_NAME, timestamp, request.getSourceSystemName())); } - private List addResolvedNames(Map updateRequestMap, - byte[] handle) throws InvalidRequestException { - var resolvableKeys = updateRequestMap.entrySet().stream().filter( - entry -> RESOLVABLE_KEYS.containsKey(entry.getKey()) && !hasResolvedPairInRequest( - updateRequestMap, entry.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - if (resolvableKeys.isEmpty()) { - return new ArrayList<>(); + private String getObjectName(String url, String name) throws InvalidRequestException { + if (name != null) { + return name; } - ArrayList resolvedPidNameAttributes = new ArrayList<>(); - for (var resolvableKey : resolvableKeys.entrySet()) { - var targetAttribute = RESOLVABLE_KEYS.get(resolvableKey.getKey()); - var resolvedPid = getObjectName(resolvableKey.getValue().asText()); - resolvedPidNameAttributes.add(new HandleAttribute(FdoProfile.retrieveIndex(targetAttribute), - handle, targetAttribute, resolvedPid.getBytes(StandardCharsets.UTF_8))); + if (url.contains(ROR_DOMAIN)) { + return pidResolver.getObjectName(getRor(url)); + } else if (url.contains(HANDLE_DOMAIN) || url.contains(DOI_DOMAIN)) { + return pidResolver.getObjectName(url); + } else if (url.contains(WIKIDATA_DOMAIN)) { + return pidResolver.resolveQid(url.replace(WIKIDATA_DOMAIN, WIKIDATA_API)); } - return resolvedPidNameAttributes; - } - - private boolean hasResolvedPairInRequest(Map updateRequestMap, - String pidToResolve) { - var targetName = RESOLVABLE_KEYS.get(pidToResolve); - return updateRequestMap.containsKey(targetName); + throw new InvalidRequestException(String.format(PROXY_ERROR, url, + (ROR_DOMAIN + ", " + HANDLE_DOMAIN + ", or " + DOI_DOMAIN))); } - public List prepareTombstoneAttributes(byte[] handle, - JsonNode requestAttributes) throws InvalidRequestException { - var tombstoneAttributes = new ArrayList<>( - prepareUpdateAttributes(handle, requestAttributes, FdoType.TOMBSTONE)); - tombstoneAttributes.add(new HandleAttribute(PID_STATUS, handle, "ARCHIVED")); - tombstoneAttributes.add(genLandingPage(handle)); - return tombstoneAttributes; + private static String getRor(String url) throws InvalidRequestException { + if (!url.contains(ROR_DOMAIN)) { + throw new InvalidRequestException(String.format(PROXY_ERROR, url, ROR_DOMAIN)); + } + return url.replace(ROR_DOMAIN, ROR_API_DOMAIN); } - private HandleAttribute genLandingPage(byte[] handle) throws InvalidRequestException { - var landingPage = new String[]{"Placeholder landing page"}; - var data = setLocations(landingPage, new String(handle, StandardCharsets.UTF_8), - FdoType.TOMBSTONE); - return new HandleAttribute(LOC.index(), handle, LOC.get(), data); + private String getDate(Instant timestamp) { + return dt.format(timestamp); } - private JsonNode setLocationXmlFromJson(JsonNode request, String handle, FdoType type) - throws InvalidRequestException { - // Format request so that the given locations array is formatted according to 10320/loc specifications - if (request.findValue(LOC_REQUEST) == null) { - return request; - } - JsonNode locNode = request.get(LOC_REQUEST); - ObjectNode requestObjectNode = request.deepCopy(); - try { - String[] locArr = mapper.treeToValue(locNode, String[].class); - requestObjectNode.put(LOC.get(), - new String(setLocations(locArr, handle, type), StandardCharsets.UTF_8)); - requestObjectNode.remove(LOC_REQUEST); - } catch (IOException e) { - throw new InvalidRequestException( - "An error has occurred parsing \"locations\" array. " + e.getMessage()); + private List defaultLocations(String handle, FdoType fdoType) { + switch (fdoType) { + case DIGITAL_SPECIMEN -> { + String api = applicationProperties.getApiUrl() + "/specimens/" + handle; + String ui = applicationProperties.getUiUrl() + "/ds/" + handle; + return List.of(api, ui); + } + case DATA_MAPPING -> { + return List.of(applicationProperties.getOrchestrationUrl() + "/mapping/" + handle); + } + case SOURCE_SYSTEM -> { + return List.of(applicationProperties.getOrchestrationUrl() + "/source-system/" + handle); + } + case DIGITAL_MEDIA -> { + String api = applicationProperties.getApiUrl() + "/digitalMedia/" + handle; + String ui = applicationProperties.getUiUrl() + "/dm/" + handle; + return List.of(api, ui); + } + case ANNOTATION -> { + return List.of(applicationProperties.getApiUrl() + "/annotations/" + handle); + } + case MAS -> { + return List.of(applicationProperties.getOrchestrationUrl() + "/mas/" + handle); + } + default -> { + // Handle, DOI, Organisation (Org locations are all in userLocations) + return Collections.emptyList(); + } } - return requestObjectNode; } - private String getDate() { - return dt.format(Instant.now()); + private String documentToString(org.w3c.dom.Document document) throws TransformerException { + var transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(document), new StreamResult(writer)); + return writer.getBuffer().toString(); } - public byte[] setLocations(String[] userLocations, String handle, FdoType type) - throws InvalidRequestException { + private String setLocations(@Nullable String[] userLocations, String handle, FdoType type) + throws InvalidRequestException { DocumentBuilder documentBuilder; try { documentBuilder = dbf.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new InvalidRequestException(e.getMessage()); } - var doc = documentBuilder.newDocument(); var locations = doc.createElement(LOC_REQUEST); doc.appendChild(locations); - String[] objectLocations = concatLocations(userLocations, handle, type); - + String[] objectLocations = concatLocations(userLocations, defaultLocations(handle, type)); + if (objectLocations.length == 0) { + return ""; + } for (int i = 0; i < objectLocations.length; i++) { var locs = doc.createElement("location"); locs.setAttribute("id", String.valueOf(i)); @@ -668,56 +790,18 @@ public byte[] setLocations(String[] userLocations, String handle, FdoType type) locations.appendChild(locs); } try { - return documentToString(doc).getBytes(StandardCharsets.UTF_8); + return documentToString(doc); } catch (TransformerException e) { throw new InvalidRequestException("An error has occurred parsing location data"); } } - private String[] concatLocations(String[] userLocations, String handle, FdoType type) { - var objectLocations = new ArrayList<>(List.of(defaultLocations(handle, type))); + private String[] concatLocations(String[] userLocations, List defaultLocations) { + var objectLocations = new ArrayList<>(defaultLocations); if (userLocations != null) { objectLocations.addAll(List.of(userLocations)); } return objectLocations.toArray(new String[0]); } - private String[] defaultLocations(String handle, FdoType type) { - switch (type) { - case DIGITAL_SPECIMEN -> { - String api = appProperties.getApiUrl() + "/specimens/" + handle; - String ui = appProperties.getUiUrl() + "/ds/" + handle; - return new String[]{ui, api}; - } - case DATA_MAPPING -> { - return new String[]{appProperties.getOrchestrationUrl() + "/mapping/" + handle}; - } - case SOURCE_SYSTEM -> { - return new String[]{appProperties.getOrchestrationUrl() + "/source-system/" + handle}; - } - case DIGITAL_MEDIA -> { - String api = appProperties.getApiUrl() + "/digitalMedia/" + handle; - String ui = appProperties.getUiUrl() + "/dm/" + handle; - return new String[]{ui, api}; - } - case ANNOTATION -> { - return new String[]{appProperties.getApiUrl() + "/annotations/" + handle}; - } - case MAS -> { - return new String[]{appProperties.getOrchestrationUrl() + "/mas/" + handle}; - } - default -> { - // Handle, DOI, Organisation (organisation handled separately) - return new String[]{}; - } - } - } - - private String documentToString(Document document) throws TransformerException { - var transformer = tf.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - StringWriter writer = new StringWriter(); - transformer.transform(new DOMSource(document), new StreamResult(writer)); - return writer.getBuffer().toString(); - } } diff --git a/src/main/java/eu/dissco/core/handlemanager/service/HandleService.java b/src/main/java/eu/dissco/core/handlemanager/service/HandleService.java index b7fc4bc9..29c95674 100644 --- a/src/main/java/eu/dissco/core/handlemanager/service/HandleService.java +++ b/src/main/java/eu/dissco/core/handlemanager/service/HandleService.java @@ -4,6 +4,7 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoType.HANDLE; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ATTRIBUTES; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_DATA; +import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ID; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -17,16 +18,19 @@ import eu.dissco.core.handlemanager.domain.fdo.OrganisationRequest; import eu.dissco.core.handlemanager.domain.fdo.SourceSystemRequest; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperWrite; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import eu.dissco.core.handlemanager.exceptions.PidResolutionException; +import eu.dissco.core.handlemanager.exceptions.UnprocessableEntityException; import eu.dissco.core.handlemanager.properties.ProfileProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; +import eu.dissco.core.handlemanager.repository.MongoRepository; import java.time.Instant; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -35,128 +39,273 @@ @Profile(Profiles.HANDLE) public class HandleService extends PidService { - public HandleService(PidRepository pidRepository, FdoRecordService fdoRecordService, - PidNameGeneratorService hf, ObjectMapper mapper, ProfileProperties profileProperties) { - super(pidRepository, fdoRecordService, hf, mapper, profileProperties); + public HandleService(FdoRecordService fdoRecordService, + PidNameGeneratorService hf, ObjectMapper mapper, ProfileProperties profileProperties, + MongoRepository mongoRepository) { + super(fdoRecordService, hf, mapper, profileProperties, mongoRepository); } // Pid Record Creation @Override public JsonApiWrapperWrite createRecords(List requests) throws InvalidRequestException { - var handles = hf.genHandleList(requests.size()).iterator(); + var handles = hf.generateNewHandles(requests.size()).iterator(); var requestAttributes = requests.stream() .map(request -> request.get(NODE_DATA).get(NODE_ATTRIBUTES)).toList(); - var type = getObjectTypeFromJsonNode(requests); - List handleAttributes; + var fdoType = getObjectTypeFromJsonNode(requests); + List fdoRecords; + List fdoDocuments; try { - switch (type) { - case ANNOTATION -> handleAttributes = createAnnotation(requestAttributes, handles); - case DIGITAL_SPECIMEN -> - handleAttributes = createDigitalSpecimen(requestAttributes, handles); - case DOI -> handleAttributes = createDoi(requestAttributes, handles); - case HANDLE -> handleAttributes = createHandle(requestAttributes, handles); - case DATA_MAPPING -> handleAttributes = createMapping(requestAttributes, handles); - case MAS -> handleAttributes = createMas(requestAttributes, handles); - case DIGITAL_MEDIA -> handleAttributes = createDigitalMedia(requestAttributes, handles); - case ORGANISATION -> handleAttributes = createOrganisation(requestAttributes, handles); - case SOURCE_SYSTEM -> handleAttributes = createSourceSystem(requestAttributes, handles); + switch (fdoType) { + case ANNOTATION -> fdoRecords = createAnnotation(requestAttributes, handles); + case DIGITAL_SPECIMEN -> fdoRecords = createDigitalSpecimen(requestAttributes, handles); + case DOI -> fdoRecords = createDoi(requestAttributes, handles); + case HANDLE -> fdoRecords = createHandle(requestAttributes, handles); + case DATA_MAPPING -> fdoRecords = createDataMapping(requestAttributes, handles); + case MAS -> fdoRecords = createMas(requestAttributes, handles); + case DIGITAL_MEDIA -> fdoRecords = createDigitalMedia(requestAttributes, handles); + case ORGANISATION -> fdoRecords = createOrganisation(requestAttributes, handles); + case SOURCE_SYSTEM -> fdoRecords = createSourceSystem(requestAttributes, handles); default -> throw new UnsupportedOperationException("Unrecognized type"); } + fdoDocuments = toMongoDbDocument(fdoRecords); } catch (JsonProcessingException | PidResolutionException e) { log.error("An error has occurred in processing request", e); throw new InvalidRequestException( "An error has occurred parsing a record in request. More information: " + e.getMessage()); } - log.info("Persisting new handles to db"); - pidRepository.postAttributesToDb(Instant.now().getEpochSecond(), handleAttributes); - return new JsonApiWrapperWrite(formatCreateRecords(handleAttributes, type)); + log.info("Persisting new handles to Document Store"); + mongoRepository.postHandleRecords(fdoDocuments); + return new JsonApiWrapperWrite(formatFdoRecord(fdoRecords, fdoType)); + } + + @Override + public JsonApiWrapperWrite updateRecords(List requests, boolean incrementVersion) + throws UnprocessableEntityException, InvalidRequestException { + var updateRequests = requests.stream() + .map(request -> request.get(NODE_DATA)).toList(); + var fdoRecordMap = processUpdateRequest(updateRequests); + var fdoType = getObjectTypeFromJsonNode(requests); + List fdoRecords; + List fdoDocuments; + try { + switch (fdoType) { + case ANNOTATION -> + fdoRecords = updateAnnotation(updateRequests, fdoRecordMap, incrementVersion); + case DIGITAL_SPECIMEN -> + fdoRecords = updateDigitalSpecimen(updateRequests, fdoRecordMap, incrementVersion); + case DOI -> fdoRecords = updateDoi(updateRequests, fdoRecordMap, incrementVersion); + case HANDLE -> fdoRecords = updateHandle(updateRequests, fdoRecordMap, incrementVersion); + case DATA_MAPPING -> + fdoRecords = updateDataMapping(updateRequests, fdoRecordMap, incrementVersion); + case MAS -> fdoRecords = updateMas(updateRequests, fdoRecordMap, incrementVersion); + case DIGITAL_MEDIA -> + fdoRecords = updateDigitalMedia(updateRequests, fdoRecordMap, incrementVersion); + case ORGANISATION -> + fdoRecords = updateOrganisation(updateRequests, fdoRecordMap, incrementVersion); + case SOURCE_SYSTEM -> + fdoRecords = updateSourceSystem(updateRequests, fdoRecordMap, incrementVersion); + default -> throw new UnsupportedOperationException("Unrecognized type"); + } + fdoDocuments = toMongoDbDocument(fdoRecords); + mongoRepository.updateHandleRecords(fdoDocuments); + return new JsonApiWrapperWrite(formatFdoRecord(fdoRecords, fdoType)); + } catch (JsonProcessingException e) { + log.error("An error has occurred processing JSON data", e); + throw new InvalidRequestException("Unable to parse FDO Record"); + } } - private List createAnnotation(List requestAttributes, - Iterator handleIterator) + private List createAnnotation(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, AnnotationRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareAnnotationAttributes(requestObject, thisHandle)); + fdoRecords.add( + fdoRecordService.prepareNewAnnotationRecord(requestObject, handleIterator.next(), + timestamp)); } - return handleAttributes; + return fdoRecords; } - private List createDoi(List requestAttributes, - Iterator handleIterator) + private List updateAnnotation(List updateRequests, + Map previousVersionMap, boolean incrementVersion) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), AnnotationRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedAnnotationRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); + } + return fdoRecords; + } + + private List createDoi(List requestAttributes, + Iterator handleIterator) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, DoiRecordRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareDoiRecordAttributes(requestObject, thisHandle, DOI)); + fdoRecords.add(fdoRecordService.prepareNewDoiRecord(requestObject, handleIterator.next(), + DOI, timestamp)); } - return handleAttributes; + return fdoRecords; } - private List createHandle(List requestAttributes, - Iterator handleIterator) + private List updateDoi(List updateRequests, + Map previousVersionMap, boolean incrementVersion) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), DoiRecordRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedDoiRecord(requestObject, DOI, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); + } + return fdoRecords; + } + + private List createHandle(List requestAttributes, + Iterator handleIterator) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, HandleRecordRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareHandleRecordAttributes(requestObject, thisHandle, HANDLE)); + fdoRecords.add(fdoRecordService.prepareNewHandleRecord(requestObject, handleIterator.next(), + HANDLE, timestamp)); } - return handleAttributes; + return fdoRecords; } - private List createMapping(List requestAttributes, - Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + private List updateHandle(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), + HandleRecordRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedHandleRecord(requestObject, HANDLE, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); + } + return fdoRecords; + } + + private List createDataMapping(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, DataMappingRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareDataMappingAttributes(requestObject, thisHandle)); + fdoRecords.add( + fdoRecordService.prepareNewDataMappingRecord(requestObject, handleIterator.next(), + timestamp)); } - return handleAttributes; + return fdoRecords; } - private List createMas(List requestAttributes, - Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + private List updateDataMapping(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), + DataMappingRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedDataMappingRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); + } + return fdoRecords; + } + + private List createMas(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, MasRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareMasRecordAttributes(requestObject, thisHandle)); + fdoRecords.add(fdoRecordService.prepareNewMasRecord(requestObject, handleIterator.next(), + timestamp)); + } + return fdoRecords; + } + + private List updateMas(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), MasRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedMasRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); } - return handleAttributes; + return fdoRecords; } - private List createOrganisation(List requestAttributes, - Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + private List createOrganisation(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, OrganisationRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareOrganisationAttributes(requestObject, thisHandle)); + fdoRecords.add( + fdoRecordService.prepareNewOrganisationRecord(requestObject, handleIterator.next(), + timestamp)); + } + return fdoRecords; + } + + private List updateOrganisation(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), + OrganisationRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedOrganisationRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); } - return handleAttributes; + return fdoRecords; } - private List createSourceSystem(List requestAttributes, - Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + private List createSourceSystem(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); var requestObject = mapper.treeToValue(request, SourceSystemRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareSourceSystemAttributes(requestObject, thisHandle)); + fdoRecords.add( + fdoRecordService.prepareNewSourceSystemRecord(requestObject, handleIterator.next(), + timestamp)); + } + return fdoRecords; + } + + private List updateSourceSystem(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), + SourceSystemRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedSourceSystemRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); } - return handleAttributes; + return fdoRecords; } } diff --git a/src/main/java/eu/dissco/core/handlemanager/service/PidNameGeneratorService.java b/src/main/java/eu/dissco/core/handlemanager/service/PidNameGeneratorService.java index addae022..126aa912 100644 --- a/src/main/java/eu/dissco/core/handlemanager/service/PidNameGeneratorService.java +++ b/src/main/java/eu/dissco/core/handlemanager/service/PidNameGeneratorService.java @@ -1,10 +1,9 @@ package eu.dissco.core.handlemanager.service; import eu.dissco.core.handlemanager.properties.ApplicationProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; +import eu.dissco.core.handlemanager.repository.MongoRepository; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; @@ -23,26 +22,22 @@ public class PidNameGeneratorService { private final ApplicationProperties applicationProperties; private static final int LENGTH = 11; - private static final String ALPHA_NUM = "ABCDEFGHJKLMNPQRSTVWXYZ1234567890"; - private final char[] symbols = ALPHA_NUM.toCharArray(); + private static final char[] SYMBOLS = "ABCDEFGHJKLMNPQRSTVWXYZ1234567890".toCharArray(); private final char[] buf = new char[LENGTH]; - private final PidRepository pidRepository; + private final MongoRepository mongoRepository; private final Random random; - public List genHandleList(int h) { - return unwrapBytes((HashSet) genHandleHash(h)); - } - - private List unwrapBytes(HashSet handleHash) { - List handleList = new ArrayList<>(); - for (ByteBuffer hash : handleHash) { - handleList.add(hash.array()); + public Set generateNewHandles(int h) { + if (h > applicationProperties.getMaxHandles()) { + log.warn("Max number of handles exceeded. Generating maximum {} handles instead", + applicationProperties.getMaxHandles()); + h = applicationProperties.getMaxHandles(); } - return handleList; + return genHandleHashSet(h); } - private Set genHandleHash(int h) { + private Set genHandleHashSet(int h) { /* * Generates a HashSet of minted handles of size h Calls the handlefactory @@ -52,93 +47,61 @@ private Set genHandleHash(int h) { */ // Generate h number of bytes and wrap it into a HashSet - List handleList = newHandle(h); - - HashSet handleHash = wrapBytes(handleList); + var handleList = newHandles(h); + var handleSet = new HashSet<>(handleList); // Check for duplicates from repository and wrap the duplicates - HashSet duplicates = wrapBytes(pidRepository.getHandlesExist(handleList)); + var duplicates = new HashSet<>(mongoRepository.getExistingHandles(handleList)); // If a duplicate was found, recursively call this function // Generate new handles for every duplicate found and add it to our hash list if (!duplicates.isEmpty()) { - handleHash.removeAll(duplicates); - handleHash.addAll(genHandleHash(duplicates.size())); + handleSet.removeAll(duplicates); + handleSet.addAll(genHandleHashSet(duplicates.size())); } - /* * It's possible we have a collision within our list now i.e. on two different * recursive cal)ls to this function, we generate the same If this occurs, we * will not have our expected number of handles */ - while (h > handleHash.size()) { - handleHash.addAll(genHandleHash(h - handleHash.size())); + while (h > handleSet.size()) { + handleSet.addAll(genHandleHashSet(h - handleSet.size())); } - return handleHash; + return handleSet; } - // Converting between List - /* - * List <----> HashSet HashSets are useful for preventing - * collisions within the list List is used to interface with repository - * layer - */ - - // Converts List --> HashSet - private HashSet wrapBytes(List byteList) { - HashSet byteHash = new HashSet<>(); - for (byte[] bytes : byteList) { - byteHash.add(ByteBuffer.wrap(bytes)); + private List newHandles(int numberOfHandles) { // Generates h number of handles + if (numberOfHandles < 1) { + return Collections.emptyList(); } - return byteHash; - } - - private String newSuffix() { - for (int idx = 0; idx < buf.length; ++idx) { - if (idx == 3 || idx == 7) { // - buf[idx] = '-'; // Sneak a lil dash in the middle - } else { - buf[idx] = symbols[random.nextInt(symbols.length)]; + // We'll use this to make sure we're not duplicating results + HashSet handleHash = new HashSet<>(); + // This is the object we'll actually return + var handleList = new ArrayList(); + String hdl; + for (int i = 0; i < numberOfHandles; i++) { + hdl = newHandle(); + while (!handleHash.add(hdl)) { + hdl = newHandle(); } + handleList.add(hdl); } - return new String(buf); + return handleList; } private String newHandle() { return applicationProperties.getPrefix() + "/" + newSuffix(); } - private byte[] newHandleBytes() { - return newHandle().getBytes(StandardCharsets.UTF_8); - } - - private List newHandle(int numberOfHandles) { // Generates h number of handles - if (numberOfHandles < 1) { - return new ArrayList<>(); - } - if (numberOfHandles > applicationProperties.getMaxHandles()) { - log.warn("Max number of handles exceeded. Generating maximum {} handles instead", - applicationProperties.getMaxHandles()); - numberOfHandles = applicationProperties.getMaxHandles(); - } - - // We'll use this to make sure we're not duplicating results - // It's of type ByteBuffer and not byte[] because ByteBuffer has equality testing - // byte[] is too primitive for our needs - HashSet handleHash = new HashSet<>(); - - // This is the object we'll actually return - List handleList = new ArrayList<>(); - byte[] hdl; - - for (int i = 0; i < numberOfHandles; i++) { - hdl = newHandleBytes(); - while (!handleHash.add(ByteBuffer.wrap(hdl))) { - hdl = newHandleBytes(); + private String newSuffix() { + for (int idx = 0; idx < buf.length; ++idx) { + if (idx == 3 || idx == 7) { // + buf[idx] = '-'; // Sneak a lil dash in the middle + } else { + buf[idx] = SYMBOLS[random.nextInt(SYMBOLS.length)]; } - handleList.add(hdl); } - return handleList; + return new String(buf); } } diff --git a/src/main/java/eu/dissco/core/handlemanager/service/PidService.java b/src/main/java/eu/dissco/core/handlemanager/service/PidService.java index 7b57be85..24deab6e 100644 --- a/src/main/java/eu/dissco/core/handlemanager/service/PidService.java +++ b/src/main/java/eu/dissco/core/handlemanager/service/PidService.java @@ -1,19 +1,20 @@ package eu.dissco.core.handlemanager.service; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ANNOTATION_HASH; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DIGITAL_OBJECT_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.LINKED_DO_PID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID_STATUS; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_MEDIA_ID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_SPECIMEN_OBJECT_ID; import static eu.dissco.core.handlemanager.domain.fdo.FdoType.ANNOTATION; import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_MEDIA; import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_SPECIMEN; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.TOMBSTONE; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ATTRIBUTES; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_DATA; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ID; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_TYPE; +import static eu.dissco.core.handlemanager.service.ServiceUtils.getField; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -21,21 +22,24 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import eu.dissco.core.handlemanager.domain.fdo.DigitalMediaRequest; import eu.dissco.core.handlemanager.domain.fdo.DigitalSpecimenRequest; +import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; import eu.dissco.core.handlemanager.domain.fdo.FdoType; +import eu.dissco.core.handlemanager.domain.fdo.TombstoneRecordRequest; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.PidStatus; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiDataLinks; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiLinks; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperRead; -import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperReadSingle; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperWrite; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import eu.dissco.core.handlemanager.exceptions.PidResolutionException; import eu.dissco.core.handlemanager.exceptions.UnprocessableEntityException; import eu.dissco.core.handlemanager.properties.ProfileProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; -import java.nio.charset.StandardCharsets; +import eu.dissco.core.handlemanager.repository.MongoRepository; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -44,218 +48,219 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; @Slf4j @RequiredArgsConstructor public abstract class PidService { - protected final PidRepository pidRepository; protected final FdoRecordService fdoRecordService; protected final PidNameGeneratorService hf; protected final ObjectMapper mapper; protected final ProfileProperties profileProperties; + protected final MongoRepository mongoRepository; - private List formatRecords(List dbRecord) { - var handleMap = mapRecords(dbRecord); - return handleMap.values().stream().map(this::jsonFormatSingleRecord).toList(); - } - - protected JsonNode jsonFormatSingleRecord(List dbRecord) { + protected JsonNode jsonFormatSingleRecord(List fdoAttributes) { ObjectNode rootNode = mapper.createObjectNode(); - for (var row : dbRecord) { - if (row.getIndex() != HS_ADMIN.index()) { - var rowData = new String(row.getData(), StandardCharsets.UTF_8); - try { - var nodeData = mapper.readTree(rowData); - rootNode.set(row.getType(), nodeData); - } catch (JsonProcessingException ignored) { - rootNode.put(row.getType(), rowData); - } - } - } + fdoAttributes.stream() + .filter(attribute -> attribute.getIndex() != HS_ADMIN.index()) + .forEach(attribute -> setNodeData(attribute, rootNode)); return rootNode; } - protected Map> mapRecords(List flatList) { - return flatList.stream() - .collect(Collectors.groupingBy(row -> new String(row.getHandle(), StandardCharsets.UTF_8))); - } - - private JsonApiDataLinks wrapResolvedData(JsonNode recordAttributes, String recordType) { - String pidLink = recordAttributes.get(PID.get()).asText(); - String pidName = getPidName(pidLink); - var handleLink = new JsonApiLinks(pidLink); - return new JsonApiDataLinks(pidName, recordType, recordAttributes, handleLink); + protected JsonNode jsonFormatSingleRecord(List fdoAttributes, + List keyAttributes) { + ObjectNode rootNode = mapper.createObjectNode(); + var indexList = keyAttributes.stream().map(FdoProfile::index).toList(); + fdoAttributes.stream() + .filter(attribute -> indexList.contains(attribute.getIndex())) + .forEach(attribute -> setNodeData(attribute, rootNode)); + return rootNode; } - private String getPidName(String pidLink) { - return pidLink.substring(profileProperties.getDomain().length()); + private void setNodeData(FdoAttribute attribute, ObjectNode rootNode) { + if (attribute.getValue() == null) { + rootNode.set(attribute.getType(), mapper.nullNode()); + } else { + try { + var nodeData = mapper.readTree(attribute.getValue()); + rootNode.set(attribute.getType(), nodeData); + } catch (JsonProcessingException ignored) { + rootNode.put(attribute.getType(), attribute.getValue()); + } + } } - protected List formatCreateRecords(List dbRecord, - FdoType objectType) { - var handleMap = mapRecords(dbRecord); - switch (objectType) { + protected List formatFdoRecord(List fdoRecords, + FdoType fdoType) { + switch (fdoType) { case ANNOTATION -> { - return formatCreateRecordsAnnotation(handleMap); + return formatAnnotationResponse(fdoRecords); } case DIGITAL_SPECIMEN -> { - return formatCreateRecordsSpecimen(handleMap); + return formatSpecimenResponse(fdoRecords); } case DIGITAL_MEDIA -> { - return formatCreateRecordsMedia(handleMap); + return formatMediaResponse(fdoRecords); } default -> { - return formatCreateRecordsDefault(handleMap, objectType); + return formatFullRecordResponse(fdoRecords); } } } - private List formatCreateRecordsAnnotation( - Map> handleMap) { + private List formatAnnotationResponse(List fdoRecords) { List dataLinksList = new ArrayList<>(); - for (var handleRecord : handleMap.entrySet()) { - var hashRow = handleRecord.getValue().stream() - .filter(row -> row.getType().equals(ANNOTATION_HASH.get())).findFirst(); - var subRecord = hashRow.map(List::of).orElse(handleRecord.getValue()); - var rootNode = jsonFormatSingleRecord(subRecord); - String pidLink = profileProperties.getDomain() + handleRecord.getKey(); + for (var handleRecord : fdoRecords) { + JsonNode attributeNode; + if (handleRecord.primaryLocalId() == null) { + attributeNode = jsonFormatSingleRecord(handleRecord.attributes()); + } else { + attributeNode = jsonFormatSingleRecord(handleRecord.attributes(), + List.of(ANNOTATION_HASH)); + } + String pidLink = profileProperties.getDomain() + handleRecord.handle(); dataLinksList.add( - new JsonApiDataLinks(handleRecord.getKey(), ANNOTATION.getDigitalObjectType(), rootNode, + new JsonApiDataLinks(handleRecord.handle(), ANNOTATION.getDigitalObjectType(), + attributeNode, new JsonApiLinks(pidLink))); } return dataLinksList; } - private List formatCreateRecordsSpecimen( - Map> handleMap) { + private List formatSpecimenResponse(List fdoRecords) { List dataLinksList = new ArrayList<>(); - for (var handleRecord : handleMap.entrySet()) { - var subRecord = handleRecord.getValue().stream() - .filter(row -> row.getType().equals(PRIMARY_SPECIMEN_OBJECT_ID.get())).toList(); - var rootNode = jsonFormatSingleRecord(subRecord); - String pidLink = profileProperties.getDomain() + handleRecord.getKey(); + for (var handleRecord : fdoRecords) { + var attributeNode = jsonFormatSingleRecord(handleRecord.attributes(), + List.of(NORMALISED_SPECIMEN_OBJECT_ID)); + String pidLink = profileProperties.getDomain() + handleRecord.handle(); dataLinksList.add( - new JsonApiDataLinks(handleRecord.getKey(), DIGITAL_SPECIMEN.getDigitalObjectType(), - rootNode, new JsonApiLinks(pidLink))); + new JsonApiDataLinks(handleRecord.handle(), DIGITAL_SPECIMEN.getDigitalObjectType(), + attributeNode, new JsonApiLinks(pidLink))); } return dataLinksList; } - private List formatCreateRecordsMedia( - Map> handleMap) { + private List formatMediaResponse(List fdoRecords) { List dataLinksList = new ArrayList<>(); - for (var handleRecord : handleMap.entrySet()) { - var subRecord = handleRecord.getValue().stream().filter( - row -> row.getType().equals(PRIMARY_MEDIA_ID.get()) || row.getType() - .equals(LINKED_DO_PID.get())).toList(); - var rootNode = jsonFormatSingleRecord(subRecord); - String pidLink = profileProperties.getDomain() + handleRecord.getKey(); + for (var handleRecord : fdoRecords) { + var attributeNode = jsonFormatSingleRecord(handleRecord.attributes(), + List.of(PRIMARY_MEDIA_ID, LINKED_DO_PID)); + String pidLink = profileProperties.getDomain() + handleRecord.handle(); dataLinksList.add( - new JsonApiDataLinks(handleRecord.getKey(), DIGITAL_MEDIA.getDigitalObjectType(), - rootNode, - new JsonApiLinks(pidLink))); + new JsonApiDataLinks(handleRecord.handle(), DIGITAL_MEDIA.getDigitalObjectType(), + attributeNode, new JsonApiLinks(pidLink))); } return dataLinksList; } - private List formatCreateRecordsDefault( - Map> handleMap, FdoType objectType) { + private List formatFullRecordResponse(List fdoRecords) { List dataLinksList = new ArrayList<>(); - for (var handleRecord : handleMap.entrySet()) { - var rootNode = jsonFormatSingleRecord(handleRecord.getValue()); - String pidLink = profileProperties.getDomain() + handleRecord.getKey(); + for (var fdoRecord : fdoRecords) { + var rootNode = jsonFormatSingleRecord(fdoRecord.attributes()); + String pidLink = profileProperties.getDomain() + fdoRecord.handle(); dataLinksList.add( - new JsonApiDataLinks(handleRecord.getKey(), objectType.getDigitalObjectType(), rootNode, + new JsonApiDataLinks(fdoRecord.handle(), fdoRecord.fdoType().getDigitalObjectType(), + rootNode, new JsonApiLinks(pidLink))); } return dataLinksList; } - private JsonApiWrapperWrite formatArchives(List> archiveRecords) { - List dataList = new ArrayList<>(); - for (var archiveRecord : archiveRecords) { - String handle = new String(archiveRecord.get(0).getHandle(), StandardCharsets.UTF_8); - var attributeNode = jsonFormatSingleRecord(archiveRecord); - dataList.add( - new JsonApiDataLinks(handle, FdoType.TOMBSTONE.getDigitalObjectType(), attributeNode, - new JsonApiLinks(profileProperties.getDomain() + handle))); - } - return new JsonApiWrapperWrite(dataList); - } - - public JsonApiWrapperReadSingle resolveSingleRecord(byte[] handle, String path) - throws PidResolutionException { - var dbRecord = pidRepository.resolveHandleAttributes(handle); - verifyHandleResolution(List.of(handle), dbRecord); - var recordAttributeList = formatRecords(dbRecord).get(0); - var dataNode = wrapResolvedData(recordAttributeList, getRecordTypeFromResolvedRecord(dbRecord)); - var linksNode = new JsonApiLinks(path); - return new JsonApiWrapperReadSingle(linksNode, dataNode); - } - - private String getRecordTypeFromResolvedRecord(List dbRecord) { - var type = dbRecord.stream().filter(row -> row.getType().equals(DIGITAL_OBJECT_TYPE.get())) - .map(val -> new String(val.getData(), StandardCharsets.UTF_8)).findFirst(); - return type.orElse(FdoType.HANDLE.getDigitalObjectType()); - } - - public JsonApiWrapperRead resolveBatchRecord(List handles, String path) - throws PidResolutionException { - var dbRecords = pidRepository.resolveHandleAttributes(handles); - verifyHandleResolution(handles, dbRecords); - var recordAttributeList = formatRecords(dbRecords); - var dataList = recordAttributeList.stream().map( - recordAttributes -> wrapResolvedData(recordAttributes, - getRecordTypeFromResolvedRecord(dbRecords))).toList(); - return new JsonApiWrapperRead(new JsonApiLinks(path), dataList); - } - // Getters - public List getHandlesPaged(int pageNum, int pageSize, byte[] pidStatus) { - return pidRepository.getAllHandles(pidStatus, pageNum, pageSize); - } - - public List getHandlesPaged(int pageNum, int pageSize) { - return pidRepository.getAllHandles(pageNum, pageSize); + public JsonApiWrapperRead resolveSingleRecord(String handle, String path) + throws PidResolutionException { + return resolveBatchRecord(List.of(handle), path); } - private void verifyHandleResolution(List handles, List dbRecords) + public JsonApiWrapperRead resolveBatchRecord(List handles, String path) throws PidResolutionException { - var resolvedHandles = dbRecords.stream().map(HandleAttribute::getHandle) - .map(handle -> new String(handle, StandardCharsets.UTF_8)).collect(Collectors.toSet()); - if (handles.size() == resolvedHandles.size()) { - return; + List fdoRecords; + try { + fdoRecords = mongoRepository.getHandleRecords(handles); + } catch (JsonProcessingException e) { + log.error("JsonProcessingException when resolving handles {}", handles, e); + throw new PidResolutionException("Unable to resolve handle records for handles: " + handles); } - var handlesString = handles.stream().map(handle -> new String(handle, StandardCharsets.UTF_8)) - .collect(Collectors.toSet()); - handlesString.removeAll(resolvedHandles); - log.error("Unable to resolve the following identifiers: {}", handlesString); - throw new PidResolutionException("PIDs not found: " + handlesString); + if (fdoRecords.size() < handles.size()) { + var hdl = new ArrayList<>(handles); + var missingHandles = hdl.removeAll(fdoRecords.stream().map(FdoRecord::handle).toList()); + log.error("Some handles do not exist: {}", missingHandles); + throw new PidResolutionException( + "Attempting to resolve handles that do not exist: \n" + missingHandles); + } + return new JsonApiWrapperRead(new JsonApiLinks(path), formatFullRecordResponse(fdoRecords)); } public JsonApiWrapperWrite searchByPhysicalSpecimenId(String normalisedPhysicalId) throws PidResolutionException { - var returnedRows = pidRepository.searchByNormalisedPhysicalIdentifierFullRecord( - List.of(normalisedPhysicalId.getBytes(StandardCharsets.UTF_8))); - var handleNames = returnedRows.stream() - .map(row -> new String(row.getHandle(), StandardCharsets.UTF_8)) - .collect(Collectors.toSet()); - if (handleNames.size() > 1) { + List specimen; + try { + specimen = mongoRepository.searchByPrimaryLocalId(NORMALISED_SPECIMEN_OBJECT_ID.get(), + List.of(normalisedPhysicalId)); + if (specimen.size() != 1) { + if (specimen.size() > 1) { + log.error("Multiple fdo records found for normalised specimen id {}", + normalisedPhysicalId); + } + throw new PidResolutionException( + "Unable to resolve specimen with id" + normalisedPhysicalId); + } + } catch (JsonProcessingException e) { + log.error( + "JsonProcessingException while reading record for specimen with normalised object id {}", + normalisedPhysicalId, e); throw new PidResolutionException( - "More than one handle record corresponds to the provided collection facility and physical identifier."); + "Unable to resolve specimen with with id " + normalisedPhysicalId); } - List dataNode = new ArrayList<>(); - - var jsonFormattedRecord = jsonFormatSingleRecord(returnedRows); - dataNode.add(wrapResolvedData(jsonFormattedRecord, DIGITAL_SPECIMEN.getDigitalObjectType())); - return new JsonApiWrapperWrite(dataNode); + return new JsonApiWrapperWrite(formatFullRecordResponse(specimen)); } // Create public abstract JsonApiWrapperWrite createRecords(List requests) throws InvalidRequestException, UnprocessableEntityException; + protected List getPreviousVersions(List handles) + throws InvalidRequestException { + List previousVersions; + try { + previousVersions = mongoRepository.getHandleRecords(handles); + } catch (JsonProcessingException e) { + throw new InvalidRequestException("Unable to process handles resolution"); + } + if (previousVersions.size() < handles.size()) { + throw new InvalidRequestException("Unable to resolve all handles"); + } + return previousVersions; + } + + // Update + public abstract JsonApiWrapperWrite updateRecords(List requests, + boolean incrementVersion) + throws InvalidRequestException, UnprocessableEntityException; + + protected void checkHandlesWritable(List previousVersions) + throws InvalidRequestException { + for (var fdoRecord : previousVersions) { + var pidStatus = getField(fdoRecord.attributes(), PID_STATUS).getValue(); + if (PidStatus.TOMBSTONED.name().equals(pidStatus)) { + log.error("Attempting to update a FDO record that has been archived"); + throw new InvalidRequestException("This PID has already been archived. It is read-only"); + } + } + } + + protected Map processUpdateRequest(List updateRequests) + throws InvalidRequestException { + var handles = updateRequests.stream() + .map(request -> request.get(NODE_ID).asText()).toList(); + checkInternalDuplicates(handles); + var previousVersions = getPreviousVersions(handles); + checkHandlesWritable(previousVersions); + return previousVersions.stream() + .collect(Collectors.toMap(FdoRecord::handle, f -> f)); + } + protected FdoType getObjectTypeFromJsonNode(List requests) { var types = requests.stream().map(request -> request.get(NODE_DATA).get(NODE_TYPE).asText()) .collect(Collectors.toSet()); @@ -266,173 +271,169 @@ protected FdoType getObjectTypeFromJsonNode(List requests) { return FdoType.fromString(type.get()); } - protected List createDigitalSpecimen(List requestAttributes, - Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { + protected List createDigitalSpecimen(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { var specimenRequests = new ArrayList(); for (var request : requestAttributes) { specimenRequests.add(mapper.treeToValue(request, DigitalSpecimenRequest.class)); } if (specimenRequests.isEmpty()) { - return new ArrayList<>(); + return Collections.emptyList(); } - verifySpecimensAreNew(specimenRequests); - var handleAttributes = new ArrayList(); + verifyObjectsAreNew(specimenRequests + .stream() + .map(DigitalSpecimenRequest::getNormalisedPrimarySpecimenObjectId) + .toList()); + var fdoRecords = new ArrayList(); + var timestamp = Instant.now(); for (var request : specimenRequests) { - var thisHandle = handleIterator.next(); - handleAttributes.addAll( - fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, thisHandle)); + fdoRecords.add( + fdoRecordService.prepareNewDigitalSpecimenRecord(request, handleIterator.next(), + timestamp)); } - return handleAttributes; + return fdoRecords; } - private void verifySpecimensAreNew(List requests) - throws InvalidRequestException { - var normalisedIds = requests.stream().map( - request -> request.getNormalisedPrimarySpecimenObjectId().getBytes(StandardCharsets.UTF_8)) - .toList(); - var existingHandles = pidRepository.searchByNormalisedPhysicalIdentifier(normalisedIds); - if (!existingHandles.isEmpty()) { - log.error("Unable to create new handles, as "); - var handleMap = existingHandles.stream().collect( - Collectors.toMap(ha -> new String(ha.getHandle(), StandardCharsets.UTF_8), - ha -> new String(ha.getData(), StandardCharsets.UTF_8))); - log.error( - "Unable to create new handles, as they already exist. Verify the following identifiers: {}", - handleMap); - throw new InvalidRequestException( - "Attempting to create handle records for specimens already in system"); + protected List updateDigitalSpecimen(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), + DigitalSpecimenRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedDigitalSpecimenRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); } + return fdoRecords; } - - protected List createDigitalMedia(List requestAttributes, - Iterator handleIterator) + protected List createDigitalMedia(List requestAttributes, + Iterator handleIterator) throws JsonProcessingException, InvalidRequestException { - List handleAttributes = new ArrayList<>(); + List fdoRecords = new ArrayList<>(); + List mediaRequests = new ArrayList<>(); + var timestamp = Instant.now(); for (var request : requestAttributes) { - var thisHandle = handleIterator.next(); - var requestObject = mapper.treeToValue(request, DigitalMediaRequest.class); - handleAttributes.addAll( - fdoRecordService.prepareDigitalMediaAttributes(requestObject, thisHandle)); + mediaRequests.add(mapper.treeToValue(request, DigitalMediaRequest.class)); } - return handleAttributes; - } - - // Update - public JsonApiWrapperWrite updateRecords(List> attributesToUpdate, - boolean incrementVersion, FdoType recordType) throws InvalidRequestException { - var recordTimestamp = Instant.now().getEpochSecond(); - var handles = attributesToUpdate.stream().map(pidRecord -> pidRecord.get(0).getHandle()) - .toList(); - checkInternalDuplicates(handles); - checkHandlesWritable(handles); - log.info("Writing updates to db"); - pidRepository.updateRecordBatch(recordTimestamp, attributesToUpdate, incrementVersion); - return formatUpdates(handles.stream().map(h -> new String(h, StandardCharsets.UTF_8)).toList(), - recordType); + if (mediaRequests.isEmpty()) { + return Collections.emptyList(); + } + verifyObjectsAreNew(mediaRequests + .stream() + .map(DigitalMediaRequest::getPrimaryMediaId) + .toList()); + for (var mediaRequest : mediaRequests) { + fdoRecords.add( + fdoRecordService.prepareNewDigitalMediaRecord(mediaRequest, handleIterator.next(), + timestamp)); + } + return fdoRecords; } - public JsonApiWrapperWrite updateRecords(List requests, boolean incrementVersion) - throws InvalidRequestException, UnprocessableEntityException { - List> attributesToUpdate = getAttributesToUpdate(requests); - var recordType = getObjectTypeFromJsonNode(requests); - return updateRecords(attributesToUpdate, incrementVersion, recordType); + protected List updateDigitalMedia(List updateRequests, + Map previousVersionMap, boolean incrementVersion) + throws JsonProcessingException, InvalidRequestException { + List fdoRecords = new ArrayList<>(); + var timestamp = Instant.now(); + for (var request : updateRequests) { + var requestObject = mapper.treeToValue(request.get(NODE_ATTRIBUTES), + DigitalMediaRequest.class); + fdoRecords.add( + fdoRecordService.prepareUpdatedDigitalMediaRecord(requestObject, timestamp, + previousVersionMap.get(request.get(NODE_ID).asText()), incrementVersion)); + } + return fdoRecords; } - protected List> getAttributesToUpdate(List requests) - throws InvalidRequestException { - List> attributesToUpdate = new ArrayList<>(); - for (JsonNode root : requests) { - JsonNode data = root.get(NODE_DATA); - byte[] handle = data.get(NODE_ID).asText().getBytes(StandardCharsets.UTF_8); - JsonNode requestAttributes = data.get(NODE_ATTRIBUTES); - FdoType type = FdoType.fromString(data.get(NODE_TYPE).asText()); - var attributes = fdoRecordService.prepareUpdateAttributes(handle, requestAttributes, type); - attributesToUpdate.add(attributes); + private void verifyObjectsAreNew(List normalisedIds) + throws InvalidRequestException, JsonProcessingException { + var existingHandles = mongoRepository + .searchByPrimaryLocalId(NORMALISED_SPECIMEN_OBJECT_ID.get(), normalisedIds); + if (!existingHandles.isEmpty()) { + var handleMap = existingHandles.stream() + .collect(Collectors.toMap( + FdoRecord::handle, + FdoRecord::primaryLocalId)); + log.error( + "Unable to create new handles, as they already exist. Verify the following identifiers: {}", + handleMap); + throw new InvalidRequestException( + "Attempting to create handle records for specimens already in system"); } - return attributesToUpdate; } - protected void checkInternalDuplicates(List handles) throws InvalidRequestException { - Set handlesToUpdateStr = handles.stream() - .map(h -> new String(h, StandardCharsets.UTF_8)).collect(Collectors.toSet()); - if (handlesToUpdateStr.size() < handles.size()) { - Set duplicateHandles = findDuplicates(handles, handlesToUpdateStr); + protected void checkInternalDuplicates(List handles) throws InvalidRequestException { + Set handlesToUpdate = new HashSet<>(handles); + if (handlesToUpdate.size() < handles.size()) { + Set duplicateHandles = handles.stream() + .filter(i -> Collections.frequency(handles, i) > 1) + .collect(Collectors.toSet()); throw new InvalidRequestException( "INVALID INPUT. Attempting to update the same record multiple times in one request. " + "The following handles are duplicated in the request: " + duplicateHandles); } } - private Set findDuplicates(List handles, Set handlesToUpdate) { - Set duplicateHandles = new HashSet<>(); - for (byte[] handle : handles) { - if (!handlesToUpdate.add(new String(handle, StandardCharsets.UTF_8))) { - duplicateHandles.add(new String(handle, StandardCharsets.UTF_8)); + // Tombstone + public JsonApiWrapperWrite tombstoneRecords(List requests) + throws InvalidRequestException { + var tombstoneRequestData = requests.stream() + .map(request -> request.get(NODE_DATA)).toList(); + var fdoRecordMap = processUpdateRequest(tombstoneRequestData); + var fdoRecords = new ArrayList(); + var timestamp = Instant.now(); + List fdoDocuments; + try { + for (var requestData : tombstoneRequestData) { + var tombstoneRequest = mapper.treeToValue(requestData.get(NODE_ATTRIBUTES), + TombstoneRecordRequest.class); + fdoRecords.add(fdoRecordService.prepareTombstoneRecord(tombstoneRequest, timestamp, + fdoRecordMap.get(requestData.get(NODE_ID).asText()))); } + fdoDocuments = toMongoDbDocument(fdoRecords); + } catch (JsonProcessingException e) { + log.error("JsonProcessingException while tombstoning records", e); + throw new InvalidRequestException("Unable to read request"); } - return duplicateHandles; + mongoRepository.updateHandleRecords(fdoDocuments); + return new JsonApiWrapperWrite(formatFdoRecord(fdoRecords, TOMBSTONE)); } - protected JsonApiWrapperWrite formatUpdates(List handles, FdoType type) { - List dataList = new ArrayList<>(); - for (var handle : handles) { - dataList.add(new JsonApiDataLinks(handle, type.getDigitalObjectType(), null, - new JsonApiLinks(profileProperties.getDomain() + handle))); - } - return new JsonApiWrapperWrite(dataList); + public void rollbackHandles(List handles) { + mongoRepository.rollbackHandles(handles); } - protected void checkHandlesWritable(List handles) throws PidResolutionException { - Set handlesToUpdate = new HashSet<>(handles); - Set handlesExist = new HashSet<>(pidRepository.checkHandlesWritable(handles)); - if (handlesExist.size() < handles.size()) { - handlesToUpdate.removeAll(handlesExist); - Set handlesDontExist = handlesToUpdate.stream() - .map(h -> new String(h, StandardCharsets.UTF_8)).collect(Collectors.toSet()); - throw new PidResolutionException( - "INVALID INPUT. One or more identifiers in request do not exist or are archived. Verify the following handle(s): " - + handlesDontExist); - } + public void rollbackHandlesFromPhysId(List physicalIds) { + mongoRepository.rollbackHandles(NORMALISED_SPECIMEN_OBJECT_ID.get(), physicalIds); } - // Archive - public JsonApiWrapperWrite archiveRecordBatch(List requests) - throws InvalidRequestException { - var recordTimestamp = Instant.now().getEpochSecond(); - List handles = new ArrayList<>(); - var archiveAttributesFlat = new ArrayList(); - var archiveAttributes = new ArrayList>(); - - for (JsonNode root : requests) { - JsonNode data = root.get(NODE_DATA); - JsonNode requestAttributes = data.get(NODE_ATTRIBUTES); - var handle = data.get(NODE_ID).asText().getBytes(StandardCharsets.UTF_8); - handles.add(handle); - var recordAttributes = fdoRecordService.prepareTombstoneAttributes(handle, requestAttributes); - archiveAttributesFlat.addAll(recordAttributes); - archiveAttributes.add(recordAttributes); - } - - checkInternalDuplicates(handles); - checkHandlesWritable(handles); - - pidRepository.archiveRecords(recordTimestamp, archiveAttributesFlat, - handles.stream().map(h -> new String(h, StandardCharsets.UTF_8)).toList()); - return formatArchives(archiveAttributes); + protected List toMongoDbDocument(List fdoRecords) + throws JsonProcessingException { + var documentList = new ArrayList(); + for (var fdoRecord : fdoRecords) { + var doc = Document.parse(mapper.writeValueAsString(fdoRecord)); + addLocalId(fdoRecord, doc); + documentList.add(doc); + } + return documentList; } - public void rollbackHandles(List handles) { - pidRepository.rollbackHandles(handles); + private void addLocalId(FdoRecord fdoRecord, Document doc) { + if (fdoRecord.primaryLocalId() == null) { + return; + } + if (DIGITAL_SPECIMEN.equals(fdoRecord.fdoType())) { + doc.append(NORMALISED_SPECIMEN_OBJECT_ID.get(), fdoRecord.primaryLocalId()); + } else if (DIGITAL_MEDIA.equals(fdoRecord.fdoType())) { + doc.append(PRIMARY_MEDIA_ID.get(), fdoRecord.primaryLocalId()); + } else if (ANNOTATION.equals(fdoRecord.fdoType())) { + doc.append(ANNOTATION_HASH.get(), fdoRecord.primaryLocalId()); + } } - public void rollbackHandlesFromPhysId(List physicalIds) { - var physicalIdsBytes = physicalIds.stream().map(id -> id.getBytes(StandardCharsets.UTF_8)) - .toList(); - var handles = pidRepository.searchByNormalisedPhysicalIdentifier(physicalIdsBytes).stream() - .map(ha -> new String(ha.getHandle(), StandardCharsets.UTF_8)).toList(); - pidRepository.rollbackHandles(handles); - } } diff --git a/src/main/java/eu/dissco/core/handlemanager/service/ServiceUtils.java b/src/main/java/eu/dissco/core/handlemanager/service/ServiceUtils.java new file mode 100644 index 00000000..edfc4ef4 --- /dev/null +++ b/src/main/java/eu/dissco/core/handlemanager/service/ServiceUtils.java @@ -0,0 +1,25 @@ +package eu.dissco.core.handlemanager.service; + +import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import java.util.List; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ServiceUtils { + + private ServiceUtils() { + } + + public static FdoAttribute getField(List fdoAttributes, FdoProfile targetField) { + for (var attribute : fdoAttributes) { + if (attribute.getIndex() == targetField.index()) { + return attribute; + } + } + log.error("Unable to find field {} in record {}", targetField, fdoAttributes); + throw new IllegalStateException(); + } + + +} diff --git a/src/test/java/eu/dissco/core/handlemanager/controller/PidControllerTest.java b/src/test/java/eu/dissco/core/handlemanager/controller/PidControllerTest.java index 6b0cee9d..7dfa6944 100644 --- a/src/test/java/eu/dissco/core/handlemanager/controller/PidControllerTest.java +++ b/src/test/java/eu/dissco/core/handlemanager/controller/PidControllerTest.java @@ -6,27 +6,24 @@ import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_TYPE; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_ALT; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_DOMAIN; import static eu.dissco.core.handlemanager.testUtils.TestUtils.MAPPER; import static eu.dissco.core.handlemanager.testUtils.TestUtils.PREFIX; import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.SUFFIX; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.UI_URL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.genCreateRecordRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRecordRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRequestAltLoc; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectNullOptionals; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiRecordRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMediaRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseRead; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseReadSingle; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWrite; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWriteAltLoc; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWriteArchive; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWriteGeneric; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenReadResponse; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenTombstoneRecordRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdateRequest; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.mockito.ArgumentMatchers.anyList; @@ -35,7 +32,6 @@ import static org.mockito.BDDMockito.then; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import eu.dissco.core.handlemanager.Profiles; import eu.dissco.core.handlemanager.domain.fdo.DigitalSpecimenRequest; @@ -49,7 +45,7 @@ import eu.dissco.core.handlemanager.exceptions.PidResolutionException; import eu.dissco.core.handlemanager.properties.ApplicationProperties; import eu.dissco.core.handlemanager.service.PidService; -import java.nio.charset.StandardCharsets; +import eu.dissco.core.handlemanager.testUtils.TestUtils; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -77,9 +73,6 @@ class PidControllerTest { private PidController controller; - private final String SANDBOX_URI = "https://sandbox.dissco.tech"; - - public ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); @Mock private ApplicationProperties applicationProperties; @@ -89,16 +82,16 @@ void setup() { } @Test - void testResolveSingleHandle() throws PidResolutionException { + void testResolveSingleHandle() throws Exception { // Given - String path = SANDBOX_URI + PREFIX + "/" + SUFFIX; - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); + String path = UI_URL + "/" + PREFIX + "/" + SUFFIX; MockHttpServletRequest r = new MockHttpServletRequest(); r.setRequestURI(PREFIX + "/" + SUFFIX); - var responseExpected = givenRecordResponseReadSingle(HANDLE, path, FdoType.HANDLE, null); + var responseExpected = givenReadResponse(List.of(HANDLE), path, FdoType.HANDLE, + HANDLE_DOMAIN); - given(applicationProperties.getUiUrl()).willReturn(SANDBOX_URI); - given(service.resolveSingleRecord(handle, path)).willReturn(responseExpected); + given(applicationProperties.getUiUrl()).willReturn(UI_URL); + given(service.resolveSingleRecord(HANDLE, path)).willReturn(responseExpected); given(applicationProperties.getPrefix()).willReturn(PREFIX); // When @@ -120,8 +113,8 @@ void testResolvePidBadPrefix() { void testSearchByPhysicalId() throws Exception { // Given - var responseExpected = givenRecordResponseWriteGeneric( - List.of(HANDLE.getBytes(StandardCharsets.UTF_8)), FdoType.DIGITAL_SPECIMEN); + var responseExpected = TestUtils.givenWriteResponseFull( + List.of(HANDLE), FdoType.DIGITAL_SPECIMEN); given( service.searchByPhysicalSpecimenId(PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL )).willReturn(responseExpected); @@ -139,8 +132,8 @@ void testSearchByPhysicalId() throws Exception { void testSearchByPhysicalIdCombined() throws Exception { // Given String physicalId = PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; - var responseExpected = givenRecordResponseWriteGeneric( - List.of(HANDLE.getBytes(StandardCharsets.UTF_8)), FdoType.DIGITAL_SPECIMEN); + var responseExpected = TestUtils.givenWriteResponseFull( + List.of(HANDLE), FdoType.DIGITAL_SPECIMEN); given( service.searchByPhysicalSpecimenId(physicalId)).willReturn( responseExpected); @@ -156,16 +149,15 @@ void testSearchByPhysicalIdCombined() throws Exception { @Test void testResolveBatchHandle() throws Exception { // Given - String path = SANDBOX_URI + "view"; + String path = UI_URL + "/view"; MockHttpServletRequest r = new MockHttpServletRequest(); r.setRequestURI("view"); List handleString = List.of(HANDLE, HANDLE_ALT); - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - var responseExpected = givenRecordResponseRead(handles, path, FdoType.HANDLE); - given(applicationProperties.getUiUrl()).willReturn(SANDBOX_URI); + var responseExpected = givenReadResponse(handleString, path, FdoType.HANDLE, + HANDLE_DOMAIN); + given(applicationProperties.getUiUrl()).willReturn(UI_URL); given(applicationProperties.getMaxHandles()).willReturn(1000); given(service.resolveBatchRecord(anyList(), eq(path))).willReturn(responseExpected); @@ -201,10 +193,10 @@ void testResolveBatchHandleExceedsMax() { @Test void testCreateHandleRecord() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); HandleRecordRequest requestObject = givenHandleRecordRequestObject(); ObjectNode requestNode = genCreateRecordRequest(requestObject, FdoType.HANDLE); - JsonApiWrapperWrite responseExpected = givenRecordResponseWrite(List.of(handle), + JsonApiWrapperWrite responseExpected = TestUtils.givenWriteResponseFull( + List.of(HANDLE), FdoType.HANDLE); given(service.createRecords(List.of(requestNode))).willReturn(responseExpected); @@ -220,10 +212,10 @@ void testCreateHandleRecord() throws Exception { @Test void testCreateDoiRecord() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); HandleRecordRequest requestObject = givenDoiRecordRequestObject(); ObjectNode requestNode = genCreateRecordRequest(requestObject, FdoType.DOI); - JsonApiWrapperWrite responseExpected = givenRecordResponseWrite(List.of(handle), + JsonApiWrapperWrite responseExpected = TestUtils.givenWriteResponseFull( + List.of(HANDLE), FdoType.DOI); given(service.createRecords(List.of(requestNode))).willReturn(responseExpected); @@ -239,10 +231,10 @@ void testCreateDoiRecord() throws Exception { @Test void testCreateDigitalSpecimenRecord() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); DigitalSpecimenRequest requestObject = givenDigitalSpecimenRequestObjectNullOptionals(); ObjectNode requestNode = genCreateRecordRequest(requestObject, FdoType.DIGITAL_SPECIMEN); - JsonApiWrapperWrite responseExpected = givenRecordResponseWrite(List.of(handle), + JsonApiWrapperWrite responseExpected = TestUtils.givenWriteResponseFull( + List.of(HANDLE), FdoType.DIGITAL_SPECIMEN); given(service.createRecords(List.of(requestNode))).willReturn(responseExpected); @@ -258,10 +250,10 @@ void testCreateDigitalSpecimenRecord() throws Exception { @Test void testCreateDigitalMediaRecord() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - HandleRecordRequest requestObject = givenMediaRequestObject(); + HandleRecordRequest requestObject = givenDigitalMediaRequestObject(); ObjectNode requestNode = genCreateRecordRequest(requestObject, FdoType.DIGITAL_MEDIA); - JsonApiWrapperWrite responseExpected = givenRecordResponseWrite(List.of(handle), + JsonApiWrapperWrite responseExpected = TestUtils.givenWriteResponseFull( + List.of(HANDLE), FdoType.DIGITAL_MEDIA); given(service.createRecords(List.of(requestNode))).willReturn(responseExpected); @@ -277,16 +269,14 @@ void testCreateDigitalMediaRecord() throws Exception { @Test void testCreateHandleRecordBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); handles.forEach(handle -> requests.add( genCreateRecordRequest(givenHandleRecordRequestObject(), FdoType.HANDLE))); - var responseExpected = givenRecordResponseWrite(handles, FdoType.HANDLE); + var responseExpected = TestUtils.givenWriteResponseFull(handles, FdoType.HANDLE); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -300,15 +290,13 @@ void testCreateHandleRecordBatch() throws Exception { @Test void testCreateDoiRecordBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); handles.forEach( handle -> requests.add(genCreateRecordRequest(givenDoiRecordRequestObject(), FdoType.DOI))); - var responseExpected = givenRecordResponseWrite(handles, FdoType.DOI); + var responseExpected = TestUtils.givenWriteResponseFull(handles, FdoType.DOI); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -322,18 +310,15 @@ void testCreateDoiRecordBatch() throws Exception { @Test void testCreateDigitalSpecimenBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); - handles.forEach(handle -> requests.add( genCreateRecordRequest(givenDigitalSpecimenRequestObjectNullOptionals(), FdoType.DIGITAL_SPECIMEN)) ); - var responseExpected = givenRecordResponseWrite(handles, FdoType.DIGITAL_SPECIMEN); + var responseExpected = TestUtils.givenWriteResponseFull(handles, + FdoType.DIGITAL_SPECIMEN); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -347,16 +332,12 @@ void testCreateDigitalSpecimenBatch() throws Exception { @Test void testCreateMediaRecordBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); for (int i = 0; i < handles.size(); i++) { - requests.add(genCreateRecordRequest(givenMediaRequestObject(), FdoType.DIGITAL_MEDIA)); + requests.add(genCreateRecordRequest(givenDigitalMediaRequestObject(), FdoType.DIGITAL_MEDIA)); } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.DOI); + var responseExpected = TestUtils.givenWriteResponseFull(handles, FdoType.DOI); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -370,15 +351,13 @@ void testCreateMediaRecordBatch() throws Exception { @Test void testCreateSourceSystemsBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); handles.forEach(handle -> requests.add( genCreateRecordRequest(givenSourceSystemRequestObject(), FdoType.SOURCE_SYSTEM))); - var responseExpected = givenRecordResponseWrite(handles, FdoType.SOURCE_SYSTEM); + var responseExpected = TestUtils.givenWriteResponseFull(handles, + FdoType.SOURCE_SYSTEM); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -392,15 +371,12 @@ void testCreateSourceSystemsBatch() throws Exception { @Test void testCreateAnnotationsBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); handles.forEach(handle -> requests.add( genCreateRecordRequest(givenAnnotationRequestObject(), FdoType.ANNOTATION))); - - var responseExpected = givenRecordResponseWrite(handles, FdoType.ANNOTATION); + var responseExpected = TestUtils.givenWriteResponseFull(handles, + FdoType.ANNOTATION); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -414,15 +390,13 @@ void testCreateAnnotationsBatch() throws Exception { @Test void testCreateMappingBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - + var handles = List.of(HANDLE, HANDLE_ALT); List requests = new ArrayList<>(); handles.forEach(handle -> requests.add( genCreateRecordRequest(givenDataMappingRequestObject(), FdoType.DATA_MAPPING))); - var responseExpected = givenRecordResponseWrite(handles, FdoType.DATA_MAPPING); + var responseExpected = TestUtils.givenWriteResponseFull(handles, + FdoType.DATA_MAPPING); given(service.createRecords(requests)).willReturn(responseExpected); // When @@ -433,101 +407,58 @@ void testCreateMappingBatch() throws Exception { assertThat(responseReceived.getBody()).isEqualTo(responseExpected); } - private JsonNode givenJsonNode(String id, String type, JsonNode attributes) { - ObjectNode node = mapper.createObjectNode(); - node.put(NODE_ID, id); - node.put(NODE_TYPE, type); - node.set(NODE_ATTRIBUTES, attributes); - return node; - } - @Test void testUpdateRecord() throws Exception { // Given - byte[] handle = HANDLE.getBytes(); - var updateAttributes = genUpdateRequestAltLoc(); - ObjectNode updateRequestNode = mapper.createObjectNode(); - updateRequestNode.set(NODE_DATA, - givenJsonNode(HANDLE, FdoType.HANDLE.getDigitalObjectType(), updateAttributes)); - - var responseExpected = givenRecordResponseWriteAltLoc(List.of(handle)); - given(service.updateRecords(List.of(updateRequestNode), true)).willReturn( - responseExpected); + var request = givenUpdateRequest(); // When - var responseReceived = controller.updateRecord(PREFIX, SUFFIX, updateRequestNode, + var result = controller.updateRecord(PREFIX, SUFFIX, request.get(0), authentication); // Then - assertThat(responseReceived.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseReceived.getBody()).isEqualTo(responseExpected); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + then(service).should().updateRecords(request, true); } @Test void testUpdateRecordBadRequest() { - // Given - var updateAttributes = genUpdateRequestAltLoc(); - ObjectNode updateRequestNode = mapper.createObjectNode(); - updateRequestNode.set("data", - givenJsonNode(HANDLE_ALT, FdoType.HANDLE.getDigitalObjectType(), updateAttributes)); + var request = MAPPER.createObjectNode() + .set(NODE_DATA, MAPPER.createObjectNode() + .put(NODE_TYPE, FdoType.HANDLE.getDigitalObjectType()) + .put(NODE_ID, HANDLE_ALT) + .set(NODE_ATTRIBUTES, MAPPER.valueToTree(givenHandleRecordRequestObjectUpdate()))); // Then assertThrowsExactly(InvalidRequestException.class, - () -> controller.updateRecord(PREFIX, SUFFIX, updateRequestNode, authentication)); + () -> controller.updateRecord(PREFIX, SUFFIX, request, authentication)); } @Test void testUpdateRecordBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - - List updateRequestList = new ArrayList<>(); - var responseExpected = givenRecordResponseWriteAltLoc(handles); - handles.forEach(h -> { - var updateAttributes = genUpdateRequestAltLoc(); - ObjectNode updateRequestNode = mapper.createObjectNode(); - updateRequestNode.set("data", - givenJsonNode(HANDLE, FdoType.HANDLE.getDigitalObjectType(), updateAttributes)); - updateRequestList.add(updateRequestNode.deepCopy()); - }); - given(service.updateRecords(updateRequestList, true)).willReturn(responseExpected); + var request = givenUpdateRequest(); // When - var responseReceived = controller.updateRecords(updateRequestList, authentication); + var responseReceived = controller.updateRecords(request, authentication); // Then assertThat(responseReceived.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseReceived.getBody()).isEqualTo(responseExpected); + then(service).should().updateRecords(request, true); } @Test void testRollbackUpdate() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - - List updateRequestList = new ArrayList<>(); - var responseExpected = givenRecordResponseWriteAltLoc(handles); - handles.forEach(h -> { - var updateAttributes = genUpdateRequestAltLoc(); - ObjectNode updateRequestNode = mapper.createObjectNode(); - updateRequestNode.set("data", - givenJsonNode(HANDLE, FdoType.HANDLE.getDigitalObjectType(), updateAttributes)); - updateRequestList.add(updateRequestNode.deepCopy()); - }); - - given(service.updateRecords(updateRequestList, false)).willReturn(responseExpected); + var request = givenUpdateRequest(); // When - var responseReceived = controller.rollbackHandleUpdate(updateRequestList, authentication); + var responseReceived = controller.rollbackHandleUpdate(request, authentication); // Then assertThat(responseReceived.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseReceived.getBody()).isEqualTo(responseExpected); + then(service).should().updateRecords(request, false); } @Test @@ -575,12 +506,9 @@ void testRollbackHandlesBadRequest() { @Test void testArchiveRecord() throws Exception { // Given - - byte[] handle = HANDLE.getBytes(); - - var responseExpected = givenRecordResponseWriteArchive(List.of(handle)); + var responseExpected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.TOMBSTONE); var archiveRequest = givenArchiveRequest(); - given(service.archiveRecordBatch(List.of(archiveRequest))).willReturn( + given(service.tombstoneRecords(List.of(archiveRequest))).willReturn( responseExpected); // When @@ -605,13 +533,11 @@ void testArchiveRecordBadHandle() { @Test void testArchiveRecordBatch() throws Exception { // Given - List handles = List.of( - HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); + var handles = List.of(HANDLE, HANDLE_ALT); List archiveRequestList = new ArrayList<>(); handles.forEach(h -> archiveRequestList.add(givenArchiveRequest())); - var responseExpected = givenRecordResponseWriteArchive(handles); - given(service.archiveRecordBatch(archiveRequestList)).willReturn(responseExpected); + var responseExpected = TestUtils.givenWriteResponseFull(handles, FdoType.TOMBSTONE); + given(service.tombstoneRecords(archiveRequestList)).willReturn(responseExpected); // When var responseReceived = controller.archiveRecords(archiveRequestList, authentication); @@ -625,27 +551,14 @@ private JsonNode givenArchiveRequest() { ObjectNode archiveRequest = MAPPER.createObjectNode(); ObjectNode archiveRequestData = MAPPER.createObjectNode(); archiveRequestData.put(NODE_ID, HANDLE); - archiveRequestData.set(NODE_ATTRIBUTES, MAPPER.valueToTree(genTombstoneRecordRequestObject())); + archiveRequestData.set(NODE_ATTRIBUTES, + MAPPER.valueToTree(givenTombstoneRecordRequestObject())); archiveRequest.set(NODE_DATA, archiveRequestData); return archiveRequest; } - - @Test - void testArchiveRecordBadRequest() { - // Given - var archiveAttributes = genTombstoneRequest(); - ObjectNode archiveRequestNode = mapper.createObjectNode(); - archiveRequestNode.set("data", - givenJsonNode(HANDLE_ALT, FdoType.HANDLE.getDigitalObjectType(), archiveAttributes)); - - // Then - assertThrowsExactly(InvalidRequestException.class, - () -> controller.updateRecord(PREFIX, SUFFIX, archiveRequestNode, authentication)); - } - @Test - void testPiDResolutionException() throws Exception { + void testPidResolutionException() throws Exception { // Given DoiRecordRequest request = givenDoiRecordRequestObject(); ObjectNode requestNode = genCreateRecordRequest(request, FdoType.DOI); diff --git a/src/test/java/eu/dissco/core/handlemanager/domain/FdoProfileTest.java b/src/test/java/eu/dissco/core/handlemanager/domain/FdoProfileTest.java deleted file mode 100644 index 8267d48f..00000000 --- a/src/test/java/eu/dissco/core/handlemanager/domain/FdoProfileTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package eu.dissco.core.handlemanager.domain; - -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID_ISSUER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertThrows; - -import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; -import org.junit.jupiter.api.Test; - -class FdoProfileTest { - - @Test - void testSearchFdoProfileSuccess() { - // Given - var targetStr = PID_ISSUER.get(); - var expected = PID_ISSUER.index(); - - // When - var result = FdoProfile.retrieveIndex(targetStr); - - // Then - assertThat(result).isEqualTo(expected); - } - - @Test - void testUnrecognizedProperty() { - assertThrows(IllegalStateException.class, - () -> FdoProfile.retrieveIndex("aaa")); - } - -} diff --git a/src/test/java/eu/dissco/core/handlemanager/domain/JsonSchemaValidatorTest.java b/src/test/java/eu/dissco/core/handlemanager/domain/JsonSchemaValidatorTest.java index f3116663..3aedcc1c 100644 --- a/src/test/java/eu/dissco/core/handlemanager/domain/JsonSchemaValidatorTest.java +++ b/src/test/java/eu/dissco/core/handlemanager/domain/JsonSchemaValidatorTest.java @@ -1,39 +1,26 @@ package eu.dissco.core.handlemanager.domain; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MAS_NAME; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_HOST; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ORGANISATION_ID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID_ISSUER; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.REFERENT_NAME; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.SOURCE_DATA_STANDARD; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.SOURCE_SYSTEM_NAME; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.SPECIMEN_HOST; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TARGET_TYPE; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONE_TEXT; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONED_TEXT; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ATTRIBUTES; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_DATA; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ID; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_TYPE; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; import static eu.dissco.core.handlemanager.testUtils.TestUtils.LOC_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.MAPPER; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.MEDIA_HOST_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.PID_ISSUER_TESTVAL_OTHER; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.REFERENT_DOI_NAME_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.SPECIMEN_HOST_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.genCreateRecordRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRequestBatch; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRequestAltLoc; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectNullOptionals; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiRecordRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasRecordRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMediaRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenTombstoneRequest; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; @@ -45,14 +32,10 @@ import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.StructuralType; import eu.dissco.core.handlemanager.domain.validation.JsonSchemaValidator; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; -import java.util.List; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.junit.jupiter.MockitoExtension; @@ -118,7 +101,7 @@ void testPostDigitalSpecimenRequest() { @Test void testPostDigitalMediaRequest() throws Exception { // Given - var request = genCreateRecordRequest(givenMediaRequestObject(), FdoType.DIGITAL_MEDIA); + var request = genCreateRecordRequest(givenDigitalMediaRequestObject(), FdoType.DIGITAL_MEDIA); // Then assertDoesNotThrow(() -> schemaValidator.validatePostRequest(request)); @@ -162,7 +145,7 @@ void testPostOrganisationNullTypeRequest() { } @Test - void testPostDataMappingRequest() { + void testPostMappingRequest() { // Given var request = genCreateRecordRequest(givenDataMappingRequestObject(), FdoType.DATA_MAPPING); @@ -190,113 +173,13 @@ void testPostMasRequest() { } @Test - void testHandlePatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.HANDLE, PID_ISSUER.get(), - PID_ISSUER_TESTVAL_OTHER); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testDoiPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.DOI, REFERENT_NAME.get(), - REFERENT_DOI_NAME_TESTVAL); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testDigitalSpecimenPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.DIGITAL_SPECIMEN, SPECIMEN_HOST.get(), - SPECIMEN_HOST_TESTVAL); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testDigitalMediaPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.DIGITAL_MEDIA, MEDIA_HOST.get(), MEDIA_HOST_TESTVAL); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testAnnotationPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.ANNOTATION, TARGET_TYPE.get(), - "Annotation"); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testOrganisationPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.ORGANISATION, ORGANISATION_ID.get(), "new"); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testDataMappingPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.DATA_MAPPING, SOURCE_DATA_STANDARD.get(), "new"); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testSourceSystemPatchRequest() { - // Given - var request = givenUpdateRequest(FdoType.SOURCE_SYSTEM, SOURCE_SYSTEM_NAME.get(), "new"); - - // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - @Test - void testMasPatchRequest() { + void testTombstoneRequest() { // Given - var request = givenUpdateRequest(FdoType.MAS, MAS_NAME.get(), "new"); + var request = givenTombstoneRequest().get(0); // Then - assertDoesNotThrow(() -> schemaValidator.validatePatchRequest(request)); - } - - private ObjectNode givenUpdateRequest(FdoType type, String key, String val) { - ObjectNode request = MAPPER.createObjectNode(); - ObjectNode data = MAPPER.createObjectNode(); - var attributes = (ObjectNode) genUpdateRequestAltLoc(); - attributes.put(key, val); - - data.put(NODE_TYPE, type.getDigitalObjectType()); - data.put(NODE_ID, HANDLE); - data.set(NODE_ATTRIBUTES, attributes); - request.set("data", data); - return request; - } - - @Test - void testTombstoneRequest() { - var data = MAPPER.createObjectNode(); - data.put(NODE_ID, HANDLE); - var attributes = genTombstoneRequest(); - data.set(NODE_ATTRIBUTES, attributes); - var request = MAPPER.createObjectNode(); - request.set(NODE_DATA, data); - assertDoesNotThrow(() -> schemaValidator.validatePutRequest(request)); + } @ParameterizedTest @@ -412,54 +295,16 @@ void testBadPostDigitalMediaRequestMissingProperty() { assertThat(e.getMessage()).contains(MISSING_MSG).contains(missingAttribute); } - @Test - void testBadPatchRequest() { - // Given - ObjectNode request = MAPPER.createObjectNode(); - request.set("data", MAPPER.createObjectNode() - .put(NODE_TYPE, "bad type") - .put(NODE_ID, HANDLE) - .set(NODE_ATTRIBUTES, genUpdateRequestAltLoc())); - - // Then - Exception e = assertThrowsExactly(InvalidRequestException.class, - () -> schemaValidator.validatePatchRequest(request)); - assertThat(e.getMessage()).contains(ENUM_MSG).contains(NODE_TYPE); - } - - @ParameterizedTest - @MethodSource("provideFdoTypes") - void testBadPatchRequestUnknownProperty(FdoType recordType) { - // Given - var request = givenUpdateRequest(recordType, UNKNOWN_ATTRIBUTE, UNKNOWN_VAL); - - // Then - Exception e = assertThrowsExactly(InvalidRequestException.class, - () -> schemaValidator.validatePatchRequest(request)); - - assertThat(e.getMessage()).contains(UNRECOGNIZED_MSG).contains(UNKNOWN_ATTRIBUTE); - } - - private static Stream provideFdoTypes() { - return Stream.of( - Arguments.of(FdoType.HANDLE), - Arguments.of(FdoType.DOI), - Arguments.of(FdoType.DIGITAL_SPECIMEN), - Arguments.of(FdoType.DIGITAL_MEDIA) - ); - - } - @Test void testBadArchiveRequest() { // Given - var request = genTombstoneRequestBatch(List.of(HANDLE)).get(0); + var request = givenTombstoneRequest().get(0); ((ObjectNode) request.get(NODE_DATA)).remove(NODE_TYPE); ((ObjectNode) request.get(NODE_DATA)).remove(NODE_ID); // When Exception e = assertThrowsExactly(InvalidRequestException.class, - () -> schemaValidator.validatePutRequest(request)); + () -> schemaValidator.validatePatchRequest(request)); // Then assertThat(e.getMessage()).contains(MISSING_MSG).contains(NODE_ID); @@ -468,16 +313,15 @@ void testBadArchiveRequest() { @Test void testBadArchiveRequestMissingProperty() { // Given - var request = genTombstoneRequestBatch(List.of(HANDLE)).get(0); - ((ObjectNode) request.get(NODE_DATA)).remove(NODE_TYPE); - ((ObjectNode) request.get(NODE_DATA).get(NODE_ATTRIBUTES)).remove(TOMBSTONE_TEXT.get()); + var request = givenTombstoneRequest().get(0); + ((ObjectNode) request.get(NODE_DATA).get(NODE_ATTRIBUTES)).remove(TOMBSTONED_TEXT.get()); // When Exception e = assertThrowsExactly(InvalidRequestException.class, () -> schemaValidator.validatePutRequest(request)); // Then - assertThat(e.getMessage()).contains(MISSING_MSG).contains(TOMBSTONE_TEXT.get()); + assertThat(e.getMessage()).contains(MISSING_MSG).contains(TOMBSTONED_TEXT.get()); } } diff --git a/src/test/java/eu/dissco/core/handlemanager/domain/TopicDisciplineTest.java b/src/test/java/eu/dissco/core/handlemanager/domain/TopicDisciplineTest.java deleted file mode 100644 index 475efee6..00000000 --- a/src/test/java/eu/dissco/core/handlemanager/domain/TopicDisciplineTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package eu.dissco.core.handlemanager.domain; - -import static org.assertj.core.api.Assertions.assertThat; - -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicCategory; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicDiscipline; -import org.junit.jupiter.api.Test; - -class TopicDisciplineTest { - - @Test - void testAnthro() { - // Given - var targetDisc = TopicDiscipline.ANTHRO; - var targetCategory = TopicCategory.HUMAN; - var notTargetCategory = TopicCategory.ALGAE; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testBotany() { - // Given - var targetDisc = TopicDiscipline.BOTANY; - var targetCategory = TopicCategory.MYCOLOGY; - var notTargetCategory = TopicCategory.FINDS; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testGeology() { - // Given - var targetDisc = TopicDiscipline.GEOLOGY; - var targetCategory = TopicCategory.MINERALS; - var notTargetCategory = TopicCategory.FINDS; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testZoology() { - // Given - var targetDisc = TopicDiscipline.ZOO; - var targetCategory = TopicCategory.AMPHIBIANS; - var notTargetCategory = TopicCategory.FINDS; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testMicro() { - // Given - var targetDisc = TopicDiscipline.MICRO; - var targetCategory = TopicCategory.PHAGES; - var notTargetCategory = TopicCategory.FINDS; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testPaleo() { - // Given - var targetDisc = TopicDiscipline.PALEO; - var targetCategory = TopicCategory.BOTANY_FOSSILS; - var notTargetCategory = TopicCategory.FINDS; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testAstro() { - // Given - var targetDisc = TopicDiscipline.ASTRO; - var targetCategory = TopicCategory.FINDS; - var notTargetCategory = TopicCategory.LOOSE_SEDIMENT; - - // When - assertThat(targetDisc.isCorrectCategory(targetCategory)).isTrue(); - assertThat(targetDisc.isCorrectCategory(notTargetCategory)).isFalse(); - } - - @Test - void testDefault() { - // Given - var targetDisc = TopicDiscipline.ECO; - - // When - assertThat(targetDisc.getTopicCategories()).isEmpty(); - } - -} diff --git a/src/test/java/eu/dissco/core/handlemanager/repository/BaseRepositoryIT.java b/src/test/java/eu/dissco/core/handlemanager/repository/BaseRepositoryIT.java deleted file mode 100644 index 4af4323c..00000000 --- a/src/test/java/eu/dissco/core/handlemanager/repository/BaseRepositoryIT.java +++ /dev/null @@ -1,52 +0,0 @@ -package eu.dissco.core.handlemanager.repository; - -import static org.testcontainers.containers.PostgreSQLContainer.IMAGE; - -import com.zaxxer.hikari.HikariDataSource; -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; -import org.flywaydb.core.Flyway; -import org.jooq.DSLContext; -import org.jooq.Record4; -import org.jooq.SQLDialect; -import org.jooq.impl.DefaultDSLContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; - -@Testcontainers -public class BaseRepositoryIT { - - private static final DockerImageName POSTGIS = - DockerImageName.parse("postgres:15.2").asCompatibleSubstituteFor(IMAGE); - - @Container - private static final PostgreSQLContainer CONTAINER = new PostgreSQLContainer<>(POSTGIS); - protected DSLContext context; - protected HikariDataSource dataSource; - - @BeforeEach - void prepareDatabase() { - dataSource = new HikariDataSource(); - dataSource.setJdbcUrl(CONTAINER.getJdbcUrl()); - dataSource.setUsername(CONTAINER.getUsername()); - dataSource.setPassword(CONTAINER.getPassword()); - dataSource.setMaximumPoolSize(2); - dataSource.setConnectionInitSql(CONTAINER.getTestQueryString()); - Flyway.configure().mixed(true).dataSource(dataSource).load().migrate(); - context = new DefaultDSLContext(dataSource, SQLDialect.POSTGRES); - } - - @AfterEach - void disposeDataSource() { - dataSource.close(); - } - - protected HandleAttribute mapToAttribute(Record4 row) { - return new HandleAttribute(row.get(Handles.HANDLES.IDX), row.get(Handles.HANDLES.HANDLE), - new String(row.get(Handles.HANDLES.TYPE)), row.get(Handles.HANDLES.DATA)); - } -} \ No newline at end of file diff --git a/src/test/java/eu/dissco/core/handlemanager/repository/BatchInserterIT.java b/src/test/java/eu/dissco/core/handlemanager/repository/BatchInserterIT.java deleted file mode 100644 index 92a52014..00000000 --- a/src/test/java/eu/dissco/core/handlemanager/repository/BatchInserterIT.java +++ /dev/null @@ -1,84 +0,0 @@ -package eu.dissco.core.handlemanager.repository; - -import static eu.dissco.core.handlemanager.database.jooq.Tables.HANDLES; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.CREATED; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDigitalSpecimenAttributes; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertThrows; - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; -import eu.dissco.core.handlemanager.exceptions.DatabaseCopyException; -import java.nio.charset.StandardCharsets; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.postgresql.copy.CopyManager; -import org.postgresql.core.BaseConnection; - -class BatchInserterIT extends BaseRepositoryIT { - - private BatchInserter batchInserter; - - @BeforeEach - void setup() throws SQLException { - var connection = DriverManager.getConnection(dataSource.getJdbcUrl(), dataSource.getUsername(), - dataSource.getPassword()); - var copyManager = new CopyManager((BaseConnection) connection); - batchInserter = new BatchInserter(copyManager); - } - - @AfterEach - void destroy() { - context.truncate(HANDLES).execute(); - } - - @Test - void testBatchInsert() throws Exception { - // Given - var attributes = genDigitalSpecimenAttributes(HANDLE.getBytes(StandardCharsets.UTF_8)); - - // When - batchInserter.batchCopy(CREATED.getEpochSecond(), attributes); - var response = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(HANDLES).fetch(this::mapToAttribute); - - // Then - assertThat(response).hasSameElementsAs(attributes); - } - - @Test - void testBatchInsertIllegalChar() { - // Given - var attributes = List.of( - new HandleAttribute(FdoProfile.SPECIMEN_HOST, HANDLE.getBytes(StandardCharsets.UTF_8), - "this is \n bad data") - ); - var created = CREATED.getEpochSecond(); - - // Then - assertThrows(DatabaseCopyException.class, - () -> batchInserter.batchCopy(created, attributes)); - } - - @Test - void testDelimiterInData() throws Exception { - var attributes = List.of(new HandleAttribute( - FdoProfile.SPECIMEN_HOST, HANDLE.getBytes(StandardCharsets.UTF_8), "this, has a comma" - )); - - // When - batchInserter.batchCopy(CREATED.getEpochSecond(), attributes); - var response = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(HANDLES).fetch(this::mapToAttribute); - - // Then - assertThat(response).isEqualTo(attributes); - } - -} diff --git a/src/test/java/eu/dissco/core/handlemanager/repository/MongoRepositoryIT.java b/src/test/java/eu/dissco/core/handlemanager/repository/MongoRepositoryIT.java new file mode 100644 index 00000000..d57156ba --- /dev/null +++ b/src/test/java/eu/dissco/core/handlemanager/repository/MongoRepositoryIT.java @@ -0,0 +1,251 @@ +package eu.dissco.core.handlemanager.repository; + + +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.in; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.CREATED; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_ALT; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.MAPPER; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PREFIX; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_MEDIA_ID_TESTVAL; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMongoDocument; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdatedFdoRecord; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; +import eu.dissco.core.handlemanager.domain.fdo.FdoType; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +class MongoRepositoryIT { + + private static final String SPECIMEN_ID = PREFIX + "/SPECIMEN"; + private static final String MEDIA_ID = PREFIX + "/MEDIA"; + + private static final DockerImageName MONGODB = + DockerImageName.parse("mongo:7.0.12"); + + @Container + private static final MongoDBContainer CONTAINER = new MongoDBContainer(MONGODB); + private MongoDatabase database; + private MongoClient client; + private MongoCollection collection; + private MongoRepository repository; + + @BeforeEach + void prepareDocumentStore() { + client = MongoClients.create(CONTAINER.getConnectionString()); + database = client.getDatabase("dissco"); + collection = database.getCollection("handles"); + repository = new MongoRepository(collection, MAPPER); + } + + @AfterEach + void disposeDocumentStore() { + database.drop(); + client.close(); + } + + @Test + void testGetHandleRecordsHandle() throws Exception { + // Given + populateMongoDB(); + var expected = List.of(givenHandleFdoRecord(HANDLE)); + + // When + var result = repository.getHandleRecords(List.of(HANDLE)); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testGetHandleRecordsSpecimen() throws Exception { + // Given + populateMongoDB(); + var expected = List.of(givenDigitalSpecimenFdoRecord(SPECIMEN_ID)); + + // When + var result = repository.getHandleRecords(List.of(SPECIMEN_ID)); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testGetHandleRecordsMedia() throws Exception { + // Given + populateMongoDB(); + var expected = List.of(givenDigitalMediaFdoRecord(MEDIA_ID)); + + // When + var result = repository.getHandleRecords(List.of(MEDIA_ID)); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testGetHandleRecordsNotFound() throws Exception { + // Given + populateMongoDB(); + + // When + var result = repository.getHandleRecords(List.of(HANDLE_ALT)); + + // Then + assertThat(result).isEmpty(); + } + + @Test + void testGetExistingHandles() throws Exception { + // Given + populateMongoDB(); + + // When + var result = repository.getExistingHandles(List.of(HANDLE)); + + // Then + assertThat(result).isEqualTo(List.of(HANDLE)); + } + + @Test + void testGetExistingHandlesNotFound() throws Exception { + // Given + populateMongoDB(); + + // When + var result = repository.getExistingHandles(List.of(HANDLE_ALT)); + + // Then + assertThat(result).isEmpty(); + } + + @Test + void testPostHandleRecords() throws Exception { + // Given + var fdoRecord = givenDigitalSpecimenFdoRecord(HANDLE); + var expected = givenMongoDocument(fdoRecord); + + // When + repository.postHandleRecords(List.of(expected)); + var result = collection.find(eq("_id", HANDLE)).first(); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testUpdateHandleRecords() throws Exception { + // Given + var specimenDoc = givenMongoDocument(givenDigitalSpecimenFdoRecord(HANDLE)); + collection.insertOne(specimenDoc); + var expected = givenMongoDocument(givenUpdatedFdoRecord(FdoType.DIGITAL_SPECIMEN, + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); + + // When + repository.updateHandleRecords(List.of(expected)); + var result = collection.find(eq("_id", HANDLE)).first(); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testSearchByPrimaryLocalIdSpecimen() throws Exception { + // Given + populateMongoDB(); + var expected = List.of(givenDigitalSpecimenFdoRecord(SPECIMEN_ID)); + + // When + var result = repository.searchByPrimaryLocalId(FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID.get(), + List.of(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testSearchByPrimaryLocalIdMedia() throws Exception { + // Given + populateMongoDB(); + var expected = List.of(givenDigitalMediaFdoRecord(MEDIA_ID)); + + // When + var result = repository.searchByPrimaryLocalId(FdoProfile.PRIMARY_MEDIA_ID.get(), + List.of(PRIMARY_MEDIA_ID_TESTVAL)); + + // Then + assertThat(result).isEqualTo(expected); + } + + @Test + void testRollbackHandles() throws Exception { + // Given + populateMongoDB(); + var idList = List.of(HANDLE, SPECIMEN_ID, MEDIA_ID); + + // When + repository.rollbackHandles(idList); + var result = collection.find(in("_id", idList)); + + // Then + assertThat(result).isEmpty(); + } + + @Test + void testRollbackHandlesFromPhysicalId() throws Exception { + // Given + populateMongoDB(); + + // When + repository.rollbackHandles(FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID.get(), + List.of(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); + var result = collection.find(in("_id", SPECIMEN_ID)); + + // Then + assertThat(result).isEmpty(); + } + + @Test + void testFdoTypeNotFound() throws Exception { + // Given + var fdoRecord = new FdoRecord(HANDLE, FdoType.HANDLE, + List.of(new FdoAttribute(FdoProfile.FDO_RECORD_LICENSE, CREATED, "License")), null); + collection.insertOne(givenMongoDocument(fdoRecord)); + var handleList = List.of(HANDLE); + + // When / Then + assertThrows(IllegalStateException.class, () -> repository.getHandleRecords(handleList)); + } + + private void populateMongoDB() throws Exception { + var handleDoc = givenMongoDocument(givenHandleFdoRecord(HANDLE)); + var specimenDoc = givenMongoDocument(givenDigitalSpecimenFdoRecord(SPECIMEN_ID)); + var mediaDoc = givenMongoDocument(givenDigitalMediaFdoRecord(MEDIA_ID)); + collection.insertOne(handleDoc); + collection.insertOne(specimenDoc); + collection.insertOne(mediaDoc); + } + + +} \ No newline at end of file diff --git a/src/test/java/eu/dissco/core/handlemanager/repository/PidRepositoryIT.java b/src/test/java/eu/dissco/core/handlemanager/repository/PidRepositoryIT.java deleted file mode 100644 index 73bc3fdd..00000000 --- a/src/test/java/eu/dissco/core/handlemanager/repository/PidRepositoryIT.java +++ /dev/null @@ -1,581 +0,0 @@ -package eu.dissco.core.handlemanager.repository; - -import eu.dissco.core.handlemanager.database.jooq.tables.Handles; -import eu.dissco.core.handlemanager.domain.fdo.FdoType; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; -import org.jooq.Query; -import org.junit.jupiter.api.AfterEach; -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.junit.jupiter.MockitoExtension; - -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.stream.Stream; - -import static eu.dissco.core.handlemanager.database.jooq.Tables.HANDLES; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.*; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.BDDMockito.then; - -@ExtendWith(MockitoExtension.class) -class PidRepositoryIT extends BaseRepositoryIT { - - private PidRepository pidRepository; - @Mock - BatchInserter batchInserter; - - @BeforeEach - void setup() { - pidRepository = new PidRepository(context, batchInserter); - } - - @AfterEach - void destroy() { - context.truncate(HANDLES).execute(); - } - - @Test - void testCreateRecord() throws Exception { - // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - List attributesToPost = genHandleRecordAttributes(handle, FdoType.HANDLE); - - // When - pidRepository.postAttributesToDb(CREATED.getEpochSecond(), attributesToPost); - - // Then - then(batchInserter).should().batchCopy(CREATED.getEpochSecond(), attributesToPost); - } - - @Test - void testHandlesExistTrue() { - // Given - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - List rows = List.of(new HandleAttribute(1, handles.get(0), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8)), - new HandleAttribute(1, handles.get(1), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8))); - - postAttributes(rows); - - // When - List collisions = pidRepository.getHandlesExist(handles); - - // Then - assertThat(collisions).hasSize(handles.size()); - assert (byteArrListsAreEqual(handles, collisions)); - } - - @Test - void testHandlesExistFalse() { - // Given - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - - // When - List collisions = pidRepository.getHandlesExist(handles); - - // Then - assertThat(collisions).isEmpty(); - assertFalse(byteArrListsAreEqual(handles, collisions)); - } - - @Test - void testHandlesWritableTrue() { - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - List rows = List.of(new HandleAttribute(1, handles.get(0), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8)), - new HandleAttribute(1, handles.get(1), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8))); - - postAttributes(rows); - - // When - List collisions = pidRepository.checkHandlesWritable(handles); - - // Then - assertThat(collisions).hasSize(handles.size()); - assert (byteArrListsAreEqual(handles, collisions)); - } - - @Test - void testHandlesWritableFalse() { - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - List rows = List.of(new HandleAttribute(1, handles.get(0), PID_STATUS.get(), - "ARCHIVED".getBytes(StandardCharsets.UTF_8)), - new HandleAttribute(1, handles.get(1), PID_STATUS.get(), - "ARCHIVED".getBytes(StandardCharsets.UTF_8))); - - postAttributes(rows); - - // When - List collisions = pidRepository.checkHandlesWritable(handles); - - // Then - assertThat(collisions).isEmpty(); - assertFalse(byteArrListsAreEqual(handles, collisions)); - } - - @Test - void testResolveSingleRecord() throws Exception { - // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - List responseExpected = genHandleRecordAttributes(handle, FdoType.HANDLE); - postAttributes(responseExpected); - - // When - var responseReceived = pidRepository.resolveHandleAttributes(handle); - - // Then - assertThat(responseReceived).isEqualTo(responseExpected); - } - - @Test - void testGetPrimarySpecimenObjectIds() throws Exception { - // Given - var handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var handleAlt = HANDLE_ALT.getBytes(StandardCharsets.UTF_8); - var postedAttributes = Stream.concat( - genHandleRecordAttributes(handle, FdoType.DIGITAL_SPECIMEN).stream(), - genHandleRecordAttributes(handleAlt, FdoType.DIGITAL_SPECIMEN).stream()).toList(); - postAttributes(postedAttributes); - List expected = new ArrayList<>(); - for (var row : postedAttributes) { - if (row.getType().equals(PRIMARY_SPECIMEN_OBJECT_ID.get())) { - expected.add(row); - } - } - // When - var result = pidRepository.getPrimarySpecimenObjectId(List.of(handle, handleAlt)); - - // Then - assertThat(result).hasSameElementsAs(expected); - } - - @Test - void testResolveBatchRecord() throws Exception { - // Given - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - - List responseExpected = new ArrayList<>(); - for (byte[] handle : handles) { - responseExpected.addAll(genHandleRecordAttributes(handle, FdoType.HANDLE)); - } - - postAttributes(responseExpected); - - // When - var responseReceived = pidRepository.resolveHandleAttributes(handles); - - // Then - assertThat(responseReceived).isEqualTo(responseExpected); - } - - @Test - void testGetAllHandlesPaging() { - // Given - int pageNum = 1; - int pageSize = 5; - - List handles = genListofHandlesString(pageSize); - List rows = new ArrayList<>(); - - for (String handle : handles) { - rows.add(new HandleAttribute(1, handle.getBytes(StandardCharsets.UTF_8), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8))); - } - postAttributes(rows); - - // When - List responseReceived = pidRepository.getAllHandles(pageNum, pageSize); - - // Then - assertThat(responseReceived).hasSameElementsAs(handles); - } - - @Test - void testGetAllHandlesLastPage() { - // Given - int pageNum = 2; - int pageSize = 5; - - List handles = genListofHandlesString(pageSize + 1); - List rows = new ArrayList<>(); - - for (String handle : handles) { - rows.add(new HandleAttribute(1, handle.getBytes(StandardCharsets.UTF_8), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8))); - } - postAttributes(rows); - - // When - List responseReceived = pidRepository.getAllHandles(pageNum, pageSize); - - // Then - assertThat(responseReceived).hasSize(1); - } - - @Test - void testGetAllHandlesPagingByPidStatus() { - // Given - int pageNum = 1; - int pageSize = 5; - byte[] pidStatusTarget = "ARCHIVED".getBytes(StandardCharsets.UTF_8); - - List handles = genListofHandlesString(pageSize + 2); - List responseExpected = handles.subList(0, pageSize); - List extraHandles = handles.subList(pageSize, handles.size()); - List rows = new ArrayList<>(); - - for (String handle : responseExpected) { - rows.add(new HandleAttribute(1, handle.getBytes(StandardCharsets.UTF_8), PID_STATUS.get(), - pidStatusTarget)); - } - for (String handle : extraHandles) { - rows.add(new HandleAttribute(1, handle.getBytes(StandardCharsets.UTF_8), PID_STATUS.get(), - PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8))); - } - postAttributes(rows); - - // When - List responseReceived = pidRepository.getAllHandles(pidStatusTarget, pageNum, pageSize); - - // Then - assertThat(responseReceived).hasSameElementsAs(responseExpected); - } - - @Test - void testSearchByPhysicalIdentifierFullRecord() { - // Given - var targetPhysicalIdentifier = NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL.getBytes( - StandardCharsets.UTF_8); - List responseExpected = new ArrayList<>(); - responseExpected.add(new HandleAttribute(1, HANDLE.getBytes(StandardCharsets.UTF_8), - NORMALISED_SPECIMEN_OBJECT_ID.get(), targetPhysicalIdentifier)); - responseExpected.add( - new HandleAttribute(2, HANDLE.getBytes(StandardCharsets.UTF_8), SPECIMEN_HOST.get(), - SPECIMEN_HOST_TESTVAL.getBytes( - StandardCharsets.UTF_8))); - - List nonTargetAttributes = new ArrayList<>(); - nonTargetAttributes.add(new HandleAttribute(1, HANDLE_ALT.getBytes(StandardCharsets.UTF_8), - NORMALISED_SPECIMEN_OBJECT_ID.get(), "A".getBytes( - StandardCharsets.UTF_8))); - - postAttributes(responseExpected); - postAttributes(nonTargetAttributes); - - // When - var responseReceived = pidRepository.searchByNormalisedPhysicalIdentifierFullRecord( - List.of(targetPhysicalIdentifier)); - - // Then - assertThat(responseReceived).hasSameElementsAs(responseExpected); - } - - @Test - void testSearchByPhysicalSpecimenId() throws Exception { - //Given - var handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var dbRecord = List.of(new HandleAttribute(NORMALISED_SPECIMEN_OBJECT_ID.index(), handle, - NORMALISED_SPECIMEN_OBJECT_ID.get(), - NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL.getBytes(StandardCharsets.UTF_8))); - - postAttributes(genDoiRecordAttributes(handle, FdoType.DOI)); - postAttributes(dbRecord); - - // When - var response = pidRepository.searchByNormalisedPhysicalIdentifier( - List.of(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL.getBytes(StandardCharsets.UTF_8))); - - // Then - assertThat(response).isEqualTo(dbRecord); - } - - @Test - void testSearchByPhysicalSpecimenIdIsArchived() { - //Given - var handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var dbRecord = List.of(new HandleAttribute(NORMALISED_SPECIMEN_OBJECT_ID, handle, - NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL), - new HandleAttribute(PID_STATUS, handle, "ARCHIVED")); - - postAttributes(dbRecord); - - // When - var response = pidRepository.searchByNormalisedPhysicalIdentifier( - List.of(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL.getBytes(StandardCharsets.UTF_8))); - - // Then - assertThat(response).isEmpty(); - } - - @Test - void testUpdateRecord() throws Exception { - // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - List originalRecord = genHandleRecordAttributes(handle); - List recordUpdate = genUpdateRecordAttributesAltLoc(handle); - var responseExpected = incrementVersion(genHandleRecordAttributesAltLoc(handle), true); - postAttributes(originalRecord); - - // When - pidRepository.updateRecord(CREATED.getEpochSecond(), recordUpdate, true); - var responseReceived = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(Handles.HANDLES) - .where(Handles.HANDLES.HANDLE.eq(handle)).and(Handles.HANDLES.TYPE.notEqual( - HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) // Omit HS_ADMIN - .fetch(this::mapToAttribute); - - // Then - assertThat(responseReceived).hasSameElementsAs(responseExpected); - } - - @Test - void testUpdateRecordBatch() throws Exception { - - // Given - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - - List> updateAttributes = new ArrayList<>(); - List responseExpected = new ArrayList<>(); - for (byte[] handle : handles) { - postAttributes(genHandleRecordAttributes(handle)); - updateAttributes.add(genUpdateRecordAttributesAltLoc(handle)); - responseExpected.addAll(incrementVersion(genHandleRecordAttributesAltLoc(handle), true)); - } - - // When - pidRepository.updateRecordBatch(CREATED.getEpochSecond(), updateAttributes, true); - var responseReceived = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(Handles.HANDLES) - .where(Handles.HANDLES.HANDLE.in(handles)).and(Handles.HANDLES.TYPE.notEqual( - HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) // Omit HS_ADMIN - .fetch(this::mapToAttribute); - - // Then - assertThat(responseReceived).hasSameElementsAs(responseExpected); - } - - @Test - void testUpdateRecordBatchNoIncrement() throws Exception { - - // Given - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - - List> updateAttributes = new ArrayList<>(); - List responseExpected = new ArrayList<>(); - for (byte[] handle : handles) { - postAttributes(genHandleRecordAttributes(handle)); - updateAttributes.add(genUpdateRecordAttributesAltLoc(handle)); - responseExpected.addAll(incrementVersion(genHandleRecordAttributesAltLoc(handle), false)); - } - - // When - pidRepository.updateRecordBatch(CREATED.getEpochSecond(), updateAttributes, false); - var responseReceived = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(Handles.HANDLES) - .where(Handles.HANDLES.HANDLE.in(handles)).and(Handles.HANDLES.TYPE.notEqual( - HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) // Omit HS_ADMIN - .fetch(this::mapToAttribute); - - // Then - assertThat(responseReceived).hasSameElementsAs(responseExpected); - } - - @Test - void testArchiveRecordBatch() throws Exception { - - // Given - List handles = List.of(HANDLE.getBytes(StandardCharsets.UTF_8), - HANDLE_ALT.getBytes(StandardCharsets.UTF_8)); - List handlesStr = List.of(HANDLE, HANDLE_ALT); - - List tombstoneAttributes = new ArrayList<>(); - for (var handle : handles) { - postAttributes(genDigitalSpecimenAttributes(handle)); - tombstoneAttributes.addAll(incrementVersion(genTombstoneRecordFullAttributes(handle), true)); - } - - // When - pidRepository.archiveRecords(CREATED.getEpochSecond(), tombstoneAttributes, handlesStr); - var responseReceived = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(Handles.HANDLES) - .where(Handles.HANDLES.HANDLE.in(handles)).and(Handles.HANDLES.TYPE.notEqual( - HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) // Omit HS_ADMIN - .fetch(this::mapToAttribute); - - // Then - assertThat(responseReceived).hasSameElementsAs(tombstoneAttributes); - } - - @Test - void testArchiveRecord() throws Exception { - // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - List originalRecord = genDigitalSpecimenAttributes(handle); - var tombstoneAttributes = incrementVersion(genTombstoneRecordFullAttributes(handle), true); - - postAttributes(originalRecord); - - // When - pidRepository.archiveRecords(CREATED.getEpochSecond(), tombstoneAttributes, List.of(HANDLE)); - var responseReceived = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(Handles.HANDLES) - .where(Handles.HANDLES.HANDLE.eq(handle)).and(Handles.HANDLES.TYPE.notEqual( - HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) // Omit HS_ADMIN - .fetch(this::mapToAttribute); - - // Then - assertThat(responseReceived).hasSameElementsAs(tombstoneAttributes); - } - - @Test - void testRollbackHandleCreation() throws Exception { - // Given - var expected = genHandleRecordAttributes(HANDLE.getBytes(StandardCharsets.UTF_8)); - postAttributes(expected); - postAttributes(genHandleRecordAttributes(HANDLE_ALT.getBytes(StandardCharsets.UTF_8))); - - // When - pidRepository.rollbackHandles(List.of(HANDLE_ALT)); - var response = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(HANDLES).fetch(this::mapToAttribute); - - // Then - assertThat(response).hasSameElementsAs(expected); - } - - @Test - void testRollbackHandleUpdate() throws Exception { - // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - List originalRecord = genHandleRecordAttributes(handle); - List recordUpdate = genUpdateRecordAttributesAltLoc(handle); - var responseExpected = incrementVersion(genHandleRecordAttributesAltLoc(handle), false); - postAttributes(originalRecord); - - // When - pidRepository.updateRecord(CREATED.getEpochSecond(), recordUpdate, false); - var responseReceived = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(Handles.HANDLES) - .where(Handles.HANDLES.HANDLE.eq(handle)).and(Handles.HANDLES.TYPE.notEqual( - HS_ADMIN.get().getBytes(StandardCharsets.UTF_8))) // Omit HS_ADMIN - .fetch(this::mapToAttribute); - - // Then - assertThat(responseReceived).hasSameElementsAs(responseExpected); - } - - @Test - void testPostAndUpdateHandles() throws Exception { - // Given - var handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var handleAlt = HANDLE_ALT.getBytes(StandardCharsets.UTF_8); - var existingRecord = genHandleRecordAttributes(handle, FdoType.HANDLE); - postAttributes(existingRecord); - var updatedRecord = new ArrayList<>(existingRecord); - updatedRecord.add( - new HandleAttribute(MATERIAL_SAMPLE_TYPE.index(), handle, MATERIAL_SAMPLE_TYPE.get(), - "digital".getBytes(StandardCharsets.UTF_8))); - updatedRecord.add( - new HandleAttribute(PID_RECORD_ISSUE_NUMBER.index(), handle, PID_RECORD_ISSUE_NUMBER.get(), - String.valueOf(2).getBytes( - StandardCharsets.UTF_8))); - updatedRecord.remove( - new HandleAttribute(PID_RECORD_ISSUE_NUMBER.index(), handle, PID_RECORD_ISSUE_NUMBER.get(), - String.valueOf(1).getBytes( - StandardCharsets.UTF_8))); - - var newRecord = genHandleRecordAttributes(handleAlt, FdoType.HANDLE); - var expected = Stream.concat(updatedRecord.stream(), newRecord.stream()).toList(); - - // When - pidRepository.postAndUpdateHandles(CREATED.getEpochSecond(), newRecord, List.of(updatedRecord)); - var response = context.select(Handles.HANDLES.IDX, Handles.HANDLES.HANDLE, - Handles.HANDLES.TYPE, Handles.HANDLES.DATA).from(HANDLES).fetch(this::mapToAttribute); - - // Then - assertThat(response).hasSameElementsAs(expected); - } - - private void postAttributes(List rows) { - List queryList = new ArrayList<>(); - for (var handleAttribute : rows) { - var query = context.insertInto(Handles.HANDLES) - .set(Handles.HANDLES.HANDLE, handleAttribute.getHandle()) - .set(Handles.HANDLES.IDX, handleAttribute.getIndex()) - .set(Handles.HANDLES.TYPE, handleAttribute.getType().getBytes(StandardCharsets.UTF_8)) - .set(Handles.HANDLES.DATA, handleAttribute.getData()).set(Handles.HANDLES.TTL, 86400) - .set(Handles.HANDLES.TIMESTAMP, CREATED.getEpochSecond()) - .set(Handles.HANDLES.ADMIN_READ, true).set(Handles.HANDLES.ADMIN_WRITE, true) - .set(Handles.HANDLES.PUB_READ, true).set(Handles.HANDLES.PUB_WRITE, false); - queryList.add(query); - } - context.batch(queryList).execute(); - } - - private boolean byteArrListsAreEqual(List a, List b) { - if (a.size() != b.size()) { - return false; - } - - List aStr = new ArrayList<>(); - List bStr = new ArrayList<>(); - - for (int i = 0; i < a.size(); i++) { - aStr.add(new String(a.get(i))); - bStr.add(new String(b.get(i))); - } - - Collections.sort(aStr); - Collections.sort(bStr); - - return aStr.equals(bStr); - } - - private List genListofHandlesString(int numberOfHandles) { - Random random = new Random(); - int length = 3; - char[] buffer = new char[length]; - char[] symbols = "ABCDEFGHJKLMNPQRSTUVWXYZ1234567890".toCharArray(); - Set handles = new HashSet<>(); - - while (handles.size() < numberOfHandles) { - for (int j = 0; j < length; j++) { - buffer[j] = symbols[random.nextInt(symbols.length)]; - } - handles.add(new String(buffer)); - } - return new ArrayList<>(handles); - } - - private List incrementVersion(List handleAttributes, - boolean increaseVersionNum) { - for (int i = 0; i < handleAttributes.size(); i++) { - if (handleAttributes.get(i).getType().equals(PID_RECORD_ISSUE_NUMBER.get())) { - var removedRecord = handleAttributes.remove(i); - var currentVersion = Integer.parseInt(new String(removedRecord.getData())); - var newVersionNum = increaseVersionNum ? currentVersion + 1 : currentVersion; - byte[] issueNum = String.valueOf(newVersionNum).getBytes(StandardCharsets.UTF_8); - handleAttributes.add(i, - new HandleAttribute(removedRecord.getIndex(), removedRecord.getHandle(), - removedRecord.getType(), - issueNum)); - } - } - return handleAttributes; - } - -} diff --git a/src/test/java/eu/dissco/core/handlemanager/service/DoiServiceTest.java b/src/test/java/eu/dissco/core/handlemanager/service/DoiServiceTest.java index a9527d12..1a7ad4f2 100644 --- a/src/test/java/eu/dissco/core/handlemanager/service/DoiServiceTest.java +++ b/src/test/java/eu/dissco/core/handlemanager/service/DoiServiceTest.java @@ -1,26 +1,29 @@ package eu.dissco.core.handlemanager.service; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.LINKED_DO_PID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_MEDIA_ID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_SPECIMEN_OBJECT_ID; import static eu.dissco.core.handlemanager.testUtils.TestUtils.CREATED; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.DOI_DOMAIN; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_DOMAIN; import static eu.dissco.core.handlemanager.testUtils.TestUtils.MAPPER; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_MEDIA_ID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.genCreateRecordRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDigitalSpecimenAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genObjectNodeAttributeRecord; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRecordAttributesAltLoc; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRequestBatch; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectNullOptionals; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMediaRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseNullAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWriteSmallResponse; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMongoDocument; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdateRequest; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdatedFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenWriteResponseIdsOnly; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.jsonFormatFdoRecord; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.doThrow; @@ -28,23 +31,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import eu.dissco.core.handlemanager.Profiles; import eu.dissco.core.handlemanager.domain.datacite.DataCiteEvent; import eu.dissco.core.handlemanager.domain.datacite.EventType; import eu.dissco.core.handlemanager.domain.fdo.FdoType; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import eu.dissco.core.handlemanager.exceptions.UnprocessableEntityException; import eu.dissco.core.handlemanager.properties.ProfileProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; -import eu.dissco.core.handlemanager.testUtils.TestUtils; -import java.nio.charset.StandardCharsets; +import eu.dissco.core.handlemanager.repository.MongoRepository; import java.time.Clock; import java.time.Instant; import java.time.ZoneOffset; -import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,8 +57,6 @@ @ActiveProfiles(profiles = Profiles.DOI) class DoiServiceTest { - @Mock - private PidRepository pidRepository; @Mock private FdoRecordService fdoRecordService; @Mock @@ -68,6 +65,8 @@ class DoiServiceTest { private ProfileProperties profileProperties; @Mock private DataCiteService dataCiteService; + @Mock + private MongoRepository mongoRepository; private PidService service; private MockedStatic mockedStatic; private MockedStatic mockedClock; @@ -75,8 +74,8 @@ class DoiServiceTest { @BeforeEach void setup() { initTime(); - service = new DoiService(pidRepository, fdoRecordService, pidNameGeneratorService, MAPPER, - profileProperties, dataCiteService); + service = new DoiService(fdoRecordService, pidNameGeneratorService, MAPPER, profileProperties, + dataCiteService, mongoRepository); } private void initTime() { @@ -98,21 +97,17 @@ void destroy() { @Test void testCreateDigitalSpecimen() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); var request = genCreateRecordRequest(givenDigitalSpecimenRequestObjectNullOptionals(), FdoType.DIGITAL_SPECIMEN); - List digitalSpecimen = genDigitalSpecimenAttributes(handle); - var digitalSpecimenSublist = digitalSpecimen.stream() - .filter(row -> row.getType().equals(PRIMARY_SPECIMEN_OBJECT_ID.get())).toList(); - - var responseExpected = givenRecordResponseWriteSmallResponse(digitalSpecimenSublist, - List.of(handle), FdoType.DIGITAL_SPECIMEN); - var dataCiteEvent = new DataCiteEvent(genObjectNodeAttributeRecord(digitalSpecimen), + var fdoRecord = givenDigitalSpecimenFdoRecord(HANDLE); + var responseExpected = givenWriteResponseIdsOnly(List.of(fdoRecord), + FdoType.DIGITAL_SPECIMEN, DOI_DOMAIN); + var dataCiteEvent = new DataCiteEvent(jsonFormatFdoRecord(fdoRecord.attributes()), EventType.CREATE); - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareDigitalSpecimenRecordAttributes(any(), any())).willReturn( - digitalSpecimen); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDigitalSpecimenRecord(any(), any(), any())).willReturn( + fdoRecord); + given(profileProperties.getDomain()).willReturn(DOI_DOMAIN); // When var responseReceived = service.createRecords(List.of(request)); @@ -125,19 +120,16 @@ void testCreateDigitalSpecimen() throws Exception { @Test void testCreateDigitalMedia() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var request = genCreateRecordRequest(givenMediaRequestObject(), FdoType.DIGITAL_MEDIA); - List digitalMedia = TestUtils.genDigitalMediaAttributes(handle); - var mediaSublist = digitalMedia.stream().filter( - row -> row.getType().equals(PRIMARY_MEDIA_ID.get()) || row.getType() - .equals(LINKED_DO_PID.get())).toList(); - var responseExpected = givenRecordResponseWriteSmallResponse(mediaSublist, List.of(handle), - FdoType.DIGITAL_MEDIA); - var dataCiteEvent = new DataCiteEvent(genObjectNodeAttributeRecord(digitalMedia), + var request = genCreateRecordRequest(givenDigitalMediaRequestObject(), FdoType.DIGITAL_MEDIA); + var digitalMedia = givenDigitalMediaFdoRecord(HANDLE); + var responseExpected = givenWriteResponseIdsOnly(List.of(digitalMedia), + FdoType.DIGITAL_MEDIA, DOI_DOMAIN); + var dataCiteEvent = new DataCiteEvent(jsonFormatFdoRecord(digitalMedia.attributes()), EventType.CREATE); - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareDigitalMediaAttributes(any(), any())).willReturn(digitalMedia); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDigitalMediaRecord(any(), any(), any())).willReturn( + digitalMedia); + given(profileProperties.getDomain()).willReturn(DOI_DOMAIN); // When var responseReceived = service.createRecords(List.of(request)); @@ -150,13 +142,12 @@ void testCreateDigitalMedia() throws Exception { @Test void testCreateDigitalSpecimenDataCiteFails() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); var request = List.of( (JsonNode) genCreateRecordRequest(givenDigitalSpecimenRequestObjectNullOptionals(), FdoType.DIGITAL_SPECIMEN)); - List digitalSpecimen = genDigitalSpecimenAttributes(handle); - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareDigitalSpecimenRecordAttributes(any(), any())).willReturn( + var digitalSpecimen = givenDigitalSpecimenFdoRecord(HANDLE); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDigitalSpecimenRecord(any(), any(), any())).willReturn( digitalSpecimen); doThrow(JsonProcessingException.class).when(dataCiteService).publishToDataCite(any(), any()); @@ -164,59 +155,89 @@ void testCreateDigitalSpecimenDataCiteFails() throws Exception { assertThrows(UnprocessableEntityException.class, () -> service.createRecords(request)); // Then - then(pidRepository).should().rollbackHandles(List.of(HANDLE)); + then(mongoRepository).should().rollbackHandles(List.of(HANDLE)); } @Test - void testUpdateRecordLocation() throws Exception { + void testUpdateDigitalSpecimen() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var updateRequest = genUpdateRequestBatch(List.of(handle), FdoType.DIGITAL_SPECIMEN); - var updatedAttributeRecord = genUpdateRecordAttributesAltLoc(handle); - var responseExpected = givenRecordResponseNullAttributes(List.of(handle), - FdoType.DIGITAL_SPECIMEN); + var previousVersion = givenDigitalSpecimenFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenDigitalSpecimenRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DIGITAL_SPECIMEN, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DIGITAL_SPECIMEN, + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var responseExpected = givenWriteResponseIdsOnly(List.of(updatedAttributeRecord), + FdoType.DIGITAL_SPECIMEN, DOI_DOMAIN); var expectedEvent = new DataCiteEvent( - ((ObjectNode) genObjectNodeAttributeRecord(updatedAttributeRecord)) - .put("pid", HANDLE), + (jsonFormatFdoRecord(updatedAttributeRecord.attributes())), EventType.UPDATE); - given(pidRepository.checkHandlesWritable(anyList())).willReturn(List.of(handle)); - given(fdoRecordService.prepareUpdateAttributes(any(), any(), any())).willReturn( - updatedAttributeRecord); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedDigitalSpecimenRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); + given(profileProperties.getDomain()).willReturn(DOI_DOMAIN); // When var responseReceived = service.updateRecords(updateRequest, true); // Then assertThat(responseReceived).isEqualTo(responseExpected); - then(pidRepository).should() - .updateRecordBatch(CREATED.getEpochSecond(), List.of(updatedAttributeRecord), true); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); then(dataCiteService).should().publishToDataCite(expectedEvent, FdoType.DIGITAL_SPECIMEN); } @Test - void testUpdateInvalidType() { + void testUpdateDigitalMedia() throws Exception { // Given - var updateRequest = genUpdateRequestBatch(List.of(HANDLE.getBytes(StandardCharsets.UTF_8)), - FdoType.HANDLE); + var previousVersion = givenDigitalMediaFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenDigitalMediaRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DIGITAL_MEDIA, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DIGITAL_MEDIA, + PRIMARY_MEDIA_ID_TESTVAL); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var responseExpected = givenWriteResponseIdsOnly(List.of(updatedAttributeRecord), + FdoType.DIGITAL_MEDIA, DOI_DOMAIN); + var expectedEvent = new DataCiteEvent( + (jsonFormatFdoRecord(updatedAttributeRecord.attributes())), + EventType.UPDATE); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedDigitalMediaRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); + given(profileProperties.getDomain()).willReturn(DOI_DOMAIN); + + // When + var responseReceived = service.updateRecords(updateRequest, true); + + // Then + assertThat(responseReceived).isEqualTo(responseExpected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); + then(dataCiteService).should().publishToDataCite(expectedEvent, FdoType.DIGITAL_MEDIA); + } + + @Test + void testUpdateInvalidType() throws Exception { + // Given + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.HANDLE, + MAPPER.valueToTree(givenDigitalSpecimenRequestObjectUpdate())); // When Then assertThrowsExactly(InvalidRequestException.class, () -> service.updateRecords(updateRequest, true)); } - @Test void testUpdateRecordLocationDataCiteFails() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var updateRequest = genUpdateRequestBatch(List.of(handle), FdoType.DIGITAL_SPECIMEN); - var updatedAttributeRecord = genUpdateRecordAttributesAltLoc(handle); - - given(pidRepository.checkHandlesWritable(anyList())).willReturn(List.of(handle)); - given(fdoRecordService.prepareUpdateAttributes(any(), any(), any())).willReturn( - updatedAttributeRecord); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + var requestAttributes = MAPPER.valueToTree(givenDigitalSpecimenRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DIGITAL_SPECIMEN, + requestAttributes); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DIGITAL_SPECIMEN, + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); + var previousVersion = givenDigitalSpecimenFdoRecord(HANDLE); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + given(fdoRecordService.prepareUpdatedDigitalSpecimenRecord(any(), any(), eq(previousVersion), + eq(true))).willReturn(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); doThrow(JsonProcessingException.class).when(dataCiteService).publishToDataCite(any(), any()); // When @@ -224,9 +245,24 @@ void testUpdateRecordLocationDataCiteFails() throws Exception { () -> service.updateRecords(updateRequest, true)); // Then - then(pidRepository).should() - .updateRecordBatch(CREATED.getEpochSecond(), List.of(updatedAttributeRecord), true); - then(pidRepository).shouldHaveNoMoreInteractions(); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); + then(mongoRepository).shouldHaveNoMoreInteractions(); + } + + @Test + void testCreateInvalidType() { + // Given + var requestJson = + MAPPER.createObjectNode() + .set("data", MAPPER.createObjectNode() + .put("type", FdoType.HANDLE.getFdoProfile()) + .set("attributes", MAPPER.createObjectNode())); + var request = List.of(requestJson); + + // When Then + assertThrows(UnsupportedOperationException.class, () -> + service.createRecords(request)); + } } diff --git a/src/test/java/eu/dissco/core/handlemanager/service/FdoRecordServiceTest.java b/src/test/java/eu/dissco/core/handlemanager/service/FdoRecordServiceTest.java index e946d4c9..7119efe2 100644 --- a/src/test/java/eu/dissco/core/handlemanager/service/FdoRecordServiceTest.java +++ b/src/test/java/eu/dissco/core/handlemanager/service/FdoRecordServiceTest.java @@ -1,114 +1,85 @@ package eu.dissco.core.handlemanager.service; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.LOC; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.SPECIMEN_HOST; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.SPECIMEN_HOST_NAME; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.STRUCTURAL_TYPE; import static eu.dissco.core.handlemanager.testUtils.TestUtils.API_URL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.CREATED; import static eu.dissco.core.handlemanager.testUtils.TestUtils.DOC_BUILDER_FACTORY; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_ALT; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_DOMAIN; import static eu.dissco.core.handlemanager.testUtils.TestUtils.ISSUED_FOR_AGENT_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.LICENSE_NAME_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.LINKED_DIGITAL_OBJECT_TYPE_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.LINKED_DO_PID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.LOC_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.MAPPER; import static eu.dissco.core.handlemanager.testUtils.TestUtils.MEDIA_HOST_NAME_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.MEDIA_HOST_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.MOTIVATION_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.ORCHESTRATION_URL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.PID_ISSUER_TESTVAL_OTHER; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PREFIX; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_MEDIA_ID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_REFERENT_TYPE_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.REFERENT_NAME_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.ROR_DOMAIN; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.SPECIMEN_HOST_NAME_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.SPECIMEN_HOST_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.STRUCTURAL_TYPE_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.TARGET_DOI_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.TARGET_TYPE_TESTVAL; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.TOMBSTONE_TEXT_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.TRANSFORMER_FACTORY; import static eu.dissco.core.handlemanager.testUtils.TestUtils.UI_URL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genAnnotationAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDataMappingAttributes; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.UPDATED; import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDigitalMediaAttributes; import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDigitalSpecimenAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDoiRecordAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genHandleRecordAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genMasAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genOrganisationAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genSourceSystemAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRecordRequestAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRecordAttributesAltLoc; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRequestAltLoc; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneAttributes; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObjectNoHash; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObject; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectNullOptionals; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiRecordRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasRecordRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMediaRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.setLocations; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenTombstoneFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenTombstoneRecordRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdatedFdoRecord; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; -import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.verifyNoInteractions; -import com.fasterxml.jackson.databind.JsonNode; import eu.dissco.core.handlemanager.Profiles; -import eu.dissco.core.handlemanager.domain.fdo.AnnotationRequest; import eu.dissco.core.handlemanager.domain.fdo.DigitalMediaRequest; -import eu.dissco.core.handlemanager.domain.fdo.DigitalSpecimenRequest; import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; import eu.dissco.core.handlemanager.domain.fdo.FdoType; -import eu.dissco.core.handlemanager.domain.fdo.HandleRecordRequest; +import eu.dissco.core.handlemanager.domain.fdo.TombstoneRecordRequest; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.media.DcTermsType; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.media.MediaFormat; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.BaseTypeOfSpecimen; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.InformationArtefactType; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.LivingOrPreserved; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.MaterialOrDigitalEntity; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.MaterialSampleType; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.OtherSpecimenId; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.PrimarySpecimenObjectIdType; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicCategory; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicDiscipline; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicDomain; -import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicOrigin; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; -import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.PidResolutionException; import eu.dissco.core.handlemanager.properties.ApplicationProperties; -import eu.dissco.core.handlemanager.properties.ProfileProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; -import eu.dissco.core.handlemanager.testUtils.TestUtils; import eu.dissco.core.handlemanager.web.PidResolver; -import java.nio.charset.StandardCharsets; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneOffset; import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import org.junit.jupiter.api.AfterEach; +import java.util.Collections; 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.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -118,614 +89,456 @@ @MockitoSettings(strictness = Strictness.LENIENT) class FdoRecordServiceTest { - private static final HandleAttribute ADMIN_HANDLE; - - static { - ADMIN_HANDLE = new HandleAttribute(100, HANDLE.getBytes(StandardCharsets.UTF_8), - HS_ADMIN.get(), - "\\\\x0FFF000000153330303A302E4E412F32302E353030302E31303235000000C8".getBytes( - StandardCharsets.UTF_8)); - } - - private final byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); private FdoRecordService fdoRecordService; @Mock private PidResolver pidResolver; @Mock - private PidRepository pidRepository; - @Mock - private ApplicationProperties appProperties; + private ApplicationProperties applicationProperties; @Mock Environment environment; - @Mock - ProfileProperties profileProperties; - private MockedStatic mockedStatic; - private MockedStatic mockedClock; - private static final String ROR_API = "https://api.ror.org/organizations/"; @BeforeEach - void init() { + void init() throws PidResolutionException { fdoRecordService = new FdoRecordService(TRANSFORMER_FACTORY, DOC_BUILDER_FACTORY, pidResolver, - MAPPER, appProperties, profileProperties); - initTime(); - given(appProperties.getApiUrl()).willReturn(API_URL); - given(appProperties.getOrchestrationUrl()).willReturn(ORCHESTRATION_URL); - given(appProperties.getUiUrl()).willReturn(UI_URL); + MAPPER, applicationProperties); + given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) + .willReturn(ISSUED_FOR_AGENT_TESTVAL); + given(applicationProperties.getPrefix()).willReturn(PREFIX); + given(applicationProperties.getApiUrl()).willReturn(API_URL); + given(applicationProperties.getOrchestrationUrl()).willReturn(ORCHESTRATION_URL); + given(applicationProperties.getUiUrl()).willReturn(UI_URL); given(environment.matchesProfiles(Profiles.DOI)).willReturn(false); } - @AfterEach - void destroy() { - mockedStatic.close(); - mockedClock.close(); - } - @Test - void testPrepareHandleRecordAttributes() throws Exception { + void testPrepareNewHandleRecord() throws Exception { // Given var request = givenHandleRecordRequestObject(); - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var expected = genHandleRecordAttributes(handle, FdoType.HANDLE); - expected.add(ADMIN_HANDLE); + var expected = givenHandleFdoRecord(HANDLE); // When - var result = fdoRecordService.prepareHandleRecordAttributes(request, handle, FdoType.HANDLE); + var result = fdoRecordService.prepareNewHandleRecord(request, HANDLE, FdoType.HANDLE, CREATED); // Then - assertThat(result).hasSameSizeAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDoiRecordAttributes() throws Exception { + void testPrepareUpdatedHandleRecord() throws Exception { // Given - var request = givenDoiRecordRequestObject(); - var expected = genDoiRecordAttributes(handle, FdoType.DOI); - expected.add(ADMIN_HANDLE); - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + var previousVersion = givenHandleFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.HANDLE, null); + var request = givenHandleRecordRequestObjectUpdate(); // When - var result = fdoRecordService.prepareDoiRecordAttributes(request, handle, FdoType.DOI); + var result = fdoRecordService.prepareUpdatedHandleRecord(request, FdoType.HANDLE, UPDATED, + previousVersion, true); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDoiRecordAttributesDoiProfile() throws Exception { + void testPrepareUpdatedHandleRecordNoIncrement() throws Exception { // Given - var request = givenDoiRecordRequestObject(); - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var expected = genDoiRecordAttributes(handle, FdoType.DOI); - expected.add(ADMIN_HANDLE); - var replaceThis = new HandleAttribute(LOC, handle, new String( - setLocations(request.getLocations(), new String(handle, StandardCharsets.UTF_8), - FdoType.DOI, false), StandardCharsets.UTF_8)); - var withThis = new HandleAttribute(LOC, handle, new String( - setLocations(request.getLocations(), new String(handle, StandardCharsets.UTF_8), - FdoType.DOI, true), StandardCharsets.UTF_8)); - expected.remove(replaceThis); - expected.add(withThis); + var previousVersion = givenHandleFdoRecord(HANDLE); + var expectedAttributes = new ArrayList<>( + givenUpdatedFdoRecord(FdoType.HANDLE, null).attributes()); + expectedAttributes.set(expectedAttributes.indexOf( + new FdoAttribute(FdoProfile.PID_RECORD_ISSUE_NUMBER, UPDATED, "2")), + new FdoAttribute(FdoProfile.PID_RECORD_ISSUE_NUMBER, CREATED, "1")); + var expected = new FdoRecord(HANDLE, FdoType.HANDLE, expectedAttributes, null); + + var request = givenHandleRecordRequestObjectUpdate(); // When - var result = fdoRecordService.prepareDoiRecordAttributes(request, handle, FdoType.DOI); + var result = fdoRecordService.prepareUpdatedHandleRecord(request, FdoType.HANDLE, UPDATED, + previousVersion, false); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDigitalMediaAttributesMandatory() throws Exception { + void testPrepareNewDoiRecord() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenMediaRequestObject(); - var expected = TestUtils.genDigitalMediaAttributes(handle); - expected.add(ADMIN_HANDLE); + var request = givenDoiRecordRequestObject(); + var expected = givenDoiFdoRecord(HANDLE); // When - var result = fdoRecordService.prepareDigitalMediaAttributes(request, handle); + var result = fdoRecordService.prepareNewDoiRecord(request, HANDLE, FdoType.DOI, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDigitalMediaAttributesOptional() throws Exception { + void testPrepareUpdatedDoiRecord() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL).willReturn(MEDIA_HOST_NAME_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = new DigitalMediaRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, - MEDIA_HOST_TESTVAL, null, - MediaFormat.TEXT, Boolean.TRUE, LINKED_DO_PID_TESTVAL, - LINKED_DIGITAL_OBJECT_TYPE_TESTVAL, - "a", HANDLE, PrimarySpecimenObjectIdType.RESOLVABLE, "b", DcTermsType.IMAGE, "jpeg", - "c", - "license", "license", "c", "d", null, "e"); - var expected = genDigitalMediaAttributes(handle, request); - expected.add(ADMIN_HANDLE); + var previousVersion = givenDoiFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.DOI, null); + var request = givenDoiRecordRequestObjectUpdate(); // When - var result = fdoRecordService.prepareDigitalMediaAttributes(request, handle); + var result = fdoRecordService.prepareUpdatedDoiRecord(request, FdoType.DOI, UPDATED, + previousVersion, true); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDigitalMediaFullAttributes() throws Exception { - // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL).willReturn(MEDIA_HOST_NAME_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = new DigitalMediaRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, - MEDIA_HOST_TESTVAL, - MEDIA_HOST_NAME_TESTVAL, MediaFormat.TEXT, Boolean.TRUE, LINKED_DO_PID_TESTVAL, - LINKED_DIGITAL_OBJECT_TYPE_TESTVAL, "a", "b", PrimarySpecimenObjectIdType.GLOBAL, - "d", - DcTermsType.IMAGE, "e", "f", LICENSE_NAME_TESTVAL, "g", "h", "i", - PrimarySpecimenObjectIdType.LOCAL, "j"); - var expected = genDigitalMediaAttributes(handle, request); - expected.add(ADMIN_HANDLE); + void testPrepareNewMediaRecordMin() throws Exception { + var request = givenDigitalMediaRequestObject(); + var expected = givenDigitalMediaFdoRecord(HANDLE); // When - var result = fdoRecordService.prepareDigitalMediaAttributes(request, handle); + var result = fdoRecordService.prepareNewDigitalMediaRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDigitalSpecimenRecordMandatoryAttributes() throws Exception { - // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL).willReturn(SPECIMEN_HOST_NAME_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenDigitalSpecimenRequestObjectNullOptionals(); - var expected = genDigitalSpecimenAttributes(handle, request); - expected.add(ADMIN_HANDLE); + void testPrepareNewMediaRecordFull() throws Exception { + var request = new DigitalMediaRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, MEDIA_HOST_TESTVAL, + MEDIA_HOST_NAME_TESTVAL, MediaFormat.TEXT, Boolean.TRUE, LINKED_DO_PID_TESTVAL, + LINKED_DIGITAL_OBJECT_TYPE_TESTVAL, "a", PRIMARY_MEDIA_ID_TESTVAL, + PrimarySpecimenObjectIdType.RESOLVABLE, "b", DcTermsType.IMAGE, "jpeg", "c", "license", + "license", "c", "d", null, "e"); + var expected = new FdoRecord(HANDLE, FdoType.DIGITAL_MEDIA, + genDigitalMediaAttributes(HANDLE, request, CREATED), PRIMARY_MEDIA_ID_TESTVAL); // When - var result = fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, handle); + var result = fdoRecordService.prepareNewDigitalMediaRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDigitalSpecimenRecordMandatoryAttributesQNumber() throws Exception { + void testPrepareUpdatedMediaRecord() throws Exception { // Given - String qid = "https://www.wikidata.org/wiki/Q12345"; - String qidUrl = "https://wikidata.org/w/rest.php/wikibase/v0/entities/items/Q12345"; - given(pidResolver.getObjectName(any())).willReturn("placeholder"); - given(pidResolver.resolveQid(any())).willReturn("placeholder"); - var request = new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, qid, null, - "PhysicalId", - null, null, NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, null, null, - null, - null, null, null, null, null, null, null, null, null); - + var previousVersion = givenDigitalMediaFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.DIGITAL_MEDIA, PRIMARY_MEDIA_ID_TESTVAL); + var request = givenDigitalMediaRequestObjectUpdate(); // When - fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, handle); + var result = fdoRecordService.prepareUpdatedDigitalMediaRecord(request, UPDATED, + previousVersion, true); // Then - then(pidResolver).should().resolveQid(qidUrl); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDigitalSpecimenRecordMandatoryAttributesBadSpecimenHost() throws Exception { - // Given - String specimenId = "12345"; - given(pidResolver.getObjectName(any())).willReturn("placeholder"); - var request = new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, specimenId, null, - "PhysicalId", null, null, NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, - null, - null, null, null, null, null, null, null, null, null, null, null); - - // When - assertThrowsExactly(PidResolutionException.class, - () -> fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, handle)); - } - - @Test - void testPrepareDigitalSpecimenRecordOptionalAttributes() throws Exception { - // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL).willReturn(SPECIMEN_HOST_NAME_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenDigitalSpecimenRequestObjectOptionalsInit(); - var expected = genDigitalSpecimenAttributes(handle, request); - expected.add(ADMIN_HANDLE); + void testPrepareNewDigitalSpecimenRecordMin() throws Exception { + var request = givenDigitalSpecimenRequestObjectNullOptionals(); + var expected = givenDigitalSpecimenFdoRecord(HANDLE); // When - var result = fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, handle); + var result = fdoRecordService.prepareNewDigitalSpecimenRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareAnnotationAttributesOptional() throws Exception { - // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(pidRepository.resolveHandleAttributes(any(byte[].class))).willReturn( - genHandleRecordAttributes(handle, FdoType.ANNOTATION)); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenAnnotationRequestObject(); - var expected = genAnnotationAttributes(handle, true); - expected.add(ADMIN_HANDLE); + void testPrepareNewSpecimenRecordFull() throws Exception { + var request = givenDigitalSpecimenRequestObject(); + var expected = new FdoRecord(HANDLE, FdoType.DIGITAL_SPECIMEN, + genDigitalSpecimenAttributes(HANDLE, request, CREATED), + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); // When - var result = fdoRecordService.prepareAnnotationAttributes(request, handle); + var result = fdoRecordService.prepareNewDigitalSpecimenRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareAnnotationAttributes() throws Exception { + void testPrepareUpdatedSpecimenRecord() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(pidRepository.resolveHandleAttributes(any(byte[].class))).willReturn( - genHandleRecordAttributes(handle, FdoType.ANNOTATION)); - given(pidRepository.resolveHandleAttributes(any(byte[].class))).willReturn( - genHandleRecordAttributes(handle, FdoType.ANNOTATION)); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = new AnnotationRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, TARGET_DOI_TESTVAL, TARGET_TYPE_TESTVAL, MOTIVATION_TESTVAL, null); - var expected = genAnnotationAttributes(handle, false); - expected.add(ADMIN_HANDLE); + var previousVersion = givenDigitalSpecimenFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.DIGITAL_SPECIMEN, + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); + var request = givenDigitalSpecimenRequestObjectUpdate(); // When - var result = fdoRecordService.prepareAnnotationAttributes(request, handle); + var result = fdoRecordService.prepareUpdatedDigitalSpecimenRecord(request, UPDATED, + previousVersion, true); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareMasRecordAttributes() throws Exception { + void testPrepareNewAnnotationRecordMin() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenMasRecordRequestObject(); - var expected = genMasAttributes(handle); - expected.add(ADMIN_HANDLE); + var request = givenAnnotationRequestObjectNoHash(); + var expected = givenAnnotationFdoRecord(HANDLE, false); // When - var result = fdoRecordService.prepareMasRecordAttributes(request, handle); + var result = fdoRecordService.prepareNewAnnotationRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareDataMappingAttributes() throws Exception { + void testPrepareNewAnnotationRecordFull() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenDataMappingRequestObject(); - var expected = genDataMappingAttributes(handle); - expected.add(ADMIN_HANDLE); + var request = givenAnnotationRequestObject(); + var expected = givenAnnotationFdoRecord(HANDLE, true); // When - var result = fdoRecordService.prepareDataMappingAttributes(request, handle); + var result = fdoRecordService.prepareNewAnnotationRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } - @Test - void testPrepareSourceSystemAttributes() throws Exception { - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenSourceSystemRequestObject(); - var expected = genSourceSystemAttributes(handle); - expected.add(ADMIN_HANDLE); + void testPrepareUpdatedAnnotationRecord() throws Exception { + // Given + var previousVersion = givenAnnotationFdoRecord(HANDLE, false); + var expected = givenUpdatedFdoRecord(FdoType.ANNOTATION, null); + var request = givenAnnotationRequestObjectUpdate(); // When - var result = fdoRecordService.prepareSourceSystemAttributes(request, handle); + var result = fdoRecordService.prepareUpdatedAnnotationRecord(request, UPDATED, + previousVersion, true); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPrepareOrganisationAttributes() throws Exception { - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL).willReturn(SPECIMEN_HOST_NAME_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = givenOrganisationRequestObject(); - var expected = genOrganisationAttributes(handle, request); - expected.add(ADMIN_HANDLE); + void testPrepareNewMasRecord() throws Exception { + // Given + var request = givenMasRecordRequestObject(); + var expected = givenMasFdoRecord(HANDLE); // When - var result = fdoRecordService.prepareOrganisationAttributes(request, handle); + var result = fdoRecordService.prepareNewMasRecord(request, HANDLE, CREATED); // Then - assertThat(result).hasSameElementsAs(expected).hasSameSizeAs(expected); - + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPidIssuerIsRor() throws Exception { + void testPrepareUpdatedMasRecord() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var request = new HandleRecordRequest(ISSUED_FOR_AGENT_TESTVAL, ISSUED_FOR_AGENT_TESTVAL, - STRUCTURAL_TYPE_TESTVAL, null); - var expected = genHandleRecordAttributes(handle, FdoType.HANDLE); - expected.add(ADMIN_HANDLE); + var previousVersion = givenMasFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.MAS, null); + var request = givenMasRecordRequestObjectUpdate(); // When - var result = fdoRecordService.prepareHandleRecordAttributes(request, handle, FdoType.HANDLE); + var result = fdoRecordService.prepareUpdatedMasRecord(request, UPDATED, previousVersion, true); // Then - assertThat(result).hasSameSizeAs(expected).hasSameSizeAs(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testPidIssuerBad() throws Exception { + void testPrepareNewDataMappingRecord() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn("placeholder"); - var request = new HandleRecordRequest(ISSUED_FOR_AGENT_TESTVAL, "abc", - STRUCTURAL_TYPE_TESTVAL, - null); + var request = givenDataMappingRequestObject(); + var expected = givenDataMappingFdoRecord(HANDLE); + + // When + var result = fdoRecordService.prepareNewDataMappingRecord(request, HANDLE, CREATED); // Then - var e = assertThrowsExactly(InvalidRequestException.class, - () -> fdoRecordService.prepareHandleRecordAttributes(request, handle, FdoType.HANDLE)); - assertThat(e.getMessage()).contains(ROR_DOMAIN).contains(HANDLE_DOMAIN); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testBadRor() throws Exception { + void testPrepareUpdatedDataMappingRecord() throws Exception { // Given - given(pidResolver.getObjectName(any())).willReturn("placeholder"); - var request = new HandleRecordRequest("abc", ISSUED_FOR_AGENT_TESTVAL, - STRUCTURAL_TYPE_TESTVAL, - null); - - var e = assertThrowsExactly(InvalidRequestException.class, - () -> fdoRecordService.prepareHandleRecordAttributes(request, handle, FdoType.HANDLE)); - assertThat(e.getMessage()).contains(ROR_DOMAIN); - } - - @Test - void testSpecimenHostResolvable() throws Exception { - given(pidResolver.getObjectName(any())).willReturn(PID_ISSUER_TESTVAL_OTHER) - .willReturn(ISSUED_FOR_AGENT_TESTVAL); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - var expected = genDigitalSpecimenAttributes(handle); - expected.add(ADMIN_HANDLE); - - var request = new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, - SPECIMEN_HOST_TESTVAL, - null, PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, - NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, null, null, null, null, null, - null, null, null, null, null, null, null); + var previousVersion = givenDataMappingFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.DATA_MAPPING, null); + var request = givenDataMappingRequestObjectUpdate(); // When - var result = fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, handle); - - // Then - assertThat(result).hasSameSizeAs(expected).hasSameSizeAs(expected); - } - - @Test - void testSpecimenHostNotResolvable() throws Exception { - var request = new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, - SPECIMEN_HOST_TESTVAL, - null, PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, - NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, null, null, null, null, null, - null, null, null, null, null, null, null); - - var specimenHostRorApi = request.getSpecimenHost().replace(ROR_DOMAIN, ROR_API); - given(pidResolver.getObjectName(specimenHostRorApi)).willThrow(PidResolutionException.class); - given(pidResolver.getObjectName(not(eq(specimenHostRorApi)))).willReturn("placeholder"); + var result = fdoRecordService.prepareUpdatedDataMappingRecord(request, UPDATED, previousVersion, + true); // Then - assertThrowsExactly(PidResolutionException.class, - () -> fdoRecordService.prepareDigitalSpecimenRecordAttributes(request, handle)); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testUpdateSpecimenHostResolveName() throws Exception { + void testPrepareNewSourceSystemRecord() throws Exception { // Given - var request = generalUpdateRequest(List.of(SPECIMEN_HOST.get()), SPECIMEN_HOST_TESTVAL); - var apiLocation = "https://api.ror.org/organizations/0x123"; - given(pidResolver.getObjectName(apiLocation)).willReturn(SPECIMEN_HOST_NAME_TESTVAL); - ArrayList expected = new ArrayList<>(); - expected.add(new HandleAttribute(SPECIMEN_HOST.index(), handle, SPECIMEN_HOST.get(), - SPECIMEN_HOST_TESTVAL.getBytes(StandardCharsets.UTF_8))); - expected.add(new HandleAttribute(SPECIMEN_HOST_NAME.index(), handle, SPECIMEN_HOST_NAME.get(), - SPECIMEN_HOST_NAME_TESTVAL.getBytes(StandardCharsets.UTF_8))); + var request = givenSourceSystemRequestObject(); + var expected = givenSourceSystemFdoRecord(HANDLE); // When - var response = fdoRecordService.prepareUpdateAttributes(HANDLE.getBytes(), request, - FdoType.DIGITAL_SPECIMEN); + var result = fdoRecordService.prepareNewSourceSystemRecord(request, HANDLE, CREATED); // Then - assertThat(response).isEqualTo(expected); - assertThat(hasNoDuplicateElements(response)).isTrue(); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testUpdateSpecimenHostNameInRequest() throws Exception { + void testPrepareUpdatedSourceSystemRecord() throws Exception { // Given - var pid = HANDLE.getBytes(StandardCharsets.UTF_8); - var request = generalUpdateRequest(List.of(SPECIMEN_HOST.get(), SPECIMEN_HOST_NAME.get()), - SPECIMEN_HOST_TESTVAL); - ArrayList expected = new ArrayList<>(); - expected.add(new HandleAttribute(SPECIMEN_HOST.index(), pid, SPECIMEN_HOST.get(), - SPECIMEN_HOST_TESTVAL.getBytes(StandardCharsets.UTF_8))); - expected.add(new HandleAttribute(SPECIMEN_HOST_NAME.index(), pid, SPECIMEN_HOST_NAME.get(), - SPECIMEN_HOST_TESTVAL.getBytes(StandardCharsets.UTF_8))); + var previousVersion = givenSourceSystemFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.SOURCE_SYSTEM, null); + var request = givenSourceSystemRequestObjectUpdate(); // When - var response = fdoRecordService.prepareUpdateAttributes(HANDLE.getBytes(), request, - FdoType.DIGITAL_SPECIMEN); + var result = fdoRecordService.prepareUpdatedSourceSystemRecord(request, UPDATED, + previousVersion, true); // Then - assertThat(response).isEqualTo(expected); - verifyNoInteractions(pidResolver); - assertThat(hasNoDuplicateElements(response)).isTrue(); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testUpdateAttributesAltLoc() throws Exception { + void testPrepareNewOrganisationRecord() throws Exception { // Given - var updateRequest = genUpdateRequestAltLoc(); - var expected = genUpdateRecordAttributesAltLoc(HANDLE.getBytes(StandardCharsets.UTF_8)); + var request = givenOrganisationRequestObject(); + var expected = givenOrganisationFdoRecord(HANDLE); // When - var response = fdoRecordService.prepareUpdateAttributes(HANDLE.getBytes(StandardCharsets.UTF_8), - updateRequest, FdoType.HANDLE); + var result = fdoRecordService.prepareNewOrganisationRecord(request, HANDLE, CREATED); // Then - assertThat(response).isEqualTo(expected); - assertThat(hasNoDuplicateElements(response)).isTrue(); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isEqualTo(expected.primaryLocalId()); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testUpdateAttributesStructuralType() throws Exception { + void testPrepareUpdatedOrganisationRecord() throws Exception { // Given - var updateRequest = MAPPER.createObjectNode(); - updateRequest.put(STRUCTURAL_TYPE.get(), STRUCTURAL_TYPE_TESTVAL.toString()); - var expected = List.of( - new HandleAttribute(STRUCTURAL_TYPE.index(), HANDLE.getBytes(StandardCharsets.UTF_8), - STRUCTURAL_TYPE.get(), - STRUCTURAL_TYPE_TESTVAL.toString().getBytes(StandardCharsets.UTF_8))); + var previousVersion = givenOrganisationFdoRecord(HANDLE); + var expected = givenUpdatedFdoRecord(FdoType.ORGANISATION, null); + var request = givenOrganisationRequestObjectUpdate(); // When - var response = fdoRecordService.prepareUpdateAttributes(HANDLE.getBytes(StandardCharsets.UTF_8), - updateRequest, FdoType.DIGITAL_SPECIMEN); + var result = fdoRecordService.prepareUpdatedOrganisationRecord(request, UPDATED, + previousVersion, true); // Then - assertThat(response).isEqualTo(expected); - assertThat(hasNoDuplicateElements(response)).isTrue(); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testUpdateAttributesOtherSpecimenIds() throws Exception { - // Given - var updateRequest = MAPPER.readTree(""" - { - "otherSpecimenIds": [ - { - "identifierValue":"a", - "identifierType":"localId" - } - ] - } - """); - var expectedStr = MAPPER.writeValueAsString(MAPPER.readTree(""" - [ - { - "identifierValue":"a", - "identifierType":"localId" - } - ] - """)); - var expected = List.of( - new HandleAttribute(FdoProfile.OTHER_SPECIMEN_IDS, HANDLE.getBytes(StandardCharsets.UTF_8), - expectedStr)); + void testPrepareTombstoneRecordNoRelatedIds() throws Exception { + var previousVersion = givenHandleFdoRecord(HANDLE); + var request = new TombstoneRecordRequest(TOMBSTONE_TEXT_TESTVAL, null); + var expected = new FdoRecord(HANDLE, FdoType.HANDLE, genTombstoneAttributes(request), + null); // When - var response = fdoRecordService.prepareUpdateAttributes(HANDLE.getBytes(StandardCharsets.UTF_8), - updateRequest, FdoType.DIGITAL_SPECIMEN); + var result = fdoRecordService.prepareTombstoneRecord(request, UPDATED, previousVersion); // Then - assertThat(response).isEqualTo(expected); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testUpdateAttributesNullValue() throws Exception { - // Given - var updateRequest = MAPPER.readTree(""" - { - "otherSpecimenIds": null - } - """); + void testPrepareTombstoneRecordEmptyRelatedIds() throws Exception { + var previousVersion = givenHandleFdoRecord(HANDLE); + var request = new TombstoneRecordRequest(TOMBSTONE_TEXT_TESTVAL, Collections.emptyList()); + var expected = new FdoRecord(HANDLE, FdoType.HANDLE, genTombstoneAttributes(request), + null); // When - var response = fdoRecordService.prepareUpdateAttributes(HANDLE.getBytes(StandardCharsets.UTF_8), - updateRequest, FdoType.DIGITAL_SPECIMEN); + var result = fdoRecordService.prepareTombstoneRecord(request, UPDATED, previousVersion); // Then - assertThat(response).isEmpty(); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } @Test - void testTombstoneAttributes() throws Exception { - // Given - var expected = genTombstoneRecordRequestAttributes(HANDLE.getBytes(StandardCharsets.UTF_8)); + void testPrepareTombstoneRecordFull() throws Exception { + var previousVersion = givenHandleFdoRecord(HANDLE); + var request = givenTombstoneRecordRequestObject(); + var expected = givenTombstoneFdoRecord(); // When - var response = fdoRecordService.prepareTombstoneAttributes(HANDLE.getBytes(), - genTombstoneRequest()); + var result = fdoRecordService.prepareTombstoneRecord(request, UPDATED, previousVersion); // Then - assertThat(response).isEqualTo(expected); - assertThat(hasNoDuplicateElements(response)).isTrue(); - } - - private DigitalSpecimenRequest givenDigitalSpecimenRequestObjectOptionalsInit() - throws InvalidRequestException { - return new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, - SPECIMEN_HOST_TESTVAL, - SPECIMEN_HOST_NAME_TESTVAL, PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, - PrimarySpecimenObjectIdType.LOCAL, "b", - NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, - List.of(new OtherSpecimenId("Id", "local identifier")), TopicOrigin.NATURAL, - TopicDomain.LIFE, TopicDiscipline.ZOO, TopicCategory.AMPHIBIANS, - LivingOrPreserved.LIVING, - BaseTypeOfSpecimen.INFO, InformationArtefactType.MOVING_IMG, - MaterialSampleType.ORG_PART, - MaterialOrDigitalEntity.DIGITAL, false, HANDLE_ALT, HANDLE_ALT); - } - - private JsonNode generalUpdateRequest(List attributesToUpdate, String placeholder) { - var requestAttributes = MAPPER.createObjectNode(); - for (var attribute : attributesToUpdate) { - requestAttributes.put(attribute, placeholder); - } - return requestAttributes; - } - - private boolean hasNoDuplicateElements(List fdoRecord) { - return fdoRecord.size() == (new HashSet<>(fdoRecord).size()); - } - - private void initTime() { - Clock clock = Clock.fixed(CREATED, ZoneOffset.UTC); - Instant instant = Instant.now(clock); - mockedStatic = mockStatic(Instant.class); - mockedStatic.when(Instant::now).thenReturn(instant); - mockedStatic.when(() -> Instant.from(any())).thenReturn(instant); - mockedClock = mockStatic(Clock.class); - mockedClock.when(Clock::systemUTC).thenReturn(clock); + assertThat(result.attributes()).hasSameElementsAs(expected.attributes()); + assertThat(result.primaryLocalId()).isNull(); + assertThat(result.fdoType()).isEqualTo(expected.fdoType()); + assertThat(result.handle()).isEqualTo(expected.handle()); } } diff --git a/src/test/java/eu/dissco/core/handlemanager/service/HandleServiceTest.java b/src/test/java/eu/dissco/core/handlemanager/service/HandleServiceTest.java index b1110d29..db6d85b8 100644 --- a/src/test/java/eu/dissco/core/handlemanager/service/HandleServiceTest.java +++ b/src/test/java/eu/dissco/core/handlemanager/service/HandleServiceTest.java @@ -1,75 +1,78 @@ package eu.dissco.core.handlemanager.service; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.LINKED_DO_PID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_MEDIA_ID; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PRIMARY_SPECIMEN_OBJECT_ID; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.PID_STATUS; import static eu.dissco.core.handlemanager.testUtils.TestUtils.CREATED; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_ALT; import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_DOMAIN; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.HANDLE_LIST_STR; import static eu.dissco.core.handlemanager.testUtils.TestUtils.MAPPER; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PATH; import static eu.dissco.core.handlemanager.testUtils.TestUtils.PID_STATUS_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.UI_URL; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genAnnotationAttributes; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.PRIMARY_MEDIA_ID_TESTVAL; import static eu.dissco.core.handlemanager.testUtils.TestUtils.genCreateRecordRequest; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDataMappingAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDigitalSpecimenAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genDoiRecordAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genHandleRecordAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genMasAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genObjectNodeAttributeRecord; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genOrganisationAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genSourceSystemAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRecordRequestAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genTombstoneRequestBatch; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRecordAttributesAltLoc; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.genUpdateRequestBatch; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObjectNoHash; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationResponseWrite; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenAnnotationRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectNullOptionals; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDataMappingRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalMediaRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDigitalSpecimenRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiRecordRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenDoiRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMediaRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenHandleRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasRecordRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMasRecordRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenMongoDocument; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationRequestObject; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseNullAttributes; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseRead; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseReadSingle; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWrite; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWriteArchive; -import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenRecordResponseWriteSmallResponse; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenOrganisationRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenReadResponse; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemFdoRecord; import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemRequestObject; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenSourceSystemRequestObjectUpdate; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenTombstoneFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenTombstoneRequest; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdateRequest; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenUpdatedFdoRecord; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenWriteResponseFull; +import static eu.dissco.core.handlemanager.testUtils.TestUtils.givenWriteResponseIdsOnly; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mockStatic; -import com.fasterxml.jackson.databind.JsonNode; import eu.dissco.core.handlemanager.Profiles; -import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; import eu.dissco.core.handlemanager.domain.fdo.FdoType; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.PidStatus; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import eu.dissco.core.handlemanager.exceptions.PidResolutionException; import eu.dissco.core.handlemanager.properties.ProfileProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; +import eu.dissco.core.handlemanager.repository.MongoRepository; import eu.dissco.core.handlemanager.testUtils.TestUtils; import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Instant; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.stream.Stream; +import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -83,14 +86,14 @@ @ActiveProfiles(profiles = Profiles.HANDLE) class HandleServiceTest { - @Mock - private PidRepository pidRepository; @Mock private FdoRecordService fdoRecordService; @Mock private PidNameGeneratorService pidNameGeneratorService; @Mock private ProfileProperties profileProperties; + @Mock + MongoRepository mongoRepository; private PidService service; private List handles; private MockedStatic mockedStatic; @@ -100,8 +103,8 @@ class HandleServiceTest { void setup() { initTime(); initHandleList(); - service = new HandleService(pidRepository, fdoRecordService, pidNameGeneratorService, MAPPER, - profileProperties); + service = new HandleService(fdoRecordService, pidNameGeneratorService, MAPPER, + profileProperties, mongoRepository); } private void initTime() { @@ -126,665 +129,577 @@ void destroy() { mockedClock.close(); } - @Test - void testResolveSingleRecord() throws Exception { - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - String path = UI_URL + HANDLE; - List recordAttributeList = genHandleRecordAttributes(handle, - FdoType.HANDLE); - - var responseExpected = givenRecordResponseReadSingle(HANDLE, path, FdoType.HANDLE, - genObjectNodeAttributeRecord(recordAttributeList)); - - given(pidRepository.resolveHandleAttributes(any(byte[].class))).willReturn(recordAttributeList); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + void testCreateAnnotationNoHash() throws Exception { + var request = genCreateRecordRequest(givenAnnotationRequestObject(), FdoType.ANNOTATION); + var fdoRecord = givenAnnotationFdoRecord(HANDLE, false); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.ANNOTATION); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewAnnotationRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.resolveSingleRecord(handle, path); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testRemoveHsAdmin() throws Exception { - - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - String path = UI_URL + HANDLE; - var adminHandle = new HandleAttribute(HS_ADMIN.index(), handle, HS_ADMIN.get(), - "\\\\x0FFF000000153330303A302E4E412F32302E353030302E31303235000000C8".getBytes( - StandardCharsets.UTF_8)); - var recordAttributeList = genHandleRecordAttributes(handle, FdoType.HANDLE); - recordAttributeList.add(adminHandle); - - var responseExpected = givenRecordResponseReadSingle(HANDLE, path, FdoType.HANDLE, - genObjectNodeAttributeRecord(recordAttributeList)); - - given(pidRepository.resolveHandleAttributes(any(byte[].class))).willReturn(recordAttributeList); + void testCreateAnnotationIncludeHash() throws Exception { + var request = genCreateRecordRequest(givenAnnotationRequestObject(), FdoType.ANNOTATION); + var fdoRecord = givenAnnotationFdoRecord(HANDLE, true); + var expected = givenWriteResponseIdsOnly(List.of(fdoRecord), FdoType.ANNOTATION, + HANDLE_DOMAIN); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewAnnotationRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.resolveSingleRecord(handle, path); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } - @Test - void testResolveSingleRecordNotFound() { - - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - String path = UI_URL + HANDLE; - given(pidRepository.resolveHandleAttributes(any(byte[].class))).willReturn(new ArrayList<>()); - - // When - var exception = assertThrowsExactly(PidResolutionException.class, - () -> service.resolveSingleRecord(handle, path)); - // Then - assertThat(exception.getMessage()).contains(HANDLE); - } @Test - void testResolveBatchRecord() throws Exception { + void testUpdateAnnotation() throws Exception { // Given - String path = UI_URL; - List repositoryResponse = new ArrayList<>(); - for (byte[] handle : handles) { - repositoryResponse.addAll(genHandleRecordAttributes(handle, FdoType.HANDLE)); - } - var responseExpected = givenRecordResponseRead(handles, path, FdoType.HANDLE); - - given(pidRepository.resolveHandleAttributes(anyList())).willReturn(repositoryResponse); + var previousVersion = givenAnnotationFdoRecord(HANDLE, false); + var request = MAPPER.valueToTree(givenAnnotationRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.ANNOTATION, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.ANNOTATION, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseIdsOnly(List.of(updatedAttributeRecord), + FdoType.ANNOTATION, HANDLE_DOMAIN); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedAnnotationRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.resolveBatchRecord(handles, path); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testResolveBatchDigitalSpecimenRecord() throws Exception { - // Given - String path = UI_URL; - List repositoryResponse = new ArrayList<>(); - for (byte[] handle : handles) { - repositoryResponse.addAll(genDigitalSpecimenAttributes(handle)); - } - var responseExpected = givenRecordResponseRead(handles, path, + void testCreateDigitalSpecimen() throws Exception { + var request = genCreateRecordRequest(givenDigitalSpecimenRequestObject(), FdoType.DIGITAL_SPECIMEN); - - given(pidRepository.resolveHandleAttributes(anyList())).willReturn(repositoryResponse); + var fdoRecord = givenDigitalSpecimenFdoRecord(HANDLE); + var expected = givenWriteResponseIdsOnly(List.of(fdoRecord), + FdoType.DIGITAL_SPECIMEN, HANDLE_DOMAIN); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDigitalSpecimenRecord(any(), any(), any())).willReturn( + fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.resolveBatchRecord(handles, path); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testSearchByPhysicalSpecimenId() throws Exception { - // Given - var expectedAttributes = genDigitalSpecimenAttributes(HANDLE.getBytes(StandardCharsets.UTF_8)); - var responseExpected = givenRecordResponseWrite( - List.of(HANDLE.getBytes(StandardCharsets.UTF_8)), FdoType.DIGITAL_SPECIMEN); + void testCreateDigitalSpecimenObjectExists() throws Exception { + var request = genCreateRecordRequest(givenDigitalSpecimenRequestObject(), + FdoType.DIGITAL_SPECIMEN); + var fdoRecord = givenDigitalSpecimenFdoRecord(HANDLE); + given(mongoRepository.searchByPrimaryLocalId(any(), any())).willReturn(List.of(fdoRecord)); + + // When / Then + assertThrows(InvalidRequestException.class, () -> service.createRecords(List.of(request))); - given(pidRepository.searchByNormalisedPhysicalIdentifierFullRecord(anyList())).willReturn( - expectedAttributes); + } + + @Test + void testUpdateDigitalSpecimen() throws Exception { + // Given + var previousVersion = givenDigitalSpecimenFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenDigitalSpecimenRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DIGITAL_SPECIMEN, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DIGITAL_SPECIMEN, + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseIdsOnly(List.of(updatedAttributeRecord), + FdoType.DIGITAL_SPECIMEN, HANDLE_DOMAIN); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedDigitalSpecimenRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.searchByPhysicalSpecimenId(PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testSearchByPhysicalSpecimenIdTwoResolution() throws Exception { - // Given - List attributeList = new ArrayList<>(); - attributeList.addAll(genDigitalSpecimenAttributes(HANDLE.getBytes(StandardCharsets.UTF_8))); - attributeList.addAll(genDigitalSpecimenAttributes(HANDLE_ALT.getBytes(StandardCharsets.UTF_8))); - - given(pidRepository.searchByNormalisedPhysicalIdentifierFullRecord(anyList())).willReturn( - attributeList); + void testCreateDoi() throws Exception { + var request = genCreateRecordRequest(givenDoiRecordRequestObject(), FdoType.DOI); + var fdoRecord = givenDoiFdoRecord(HANDLE); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.DOI); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDoiRecord(any(), any(), any(), any())).willReturn(fdoRecord); + given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - Exception e = assertThrowsExactly(PidResolutionException.class, - () -> service.searchByPhysicalSpecimenId(PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); + var result = service.createRecords(List.of(request)); // Then - assertThat(e).hasMessage( - "More than one handle record corresponds to the provided collection facility and physical identifier."); + assertThat(result).isEqualTo(expected); } @Test - void testCreateHandleRecord() throws Exception { + void testUpdateDoiRecord() throws Exception { // Given - byte[] handle = handles.get(0); - var request = genCreateRecordRequest(givenHandleRecordRequestObject(), FdoType.HANDLE); - var responseExpected = givenRecordResponseWrite(List.of(handle), FdoType.HANDLE); - List handleRecord = genHandleRecordAttributes(handle, FdoType.HANDLE); - - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareHandleRecordAttributes(any(), any(), - eq(FdoType.HANDLE))).willReturn(handleRecord); + var previousVersion = givenDoiFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenDoiRecordRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DOI, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DOI, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseFull(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedDoiRecord(any(), any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(List.of(request)); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testCreateDoiRecord() throws Exception { - // Given - byte[] handle = handles.get(0); - var request = genCreateRecordRequest(givenDoiRecordRequestObject(), FdoType.DOI); - var responseExpected = givenRecordResponseWrite(List.of(handle), FdoType.DOI); - List doiRecord = genDoiRecordAttributes(handle, FdoType.DOI); - - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareDoiRecordAttributes(any(), any(), eq(FdoType.DOI))).willReturn( - doiRecord); + void testCreateHandle() throws Exception { + var request = genCreateRecordRequest(givenHandleRecordRequestObject(), FdoType.HANDLE); + var fdoRecord = givenHandleFdoRecord(HANDLE); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.HANDLE); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewHandleRecord(any(), any(), any(), any())).willReturn( + fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(List.of(request)); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testCreateDigitalSpecimen() throws Exception { + void testUpdateHandleRecord() throws Exception { // Given - byte[] handle = handles.get(0); - var request = genCreateRecordRequest(givenDigitalSpecimenRequestObjectNullOptionals(), - FdoType.DIGITAL_SPECIMEN); - List digitalSpecimen = genDigitalSpecimenAttributes(handle); - var digitalSpecimenSublist = digitalSpecimen.stream() - .filter(row -> row.getType().equals(PRIMARY_SPECIMEN_OBJECT_ID.get())).toList(); - - var responseExpected = givenRecordResponseWriteSmallResponse(digitalSpecimenSublist, - List.of(handle), FdoType.DIGITAL_SPECIMEN); - - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareDigitalSpecimenRecordAttributes(any(), any())).willReturn( - digitalSpecimen); + var previousVersion = givenHandleFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenHandleRecordRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.HANDLE, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.HANDLE, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseFull(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedHandleRecord(any(), any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(List.of(request)); + var result = service.updateRecords(updateRequest, true); // Then - then(pidRepository).should().postAttributesToDb(CREATED.getEpochSecond(), digitalSpecimen); - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testCreateDigitalSpecimenSpecimenAlreadyExists() { + void testUpdateHandleRecordInternalDuplicates() throws Exception { // Given - byte[] handle = handles.get(0); - var digitalSpecimen = givenDigitalSpecimenRequestObjectNullOptionals(); - var request = List.of((JsonNode) genCreateRecordRequest(digitalSpecimen, - FdoType.DIGITAL_SPECIMEN)); - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(pidRepository.searchByNormalisedPhysicalIdentifier(anyList())).willReturn(List.of( - new HandleAttribute(FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID, handle, - digitalSpecimen.getNormalisedPrimarySpecimenObjectId()))); + var attributes = new ArrayList<>(givenHandleFdoRecord(HANDLE).attributes()); + attributes.set(attributes.indexOf(new FdoAttribute(PID_STATUS, CREATED, PID_STATUS_TESTVAL)), + new FdoAttribute(PID_STATUS, CREATED, PidStatus.TOMBSTONED.name())); + var previousVersion = new FdoRecord(HANDLE_ALT, FdoType.HANDLE, attributes, null); + var request = MAPPER.valueToTree(givenHandleRecordRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.HANDLE, request); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); - // When Then - assertThrowsExactly(InvalidRequestException.class, () -> service.createRecords(request)); + // When / Then + assertThrows(InvalidRequestException.class, () -> service.updateRecords(updateRequest, true)); } @Test - void testCreateDigitalMediaRecord() throws Exception { + void testUpdateHandleRecordNotWritableInternalDuplicates() throws Exception { // Given - byte[] handle = handles.get(0); - var request = genCreateRecordRequest(givenMediaRequestObject(), FdoType.DIGITAL_MEDIA); - var handleRecord = TestUtils.genDigitalMediaAttributes(handle); - var handleRecordSublist = handleRecord.stream().filter( - row -> row.getType().equals(PRIMARY_MEDIA_ID.get()) || row.getType() - .equals(LINKED_DO_PID.get())).toList(); - - var responseExpected = givenRecordResponseWriteSmallResponse(handleRecordSublist, - List.of(handle), FdoType.DIGITAL_MEDIA); - - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareDigitalMediaAttributes(any(), any())).willReturn( - handleRecordSublist); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); - - // When - var responseReceived = service.createRecords(List.of(request)); + var request = MAPPER.valueToTree(givenHandleRecordRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.HANDLE, request); - // Then - assertThat(responseReceived).isEqualTo(responseExpected); + // When / Then + assertThrows(InvalidRequestException.class, () -> service.updateRecords(updateRequest, true)); } @Test - void testCreateMasRecord() throws Exception { + void testUpdateHandleRecordNotFound() throws Exception { // Given - byte[] handle = handles.get(0); - var request = genCreateRecordRequest(givenHandleRecordRequestObject(), FdoType.MAS); - var responseExpected = givenRecordResponseWrite(List.of(handle), FdoType.MAS); - List handleRecord = genMasAttributes(handle); + var request = MAPPER.valueToTree(givenHandleRecordRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.HANDLE, request); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(Collections.emptyList()); + + // When + assertThrows(InvalidRequestException.class, + () -> service.updateRecords(updateRequest, true)); + } - given(pidNameGeneratorService.genHandleList(1)).willReturn(new ArrayList<>(List.of(handle))); - given(fdoRecordService.prepareMasRecordAttributes(any(), any())).willReturn(handleRecord); + @Test + void testCreateDataMapping() throws Exception { + var request = genCreateRecordRequest(givenDataMappingRequestObject(), FdoType.DATA_MAPPING); + var fdoRecord = givenDataMappingFdoRecord(HANDLE); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.DATA_MAPPING); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDataMappingRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - - var responseReceived = service.createRecords(List.of(request)); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testCreateHandleRecordBatch() throws Exception { + void testUpdateDataMapping() throws Exception { // Given - - List requests = new ArrayList<>(); - for (int i = 0; i < handles.size(); i++) { - requests.add(genCreateRecordRequest(givenHandleRecordRequestObject(), FdoType.HANDLE)); - } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.HANDLE); - - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareHandleRecordAttributes(any(), any(), any())).willReturn( - genHandleRecordAttributes(handles.get(0), FdoType.HANDLE)) - .willReturn(genHandleRecordAttributes(handles.get(1), FdoType.HANDLE)); + var previousVersion = givenDataMappingFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenDataMappingRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DATA_MAPPING, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DATA_MAPPING, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseFull(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedDataMappingRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testCreateDoiRecordBatch() throws Exception { - // Given - List flatList = new ArrayList<>(); - - List requests = new ArrayList<>(); - for (byte[] handle : handles) { - requests.add(genCreateRecordRequest(givenDoiRecordRequestObject(), FdoType.DOI)); - flatList.addAll(genDoiRecordAttributes(handle, FdoType.DOI)); - } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.DOI); - - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareDoiRecordAttributes(any(), any(), any())).willReturn(flatList); + void testCreateMas() throws Exception { + var request = genCreateRecordRequest(givenMasRecordRequestObject(), FdoType.MAS); + var fdoRecord = givenMasFdoRecord(HANDLE); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.MAS); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewMasRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testCreateDigitalSpecimenBatch() throws Exception { + void testUpdateMas() throws Exception { // Given - List requests = new ArrayList<>(); - for (byte[] handle : handles) { - var physId = new String(handle) + "a"; - requests.add(genCreateRecordRequest(givenDigitalSpecimenRequestObjectNullOptionals(physId), - FdoType.DIGITAL_SPECIMEN)); - } - var sublist = Stream.concat(genDigitalSpecimenAttributes(handles.get(0)).stream() - .filter(row -> row.getType().equals(PRIMARY_SPECIMEN_OBJECT_ID.get())), - genDigitalSpecimenAttributes(handles.get(1)).stream() - .filter(row -> row.getType().equals(PRIMARY_SPECIMEN_OBJECT_ID.get()))).toList(); - - var responseExpected = givenRecordResponseWriteSmallResponse(sublist, handles, - FdoType.DIGITAL_SPECIMEN); - - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareDigitalSpecimenRecordAttributes(any(), any())).willReturn( - genDigitalSpecimenAttributes(handles.get(0))) - .willReturn(genDigitalSpecimenAttributes(handles.get(1))); + var previousVersion = givenMasFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenMasRecordRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.MAS, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.MAS, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseFull(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedMasRecord(any(), any(), any(), anyBoolean())).willReturn( + updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testCreateAnnotationsBatch() throws Exception { - // Given - - List requests = new ArrayList<>(); - for (int i = 0; i < handles.size(); i++) { - requests.add(genCreateRecordRequest(givenAnnotationRequestObject(), FdoType.ANNOTATION)); - } - - var responseExpected = givenAnnotationResponseWrite(handles); - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareAnnotationAttributes(any(), any())).willReturn( - genAnnotationAttributes(handles.get(0), true)) - .willReturn(genAnnotationAttributes(handles.get(1), true)); + void testCreateDigitalMedia() throws Exception { + var request = genCreateRecordRequest(givenDigitalMediaRequestObject(), FdoType.DIGITAL_MEDIA); + var fdoRecord = givenDigitalMediaFdoRecord(HANDLE); + var expected = givenWriteResponseIdsOnly(List.of(fdoRecord), FdoType.DIGITAL_MEDIA, + HANDLE_DOMAIN); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewDigitalMediaRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testCreateAnnotationsBatchNoHash() throws Exception { - // Given - - List requests = new ArrayList<>(); - for (int i = 0; i < handles.size(); i++) { - requests.add( - genCreateRecordRequest(givenAnnotationRequestObjectNoHash(), FdoType.ANNOTATION)); - } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.ANNOTATION); - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareAnnotationAttributes(any(), any())).willReturn( - genAnnotationAttributes(handles.get(0), false)) - .willReturn(genAnnotationAttributes(handles.get(1), false)); + void testUpdateDigitalMedia() throws Exception { + var previousVersion = givenDigitalMediaFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenDigitalMediaRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.DIGITAL_MEDIA, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.DIGITAL_MEDIA, + PRIMARY_MEDIA_ID_TESTVAL); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var responseExpected = givenWriteResponseIdsOnly(List.of(updatedAttributeRecord), + FdoType.DIGITAL_MEDIA, HANDLE_DOMAIN); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedDigitalMediaRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var responseReceived = service.updateRecords(updateRequest, true); // Then assertThat(responseReceived).isEqualTo(responseExpected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testCreateMappingBatch() throws Exception { - // Given - List requests = new ArrayList<>(); - for (int i = 0; i < handles.size(); i++) { - requests.add(genCreateRecordRequest(givenDataMappingRequestObject(), FdoType.DATA_MAPPING)); - } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.DATA_MAPPING); - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareDataMappingAttributes(any(), any())).willReturn( - genDataMappingAttributes(handles.get(0))) - .willReturn(genDataMappingAttributes(handles.get(1))); + void testCreateSourceSystem() throws Exception { + var request = genCreateRecordRequest(givenSourceSystemRequestObject(), FdoType.SOURCE_SYSTEM); + var fdoRecord = givenSourceSystemFdoRecord(HANDLE); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.SOURCE_SYSTEM); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewSourceSystemRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testCreateSourceSystemBatch() throws Exception { + void testUpdateSourceSystem() throws Exception { // Given - List requests = new ArrayList<>(); - for (int i = 0; i < handles.size(); i++) { - requests.add( - genCreateRecordRequest(givenSourceSystemRequestObject(), FdoType.SOURCE_SYSTEM)); - } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.SOURCE_SYSTEM); - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareSourceSystemAttributes(any(), any())).willReturn( - genSourceSystemAttributes(handles.get(0))) - .willReturn(genSourceSystemAttributes(handles.get(1))); + var previousVersion = givenMasFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenSourceSystemRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.SOURCE_SYSTEM, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.SOURCE_SYSTEM, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseFull(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedSourceSystemRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testCreateOrganisationBatch() throws Exception { - // Given - List flatList = new ArrayList<>(); - - List requests = new ArrayList<>(); - for (byte[] handle : handles) { - requests.add( - genCreateRecordRequest(givenOrganisationRequestObject(), FdoType.ORGANISATION)); - flatList.addAll(genOrganisationAttributes(handle)); - } - - var responseExpected = givenRecordResponseWrite(handles, FdoType.ORGANISATION); - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareOrganisationAttributes(any(), any())).willReturn(flatList); + void testCreateOrganisation() throws Exception { + var request = genCreateRecordRequest(givenOrganisationRequestObject(), FdoType.ORGANISATION); + var fdoRecord = givenOrganisationFdoRecord(HANDLE); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.ORGANISATION); + given(pidNameGeneratorService.generateNewHandles(1)).willReturn(Set.of(HANDLE)); + given(fdoRecordService.prepareNewOrganisationRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.createRecords(List.of(request)); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testCreateDigitalMediaBatch() throws Exception { + void testUpdateOrganisation() throws Exception { // Given - List requests = new ArrayList<>(); - for (int i = 0; i < handles.size(); i++) { - requests.add(genCreateRecordRequest(givenMediaRequestObject(), FdoType.DIGITAL_MEDIA)); - } - var sublist = Stream.concat(TestUtils.genDigitalMediaAttributes(handles.get(0)).stream().filter( - row -> row.getType().equals(PRIMARY_MEDIA_ID.get()) || row.getType() - .equals(LINKED_DO_PID.get())), - TestUtils.genDigitalMediaAttributes(handles.get(1)).stream().filter( - row -> row.getType().equals(PRIMARY_MEDIA_ID.get()) || row.getType() - .equals(LINKED_DO_PID.get()))).toList(); - - var responseExpected = givenRecordResponseWriteSmallResponse(sublist, handles, - FdoType.DIGITAL_MEDIA); - given(pidNameGeneratorService.genHandleList(handles.size())).willReturn(handles); - given(fdoRecordService.prepareDigitalMediaAttributes(any(), any())).willReturn( - TestUtils.genDigitalMediaAttributes(handles.get(0))) - .willReturn(TestUtils.genDigitalMediaAttributes(handles.get(1))); + var previousVersion = givenOrganisationFdoRecord(HANDLE); + var request = MAPPER.valueToTree(givenOrganisationRequestObjectUpdate()); + var updateRequest = givenUpdateRequest(List.of(HANDLE), FdoType.ORGANISATION, request); + var updatedAttributeRecord = givenUpdatedFdoRecord(FdoType.ORGANISATION, null); + var expectedDocument = givenMongoDocument(updatedAttributeRecord); + var expected = givenWriteResponseFull(updatedAttributeRecord); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareUpdatedOrganisationRecord(any(), any(), any(), + anyBoolean())).willReturn(updatedAttributeRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.createRecords(requests); + var result = service.updateRecords(updateRequest, true); // Then - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testUpdateRecordLocation() throws Exception { + void testTombstoneRecords() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var updateRequest = genUpdateRequestBatch(List.of(handle)); - var updatedAttributeRecord = genUpdateRecordAttributesAltLoc(handle); - var responseExpected = givenRecordResponseNullAttributes(List.of(handle)); - - given(pidRepository.checkHandlesWritable(anyList())).willReturn(List.of(handle)); - given(fdoRecordService.prepareUpdateAttributes(any(), any(), any())).willReturn( - updatedAttributeRecord); + var request = givenTombstoneRequest(); + var previousVersion = givenHandleFdoRecord(HANDLE); + var fdoRecord = givenTombstoneFdoRecord(); + var expectedDocument = givenMongoDocument(fdoRecord); + var expected = givenWriteResponseFull(List.of(HANDLE), FdoType.TOMBSTONE); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(List.of(previousVersion)); + given(fdoRecordService.prepareTombstoneRecord(any(), any(), any())).willReturn(fdoRecord); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.updateRecords(updateRequest, true); + var result = service.tombstoneRecords(request); // Then - assertThat(responseReceived).isEqualTo(responseExpected); - then(pidRepository).should() - .updateRecordBatch(CREATED.getEpochSecond(), List.of(updatedAttributeRecord), true); + assertThat(result).isEqualTo(expected); + then(mongoRepository).should().updateHandleRecords(List.of(expectedDocument)); } @Test - void testUpdateRecordLocationBatch() throws Exception { + void testResolveSingleRecord() throws Exception { // Given - - List updateRequest = genUpdateRequestBatch(handles); - - var responseExpected = givenRecordResponseNullAttributes(handles); - var updatedAttributes = List.of(genUpdateRecordAttributesAltLoc(handles.get(0)), - genUpdateRecordAttributesAltLoc(handles.get(1))); - - given(pidRepository.checkHandlesWritable(anyList())).willReturn(handles); - given(fdoRecordService.prepareUpdateAttributes(any(), any(), any())).willReturn( - genUpdateRecordAttributesAltLoc(handles.get(0))) - .willReturn(genUpdateRecordAttributesAltLoc(handles.get(1))); + var expected = givenReadResponse(List.of(HANDLE), PATH, FdoType.HANDLE, HANDLE_DOMAIN); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn( + List.of(givenHandleFdoRecord(HANDLE))); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.updateRecords(updateRequest, true); + var result = service.resolveSingleRecord(HANDLE, PATH); // Then - then(pidRepository).should() - .updateRecordBatch(CREATED.getEpochSecond(), updatedAttributes, true); - assertThat(responseReceived).isEqualTo(responseExpected); + assertThat(result).isEqualTo(expected); } @Test - void testUpdateRecordInternalDuplicates() throws Exception { + void testResolveBatchRecord() throws Exception { // Given - List updateRequest = genUpdateRequestBatch(handles); - given(fdoRecordService.prepareUpdateAttributes(any(), any(), any())).willReturn( - genDigitalSpecimenAttributes(HANDLE.getBytes(StandardCharsets.UTF_8))); + var expected = givenReadResponse(List.of(HANDLE), PATH, FdoType.HANDLE, HANDLE_DOMAIN); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn( + List.of(givenHandleFdoRecord(HANDLE))); + given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + + // When + var result = service.resolveBatchRecord(List.of(HANDLE), PATH); // Then - assertThrowsExactly(InvalidRequestException.class, () -> { - service.updateRecords(updateRequest, true); - }); + assertThat(result).isEqualTo(expected); } @Test - void testUpdateRecordNonWritable() throws Exception { + void testResolveBatchRecordNotFound() throws Exception { // Given - List updateRequest = genUpdateRequestBatch(handles); - given(pidRepository.checkHandlesWritable(anyList())).willReturn(new ArrayList<>()); - given(fdoRecordService.prepareUpdateAttributes(any(), any(), any())) - .willReturn(genDigitalSpecimenAttributes(HANDLE.getBytes(StandardCharsets.UTF_8))) - .willReturn(genDigitalSpecimenAttributes(HANDLE_ALT.getBytes(StandardCharsets.UTF_8))); + given(mongoRepository.getHandleRecords(List.of(HANDLE))).willReturn(Collections.emptyList()); - // Then - assertThrowsExactly(PidResolutionException.class, () -> { - service.updateRecords(updateRequest, true); - }); + // When / Then + assertThrows(PidResolutionException.class, + () -> service.resolveBatchRecord(List.of(HANDLE), "")); } @Test - void testArchiveRecord() throws Exception { + void testSearchByPhysicalSpecimenId() throws Exception { // Given - byte[] handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var archiveRequest = genTombstoneRequestBatch(List.of(HANDLE)); - - var responseExpected = givenRecordResponseWriteArchive(List.of(handle)); - var tombstoneAttributes = genTombstoneRecordRequestAttributes(handle); - - given(pidRepository.checkHandlesWritable(anyList())).willReturn(handles); - given(fdoRecordService.prepareTombstoneAttributes(any(), any())).willReturn( - tombstoneAttributes); + var expected = TestUtils.givenWriteResponseFull(List.of(HANDLE), FdoType.DIGITAL_SPECIMEN); + given(mongoRepository.searchByPrimaryLocalId(any(), any())).willReturn( + List.of(givenDigitalSpecimenFdoRecord(HANDLE))); given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); // When - var responseReceived = service.archiveRecordBatch(archiveRequest); + var result = service.searchByPhysicalSpecimenId(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); // Then - assertThat(responseReceived).isEqualTo(responseExpected); - then(pidRepository).should() - .archiveRecords(CREATED.getEpochSecond(), tombstoneAttributes, List.of(HANDLE)); + assertThat(result).isEqualTo(expected); } @Test - void testArchiveRecordBatch() throws Exception { + void testSearchByPhysicalSpecimenIdDuplicates() throws Exception { // Given - var handle = HANDLE.getBytes(StandardCharsets.UTF_8); - var handleAlt = HANDLE_ALT.getBytes(StandardCharsets.UTF_8); - var archiveRequest = genTombstoneRequestBatch(List.of(HANDLE, HANDLE_ALT)); + given(mongoRepository.searchByPrimaryLocalId(any(), any())).willReturn( + List.of(givenDigitalSpecimenFdoRecord(HANDLE), + (givenDigitalSpecimenFdoRecord(HANDLE_ALT)))); - var responseExpected = givenRecordResponseWriteArchive(List.of(handle, handleAlt)); - - var tombstoneAttributes = List.of(genTombstoneRecordRequestAttributes(handle), - genTombstoneRecordRequestAttributes(handleAlt)); - var tombstoneFlatlist = Stream.concat(tombstoneAttributes.get(0).stream(), - tombstoneAttributes.get(1).stream()).toList(); - - given(pidRepository.checkHandlesWritable(anyList())).willReturn(handles); - given(fdoRecordService.prepareTombstoneAttributes(any(), any())).willReturn( - tombstoneAttributes.get(0), tombstoneAttributes.get(1)); - given(profileProperties.getDomain()).willReturn(HANDLE_DOMAIN); + // When / Then + assertThrows(PidResolutionException.class, + () -> service.searchByPhysicalSpecimenId(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); + } - // When - var responseReceived = service.archiveRecordBatch(archiveRequest); + @Test + void testSearchByPhysicalSpecimenIdNotFound() throws Exception { + // Given + given(mongoRepository.searchByPrimaryLocalId(any(), any())).willReturn(Collections.emptyList()); - // Then - assertThat(responseReceived).isEqualTo(responseExpected); - then(pidRepository).should() - .archiveRecords(CREATED.getEpochSecond(), tombstoneFlatlist, List.of(HANDLE, HANDLE_ALT)); + // When / Then + assertThrows(PidResolutionException.class, + () -> service.searchByPhysicalSpecimenId(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); } @Test - void testGetHandlesPaged() { + void testDifferentTypes() { // Given - int pageNum = 0; - int pageSize = 2; - byte[] pidStatus = PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8); + var request1 = MAPPER.createObjectNode() + .set("data", MAPPER.createObjectNode() + .put("type", FdoType.HANDLE.getFdoProfile()) + .set("attributes", MAPPER.createObjectNode() + .put("field", "val"))); + var request2 = MAPPER.createObjectNode() + .set("data", MAPPER.createObjectNode() + .put("type", FdoType.DOI.getFdoProfile()) + .set("attributes", MAPPER.createObjectNode() + .put("field", "val"))); + var requests = List.of(request1, request2); + + // When / Then + assertThrows(UnsupportedOperationException.class, () -> service.createRecords(requests)); + } - given(pidRepository.getAllHandles(pageNum, pageSize)).willReturn(HANDLE_LIST_STR); - given(pidRepository.getAllHandles(pidStatus, pageNum, pageSize)).willReturn(HANDLE_LIST_STR); + @Test + void testRollbackHandles() { + // Given // When - var responseExpectedFirst = service.getHandlesPaged(pageNum, pageSize); - var responseExpectedSecond = service.getHandlesPaged(pageNum, pageSize, pidStatus); + service.rollbackHandles(List.of(HANDLE)); // Then - assertThat(responseExpectedFirst).isEqualTo(HANDLE_LIST_STR); - assertThat(responseExpectedSecond).isEqualTo(HANDLE_LIST_STR); + then(mongoRepository).should().rollbackHandles(List.of(HANDLE)); } @Test - void testRollbackHandleCreation() { + void testRollbackHandlesFromPhysId() { // Given - var handleList = List.of(HANDLE, HANDLE_ALT); // When - service.rollbackHandles(handleList); + service.rollbackHandlesFromPhysId(List.of(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); // Then - then(pidRepository).should().rollbackHandles(handleList); + then(mongoRepository).should().rollbackHandles(NORMALISED_SPECIMEN_OBJECT_ID.get(), + List.of(NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); } @Test - void testRollbackHandlesFromPhysId() { + void testInternalDuplicates() { // Given - given(pidRepository.searchByNormalisedPhysicalIdentifier(anyList())).willReturn( - List.of(new HandleAttribute(NORMALISED_SPECIMEN_OBJECT_ID, - HANDLE.getBytes(StandardCharsets.UTF_8), PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL))); + var attributes = MAPPER.valueToTree(givenHandleRecordRequestObject()); + var request = givenUpdateRequest(List.of(HANDLE, HANDLE, HANDLE_ALT), FdoType.ORGANISATION, + attributes); // When - service.rollbackHandlesFromPhysId( - List.of(PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL)); + var e = assertThrows(InvalidRequestException.class, () -> service.updateRecords(request, true)); // Then - then(pidRepository).should().rollbackHandles(List.of(HANDLE)); + assertThat(e.getMessage()).contains(HANDLE); } + } diff --git a/src/test/java/eu/dissco/core/handlemanager/service/PidNameGeneratorServiceTest.java b/src/test/java/eu/dissco/core/handlemanager/service/PidNameGeneratorServiceTest.java index e0ccac6c..0ff597bb 100644 --- a/src/test/java/eu/dissco/core/handlemanager/service/PidNameGeneratorServiceTest.java +++ b/src/test/java/eu/dissco/core/handlemanager/service/PidNameGeneratorServiceTest.java @@ -8,11 +8,11 @@ import static org.mockito.Mockito.lenient; import eu.dissco.core.handlemanager.properties.ApplicationProperties; -import eu.dissco.core.handlemanager.repository.PidRepository; -import java.nio.charset.StandardCharsets; +import eu.dissco.core.handlemanager.repository.MongoRepository; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,7 +23,7 @@ class PidNameGeneratorServiceTest { @Mock - private PidRepository pidRepository; + private MongoRepository mongoRepository; @Mock private Random random; @@ -36,7 +36,8 @@ class PidNameGeneratorServiceTest { @BeforeEach void setup() { - this.pidNameGeneratorService = new PidNameGeneratorService(applicationProperties, pidRepository, + this.pidNameGeneratorService = new PidNameGeneratorService(applicationProperties, + mongoRepository, random); lenient().when(applicationProperties.getMaxHandles()).thenReturn(MAX_HANDLES); } @@ -44,41 +45,35 @@ void setup() { @Test void testSingleBatchGen() { // Given - String expectedHandle = PREFIX + "/AAA-AAA-AAA"; + var expected = Set.of(PREFIX + "/AAA-AAA-AAA"); given(random.nextInt(anyInt())).willReturn(0); given(applicationProperties.getPrefix()).willReturn(PREFIX); // When - String generatedHandle = new String(pidNameGeneratorService.genHandleList(1).get(0), - StandardCharsets.UTF_8); + var result = pidNameGeneratorService.generateNewHandles(1); // Then - assertThat(generatedHandle).isEqualTo(expectedHandle); + assertThat(result).isEqualTo(expected); } @Test void testBatchGen() { // Given - String expectedHandle1 = PREFIX + "/BBB-BBB-BBB"; - String expectedHandle2 = PREFIX + "/ABB-BBB-BBB"; + var expected = Set.of(PREFIX + "/ABB-BBB-BBB", PREFIX + "/BBB-BBB-BBB"); given(random.nextInt(anyInt())).willReturn(0, 1); given(applicationProperties.getPrefix()).willReturn(PREFIX); // When - List handleList = pidNameGeneratorService.genHandleList(2); - String generatedHandle1 = new String(handleList.get(0)); - String generatedHandle2 = new String(handleList.get(1)); + var handleList = pidNameGeneratorService.generateNewHandles(2); // Then - assertThat(generatedHandle1).isEqualTo(expectedHandle1); - assertThat(generatedHandle2).isEqualTo(expectedHandle2); + assertThat(handleList).isEqualTo(expected); } @Test void testInternalCollision() { - String expectedHandle1 = PREFIX + "/BBB-BBB-BBB"; - String expectedHandle2 = PREFIX + "/AAA-AAA-AAA"; + var expected = Set.of(PREFIX + "/AAA-AAA-AAA", PREFIX + "/BBB-BBB-BBB"); given(random.nextInt(anyInt())).willReturn(0, 0, 0, 0, 0, 0, 0, 0, 0)// First .willReturn(0, 0, 0, 0, 0, 0, 0, 0, 0) // Collision @@ -86,50 +81,39 @@ void testInternalCollision() { given(applicationProperties.getPrefix()).willReturn(PREFIX); // When - List handleList = pidNameGeneratorService.genHandleList(2); - String generatedHandle1 = new String(handleList.get(0)); - String generatedHandle2 = new String(handleList.get(1)); + var result = pidNameGeneratorService.generateNewHandles(2); // Then - assertThat(generatedHandle1).isEqualTo(expectedHandle1); - assertThat(generatedHandle2).isEqualTo(expectedHandle2); + assertThat(expected).isEqualTo(result); } @Test void testDbCollision() { // Given - byte[] expectedHandle1 = (PREFIX + "/BBB-BBB-BBB").getBytes(StandardCharsets.UTF_8); - byte[] expectedHandle2 = (PREFIX + "/ABB-BBB-BBB").getBytes(StandardCharsets.UTF_8); - - List handleListInternalDuplicate = new ArrayList<>(); - handleListInternalDuplicate.add(expectedHandle1); - + var expectedHandle1 = PREFIX + "/BBB-BBB-BBB"; + var expectedHandle2 = PREFIX + "/ABB-BBB-BBB"; given(random.nextInt(anyInt())).willReturn(0, 1); - given(pidRepository.getHandlesExist(anyList())) - .willReturn(handleListInternalDuplicate) + given(mongoRepository.getExistingHandles(anyList())) + .willReturn(List.of(expectedHandle1)) .willReturn(new ArrayList<>()); given(applicationProperties.getPrefix()).willReturn(PREFIX); // When - List generatedHandleList = pidNameGeneratorService.genHandleList(2); - byte[] generatedHandle1 = generatedHandleList.get(0); - byte[] generatedHandle2 = generatedHandleList.get(1); + var result = pidNameGeneratorService.generateNewHandles(2); // Then - assertThat(generatedHandle1).isEqualTo(expectedHandle1); - assertThat(generatedHandle2).isEqualTo(expectedHandle2); + assertThat(result).hasSameElementsAs(List.of(expectedHandle1, expectedHandle2)); } @Test void testInvalidNumberOfHandles() { // When - var tooFew = pidNameGeneratorService.genHandleList(-1); + var tooFew = pidNameGeneratorService.generateNewHandles(-1); // Then assertThat(tooFew).isEmpty(); } - } diff --git a/src/test/java/eu/dissco/core/handlemanager/testUtils/TestUtils.java b/src/test/java/eu/dissco/core/handlemanager/testUtils/TestUtils.java index 3c97ac91..b7441cba 100644 --- a/src/test/java/eu/dissco/core/handlemanager/testUtils/TestUtils.java +++ b/src/test/java/eu/dissco/core/handlemanager/testUtils/TestUtils.java @@ -3,6 +3,8 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ANNOTATION_HASH; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.BASE_TYPE_OF_SPECIMEN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.CATALOG_IDENTIFIER; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DCTERMS_FORMAT; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DCTERMS_SUBJECT; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DCTERMS_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DC_TERMS_CONFORMS; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DERIVED_FROM_ENTITY; @@ -10,6 +12,7 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.DIGITAL_OBJECT_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.FDO_PROFILE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.FDO_RECORD_LICENSE; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HAS_RELATED_PID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.HS_ADMIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.INFORMATION_ARTEFACT_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ISSUED_FOR_AGENT; @@ -26,10 +29,8 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MAS_NAME; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MATERIAL_OR_DIGITAL_ENTITY; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MATERIAL_SAMPLE_TYPE; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_FORMAT; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_HOST; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_HOST_NAME; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MEDIA_MIME_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.MOTIVATION; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.NORMALISED_SPECIMEN_OBJECT_ID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.ORGANISATION_ID; @@ -63,12 +64,17 @@ import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.STRUCTURAL_TYPE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TARGET_PID; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TARGET_TYPE; -import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONE_TEXT; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONED_DATE; +import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOMBSTONED_TEXT; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_CATEGORY; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_DISCIPLINE; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_DOMAIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.TOPIC_ORIGIN; import static eu.dissco.core.handlemanager.domain.fdo.FdoProfile.WAS_DERIVED_FROM_ENTITY; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.ANNOTATION; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_MEDIA; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.DIGITAL_SPECIMEN; +import static eu.dissco.core.handlemanager.domain.fdo.FdoType.TOMBSTONE; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ATTRIBUTES; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_DATA; import static eu.dissco.core.handlemanager.domain.jsonapi.JsonApiFields.NODE_ID; @@ -77,34 +83,50 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ObjectNode; +import eu.dissco.core.handlemanager.configuration.InstantDeserializer; +import eu.dissco.core.handlemanager.configuration.InstantSerializer; import eu.dissco.core.handlemanager.domain.fdo.AnnotationRequest; import eu.dissco.core.handlemanager.domain.fdo.DataMappingRequest; import eu.dissco.core.handlemanager.domain.fdo.DigitalMediaRequest; import eu.dissco.core.handlemanager.domain.fdo.DigitalSpecimenRequest; import eu.dissco.core.handlemanager.domain.fdo.DoiRecordRequest; +import eu.dissco.core.handlemanager.domain.fdo.FdoProfile; import eu.dissco.core.handlemanager.domain.fdo.FdoType; import eu.dissco.core.handlemanager.domain.fdo.HandleRecordRequest; import eu.dissco.core.handlemanager.domain.fdo.MasRequest; import eu.dissco.core.handlemanager.domain.fdo.OrganisationRequest; import eu.dissco.core.handlemanager.domain.fdo.SourceSystemRequest; import eu.dissco.core.handlemanager.domain.fdo.TombstoneRecordRequest; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.PidStatus; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.annotation.Motivation; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.media.LinkedDigitalObjectType; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.BaseTypeOfSpecimen; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.LivingOrPreserved; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.MaterialOrDigitalEntity; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.MaterialSampleType; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.OtherSpecimenId; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.PrimarySpecimenObjectIdType; import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.StructuralType; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicCategory; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicDiscipline; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicDomain; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.specimen.TopicOrigin; +import eu.dissco.core.handlemanager.domain.fdo.vocabulary.tombstone.HasRelatedPid; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiDataLinks; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiLinks; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperRead; -import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperReadSingle; import eu.dissco.core.handlemanager.domain.jsonapi.JsonApiWrapperWrite; -import eu.dissco.core.handlemanager.domain.repsitoryobjects.HandleAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoAttribute; +import eu.dissco.core.handlemanager.domain.repsitoryobjects.FdoRecord; import eu.dissco.core.handlemanager.exceptions.InvalidRequestException; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; import javax.xml.XMLConstants; @@ -117,587 +139,409 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.springframework.core.io.ClassPathResource; -import org.w3c.dom.Document; @Slf4j public class TestUtils { public static final String ISSUE_DATE_TESTVAL = "2022-11-01T09:59:24.000Z"; + public static final String UPDATE_DATE_TESTVAL = "2023-11-01T09:59:24.000Z"; public static final Instant CREATED = Instant.parse(ISSUE_DATE_TESTVAL); - public final static String HANDLE_URI = "https://hdl.handle.net/"; + public static final Instant UPDATED = Instant.parse(UPDATE_DATE_TESTVAL); public static final String PREFIX = "20.5000.1025"; public static final String HANDLE = PREFIX + "/QRS-321-ABC"; public static final String SUFFIX = "QRS-321-ABC"; public static final String HANDLE_ALT = PREFIX + "/ABC-123-QRS"; - public static final List HANDLE_LIST_STR; // Handles public static final String HANDLE_DOMAIN = "https://hdl.handle.net/"; + public static final String DOI_DOMAIN = "https://doi.org/"; public static final String ROR_DOMAIN = "https://ror.org/"; public static final String ISSUED_FOR_AGENT_TESTVAL = ROR_DOMAIN + "0566bfb96"; public static final String PID_ISSUER_TESTVAL_OTHER = HANDLE_DOMAIN + "20.5000.1025/PID-ISSUER"; public static final StructuralType STRUCTURAL_TYPE_TESTVAL = StructuralType.DIGITAL; - public static final String[] LOC_TESTVAL = {"https://sandbox.dissco.tech/", - "https://dissco.eu"}; + public static final String[] LOC_TESTVAL = {"https://sandbox.dissco.tech/", "https://dissco.eu"}; public static final String[] LOC_ALT_TESTVAL = {"naturalis.nl"}; // DOI Request Attributes public static final String REFERENT_NAME_TESTVAL = "Bird nest"; public static final String PRIMARY_REFERENT_TYPE_TESTVAL = "materialSample"; - // Generated Attributes - public static final String PID_STATUS_TESTVAL = "TEST"; + public static final String PID_STATUS_TESTVAL = PidStatus.ACTIVE.name(); public static final String REFERENT_DOI_NAME_TESTVAL = PREFIX + "/" + SUFFIX; - //DOIs //Digital Specimens public static final String ROR_IDENTIFIER = "0x123"; public static final String SPECIMEN_HOST_TESTVAL = ROR_DOMAIN + ROR_IDENTIFIER; public static final String SPECIMEN_HOST_NAME_TESTVAL = "Naturalis"; // Annotations - public static final String TARGET_DOI_TESTVAL = HANDLE_URI + PREFIX + "/111"; + public static final String TARGET_DOI_TESTVAL = HANDLE_DOMAIN + PREFIX + "/111"; public static final String TARGET_TYPE_TESTVAL = "digitalSpecimen"; public static final Motivation MOTIVATION_TESTVAL = Motivation.EDITING; public static final UUID ANNOTATION_HASH_TESTVAL = UUID.fromString( "550e8400-e29b-41d4-a716-446655440000"); - // Media Objects public static final String MEDIA_HOST_TESTVAL = SPECIMEN_HOST_TESTVAL; public static final String MEDIA_HOST_NAME_TESTVAL = SPECIMEN_HOST_NAME_TESTVAL; public static final LinkedDigitalObjectType LINKED_DIGITAL_OBJECT_TYPE_TESTVAL = LinkedDigitalObjectType.SPECIMEN; public static final String LINKED_DO_PID_TESTVAL = HANDLE; - public static final String LICENSE_NAME_TESTVAL = "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication"; + public static final String PRIMARY_MEDIA_ID_TESTVAL = "https://images.com/ABC"; // Mappings public static final String SOURCE_DATA_STANDARD_TESTVAL = "dwc"; // MAS public static final String MAS_NAME_TESTVAL = "Plant Organ detection"; - + // Tombstone Record vals + public static final String TOMBSTONE_TEXT_TESTVAL = "pid was deleted"; + // Misc public static final String API_URL = "https://sandbox.dissco.tech/api/v1"; - public static final String UI_URL = "https://sandbox.dissco.tech/"; + public static final String UI_URL = "https://sandbox.dissco.tech"; + public static final String PATH = UI_URL + HANDLE; public static final String ORCHESTRATION_URL = "https://orchestration.dissco.tech/api/v1"; public static final String PTR_TYPE_DOI = "doi"; - public final static String PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL = "BOTANICAL.QRS.123"; - public final static String NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL = + public static final String PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL = "BOTANICAL.QRS.123"; + public static final String NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL = PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL + ":" + ROR_IDENTIFIER; - public final static String EXTERNAL_PID = "21.T11148/d8de0819e144e4096645"; - public static final String DIGITAL_OBJECT_NAME_TESTVAL = "digitalSpecimen"; - - // Tombstone Record vals - public final static String TOMBSTONE_TEXT_TESTVAL = "pid was deleted"; - // Pid Type Record vals - public static ObjectMapper MAPPER = new ObjectMapper().findAndRegisterModules(); + public static final String EXTERNAL_PID = "21.T11148/d8de0819e144e4096645"; + public static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + public static final DocumentBuilderFactory DOC_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + public static final ObjectMapper MAPPER; static { - HANDLE_LIST_STR = List.of(HANDLE, HANDLE_ALT); + var mapper = new ObjectMapper().findAndRegisterModules(); + SimpleModule dateModule = new SimpleModule(); + dateModule.addSerializer(Instant.class, new InstantSerializer()); + dateModule.addDeserializer(Instant.class, new InstantDeserializer()); + mapper.registerModule(dateModule); + MAPPER = mapper; } - public static TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); - public static DocumentBuilderFactory DOC_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); - private TestUtils() { throw new IllegalStateException("Utility class"); } - // Single Handle Attribute Lists - - public static List genHandleRecordAttributes(byte[] handle) throws Exception { - return genHandleRecordAttributes(handle, FdoType.HANDLE); + // Handle Attribute Lists + public static FdoRecord givenHandleFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.HANDLE, + genHandleRecordAttributes(handle, CREATED, FdoType.HANDLE), null); } - public static List genHandleRecordAttributes(byte[] handle, FdoType type) - throws Exception { - - List fdoRecord = new ArrayList<>(); + public static List genHandleRecordAttributes(String handle, Instant timestamp, + FdoType fdoType) throws Exception { + List fdoAttributes = new ArrayList<>(); var request = givenHandleRecordRequestObject(); - byte[] loc = setLocations(request.getLocations(), - new String(handle, StandardCharsets.UTF_8), - type, false); - fdoRecord.add(new HandleAttribute(LOC.index(), handle, LOC.get(), loc)); - + var loc = setLocations(request.getLocations(), handle, fdoType); + fdoAttributes.add(new FdoAttribute(LOC, CREATED, loc)); // 1: FDO Profile - fdoRecord.add(new HandleAttribute(FDO_PROFILE.index(), handle, FDO_PROFILE.get(), - type.getFdoProfile().getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(FDO_PROFILE, timestamp, fdoType.getFdoProfile())); // 2: FDO Record License - byte[] pidKernelMetadataLicense = "https://creativecommons.org/publicdomain/zero/1.0/".getBytes( - StandardCharsets.UTF_8); - fdoRecord.add(new HandleAttribute(FDO_RECORD_LICENSE.index(), handle, - FDO_RECORD_LICENSE.get(), pidKernelMetadataLicense)); - + fdoAttributes.add(new FdoAttribute(FDO_RECORD_LICENSE, timestamp, + "https://creativecommons.org/publicdomain/zero/1.0/")); // 3: DigitalObjectType - fdoRecord.add( - new HandleAttribute(DIGITAL_OBJECT_TYPE, handle, - type.getDigitalObjectType())); - + fdoAttributes.add( + new FdoAttribute(DIGITAL_OBJECT_TYPE, timestamp, fdoType.getDigitalObjectType())); // 4: DigitalObjectName - fdoRecord.add( - new HandleAttribute(DIGITAL_OBJECT_NAME, handle, type.getDigitalObjectName())); - + fdoAttributes.add( + new FdoAttribute(DIGITAL_OBJECT_NAME, timestamp, fdoType.getDigitalObjectName())); // 5: Pid - byte[] pid = ("https://hdl.handle.net/" + new String(handle, - StandardCharsets.UTF_8)).getBytes( - StandardCharsets.UTF_8); - fdoRecord.add(new HandleAttribute(PID.index(), handle, PID.get(), pid)); - + fdoAttributes.add(new FdoAttribute(PID, timestamp, fdoType.getDomain() + handle)); // 6: PidIssuer - fdoRecord.add(new HandleAttribute(PID_ISSUER.index(), handle, PID_ISSUER.get(), - request.getPidIssuer().getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(PID_ISSUER, timestamp, request.getPidIssuer())); // 7: pidIssuerName - fdoRecord.add(new HandleAttribute(PID_ISSUER_NAME.index(), handle, PID_ISSUER_NAME.get(), - PID_ISSUER_TESTVAL_OTHER.getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(PID_ISSUER_NAME, timestamp, PID_ISSUER_TESTVAL_OTHER)); // 8: issuedForAgent - fdoRecord.add(new HandleAttribute(ISSUED_FOR_AGENT.index(), handle, ISSUED_FOR_AGENT.get(), - request.getIssuedForAgent().getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(ISSUED_FOR_AGENT, timestamp, request.getIssuedForAgent())); // 9: issuedForAgentName - fdoRecord.add( - new HandleAttribute(ISSUED_FOR_AGENT_NAME.index(), handle, - ISSUED_FOR_AGENT_NAME.get(), - ISSUED_FOR_AGENT_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(ISSUED_FOR_AGENT_NAME, timestamp, ISSUED_FOR_AGENT_TESTVAL)); // 10: pidRecordIssueDate - fdoRecord.add(new HandleAttribute(PID_RECORD_ISSUE_DATE.index(), handle, - PID_RECORD_ISSUE_DATE.get(), ISSUE_DATE_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(PID_RECORD_ISSUE_DATE, timestamp, ISSUE_DATE_TESTVAL)); // 11: pidRecordIssueNumber - fdoRecord.add(new HandleAttribute(PID_RECORD_ISSUE_NUMBER.index(), handle, - PID_RECORD_ISSUE_NUMBER.get(), "1".getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add(new FdoAttribute(PID_RECORD_ISSUE_NUMBER, timestamp, "1")); // 12: structuralType - fdoRecord.add(new HandleAttribute(STRUCTURAL_TYPE.index(), handle, - STRUCTURAL_TYPE.get(), - STRUCTURAL_TYPE_TESTVAL.toString().getBytes(StandardCharsets.UTF_8))); - + fdoAttributes.add( + new FdoAttribute(STRUCTURAL_TYPE, timestamp, STRUCTURAL_TYPE_TESTVAL)); // 13: PidStatus - fdoRecord.add(new HandleAttribute(PID_STATUS.index(), handle, PID_STATUS.get(), - "TEST".getBytes(StandardCharsets.UTF_8))); - - return fdoRecord; - } - - public static List genHandleRecordAttributesAltLoc(byte[] handle) - throws Exception { - List attributes = genHandleRecordAttributes(handle, FdoType.HANDLE); - - byte[] locOriginal = setLocations(LOC_TESTVAL, new String(handle, StandardCharsets.UTF_8), - FdoType.HANDLE, false); - var locOriginalAttr = new HandleAttribute(LOC.index(), handle, LOC.get(), locOriginal); - - byte[] locAlt = setLocations(LOC_ALT_TESTVAL, new String(handle, StandardCharsets.UTF_8), - FdoType.HANDLE, false); - var locAltAttr = new HandleAttribute(LOC.index(), handle, LOC.get(), locAlt); - - attributes.set(attributes.indexOf(locOriginalAttr), locAltAttr); - - return attributes; - } + fdoAttributes.add(new FdoAttribute(PID_STATUS, timestamp, PID_STATUS_TESTVAL)); + // 100 ADMIN + fdoAttributes.add(new FdoAttribute(timestamp, PREFIX)); - public static List genTombstoneRecordFullAttributes(byte[] handle) - throws Exception { - List attributes = genHandleRecordAttributes(handle, FdoType.TOMBSTONE); - HandleAttribute oldPidStatus = new HandleAttribute(PID_STATUS.index(), handle, - PID_STATUS.get(), PID_STATUS_TESTVAL.getBytes(StandardCharsets.UTF_8)); - attributes.addAll(genHandleRecordAttributes(handle, FdoType.TOMBSTONE)); - attributes.remove(oldPidStatus); - attributes = new ArrayList<>( - (attributes.stream().filter(row -> row.getIndex() != LOC.index())).toList()); - attributes.add(givenLandingPageAttribute(handle)); - return attributes; + return fdoAttributes; } - public static List genUpdateRecordAttributesAltLoc(byte[] handle) - throws ParserConfigurationException, TransformerException { - byte[] locAlt = setLocations(LOC_ALT_TESTVAL, new String(handle, StandardCharsets.UTF_8), - FdoType.HANDLE, false); - return List.of(new HandleAttribute(LOC.index(), handle, LOC.get(), locAlt)); - } - - public static List genTombstoneRecordRequestAttributes(byte[] handle) + public static FdoRecord givenUpdatedFdoRecord(FdoType fdoType, String primaryLocalId) throws Exception { - List tombstoneAttributes = new ArrayList<>(); - tombstoneAttributes.add( - new HandleAttribute(TOMBSTONE_TEXT.index(), handle, TOMBSTONE_TEXT.get(), - TOMBSTONE_TEXT_TESTVAL.getBytes(StandardCharsets.UTF_8))); - tombstoneAttributes.add(new HandleAttribute(PID_STATUS.index(), handle, PID_STATUS.get(), - "ARCHIVED".getBytes(StandardCharsets.UTF_8))); - tombstoneAttributes.add(givenLandingPageAttribute(handle)); - return tombstoneAttributes; + var attributes = new ArrayList<>(genAttributes(fdoType, HANDLE, UPDATED)); + var locUpdated = setLocations(LOC_ALT_TESTVAL, HANDLE, fdoType); + var attributesWithUpdatedTimeStamp = attributes.stream().map(attribute -> { + if (attribute.getIndex() == LOC.index()) { + return new FdoAttribute(LOC, UPDATED, locUpdated); + } + if (attribute.getIndex() == FDO_RECORD_LICENSE.index()) { + return new FdoAttribute(FDO_RECORD_LICENSE, CREATED, attribute.getValue()); + } + if (attribute.getIndex() == PID.index()) { + return new FdoAttribute(PID, CREATED, attribute.getValue()); + } + if (attribute.getIndex() == PID_RECORD_ISSUE_DATE.index()) { + return new FdoAttribute(PID_RECORD_ISSUE_DATE, CREATED, attribute.getValue()); + } + if (attribute.getIndex() == PID_RECORD_ISSUE_NUMBER.index()) { + return new FdoAttribute(PID_RECORD_ISSUE_NUMBER, UPDATED, "2"); + } + if (attribute.getIndex() == PID_STATUS.index()) { + return new FdoAttribute(PID_STATUS, CREATED, attribute.getValue()); + } + if (attribute.getIndex() == HS_ADMIN.index()) { + return new FdoAttribute(CREATED, PREFIX); + } + return attribute; + }).toList(); + return new FdoRecord(HANDLE, fdoType, attributesWithUpdatedTimeStamp, primaryLocalId); } - public static List genDoiRecordAttributes(byte[] handle, FdoType type) - throws Exception { - return genDoiRecordAttributes(handle, type, givenDoiRecordRequestObject()); + public static FdoRecord givenDoiFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.DOI, + genDoiRecordAttributes(handle, CREATED, FdoType.DOI, givenDoiRecordRequestObject()), null); } - public static List genDoiRecordAttributes(byte[] handle, FdoType type, - DoiRecordRequest request) - throws Exception { - List fdoRecord = genHandleRecordAttributes(handle, type); - + public static List genDoiRecordAttributes(String handle, Instant timestamp, + FdoType type, DoiRecordRequest request) throws Exception { + var fdoRecord = genHandleRecordAttributes(handle, timestamp, type); // 40: referentType - fdoRecord.add( - new HandleAttribute(REFERENT_TYPE.index(), handle, REFERENT_TYPE.get(), - request.getReferentType().getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(REFERENT_TYPE, timestamp, request.getReferentType())); // 41: referentDoiName - fdoRecord.add( - new HandleAttribute(REFERENT_DOI_NAME.index(), handle, REFERENT_DOI_NAME.get(), - REFERENT_DOI_NAME_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(REFERENT_DOI_NAME, timestamp, REFERENT_DOI_NAME_TESTVAL)); // 42: referentName - fdoRecord.add( - new HandleAttribute(REFERENT_NAME.index(), handle, REFERENT_NAME.get(), - request.getReferentName().getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(REFERENT_NAME, timestamp, request.getReferentName())); // 43: primaryReferentType fdoRecord.add( - new HandleAttribute(PRIMARY_REFERENT_TYPE.index(), handle, - PRIMARY_REFERENT_TYPE.get(), - request.getPrimaryReferentType().getBytes(StandardCharsets.UTF_8))); - + new FdoAttribute(PRIMARY_REFERENT_TYPE, timestamp, request.getPrimaryReferentType())); return fdoRecord; } - public static List genDigitalSpecimenAttributes(byte[] handle, - DigitalSpecimenRequest request) throws Exception { - List fdoRecord = genDoiRecordAttributes(handle, FdoType.DIGITAL_SPECIMEN, - request); - // 200: Specimen Host - fdoRecord.add( - new HandleAttribute(SPECIMEN_HOST.index(), handle, - SPECIMEN_HOST.get(), - request.getSpecimenHost().getBytes(StandardCharsets.UTF_8))); + public static FdoRecord givenDigitalSpecimenFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.DIGITAL_SPECIMEN, + genDigitalSpecimenAttributes(handle, givenDigitalSpecimenRequestObjectNullOptionals(), + CREATED), + NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL); + } + public static List genDigitalSpecimenAttributes(String handle, + DigitalSpecimenRequest request, Instant timestamp) throws Exception { + List fdoRecord = genDoiRecordAttributes(handle, timestamp, + FdoType.DIGITAL_SPECIMEN, request); + // 200: Specimen Host + fdoRecord.add(new FdoAttribute(SPECIMEN_HOST, timestamp, request.getSpecimenHost())); // 201: Specimen Host name - fdoRecord.add( - new HandleAttribute(SPECIMEN_HOST_NAME.index(), handle, - SPECIMEN_HOST_NAME.get(), - SPECIMEN_HOST_NAME_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(SPECIMEN_HOST_NAME, timestamp, SPECIMEN_HOST_NAME_TESTVAL)); // 202: primarySpecimenObjectId - fdoRecord.add( - new HandleAttribute(PRIMARY_SPECIMEN_OBJECT_ID.index(), handle, - PRIMARY_SPECIMEN_OBJECT_ID.get(), - request.getPrimarySpecimenObjectId().getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(PRIMARY_SPECIMEN_OBJECT_ID, timestamp, + request.getPrimarySpecimenObjectId())); // 203: primarySpecimenObjectIdType - fdoRecord.add( - new HandleAttribute(PRIMARY_SPECIMEN_OBJECT_ID_TYPE.index(), handle, - PRIMARY_SPECIMEN_OBJECT_ID_TYPE.get(), - request.getPrimarySpecimenObjectIdType().getBytes())); - - // 204-217 are optional - + fdoRecord.add(new FdoAttribute(PRIMARY_SPECIMEN_OBJECT_ID_TYPE, timestamp, + request.getPrimarySpecimenObjectIdType())); // 204: primarySpecimenObjectIdName - if (request.getPrimarySpecimenObjectIdName() != null) { - fdoRecord.add( - new HandleAttribute(PRIMARY_SPECIMEN_OBJECT_ID_NAME.index(), handle, - PRIMARY_SPECIMEN_OBJECT_ID_NAME.get(), - request.getPrimarySpecimenObjectIdName() - .getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(PRIMARY_SPECIMEN_OBJECT_ID_NAME, timestamp, + request.getPrimarySpecimenObjectIdName())); // 205: normalisedSpecimenObjectId - fdoRecord.add( - new HandleAttribute(NORMALISED_SPECIMEN_OBJECT_ID, handle, - request.getNormalisedPrimarySpecimenObjectId())); - + fdoRecord.add(new FdoAttribute(NORMALISED_SPECIMEN_OBJECT_ID, timestamp, + request.getNormalisedPrimarySpecimenObjectId())); // 206: specimenObjectIdAbsenceReason - if (request.getPrimarySpecimenObjectIdAbsenceReason() != null) { - fdoRecord.add( - new HandleAttribute(SPECIMEN_OBJECT_ID_ABSENCE_REASON.index(), handle, - SPECIMEN_OBJECT_ID_ABSENCE_REASON.get(), - request.getPrimarySpecimenObjectIdAbsenceReason() - .getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(SPECIMEN_OBJECT_ID_ABSENCE_REASON, timestamp, + request.getSpecimenObjectIdAbsenceReason())); // 207: otherSpecimenIds if (request.getOtherSpecimenIds() != null && !request.getOtherSpecimenIds().isEmpty()) { var otherSpecimenIds = MAPPER.writeValueAsString(request.getOtherSpecimenIds()); - fdoRecord.add(new HandleAttribute(OTHER_SPECIMEN_IDS, handle, otherSpecimenIds)); + fdoRecord.add(new FdoAttribute(OTHER_SPECIMEN_IDS, timestamp, otherSpecimenIds)); + } else { + fdoRecord.add(new FdoAttribute(OTHER_SPECIMEN_IDS, timestamp, null)); } - // 208: topicOrigin - if (request.getTopicOrigin() != null) { - fdoRecord.add( - new HandleAttribute(TOPIC_ORIGIN.index(), handle, - TOPIC_ORIGIN.get(), - request.getTopicOrigin().toString().getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(TOPIC_ORIGIN, timestamp, request.getTopicOrigin())); // 209: topicDomain - if (request.getTopicDomain() != null) { - fdoRecord.add( - new HandleAttribute(TOPIC_DOMAIN.index(), handle, - TOPIC_DOMAIN.get(), - request.getTopicDomain().toString().getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(TOPIC_DOMAIN, timestamp, request.getTopicDomain())); // 210: topicDiscipline - if (request.getTopicDiscipline() != null) { - fdoRecord.add( - new HandleAttribute(TOPIC_DISCIPLINE.index(), handle, - TOPIC_DISCIPLINE.get(), - request.getTopicDiscipline().toString() - .getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add( + new FdoAttribute(TOPIC_DISCIPLINE, timestamp, request.getTopicDiscipline())); // 211: topicCategory - if (request.getTopicCategory() != null) { - fdoRecord.add( - new HandleAttribute(TOPIC_CATEGORY, handle, - request.getTopicCategory().toString())); - } - + fdoRecord.add( + new FdoAttribute(TOPIC_CATEGORY, timestamp, request.getTopicCategory())); // 212: livingOrPreserved - if (request.getLivingOrPreserved() != null) { - fdoRecord.add( - new HandleAttribute(LIVING_OR_PRESERVED.index(), handle, - LIVING_OR_PRESERVED.get(), - request.getLivingOrPreserved().getBytes())); - } - + fdoRecord.add(new FdoAttribute(LIVING_OR_PRESERVED, timestamp, + request.getLivingOrPreserved())); // 213: baseTypeOfSpecimen - if (request.getBaseTypeOfSpecimen() != null) { - fdoRecord.add( - new HandleAttribute(BASE_TYPE_OF_SPECIMEN.index(), handle, - BASE_TYPE_OF_SPECIMEN.get(), - request.getBaseTypeOfSpecimen().toString() - .getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(BASE_TYPE_OF_SPECIMEN, timestamp, + request.getBaseTypeOfSpecimen())); // 214: informationArtefactType - if (request.getInformationArtefactType() != null) { - fdoRecord.add( - new HandleAttribute(INFORMATION_ARTEFACT_TYPE.index(), handle, - INFORMATION_ARTEFACT_TYPE.get(), - request.getInformationArtefactType().toString() - .getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(INFORMATION_ARTEFACT_TYPE, timestamp, + request.getInformationArtefactType())); // 215: materialSampleType - if (request.getMaterialSampleType() != null) { - fdoRecord.add( - new HandleAttribute(MATERIAL_SAMPLE_TYPE.index(), handle, - MATERIAL_SAMPLE_TYPE.get(), - request.getMaterialSampleType().toString() - .getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add(new FdoAttribute(MATERIAL_SAMPLE_TYPE, timestamp, + request.getMaterialSampleType())); // 216: materialOrDigitalEntity - if (request.getMaterialSampleType() != null) { - fdoRecord.add( - new HandleAttribute(MATERIAL_OR_DIGITAL_ENTITY, handle, - request.getMaterialOrDigitalEntity().toString())); - } - + fdoRecord.add(new FdoAttribute(MATERIAL_OR_DIGITAL_ENTITY, timestamp, + request.getMaterialOrDigitalEntity())); // 217: markedAsType - if (request.getMarkedAsType() != null) { - fdoRecord.add( - new HandleAttribute(MARKED_AS_TYPE.index(), handle, - MARKED_AS_TYPE.get(), - request.getMarkedAsType().toString().getBytes(StandardCharsets.UTF_8))); - } - + fdoRecord.add( + new FdoAttribute(MARKED_AS_TYPE, timestamp, request.getMarkedAsType())); // 218: wasDerivedFromEntity - if (request.getDerivedFromEntity() != null) { - fdoRecord.add( - new HandleAttribute(WAS_DERIVED_FROM_ENTITY.index(), handle, - WAS_DERIVED_FROM_ENTITY.get(), - request.getDerivedFromEntity().getBytes(StandardCharsets.UTF_8))); - } - var catId = request.getCatalogIdentifier(); - if (catId != null) { - fdoRecord.add(new HandleAttribute(CATALOG_IDENTIFIER, handle, catId)); - } - + fdoRecord.add(new FdoAttribute(WAS_DERIVED_FROM_ENTITY, timestamp, + String.valueOf(request.getDerivedFromEntity() != null))); + fdoRecord.add(new FdoAttribute(CATALOG_IDENTIFIER, timestamp, request.getCatalogIdentifier())); return fdoRecord; + } + public static FdoRecord givenDigitalMediaFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, DIGITAL_MEDIA, + genDigitalMediaAttributes(handle, givenDigitalMediaRequestObject(), CREATED), + PRIMARY_MEDIA_ID_TESTVAL); } - public static List genDigitalSpecimenAttributes(byte[] handle) - throws Exception { - var request = givenDigitalSpecimenRequestObjectNullOptionals(); - return genDigitalSpecimenAttributes(handle, request); + public static List genDigitalMediaAttributes(String handle, + DigitalMediaRequest request, Instant timestamp) throws Exception { + var fdoRecord = genDoiRecordAttributes(handle, timestamp, FdoType.DIGITAL_MEDIA, request); + fdoRecord.add(new FdoAttribute(MEDIA_HOST, timestamp, request.getMediaHost())); + if (request.getMediaHostName() == null) { + fdoRecord.add(new FdoAttribute(MEDIA_HOST_NAME, timestamp, MEDIA_HOST_NAME_TESTVAL)); + } else { + fdoRecord.add(new FdoAttribute(MEDIA_HOST_NAME, timestamp, request.getMediaHostName())); + } + fdoRecord.add( + new FdoAttribute(DCTERMS_FORMAT, timestamp, request.getDctermsFormat())); + fdoRecord.add(new FdoAttribute(IS_DERIVED_FROM_SPECIMEN, timestamp, + request.getIsDerivedFromSpecimen())); + fdoRecord.add(new FdoAttribute(LINKED_DO_PID, timestamp, request.getLinkedDigitalObjectPid())); + fdoRecord.add(new FdoAttribute(LINKED_DO_TYPE, timestamp, + request.getLinkedDigitalObjectType())); + + fdoRecord.add(new FdoAttribute(LINKED_ATTRIBUTE, timestamp, request.getLinkedAttribute())); + fdoRecord.add(new FdoAttribute(PRIMARY_MEDIA_ID, timestamp, request.getPrimaryMediaId())); + fdoRecord.add( + new FdoAttribute(PRIMARY_MO_ID_TYPE, timestamp, request.getPrimaryMediaObjectIdType())); + fdoRecord.add( + new FdoAttribute(PRIMARY_MO_ID_NAME, timestamp, request.getPrimaryMediaObjectIdName())); + fdoRecord.add(new FdoAttribute(DCTERMS_TYPE, timestamp, request.getDcTermsType())); + fdoRecord.add(new FdoAttribute(DCTERMS_SUBJECT, timestamp, request.getDctermsSubject())); + fdoRecord.add(new FdoAttribute(DERIVED_FROM_ENTITY, timestamp, request.getDerivedFromEntity())); + fdoRecord.add(new FdoAttribute(LICENSE_NAME, timestamp, request.getLicenseName())); + fdoRecord.add(new FdoAttribute(LICENSE_URL, timestamp, request.getLicenseUrl())); + fdoRecord.add(new FdoAttribute(RIGHTSHOLDER_NAME, timestamp, request.getRightsholderName())); + fdoRecord.add(new FdoAttribute(RIGHTSHOLDER_PID, timestamp, request.getRightsholderPid())); + fdoRecord.add(new FdoAttribute(RIGHTSHOLDER_PID_TYPE, timestamp, + request.getRightsholderPidType())); + fdoRecord.add(new FdoAttribute(DC_TERMS_CONFORMS, timestamp, request.getDctermsConformsTo())); + return fdoRecord; } - public static List genDigitalMediaAttributes(byte[] handle) + public static FdoRecord givenAnnotationFdoRecord(String handle, boolean includeHash) throws Exception { - var request = givenMediaRequestObject(); - return genDigitalMediaAttributes(handle, request); + var localId = includeHash ? ANNOTATION_HASH_TESTVAL.toString() : null; + return new FdoRecord(handle, FdoType.ANNOTATION, genAnnotationAttributes(handle, includeHash), + localId); } - public static List genDigitalMediaAttributes(byte[] handle, - DigitalMediaRequest request) throws Exception { - List fdoRecord = genDoiRecordAttributes(handle, FdoType.DIGITAL_MEDIA); - fdoRecord.add(new HandleAttribute(MEDIA_HOST, handle, request.getMediaHost())); - if (request.getMediaHostName() == null) { - fdoRecord.add(new HandleAttribute(MEDIA_HOST_NAME, handle, MEDIA_HOST_NAME_TESTVAL)); - } else { - fdoRecord.add(new HandleAttribute(MEDIA_HOST_NAME, handle, request.getMediaHostName())); - } - if (request.getMediaFormat() != null) { - fdoRecord.add( - new HandleAttribute(MEDIA_FORMAT, handle, request.getMediaFormat().toString())); - } - fdoRecord.add(new HandleAttribute(IS_DERIVED_FROM_SPECIMEN, handle, - request.getIsDerivedFromSpecimen().toString())); - if (request.getLinkedDigitalObjectPid() != null) { - fdoRecord.add( - new HandleAttribute(LINKED_DO_PID, handle, - request.getLinkedDigitalObjectPid())); - } - if (request.getLinkedDigitalObjectType() != null) { - fdoRecord.add( - new HandleAttribute(LINKED_DO_TYPE, handle, request.getLinkedDigitalObjectType() - .toString())); - } - if (request.getLinkedAttribute() != null) { - fdoRecord.add( - new HandleAttribute(LINKED_ATTRIBUTE, handle, request.getLinkedAttribute())); - } - if (request.getPrimaryMediaId() != null) { - fdoRecord.add( - new HandleAttribute(PRIMARY_MEDIA_ID, handle, request.getPrimaryMediaId())); - } - if (request.getPrimaryMediaObjectIdType() != null) { - fdoRecord.add( - new HandleAttribute(PRIMARY_MO_ID_TYPE, handle, - request.getPrimaryMediaObjectIdType().toString())); - } - if (request.getPrimaryMediaObjectIdName() != null) { - fdoRecord.add( - new HandleAttribute(PRIMARY_MO_ID_NAME, handle, - request.getPrimaryMediaObjectIdName())); - } - if (request.getDcTermsType() != null) { - fdoRecord.add( - new HandleAttribute(DCTERMS_TYPE, handle, - request.getDcTermsType().toString())); - } - if (request.getMediaMimeType() != null) { - fdoRecord.add( - new HandleAttribute(MEDIA_MIME_TYPE, handle, request.getMediaMimeType())); - } - if (request.getDerivedFromEntity() != null) { - fdoRecord.add( - new HandleAttribute(DERIVED_FROM_ENTITY, handle, - request.getDerivedFromEntity())); - } - if (request.getLicenseName() != null) { - fdoRecord.add( - new HandleAttribute(LICENSE_NAME, handle, request.getLicenseName())); - } - if (request.getLicense() != null) { - fdoRecord.add( - new HandleAttribute(LICENSE_URL, handle, request.getLicense())); - } - if (request.getRightsholderName() != null) { - fdoRecord.add( - new HandleAttribute(RIGHTSHOLDER_NAME, handle, request.getRightsholderName())); - } - if (request.getRightsholderPid() != null) { - fdoRecord.add( - new HandleAttribute(RIGHTSHOLDER_PID, handle, request.getRightsholderPid())); - } - if (request.getRightsholderPidType() != null) { - fdoRecord.add( - new HandleAttribute(RIGHTSHOLDER_PID_TYPE, handle, - request.getRightsholderPidType().toString())); - } - if (request.getDctermsConformsTo() != null) { - fdoRecord.add( - new HandleAttribute(DC_TERMS_CONFORMS, handle, request.getDctermsConformsTo())); - } + public static FdoRecord givenMasFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.MAS, genMasAttributes(handle, CREATED), null); + } - return fdoRecord; + public static FdoRecord givenSourceSystemFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.SOURCE_SYSTEM, genSourceSystemAttributes(handle, CREATED), + null); } - public static List genAnnotationAttributes(byte[] handle, boolean includeHash) + public static FdoRecord givenOrganisationFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.ORGANISATION, + genOrganisationAttributes(handle, CREATED, givenOrganisationRequestObject()), null); + } + + public static FdoRecord givenDataMappingFdoRecord(String handle) throws Exception { + return new FdoRecord(handle, FdoType.DATA_MAPPING, genMappingAttributes(handle, CREATED), null); + } + + public static FdoRecord givenTombstoneFdoRecord() throws Exception { + return new FdoRecord(HANDLE, FdoType.HANDLE, + genTombstoneAttributes(givenTombstoneRecordRequestObject()), null); + } + + public static List genAnnotationAttributes(String handle, boolean includeHash) throws Exception { - var fdoRecord = genHandleRecordAttributes(handle, FdoType.ANNOTATION); + return genAnnotationAttributes(handle, CREATED, includeHash); + } + public static List genAnnotationAttributes(String handle, Instant timestamp, + boolean includeHash) throws Exception { + var fdoRecord = genHandleRecordAttributes(handle, timestamp, FdoType.ANNOTATION); // 500 TargetPid - fdoRecord.add(new HandleAttribute(TARGET_PID, handle, TARGET_DOI_TESTVAL)); - + fdoRecord.add(new FdoAttribute(TARGET_PID, timestamp, TARGET_DOI_TESTVAL)); // 501 TargetType - fdoRecord.add(new HandleAttribute(TARGET_TYPE, handle, TARGET_TYPE_TESTVAL)); - + fdoRecord.add(new FdoAttribute(TARGET_TYPE, timestamp, TARGET_TYPE_TESTVAL)); // 502 motivation - fdoRecord.add( - new HandleAttribute(MOTIVATION, handle, MOTIVATION_TESTVAL.toString())); - + fdoRecord.add(new FdoAttribute(MOTIVATION, timestamp, MOTIVATION_TESTVAL)); // 503 AnnotationHash if (includeHash) { fdoRecord.add( - new HandleAttribute(ANNOTATION_HASH, handle, - ANNOTATION_HASH_TESTVAL.toString())); + new FdoAttribute(ANNOTATION_HASH, timestamp, ANNOTATION_HASH_TESTVAL)); + } else { + fdoRecord.add(new FdoAttribute(ANNOTATION_HASH, timestamp, null)); } return fdoRecord; } - public static List genMasAttributes(byte[] handle) + public static List genMasAttributes(String handle, Instant timestamp) throws Exception { - var fdoRecord = genHandleRecordAttributes(handle, FdoType.MAS); - - fdoRecord.add(new HandleAttribute(MAS_NAME.index(), handle, MAS_NAME.get(), - (MAS_NAME_TESTVAL).getBytes(StandardCharsets.UTF_8))); - + var fdoRecord = genHandleRecordAttributes(handle, timestamp, FdoType.MAS); + fdoRecord.add(new FdoAttribute(MAS_NAME, timestamp, MAS_NAME_TESTVAL)); return fdoRecord; } - public static List genDataMappingAttributes(byte[] handle) + public static List genMappingAttributes(String handle, Instant timestamp) throws Exception { - var fdoRecord = genHandleRecordAttributes(handle, FdoType.DATA_MAPPING); - + var fdoRecord = genHandleRecordAttributes(handle, timestamp, FdoType.DATA_MAPPING); // 500 subjectDigitalObjectId - fdoRecord.add(new HandleAttribute(SOURCE_DATA_STANDARD.index(), handle, - SOURCE_DATA_STANDARD.get(), - SOURCE_DATA_STANDARD_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(SOURCE_DATA_STANDARD, timestamp, SOURCE_DATA_STANDARD_TESTVAL)); return fdoRecord; } - public static List genSourceSystemAttributes(byte[] handle) + public static List genSourceSystemAttributes(String handle, Instant timestamp) throws Exception { - var fdoRecord = genHandleRecordAttributes(handle, FdoType.SOURCE_SYSTEM); - + var fdoRecord = genHandleRecordAttributes(handle, timestamp, FdoType.SOURCE_SYSTEM); // 600 hostInstitution - fdoRecord.add(new HandleAttribute(SOURCE_SYSTEM_NAME.index(), handle, - SOURCE_SYSTEM_NAME.get(), SPECIMEN_HOST_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(SOURCE_SYSTEM_NAME, timestamp, SPECIMEN_HOST_TESTVAL)); return fdoRecord; } - public static List genOrganisationAttributes(byte[] handle, - OrganisationRequest request) - throws Exception { - var fdoRecord = genDoiRecordAttributes(handle, FdoType.ORGANISATION, request); - + public static List genOrganisationAttributes(String handle, Instant timestamp, + OrganisationRequest request) throws Exception { + var fdoRecord = genDoiRecordAttributes(handle, timestamp, FdoType.ORGANISATION, request); // 800 OrganisationIdentifier - fdoRecord.add(new HandleAttribute(ORGANISATION_ID.index(), handle, - ORGANISATION_ID.get(), SPECIMEN_HOST_TESTVAL.getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(ORGANISATION_ID, timestamp, SPECIMEN_HOST_TESTVAL)); // 801 OrganisationIdentifier - fdoRecord.add(new HandleAttribute(ORGANISATION_ID_TYPE.index(), handle, - ORGANISATION_ID_TYPE.get(), PTR_TYPE_DOI.getBytes(StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(ORGANISATION_ID_TYPE, timestamp, PTR_TYPE_DOI)); // 802 OrganisationName - fdoRecord.add( - new HandleAttribute(ORGANISATION_NAME.index(), handle, ORGANISATION_NAME.get(), - SPECIMEN_HOST_NAME_TESTVAL.getBytes( - StandardCharsets.UTF_8))); - + fdoRecord.add(new FdoAttribute(ORGANISATION_NAME, timestamp, ISSUED_FOR_AGENT_TESTVAL)); return fdoRecord; } - public static List genOrganisationAttributes(byte[] handle) + public static List genTombstoneAttributes(TombstoneRecordRequest request) throws Exception { - return genOrganisationAttributes(handle, givenOrganisationRequestObject()); + var fdoRecord = genHandleRecordAttributes(HANDLE, CREATED, FdoType.HANDLE); + fdoRecord.add(new FdoAttribute(TOMBSTONED_TEXT, UPDATED, request.getTombstonedText())); + fdoRecord.set(fdoRecord.indexOf(new FdoAttribute(PID_RECORD_ISSUE_NUMBER, CREATED, "1")), + new FdoAttribute(PID_RECORD_ISSUE_NUMBER, UPDATED, "2")); + fdoRecord.set(fdoRecord.indexOf(new FdoAttribute(PID_STATUS, CREATED, PidStatus.ACTIVE)), + new FdoAttribute(PID_STATUS, UPDATED, PidStatus.TOMBSTONED)); + // 31: hasRelatedPID + if (request.getHasRelatedPID() != null && !request.getHasRelatedPID().isEmpty()) { + fdoRecord.add(new FdoAttribute(HAS_RELATED_PID, UPDATED, + MAPPER.writeValueAsString(request.getHasRelatedPID()))); + } else { + fdoRecord.add(new FdoAttribute(HAS_RELATED_PID, UPDATED, + MAPPER.writeValueAsString(Collections.emptyList()))); + } + // 32: tombstonedDate + fdoRecord.add(new FdoAttribute(TOMBSTONED_DATE, UPDATED, UPDATE_DATE_TESTVAL)); + return fdoRecord; } public static ObjectNode genCreateRecordRequest(T request, @@ -721,24 +565,25 @@ public static ObjectNode genCreateRecordRequest( // Single Requests public static HandleRecordRequest givenHandleRecordRequestObject() { - return new HandleRecordRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - STRUCTURAL_TYPE_TESTVAL, - LOC_TESTVAL - ); + return new HandleRecordRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + STRUCTURAL_TYPE_TESTVAL, LOC_TESTVAL); + } + + public static HandleRecordRequest givenHandleRecordRequestObjectUpdate() { + return new HandleRecordRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + STRUCTURAL_TYPE_TESTVAL, LOC_ALT_TESTVAL); } public static DoiRecordRequest givenDoiRecordRequestObject() { - return new DoiRecordRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - STRUCTURAL_TYPE_TESTVAL, - LOC_TESTVAL, - REFERENT_NAME_TESTVAL, - FdoType.DIGITAL_MEDIA.getDigitalObjectName(), - PRIMARY_REFERENT_TYPE_TESTVAL - ); + return new DoiRecordRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + STRUCTURAL_TYPE_TESTVAL, LOC_TESTVAL, REFERENT_NAME_TESTVAL, + FdoType.DIGITAL_MEDIA.getDigitalObjectName(), PRIMARY_REFERENT_TYPE_TESTVAL); + } + + public static DoiRecordRequest givenDoiRecordRequestObjectUpdate() { + return new DoiRecordRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + STRUCTURAL_TYPE_TESTVAL, LOC_ALT_TESTVAL, REFERENT_NAME_TESTVAL, + FdoType.DIGITAL_MEDIA.getDigitalObjectName(), PRIMARY_REFERENT_TYPE_TESTVAL); } public static DigitalSpecimenRequest givenDigitalSpecimenRequestObjectNullOptionals() { @@ -748,383 +593,287 @@ public static DigitalSpecimenRequest givenDigitalSpecimenRequestObjectNullOption public static DigitalSpecimenRequest givenDigitalSpecimenRequestObjectNullOptionals( String primarySpecimenObjectId) { try { - return new DigitalSpecimenRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - REFERENT_NAME_TESTVAL, - PRIMARY_REFERENT_TYPE_TESTVAL, - SPECIMEN_HOST_TESTVAL, - SPECIMEN_HOST_NAME_TESTVAL, - primarySpecimenObjectId, - PrimarySpecimenObjectIdType.GLOBAL, null, primarySpecimenObjectId, - null, null, null, null, null, null, null, - null, - null, - null, null, null, - null, null); + return new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, SPECIMEN_HOST_TESTVAL, + SPECIMEN_HOST_NAME_TESTVAL, primarySpecimenObjectId, PrimarySpecimenObjectIdType.GLOBAL, + null, NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, null, null, null, null, null, null, + null, null, null, null, null, null, null, null); } catch (InvalidRequestException e) { throw new RuntimeException(e.getMessage()); } } - public static DigitalMediaRequest givenMediaRequestObject() throws InvalidRequestException { - return new DigitalMediaRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - REFERENT_NAME_TESTVAL, - PRIMARY_REFERENT_TYPE_TESTVAL, - MEDIA_HOST_TESTVAL, MEDIA_HOST_NAME_TESTVAL, null, Boolean.TRUE, - LINKED_DO_PID_TESTVAL, - LINKED_DIGITAL_OBJECT_TYPE_TESTVAL, null, HANDLE, null, null, null, null, null, - null, - null, SPECIMEN_HOST_TESTVAL, SPECIMEN_HOST_NAME_TESTVAL, null, null - ); + public static DigitalSpecimenRequest givenDigitalSpecimenRequestObject() throws Exception { + var otherSpecimenIds = new OtherSpecimenId("Catalog Id", "Catalog Id"); + return new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, SPECIMEN_HOST_TESTVAL, + SPECIMEN_HOST_NAME_TESTVAL, PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, + PrimarySpecimenObjectIdType.GLOBAL, null, NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, + null, List.of(otherSpecimenIds), TopicOrigin.HUMAN_MADE, TopicDomain.ARCHIVE, + TopicDiscipline.ANTHRO, TopicCategory.HUMAN, LivingOrPreserved.PRESERVED, + BaseTypeOfSpecimen.MATERIAL, null, MaterialSampleType.ORG_PART, + MaterialOrDigitalEntity.DIGITAL, true, "Entity this was derived from", "Catalog id"); } - public static AnnotationRequest givenAnnotationRequestObject() { - return new AnnotationRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - TARGET_DOI_TESTVAL, - TARGET_TYPE_TESTVAL, - MOTIVATION_TESTVAL, - ANNOTATION_HASH_TESTVAL - ); + public static DigitalSpecimenRequest givenDigitalSpecimenRequestObjectUpdate() throws Exception { + return new DigitalSpecimenRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, + SPECIMEN_HOST_TESTVAL, SPECIMEN_HOST_NAME_TESTVAL, PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, + PrimarySpecimenObjectIdType.GLOBAL, null, NORMALISED_PRIMARY_SPECIMEN_OBJECT_ID_TESTVAL, + null, null, null, null, null, null, null, null, null, null, null, null, null, null); } - public static AnnotationRequest givenAnnotationRequestObjectNoHash() { - return new AnnotationRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - TARGET_DOI_TESTVAL, - TARGET_TYPE_TESTVAL, - MOTIVATION_TESTVAL, - null - ); + public static DigitalMediaRequest givenDigitalMediaRequestObject() + throws InvalidRequestException { + return new DigitalMediaRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, MEDIA_HOST_TESTVAL, + MEDIA_HOST_NAME_TESTVAL, null, Boolean.TRUE, LINKED_DO_PID_TESTVAL, + LINKED_DIGITAL_OBJECT_TYPE_TESTVAL, null, PRIMARY_MEDIA_ID_TESTVAL, null, null, null, null, + null, null, null, SPECIMEN_HOST_TESTVAL, SPECIMEN_HOST_NAME_TESTVAL, null, null); } - public static DataMappingRequest givenDataMappingRequestObject() { - return new DataMappingRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - SOURCE_DATA_STANDARD_TESTVAL - ); + public static DigitalMediaRequest givenDigitalMediaRequestObjectUpdate() + throws InvalidRequestException { + return new DigitalMediaRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, MEDIA_HOST_TESTVAL, + MEDIA_HOST_NAME_TESTVAL, null, Boolean.TRUE, LINKED_DO_PID_TESTVAL, + LINKED_DIGITAL_OBJECT_TYPE_TESTVAL, null, PRIMARY_MEDIA_ID_TESTVAL, null, null, null, null, + null, null, null, SPECIMEN_HOST_TESTVAL, SPECIMEN_HOST_NAME_TESTVAL, null, null); } - public static SourceSystemRequest givenSourceSystemRequestObject() { - return new SourceSystemRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - SPECIMEN_HOST_TESTVAL - ); - } - - public static OrganisationRequest givenOrganisationRequestObject() { - return new OrganisationRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - REFERENT_NAME_TESTVAL, - PRIMARY_REFERENT_TYPE_TESTVAL, - SPECIMEN_HOST_TESTVAL, - PTR_TYPE_DOI - ); + public static AnnotationRequest givenAnnotationRequestObject() { + return new AnnotationRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + TARGET_DOI_TESTVAL, TARGET_TYPE_TESTVAL, MOTIVATION_TESTVAL, ANNOTATION_HASH_TESTVAL); } - public static MasRequest givenMasRecordRequestObject() { - return new MasRequest( - ISSUED_FOR_AGENT_TESTVAL, - PID_ISSUER_TESTVAL_OTHER, - LOC_TESTVAL, - MAS_NAME_TESTVAL - ); + public static AnnotationRequest givenAnnotationRequestObjectUpdate() { + return new AnnotationRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, TARGET_DOI_TESTVAL, TARGET_TYPE_TESTVAL, MOTIVATION_TESTVAL, null); } - public static TombstoneRecordRequest genTombstoneRecordRequestObject() { - return new TombstoneRecordRequest( - TOMBSTONE_TEXT_TESTVAL - ); + public static AnnotationRequest givenAnnotationRequestObjectNoHash() { + return new AnnotationRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + TARGET_DOI_TESTVAL, TARGET_TYPE_TESTVAL, MOTIVATION_TESTVAL, null); } - public static JsonApiWrapperRead givenRecordResponseRead(List handles, String path, - FdoType recordType) - throws Exception { - List dataNodes = new ArrayList<>(); - - for (byte[] handle : handles) { - var testDbRecord = genAttributes(recordType, handle); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); - dataNodes.add(new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - recordType.getDigitalObjectType(), - recordAttributes, pidLink)); - } - - var responseLink = new JsonApiLinks(path); - return new JsonApiWrapperRead(responseLink, dataNodes); + public static DataMappingRequest givenDataMappingRequestObject() { + return new DataMappingRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + SOURCE_DATA_STANDARD_TESTVAL); } - public static JsonApiWrapperReadSingle givenRecordResponseReadSingle(String handle, String path, - FdoType type, JsonNode attributes) { - return new JsonApiWrapperReadSingle( - new JsonApiLinks(path), - new JsonApiDataLinks(handle, type.getDigitalObjectType(), attributes, - new JsonApiLinks("https://hdl.handle.net/" + handle))); + public static DataMappingRequest givenDataMappingRequestObjectUpdate() { + return new DataMappingRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, SOURCE_DATA_STANDARD_TESTVAL); } - public static JsonApiWrapperWrite givenRecordResponseWrite(List handles, - FdoType recordType) - throws Exception { - List dataNodes = new ArrayList<>(); - - for (byte[] handle : handles) { - var testDbRecord = genAttributes(recordType, handle); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); - - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); - dataNodes.add(new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - recordType.getDigitalObjectType(), - recordAttributes, pidLink)); - } - return new JsonApiWrapperWrite(dataNodes); + public static SourceSystemRequest givenSourceSystemRequestObject() { + return new SourceSystemRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + SPECIMEN_HOST_TESTVAL); } - public static JsonApiWrapperWrite givenAnnotationResponseWrite(List handles) { - List dataNodes = new ArrayList<>(); + public static SourceSystemRequest givenSourceSystemRequestObjectUpdate() { + return new SourceSystemRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, SPECIMEN_HOST_TESTVAL); + } - for (byte[] handle : handles) { - var testDbRecord = List.of( - new HandleAttribute(ANNOTATION_HASH, handle, - ANNOTATION_HASH_TESTVAL.toString())); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); + public static OrganisationRequest givenOrganisationRequestObject() { + return new OrganisationRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, SPECIMEN_HOST_TESTVAL, PTR_TYPE_DOI); + } - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); - dataNodes.add(new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - FdoType.ANNOTATION.getDigitalObjectType(), - recordAttributes, pidLink)); - } - return new JsonApiWrapperWrite(dataNodes); + public static OrganisationRequest givenOrganisationRequestObjectUpdate() { + return new OrganisationRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, REFERENT_NAME_TESTVAL, PRIMARY_REFERENT_TYPE_TESTVAL, + SPECIMEN_HOST_TESTVAL, PTR_TYPE_DOI); } - public static JsonApiWrapperWrite givenRecordResponseWriteSmallResponse( - List testDbRecord, List handles, FdoType type) { - List dataNodes = new ArrayList<>(); - for (var handle : handles) { - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); - dataNodes.add( - new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - type.getDigitalObjectType(), - recordAttributes, pidLink)); - } - return new JsonApiWrapperWrite(dataNodes); + public static MasRequest givenMasRecordRequestObject() { + return new MasRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, LOC_TESTVAL, + MAS_NAME_TESTVAL); } + public static MasRequest givenMasRecordRequestObjectUpdate() { + return new MasRequest(ISSUED_FOR_AGENT_TESTVAL, PID_ISSUER_TESTVAL_OTHER, + LOC_ALT_TESTVAL, MAS_NAME_TESTVAL); + } - public static JsonApiWrapperWrite givenRecordResponseWriteGeneric(List handles, - FdoType recordType) - throws Exception { - List dataNodes = new ArrayList<>(); + public static TombstoneRecordRequest givenTombstoneRecordRequestObject() { + return new TombstoneRecordRequest(TOMBSTONE_TEXT_TESTVAL, + List.of(new HasRelatedPid(HANDLE_ALT, "Media ID"))); + } - for (byte[] handle : handles) { - var testDbRecord = genAttributes(recordType, handle); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); + // Misc - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); - dataNodes.add(new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), "PID", - recordAttributes, pidLink)); + public static FdoAttribute getField(List fdoAttributes, FdoProfile targetField) { + for (var attribute : fdoAttributes) { + if (attribute.getIndex() == targetField.index()) { + return attribute; + } } - return new JsonApiWrapperWrite(dataNodes); + log.error("Unable to find field {} in record {}", targetField, fdoAttributes); + throw new IllegalStateException(); } - public static JsonApiWrapperWrite givenRecordResponseWrite(List handles, - FdoType attributeType, String recordType) - throws Exception { - List dataNodes = new ArrayList<>(); + // Json api Responses - for (byte[] handle : handles) { - var testDbRecord = genAttributes(attributeType, handle); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); - - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); + public static JsonApiWrapperRead givenReadResponse(List handles, String path, + FdoType recordType, String domain) throws Exception { + List dataNodes = new ArrayList<>(); + for (String handle : handles) { + var testDbRecord = genAttributes(recordType, handle); + JsonNode recordAttributes = jsonFormatFdoRecord(testDbRecord); + var pidLink = new JsonApiLinks(domain + handle); dataNodes.add( - new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), recordType, - recordAttributes, pidLink)); + new JsonApiDataLinks(handle, recordType.getDigitalObjectType(), recordAttributes, + pidLink)); } - return new JsonApiWrapperWrite(dataNodes); - } - - public static JsonApiWrapperWrite givenRecordResponseWriteAltLoc(List handles) - throws Exception { - return givenRecordResponseWriteAltLoc(handles, FdoType.HANDLE); + var responseLink = new JsonApiLinks(path); + return new JsonApiWrapperRead(responseLink, dataNodes); } - public static JsonApiWrapperWrite givenRecordResponseWriteAltLoc(List handles, - FdoType recordType) - throws Exception { + public static JsonApiWrapperWrite givenWriteResponseFull(List handles, + FdoType fdoType) throws Exception { List dataNodes = new ArrayList<>(); - - for (byte[] handle : handles) { - var testDbRecord = genUpdateRecordAttributesAltLoc(handle); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); - - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); + for (var handle : handles) { + var testDbRecord = genAttributes(fdoType, handle); + JsonNode recordAttributes = jsonFormatFdoRecord(testDbRecord); + var pidLink = new JsonApiLinks(HANDLE_DOMAIN + handle); + fdoType = fdoType == TOMBSTONE ? FdoType.HANDLE : fdoType; dataNodes.add( - new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - recordType.getDigitalObjectType(), - recordAttributes, pidLink)); + new JsonApiDataLinks(handle, fdoType.getDigitalObjectType(), recordAttributes, + pidLink)); } return new JsonApiWrapperWrite(dataNodes); } - public static JsonApiWrapperWrite givenRecordResponseNullAttributes(List handles) { - return givenRecordResponseNullAttributes(handles, FdoType.HANDLE); + public static JsonApiWrapperWrite givenWriteResponseFull( + FdoRecord fdoRecord) { + JsonNode recordAttributes = jsonFormatFdoRecord(fdoRecord.attributes()); + var pidLink = new JsonApiLinks(HANDLE_DOMAIN + HANDLE); + var dataNodes = List.of( + new JsonApiDataLinks(HANDLE, fdoRecord.fdoType().getDigitalObjectType(), recordAttributes, + pidLink)); + return new JsonApiWrapperWrite(dataNodes); } - public static JsonApiWrapperWrite givenRecordResponseNullAttributes(List handles, - FdoType type) { + public static JsonApiWrapperWrite givenWriteResponseIdsOnly( + List fdoRecords, FdoType fdoType, String domain) { List dataNodes = new ArrayList<>(); - for (byte[] handle : handles) { - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); + List fdoSublist; + for (var fdoRecord : fdoRecords) { + switch (fdoType) { + case ANNOTATION -> { + if (fdoRecord.primaryLocalId() == null) { + fdoSublist = fdoRecord.attributes(); + } else { + fdoSublist = List.of(getField(fdoRecord.attributes(), ANNOTATION_HASH)); + } + } + case DIGITAL_SPECIMEN -> + fdoSublist = List.of(getField(fdoRecord.attributes(), NORMALISED_SPECIMEN_OBJECT_ID)); + case DIGITAL_MEDIA -> + fdoSublist = List.of(getField(fdoRecord.attributes(), PRIMARY_MEDIA_ID), + getField(fdoRecord.attributes(), LINKED_DO_PID)); + default -> fdoSublist = fdoRecord.attributes(); + } + var recordAttributes = jsonFormatFdoRecord(fdoSublist); + var pidLink = new JsonApiLinks(domain + fdoRecord.handle()); dataNodes.add( - new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - type.getDigitalObjectType(), null, + new JsonApiDataLinks(fdoRecord.handle(), fdoType.getDigitalObjectType(), recordAttributes, pidLink)); } return new JsonApiWrapperWrite(dataNodes); } - - public static JsonApiWrapperWrite givenRecordResponseWriteArchive(List handles) - throws Exception { - List dataNodes = new ArrayList<>(); - - for (byte[] handle : handles) { - var testDbRecord = genTombstoneRecordRequestAttributes(handle); - JsonNode recordAttributes = genObjectNodeAttributeRecord(testDbRecord); - - var pidLink = new JsonApiLinks(HANDLE_URI + new String(handle, StandardCharsets.UTF_8)); - dataNodes.add( - new JsonApiDataLinks(new String(handle, StandardCharsets.UTF_8), - FdoType.TOMBSTONE.getDigitalObjectType(), - recordAttributes, pidLink)); - } - return new JsonApiWrapperWrite(dataNodes); + public static List genAttributes(FdoType fdoType, String handle) throws Exception { + return genAttributes(fdoType, handle, CREATED); } - public static List genAttributes(FdoType recordType, byte[] handle) + public static List genAttributes(FdoType fdoType, String handle, Instant timestamp) throws Exception { - switch (recordType) { + switch (fdoType) { case DOI -> { - return genDoiRecordAttributes(handle, recordType); + return genDoiRecordAttributes(handle, timestamp, fdoType, givenDoiRecordRequestObject()); } case DIGITAL_SPECIMEN -> { - return genDigitalSpecimenAttributes(handle); + return genDigitalSpecimenAttributes(handle, + givenDigitalSpecimenRequestObjectNullOptionals(), timestamp); } case DIGITAL_MEDIA -> { - return genDigitalMediaAttributes(handle); + return genDigitalMediaAttributes(handle, givenDigitalMediaRequestObject(), timestamp); } case ANNOTATION -> { - return genAnnotationAttributes(handle, false); + return genAnnotationAttributes(handle, timestamp, false); } case DATA_MAPPING -> { - return genDataMappingAttributes(handle); + return genMappingAttributes(handle, timestamp); } case SOURCE_SYSTEM -> { - return genSourceSystemAttributes(handle); + return genSourceSystemAttributes(handle, timestamp); } case ORGANISATION -> { - return genOrganisationAttributes(handle); + return genOrganisationAttributes(handle, timestamp, givenOrganisationRequestObject()); } case MAS -> { - return genMasAttributes(handle); + return genMasAttributes(handle, timestamp); + } + case TOMBSTONE -> { + return genTombstoneAttributes(givenTombstoneRecordRequestObject()); } default -> { log.warn("Default type"); - return genHandleRecordAttributes(handle, FdoType.HANDLE); + return genHandleRecordAttributes(handle, timestamp, FdoType.HANDLE); } } } - public static List genUpdateRequestBatch(List handles, FdoType type) { - ObjectMapper mapper = new ObjectMapper(); - ObjectNode requestNodeRoot = mapper.createObjectNode(); - ObjectNode requestNodeData = mapper.createObjectNode(); - List requestNodeList = new ArrayList<>(); - - for (byte[] handle : handles) { - requestNodeData.put("type", type.getDigitalObjectType()); - requestNodeData.put("id", new String(handle, StandardCharsets.UTF_8)); - requestNodeData.set("attributes", genUpdateRequestAltLoc()); - requestNodeRoot.set("data", requestNodeData); - - requestNodeList.add(requestNodeRoot.deepCopy()); - - requestNodeData.removeAll(); - requestNodeRoot.removeAll(); - } - return requestNodeList; + public static List givenUpdateRequest() { + return givenUpdateRequest(List.of(HANDLE), FdoType.HANDLE, + MAPPER.valueToTree(givenHandleRecordRequestObjectUpdate())); } - public static List genUpdateRequestBatch(List handles) { - return genUpdateRequestBatch(handles, FdoType.HANDLE); + public static JsonNode givenUpdateRequestSingle(FdoType fdoType, Object request) { + return givenUpdateRequest(List.of(HANDLE), fdoType, MAPPER.valueToTree(request)).get(0); } - public static List genTombstoneRequestBatch(List handles) { - ObjectMapper mapper = new ObjectMapper(); - ObjectNode requestNodeRoot = mapper.createObjectNode(); - ObjectNode requestNodeData = mapper.createObjectNode(); - List requestNodeList = new ArrayList<>(); - - for (String handle : handles) { - requestNodeData.put(NODE_TYPE, FdoType.HANDLE.getDigitalObjectType()); - requestNodeData.put(NODE_ID, handle); - requestNodeData.set(NODE_ATTRIBUTES, genTombstoneRequest()); - requestNodeRoot.set(NODE_DATA, requestNodeData); - - requestNodeList.add(requestNodeRoot.deepCopy()); - - requestNodeData.removeAll(); - requestNodeRoot.removeAll(); + public static List givenUpdateRequest(List handles, FdoType type, + JsonNode requestAttributes) { + var requestNodeList = new ArrayList(); + for (var handle : handles) { + requestNodeList.add(MAPPER.createObjectNode() + .set(NODE_DATA, MAPPER.createObjectNode() + .put(NODE_TYPE, type.getDigitalObjectType()) + .put(NODE_ID, handle) + .set(NODE_ATTRIBUTES, requestAttributes))); } - return requestNodeList; } - public static JsonNode genUpdateRequestAltLoc() { - ObjectMapper mapper = new ObjectMapper(); - ObjectNode rootNode = mapper.createObjectNode(); - rootNode.putArray("locations").add(LOC_ALT_TESTVAL[0]); - return rootNode; - } - - public static JsonNode genTombstoneRequest() { - ObjectMapper mapper = new ObjectMapper(); - ObjectNode rootNode = mapper.createObjectNode(); - rootNode.put(TOMBSTONE_TEXT.get(), TOMBSTONE_TEXT_TESTVAL); - return rootNode; + public static List givenTombstoneRequest() { + var request = MAPPER.createObjectNode() + .set(NODE_DATA, MAPPER.createObjectNode() + .put(NODE_ID, HANDLE) + .set(NODE_ATTRIBUTES, MAPPER.valueToTree(givenTombstoneRecordRequestObject()))); + return List.of(request); } // Handle Attributes as ObjectNode - public static JsonNode genObjectNodeAttributeRecord(List dbRecord) { + public static JsonNode jsonFormatFdoRecord(List dbRecord) { ObjectMapper mapper = new ObjectMapper(); ObjectNode rootNode = mapper.createObjectNode(); - - for (HandleAttribute row : dbRecord) { + for (var row : dbRecord) { if (row.getIndex() != HS_ADMIN.index()) { - var rowData = new String(row.getData(), StandardCharsets.UTF_8); - try { - var nodeData = mapper.readTree(rowData); - rootNode.set(row.getType(), nodeData); - } catch (JsonProcessingException ignored) { - rootNode.put(row.getType(), rowData); + var rowData = row.getValue(); + if (row.getValue() == null) { + rootNode.set(row.getType(), mapper.nullNode()); + } else { + try { + var nodeData = mapper.readTree(rowData); + rootNode.set(row.getType(), nodeData); + } catch (JsonProcessingException ignored) { + rootNode.put(row.getType(), rowData); + } } } } @@ -1132,30 +881,29 @@ public static JsonNode genObjectNodeAttributeRecord(List dbReco } // Other Functions - - public static byte[] givenLandingPage(String handle) throws Exception { - var landingPage = new String[]{"Placeholder landing page"}; - return setLocations(landingPage, handle, FdoType.TOMBSTONE, false); - } - - public static HandleAttribute givenLandingPageAttribute(byte[] handle) throws Exception { - var data = givenLandingPage(new String(handle, StandardCharsets.UTF_8)); - return new HandleAttribute(LOC.index(), handle, LOC.get(), data); + public static Document givenMongoDocument(FdoRecord fdoRecord) throws Exception { + var doc = org.bson.Document.parse(MAPPER.writeValueAsString(fdoRecord)) + .append("_id", fdoRecord.handle()); + if (DIGITAL_SPECIMEN.equals(fdoRecord.fdoType())) { + doc.append(NORMALISED_SPECIMEN_OBJECT_ID.get(), fdoRecord.primaryLocalId()); + } else if (DIGITAL_MEDIA.equals(fdoRecord.fdoType())) { + doc.append(PRIMARY_MEDIA_ID.get(), fdoRecord.primaryLocalId()); + } else if (ANNOTATION.equals(fdoRecord.fdoType()) && fdoRecord.primaryLocalId() != null) { + doc.append(ANNOTATION_HASH.get(), fdoRecord.primaryLocalId()); + } + return doc; } - public static byte[] setLocations(String[] userLocations, String handle, FdoType type, - boolean isDoiProfileTest) + public static String setLocations(String[] userLocations, String handle, FdoType type) throws TransformerException, ParserConfigurationException { - DOC_BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", - true); + DOC_BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder documentBuilder = DOC_BUILDER_FACTORY.newDocumentBuilder(); var doc = documentBuilder.newDocument(); var locations = doc.createElement("locations"); doc.appendChild(locations); - String[] objectLocations = isDoiProfileTest - ? userLocations : concatLocations(userLocations, handle, type); + String[] objectLocations = concatLocations(userLocations, handle, type); for (int i = 0; i < objectLocations.length; i++) { var locs = doc.createElement("location"); @@ -1165,7 +913,7 @@ public static byte[] setLocations(String[] userLocations, String handle, FdoType locs.setAttribute("weight", weight); locations.appendChild(locs); } - return documentToString(doc).getBytes(StandardCharsets.UTF_8); + return documentToString(doc); } private static String[] concatLocations(String[] userLocations, String handle, FdoType type) { @@ -1180,7 +928,7 @@ private static String[] defaultLocations(String handle, FdoType type) { case DIGITAL_SPECIMEN -> { String api = API_URL + "/specimens/" + handle; String ui = UI_URL + "/ds/" + handle; - return new String[]{ui, api}; + return new String[]{api, ui}; } case DATA_MAPPING -> { return new String[]{ORCHESTRATION_URL + "/mapping/" + handle}; @@ -1191,7 +939,7 @@ private static String[] defaultLocations(String handle, FdoType type) { case DIGITAL_MEDIA -> { String api = API_URL + "/digitalMedia/" + handle; String ui = UI_URL + "/dm/" + handle; - return new String[]{ui, api}; + return new String[]{api, ui}; } case ANNOTATION -> { return new String[]{API_URL + "/annotations/" + handle}; @@ -1209,7 +957,8 @@ private static String[] defaultLocations(String handle, FdoType type) { } } - private static String documentToString(Document document) throws TransformerException { + private static String documentToString(org.w3c.dom.Document document) + throws TransformerException { TRANSFORMER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); TRANSFORMER_FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); TRANSFORMER_FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); @@ -1222,8 +971,8 @@ private static String documentToString(Document document) throws TransformerExce } public static String loadResourceFile(String fileName) throws IOException { - return new String(new ClassPathResource(fileName).getInputStream() - .readAllBytes(), StandardCharsets.UTF_8); + return new String(new ClassPathResource(fileName).getInputStream().readAllBytes(), + StandardCharsets.UTF_8); } }